diff --git a/.travis.yml b/.travis.yml index 0e00462..f0f1985 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: scala env: global: -script: sbt test:compile "hot -psource=scalap -w1 -f1" "micro/jmh:run -w1 -f1" +script: sbt testAll jdk: - oraclejdk8 diff --git a/build.sbt b/build.sbt index 1a1f48f..9a89c2f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,21 @@ name := "compiler-benchmark" version := "1.0-SNAPSHOT" -scalaVersion in ThisBuild := "2.11.8" +def scala211 = "2.11.11" +def dottyLatest = "0.2.0-RC1" +scalaVersion in ThisBuild := scala211 + +commands += Command.command("testAll") { s => + "test:compile" :: + "compilation/test" :: + "hot -psource=scalap -wi 1 -i 1 -f1" :: + s"++$dottyLatest" :: + "compilation/test" :: + "hot -psource=vector -wi 1 -i 1 -f1" :: + s"++$scala211" :: + "micro/jmh:run -w1 -f1" :: + s +} resolvers += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/" @@ -36,8 +50,17 @@ lazy val compilation = addJmh(project).settings( // We should be able to switch this project to a broad range of Scala versions for comparative // benchmarking. As such, this project should only depend on the high level `MainClass` compiler API. description := "Black box benchmark of the compiler", - libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value, - mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner") + libraryDependencies += { + if (isDotty.value) "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value + else scalaOrganization.value % "scala-compiler" % scalaVersion.value + }, + crossScalaVersions := List(scala211, dottyLatest), + unmanagedSourceDirectories.in(Compile) += + sourceDirectory.in(Compile).value / (if (isDotty.value) "dotc" else "scalac"), + mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner"), + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test, + testOptions in Test += Tests.Argument(TestFrameworks.JUnit), + fork in (Test, test) := true // jmh scoped tasks run with fork := true. ).settings(addJavaOptions).dependsOn(infrastructure) lazy val micro = addJmh(project).settings( diff --git a/compilation/src/main/dotc/scala/tools/benchmark/BenchmarkDriver.scala b/compilation/src/main/dotc/scala/tools/benchmark/BenchmarkDriver.scala new file mode 100644 index 0000000..6047a33 --- /dev/null +++ b/compilation/src/main/dotc/scala/tools/benchmark/BenchmarkDriver.scala @@ -0,0 +1,22 @@ +package scala.tools.benchmark + +import java.io.File +import scala.tools.nsc.BaseBenchmarkDriver +import dotty.tools.dotc.core.Contexts.ContextBase + +trait BenchmarkDriver extends BaseBenchmarkDriver { + def compileImpl(): Unit = { + implicit val ctx = new ContextBase().initialCtx.fresh + ctx.setSetting(ctx.settings.usejavacp, true) + if (depsClasspath != null) { + ctx.setSetting(ctx.settings.classpath, + depsClasspath.mkString(File.pathSeparator)) + } + ctx.setSetting(ctx.settings.migration, true) + ctx.setSetting(ctx.settings.d, tempDir.getAbsolutePath) + ctx.setSetting(ctx.settings.language, List("Scala2")) + val compiler = new dotty.tools.dotc.Compiler + val reporter = dotty.tools.dotc.Bench.doCompile(compiler, allArgs) + assert(!reporter.hasErrors) + } +} diff --git a/compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala b/compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala index 2930021..aede754 100644 --- a/compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala +++ b/compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala @@ -12,9 +12,23 @@ import org.openjdk.jmh.annotations.Mode._ import org.openjdk.jmh.annotations._ import scala.collection.JavaConverters._ +import scala.tools.benchmark.BenchmarkDriver + +trait BaseBenchmarkDriver { + def source: String + def extraArgs: String + def extras: List[String] = if (extraArgs != null && extraArgs != "") extraArgs.split('|').toList else Nil + def allArgs: List[String] = compilerArgs ++ extras ++ sourceFiles + def corpusVersion: String + def depsClasspath: String + def tempDir: File + def corpusSourcePath: Path + def compilerArgs: List[String] + def sourceFiles: List[String] +} @State(Scope.Benchmark) -class ScalacBenchmark { +class ScalacBenchmark extends BenchmarkDriver { @Param(value = Array()) var source: String = _ @@ -26,54 +40,23 @@ class ScalacBenchmark { @Param(value = Array("latest")) var corpusVersion: String = _ - var driver: Driver = _ - var depsClasspath: String = _ - def compileImpl(): Unit = { - val (compilerArgs, sourceFiles) = - if (source.startsWith("@")) (List(source), List[String]()) - else { - import scala.collection.JavaConverters._ - val allFiles = Files.walk(findSourceDir, FileVisitOption.FOLLOW_LINKS).collect(Collectors.toList[Path]).asScala.toList - val files = allFiles.filter(f => { - val name = f.getFileName.toString - name.endsWith(".scala") || name.endsWith(".java") - }).map(_.toAbsolutePath.normalize.toString).toList - (List[String](), files) - } - - // MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x - class MainClass extends Driver with EvalLoop { - def resident(compiler: Global): Unit = loop { line => - val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError)) - compiler.reporter.reset() - new compiler.Run() compile command.files - } - - override def newCompiler(): Global = Global(settings, reporter) - - override protected def processSettingsHook(): Boolean = { - if (source == "scala") - settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString - else - settings.usejavacp.value = true - settings.outdir.value = tempDir.getAbsolutePath - settings.nowarn.value = true - if (depsClasspath != null) - settings.processArgumentString(s"-cp $depsClasspath") - true - } + def compilerArgs: List[String] = if (source.startsWith("@")) source :: Nil else Nil + + def sourceFiles: List[String] = + if (source.startsWith("@")) Nil + else { + import scala.collection.JavaConverters._ + val allFiles = Files.walk(findSourceDir, FileVisitOption.FOLLOW_LINKS).collect(Collectors.toList[Path]).asScala.toList + val files = allFiles.filter(f => { + val name = f.getFileName.toString + name.endsWith(".scala") || name.endsWith(".java") + }).map(_.toAbsolutePath.normalize.toString) + files } - val driver = new MainClass - - val extras = if (extraArgs != null && extraArgs != "") extraArgs.split('|').toList else Nil - val allArgs = compilerArgs ++ extras ++ sourceFiles - driver.process(allArgs.toArray) - assert(!driver.reporter.hasErrors) - } - private var tempDir: File = null + var tempDir: File = null // Executed once per fork @Setup(Level.Trial) def initTemp(): Unit = { @@ -86,7 +69,7 @@ class ScalacBenchmark { BenchmarkUtils.deleteRecursive(tempDir.toPath) } - private def corpusSourcePath = Paths.get(s"../corpus/$source/$corpusVersion") + def corpusSourcePath: Path = Paths.get(s"../corpus/$source/$corpusVersion") @Setup(Level.Trial) def initDepsClasspath(): Unit = { val classPath = BenchmarkUtils.initDeps(corpusSourcePath) diff --git a/compilation/src/main/scalac/scala/tools/benchmark/BenchmarkDriver.scala b/compilation/src/main/scalac/scala/tools/benchmark/BenchmarkDriver.scala new file mode 100644 index 0000000..687414a --- /dev/null +++ b/compilation/src/main/scalac/scala/tools/benchmark/BenchmarkDriver.scala @@ -0,0 +1,35 @@ +package scala.tools.benchmark + +import java.nio.file._ +import scala.tools.nsc._ + +trait BenchmarkDriver extends BaseBenchmarkDriver { + def compileImpl(): Unit = { + // MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x + class MainClass extends Driver with EvalLoop { + def resident(compiler: Global): Unit = loop { line => + val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError)) + compiler.reporter.reset() + new compiler.Run() compile command.files + } + + override def newCompiler(): Global = Global(settings, reporter) + + override protected def processSettingsHook(): Boolean = { + if (source == "scala") + settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString + else + settings.usejavacp.value = true + settings.outdir.value = tempDir.getAbsolutePath + settings.nowarn.value = true + if (depsClasspath != null) + settings.processArgumentString(s"-cp $depsClasspath") + true + } + } + val driver = new MainClass + driver.process(allArgs.toArray) + assert(!driver.reporter.hasErrors) + } + +} diff --git a/compilation/src/test/scala/scala/tools/benchmark/BenchmarkTest.scala b/compilation/src/test/scala/scala/tools/benchmark/BenchmarkTest.scala new file mode 100644 index 0000000..b76b18e --- /dev/null +++ b/compilation/src/test/scala/scala/tools/benchmark/BenchmarkTest.scala @@ -0,0 +1,15 @@ +package scala.tools.benchmark + +import scala.tools.nsc.ScalacBenchmark +import org.junit.Test + +class BenchmarkTest { + @Test def compilesOK() = { + val bench = new ScalacBenchmark + bench.source = "../corpus/vector" + bench.corpusVersion = "latest" + bench.initTemp() + bench.compileImpl() + bench.clearTemp() + } +} diff --git a/corpus/vector/fb04376/Vector.scala b/corpus/vector/fb04376/Vector.scala index a006da1..b7de3e9 100644 --- a/corpus/vector/fb04376/Vector.scala +++ b/corpus/vector/fb04376/Vector.scala @@ -82,7 +82,7 @@ override def companion: GenericCompanion[Vector] = Vector override def lengthCompare(len: Int): Int = length - len - private[collection] final def initIterator[B >: A](s: VectorIterator[B]) { + private[collection] final def initIterator[B >: A](s: VectorIterator[B]): Unit = { s.initFrom(this) if (dirty) s.stabilize(focus) if (s.depth > 1) s.gotoPos(startIndex, startIndex ^ focus) diff --git a/project/plugins.sbt b/project/plugins.sbt index f913d40..593ecca 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,6 @@ logLevel := Level.Warn // sbt-jmh plugin - pulls in JMH dependencies too addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.25") + +// sbt-dotty plugin - to support `scalaVersion := "0.x"` +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.2")