Skip to content

Commit

Permalink
Add capture checking the compiler as a test (scala#16292)
Browse files Browse the repository at this point in the history
 - Copy compiler code base into a separate test
 - Adapt code base so that it can be capture checked

Based on scala#16254

Only the last two commits are new:

-
[57527c4](scala@57527c4)
copies the compiler codebase as a separate test.
-
[6dfeaa7](scala@6dfeaa7)
makes the changes so that it passes capture checking.
  • Loading branch information
odersky authored Nov 10, 2022
2 parents a21791b + 8e9327b commit d8a6751
Show file tree
Hide file tree
Showing 449 changed files with 146,583 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class BootstrappedOnlyCompilationTests {
).checkCompile()
}

@Test def posWithCompilerCC: Unit =
implicit val testGroup: TestGroup = TestGroup("compilePosWithCompilerCC")
aggregateTests(
compileDir("tests/pos-with-compiler-cc/dotc", withCompilerOptions.and("-language:experimental.captureChecking"))
).checkCompile()

@Test def posWithCompiler: Unit = {
implicit val testGroup: TestGroup = TestGroup("compilePosWithCompiler")
aggregateTests(
Expand Down
3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ class CompilationTests {
).checkCompile()
}

@Test def recheck: Unit =
//@Test disabled in favor of posWithCompilerCC to save time.
def recheck: Unit =
given TestGroup = TestGroup("recheck")
aggregateTests(
compileFilesInDir("tests/new", recheckOptions),
Expand Down
64 changes: 64 additions & 0 deletions tests/pos-with-compiler-cc/dotc/Bench.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dotty.tools
package dotc

import core.Contexts._
import reporting.Reporter
import io.AbstractFile

import scala.annotation.internal.sharable

/** A main class for running compiler benchmarks. Can instantiate a given
* number of compilers and run each (sequentially) a given number of times
* on the same sources.
*/
object Bench extends Driver:

@sharable private var numRuns = 1

private def ntimes(n: Int)(op: => Reporter): Reporter =
(0 until n).foldLeft(emptyReporter)((_, _) => op)

@sharable private var times: Array[Int] = _

override def doCompile(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
times = new Array[Int](numRuns)
var reporter: Reporter = emptyReporter
for i <- 0 until numRuns do
val start = System.nanoTime()
reporter = super.doCompile(compiler, files)
times(i) = ((System.nanoTime - start) / 1000000).toInt
println(s"time elapsed: ${times(i)}ms")
if ctx.settings.Xprompt.value then
print("hit <return> to continue >")
System.in.nn.read()
println()
reporter

def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = {
val pos = args indexOf name
if (pos < 0) (default, args)
else (args(pos + 1).toInt, (args take pos) ++ (args drop (pos + 2)))
}

def reportTimes() =
val best = times.sorted
val measured = numRuns / 3
val avgBest = best.take(measured).sum / measured
val avgLast = times.reverse.take(measured).sum / measured
println(s"best out of $numRuns runs: ${best(0)}")
println(s"average out of best $measured: $avgBest")
println(s"average out of last $measured: $avgLast")

override def process(args: Array[String], rootCtx: Context): Reporter =
val (numCompilers, args1) = extractNumArg(args, "#compilers")
val (numRuns, args2) = extractNumArg(args1, "#runs")
this.numRuns = numRuns
var reporter: Reporter = emptyReporter
for i <- 0 until numCompilers do
reporter = super.process(args2, rootCtx)
reportTimes()
reporter

end Bench


166 changes: 166 additions & 0 deletions tests/pos-with-compiler-cc/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package dotty.tools
package dotc

import core._
import Contexts._
import SymDenotations.ClassDenotation
import Symbols._
import util.{FreshNameCreator, SourceFile, NoSource}
import util.Spans.Span
import ast.{tpd, untpd}
import tpd.{Tree, TreeTraverser}
import ast.Trees.{Import, Ident}
import typer.Nullables
import transform.SymUtils._
import core.Decorators._
import config.{SourceVersion, Feature}
import StdNames.nme
import scala.annotation.internal.sharable

class CompilationUnit protected (val source: SourceFile) {

override def toString: String = source.toString

var untpdTree: untpd.Tree = untpd.EmptyTree

var tpdTree: tpd.Tree = tpd.EmptyTree

/** Is this the compilation unit of a Java file */
def isJava: Boolean = source.file.name.endsWith(".java")

/** The source version for this unit, as determined by a language import */
var sourceVersion: Option[SourceVersion] = None

/** Pickled TASTY binaries, indexed by class. */
var pickled: Map[ClassSymbol, () => Array[Byte]] = Map()

/** The fresh name creator for the current unit.
* FIXME(#7661): This is not fine-grained enough to enable reproducible builds,
* see https://github.com/scala/scala/commit/f50ec3c866263448d803139e119b33afb04ec2bc
*/
val freshNames: FreshNameCreator = new FreshNameCreator.Default

/** Will be set to `true` if there are inline call that must be inlined after typer.
* The information is used in phase `Inlining` in order to avoid traversing trees that need no transformations.
*/
var needsInlining: Boolean = false

/** Set to `true` if inliner added anonymous mirrors that need to be completed */
var needsMirrorSupport: Boolean = false

/** Will be set to `true` if contains `Quote`.
* The information is used in phase `Staging`/`Splicing`/`PickleQuotes` in order to avoid traversing trees that need no transformations.
*/
var needsStaging: Boolean = false

/** Will be set to true if the unit contains a captureChecking language import */
var needsCaptureChecking: Boolean = false

/** Will be set to true if the unit contains a pureFunctions language import */
var knowsPureFuns: Boolean = false

var suspended: Boolean = false
var suspendedAtInliningPhase: Boolean = false

/** Can this compilation unit be suspended */
def isSuspendable: Boolean = true

/** Suspends the compilation unit by thowing a SuspendException
* and recording the suspended compilation unit
*/
def suspend()(using Context): Nothing =
assert(isSuspendable)
if !suspended then
if (ctx.settings.XprintSuspension.value)
report.echo(i"suspended: $this")
suspended = true
ctx.run.nn.suspendedUnits += this
if ctx.phase == Phases.inliningPhase then
suspendedAtInliningPhase = true
throw CompilationUnit.SuspendException()

private var myAssignmentSpans: Map[Int, List[Span]] | Null = null

/** A map from (name-) offsets of all local variables in this compilation unit
* that can be tracked for being not null to the list of spans of assignments
* to these variables.
*/
def assignmentSpans(using Context): Map[Int, List[Span]] =
if myAssignmentSpans == null then myAssignmentSpans = Nullables.assignmentSpans
myAssignmentSpans.nn
}

@sharable object NoCompilationUnit extends CompilationUnit(NoSource) {

override def isJava: Boolean = false

override def suspend()(using Context): Nothing =
throw CompilationUnit.SuspendException()

override def assignmentSpans(using Context): Map[Int, List[Span]] = Map.empty
}

object CompilationUnit {

class SuspendException extends Exception

/** Make a compilation unit for top class `clsd` with the contents of the `unpickled` tree */
def apply(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(using Context): CompilationUnit =
val file = clsd.symbol.associatedFile.nn
apply(SourceFile(file, Array.empty[Char]), unpickled, forceTrees)

/** Make a compilation unit, given picked bytes and unpickled tree */
def apply(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(using Context): CompilationUnit = {
assert(!unpickled.isEmpty, unpickled)
val unit1 = new CompilationUnit(source)
unit1.tpdTree = unpickled
if (forceTrees) {
val force = new Force
force.traverse(unit1.tpdTree)
unit1.needsStaging = force.containsQuote
unit1.needsInlining = force.containsInline
}
unit1
}

/** Create a compilation unit corresponding to `source`.
* If `mustExist` is true, this will fail if `source` does not exist.
*/
def apply(source: SourceFile, mustExist: Boolean = true)(using Context): CompilationUnit = {
val src =
if (!mustExist)
source
else if (source.file.isDirectory) {
report.error(s"expected file, received directory '${source.file.path}'")
NoSource
}
else if (!source.file.exists) {
report.error(s"source file not found: ${source.file.path}")
NoSource
}
else source
new CompilationUnit(src)
}

/** Force the tree to be loaded */
private class Force extends TreeTraverser {
var containsQuote = false
var containsInline = false
var containsCaptureChecking = false
def traverse(tree: Tree)(using Context): Unit = {
if (tree.symbol.isQuote)
containsQuote = true
if tree.symbol.is(Flags.Inline) then
containsInline = true
tree match
case Import(qual, selectors) =>
tpd.languageImport(qual) match
case Some(prefix) =>
for case untpd.ImportSelector(untpd.Ident(imported), untpd.EmptyTree, _) <- selectors do
Feature.handleGlobalLanguageImport(prefix, imported)
case _ =>
case _ =>
traverseChildren(tree)
}
}
}
Loading

0 comments on commit d8a6751

Please sign in to comment.