Skip to content

Commit e309b81

Browse files
committed
Compile benchmark with Dotty, fixes scala#29.
Note, the benchmarks currently don't actually run with Dotty because of scala/scala3#2704. - When `scalaVersion := "0.x"`, then "src/main/dotc" is added to unmanagedSourceDirectories, otherwise "src/main/scalac" is added. - compilation/test checks the setup is OK without the need to remember cli args. - `fork in run` is necessary for -usejavacp to work with compilation/run - `FileVisitOption.FOLLOW_LINKS` produces duplicate filenames, which causes "X is already compiled in this run" errors in Dotty.
1 parent ff90396 commit e309b81

File tree

6 files changed

+106
-47
lines changed

6 files changed

+106
-47
lines changed

build.sbt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,17 @@ lazy val compilation = addJmh(project).settings(
3636
// We should be able to switch this project to a broad range of Scala versions for comparative
3737
// benchmarking. As such, this project should only depend on the high level `MainClass` compiler API.
3838
description := "Black box benchmark of the compiler",
39-
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
40-
mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner")
39+
libraryDependencies += {
40+
if (isDotty.value) "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value
41+
else "org.scala-lang" % "scala-compiler" % scalaVersion.value
42+
},
43+
unmanagedSourceDirectories.in(Compile) +=
44+
sourceDirectory.in(Compile).value / (if (isDotty.value) "dotc" else "scalac"),
45+
mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner"),
46+
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test,
47+
testOptions in Test += Tests.Argument(TestFrameworks.JUnit),
48+
fork in (Test, test) := true,
49+
fork in run := true
4150
).settings(addJavaOptions).dependsOn(infrastructure)
4251

4352
lazy val micro = addJmh(project).settings(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package scala.tools.benchmark
2+
3+
import java.io.File
4+
import scala.tools.nsc.BaseBenchmarkDriver
5+
import dotty.tools.dotc.core.Contexts.ContextBase
6+
7+
trait BenchmarkDriver extends BaseBenchmarkDriver {
8+
def compileImpl(): Unit = {
9+
implicit val ctx = (new ContextBase).initialCtx.fresh
10+
ctx.setSetting(ctx.settings.usejavacp, true)
11+
if (depsClasspath != null) {
12+
ctx.setSetting(ctx.settings.classpath,
13+
depsClasspath.mkString(File.pathSeparator))
14+
}
15+
ctx.setSetting(ctx.settings.d, tempDir.getAbsolutePath)
16+
ctx.setSetting(ctx.settings.language, List("Scala2"))
17+
val compiler = new dotty.tools.dotc.Compiler
18+
val reporter =
19+
dotty.tools.dotc.Bench.doCompile(compiler, compilerArgs.toList)
20+
assert(!reporter.hasErrors)
21+
}
22+
}

compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@ import org.openjdk.jmh.annotations.Mode._
1212
import org.openjdk.jmh.annotations._
1313

1414
import scala.collection.JavaConverters._
15+
import scala.tools.benchmark.BenchmarkDriver
16+
17+
trait BaseBenchmarkDriver {
18+
def source: String
19+
def extraArgs: String
20+
def corpusVersion: String
21+
def depsClasspath: String
22+
def tempDir: File
23+
def corpusSourcePath: Path
24+
def compilerArgs: Array[String]
25+
}
1526

1627
@State(Scope.Benchmark)
17-
class ScalacBenchmark {
28+
class ScalacBenchmark extends BenchmarkDriver {
1829
@Param(value = Array())
1930
var source: String = _
2031

@@ -26,53 +37,20 @@ class ScalacBenchmark {
2637
@Param(value = Array("latest"))
2738
var corpusVersion: String = _
2839

29-
var driver: Driver = _
30-
3140
var depsClasspath: String = _
3241

33-
def compileImpl(): Unit = {
34-
val compilerArgs =
35-
if (source.startsWith("@")) Array(source)
36-
else {
37-
import scala.collection.JavaConverters._
38-
val allFiles = Files.walk(findSourceDir, FileVisitOption.FOLLOW_LINKS).collect(Collectors.toList[Path]).asScala.toList
39-
allFiles.filter(f => {
40-
val name = f.getFileName.toString
41-
name.endsWith(".scala") || name.endsWith(".java")
42-
}).map(_.toAbsolutePath.normalize.toString).toArray
43-
}
44-
45-
// MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x
46-
class MainClass extends Driver with EvalLoop {
47-
def resident(compiler: Global): Unit = loop { line =>
48-
val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError))
49-
compiler.reporter.reset()
50-
new compiler.Run() compile command.files
51-
}
52-
53-
override def newCompiler(): Global = Global(settings, reporter)
54-
55-
override protected def processSettingsHook(): Boolean = {
56-
if (source == "scala")
57-
settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString
58-
else
59-
settings.usejavacp.value = true
60-
settings.outdir.value = tempDir.getAbsolutePath
61-
settings.nowarn.value = true
62-
if (depsClasspath != null)
63-
settings.processArgumentString(s"-cp $depsClasspath")
64-
if (extraArgs != null && extraArgs != "")
65-
settings.processArgumentString(extraArgs)
66-
true
67-
}
42+
def compilerArgs =
43+
if (source.startsWith("@")) Array(source)
44+
else {
45+
import scala.collection.JavaConverters._
46+
val allFiles = Files.walk(findSourceDir).collect(Collectors.toList[Path]).asScala.toList
47+
allFiles.filter(f => {
48+
val name = f.getFileName.toString
49+
name.endsWith(".scala") || name.endsWith(".java")
50+
}).map(_.toAbsolutePath.normalize.toString).toArray
6851
}
69-
val driver = new MainClass
70-
71-
driver.process(compilerArgs)
72-
assert(!driver.reporter.hasErrors)
73-
}
7452

75-
private var tempDir: File = null
53+
var tempDir: File = null
7654

7755
// Executed once per fork
7856
@Setup(Level.Trial) def initTemp(): Unit = {
@@ -95,7 +73,7 @@ class ScalacBenchmark {
9573
})
9674
}
9775

98-
private def corpusSourcePath = Paths.get(s"../corpus/$source/$corpusVersion")
76+
def corpusSourcePath: Path = Paths.get(s"../corpus/$source/$corpusVersion")
9977

10078
@Setup(Level.Trial) def initDepsClasspath(): Unit = {
10179
val depsDir = Paths.get(ConfigFactory.load.getString("deps.localdir"))
@@ -158,6 +136,8 @@ class ColdScalacBenchmark extends ScalacBenchmark {
158136
@OutputTimeUnit(TimeUnit.MILLISECONDS)
159137
@Warmup(iterations = 0)
160138
@Measurement(iterations = 1, time = 30, timeUnit = TimeUnit.SECONDS)
139+
// @Fork triggers match error in dotty, see https://github.com/lampepfl/dotty/issues/2704
140+
// Comment out `@Fork` to run compilation/test with Dotty or wait for that issue to be fixed.
161141
@Fork(value = 3, jvmArgs = Array("-Xms2G", "-Xmx2G"))
162142
class WarmScalacBenchmark extends ScalacBenchmark {
163143
@Benchmark
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package scala.tools.benchmark
2+
3+
import java.nio.file._
4+
import scala.tools.nsc._
5+
6+
trait BenchmarkDriver extends BaseBenchmarkDriver {
7+
def compileImpl(): Unit = {
8+
// MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x
9+
class MainClass extends Driver with EvalLoop {
10+
def resident(compiler: Global): Unit = loop { line =>
11+
val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError))
12+
compiler.reporter.reset()
13+
new compiler.Run() compile command.files
14+
}
15+
16+
override def newCompiler(): Global = Global(settings, reporter)
17+
18+
override protected def processSettingsHook(): Boolean = {
19+
if (source == "scala")
20+
settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString
21+
else
22+
settings.usejavacp.value = true
23+
settings.outdir.value = tempDir.getAbsolutePath
24+
settings.nowarn.value = true
25+
if (depsClasspath != null)
26+
settings.processArgumentString(s"-cp $depsClasspath")
27+
if (extraArgs != null && extraArgs != "")
28+
settings.processArgumentString(extraArgs)
29+
true
30+
}
31+
}
32+
val driver = new MainClass
33+
driver.process(compilerArgs)
34+
assert(!driver.reporter.hasErrors)
35+
}
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package scala.tools.benchmark
2+
3+
import scala.tools.nsc.ScalacBenchmarkStandalone
4+
import org.junit.Test
5+
6+
class BenchmarkTest {
7+
@Test def compilesOK =
8+
ScalacBenchmarkStandalone.main(Array("../corpus/vector", "1"))
9+
}

project/plugins.sbt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ logLevel := Level.Warn
33

44
// sbt-jmh plugin - pulls in JMH dependencies too
55
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.25")
6+
7+
// sbt-dotty plugin - to support `scalaVersion := "0.x"`
8+
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.2")

0 commit comments

Comments
 (0)