diff options
-rw-r--r-- | compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 14 | ||||
-rw-r--r-- | compiler/src/dotty/tools/dotc/core/Constants.scala | 14 | ||||
-rw-r--r-- | compiler/src/dotty/tools/dotc/transform/FirstTransform.scala | 17 | ||||
-rw-r--r-- | compiler/test/dotc/scala-collections.blacklist | 2 | ||||
-rw-r--r-- | compiler/test/dotty/tools/dotc/CompilationTests.scala | 67 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/CompilerClassLoader.scala | 16 | ||||
-rw-r--r-- | tests/run/if-with-constant-cond.check | 4 | ||||
-rw-r--r-- | tests/run/if-with-constant-cond.scala | 9 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-1.check | 3 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-1.scala | 15 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-2.check | 3 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-2.scala | 14 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-3.check | 4 | ||||
-rw-r--r-- | tests/run/inline-constant-in-constructor-3.scala | 19 | ||||
-rw-r--r-- | tests/run/inline-constant-lazy-val.check | 2 | ||||
-rw-r--r-- | tests/run/inline-constant-lazy-val.scala | 10 |
16 files changed, 159 insertions, 54 deletions
diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index f3bce4000..49187492e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -3,7 +3,7 @@ package dotc package ast import core._ -import Flags._, Trees._, Types._, Contexts._ +import Flags._, Trees._, Types._, Contexts._, Constants._ import Names._, StdNames._, NameOps._, Decorators._, Symbols._ import util.HashSet import typer.ConstFold @@ -426,8 +426,18 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => */ def constToLiteral(tree: Tree)(implicit ctx: Context): Tree = { val tree1 = ConstFold(tree) + def canInlineConstant(value: Constant): Boolean = { + val sym = tree1.symbol + isIdempotentExpr(tree1) && // see note in documentation + // lazy value must be initialized (would not be needed with isPureExpr) + !sym.is(Lazy) && + // could hide initialization order issues (ex. val with constant type read before initialized) + (!ctx.owner.isLocalDummy || (!sym.is(Method) && !sym.is(Lazy) && value.isZero) || + ctx.scala2Mode // ignore in Scala 2 because of inlined `final val` values + ) + } tree1.tpe.widenTermRefExpr match { - case ConstantType(value) if isIdempotentExpr(tree1) => Literal(value) + case ConstantType(value) if canInlineConstant(value) => Literal(value) case _ => tree1 } } diff --git a/compiler/src/dotty/tools/dotc/core/Constants.scala b/compiler/src/dotty/tools/dotc/core/Constants.scala index ed388b7ec..8ea285c8d 100644 --- a/compiler/src/dotty/tools/dotc/core/Constants.scala +++ b/compiler/src/dotty/tools/dotc/core/Constants.scala @@ -53,6 +53,20 @@ object Constants { def isNonUnitAnyVal = BooleanTag <= tag && tag <= DoubleTag def isAnyVal = UnitTag <= tag && tag <= DoubleTag + /** Is the zero or un-initialized value of the type */ + def isZero(implicit ctx: Context): Boolean = tag match { + case BooleanTag => !value.asInstanceOf[Boolean] + case ByteTag => value.asInstanceOf[Byte] == 0 + case ShortTag => value.asInstanceOf[Short] == 0 + case CharTag => value.asInstanceOf[Char] == 0 + case IntTag => value.asInstanceOf[Int] == 0 + case LongTag => value.asInstanceOf[Long] == 0L + case FloatTag => value.asInstanceOf[Float] == 0.0 + case DoubleTag => value.asInstanceOf[Double] == 0.0 + case NullTag => true + case _ => false + } + def tpe(implicit ctx: Context): Type = tag match { case UnitTag => defn.UnitType case BooleanTag => defn.BooleanType diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index a3cf71ef2..767ee0901 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -9,7 +9,7 @@ import dotty.tools.dotc.transform.TreeTransforms._ import ast.Trees._ import Flags._ import Types._ -import Constants.Constant +import Constants._ import Contexts.Context import Symbols._ import SymDenotations._ @@ -34,6 +34,8 @@ import StdNames._ * - drops branches of ifs using the rules * if (true) A else B --> A * if (false) A else B --> B + * if (C: true) A else B --> C; A + * if (C: false) A else B --> C; B */ class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => import ast.tpd._ @@ -190,11 +192,16 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo) = constToLiteral(tree) - override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo) = - tree.cond match { - case Literal(Constant(c: Boolean)) => if (c) tree.thenp else tree.elsep - case _ => tree + override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo) = { + tree.cond.tpe.widenTermRefExpr match { + case ConstantType(Constant(condVal: Boolean)) => + val selected = if (condVal) tree.thenp else tree.elsep + if (isPureExpr(tree.cond)) selected + else Block(tree.cond :: Nil, selected) + case _ => + tree } + } // invariants: all modules have companion objects // all types are TypeTrees diff --git a/compiler/test/dotc/scala-collections.blacklist b/compiler/test/dotc/scala-collections.blacklist index d6972a645..ae43e6eb4 100644 --- a/compiler/test/dotc/scala-collections.blacklist +++ b/compiler/test/dotc/scala-collections.blacklist @@ -81,3 +81,5 @@ scala/util/control/Exception.scala # 51 | implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = # | ^ # | cyclic reference involving method mkCatcher + +scala/concurrent/duration/Duration.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 334d347fc..91a453494 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -29,13 +29,17 @@ class CompilationTests extends ParallelTesting { @Test def compilePos: Unit = { compileList("compileStdLib", StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + - compileFilesInDir("../tests/pos", defaultOptions) - }.checkCompile() - - @Test def compilePosScala2: Unit = - compileFilesInDir("../tests/pos-scala2", scala2Mode).checkCompile() - - @Test def compilePosMixedFlags: Unit = { + compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes) + + compileDir("../compiler/src/dotty/tools/dotc/transform", allowDeepSubtypes) + + compileDir("../compiler/src/dotty/tools/dotc/parsing", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/printing", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/reporting", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions) + + compileDir("../compiler/src/dotty/tools/io", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath) + compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).copyToTarget() + compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes) + @@ -65,23 +69,10 @@ class CompilationTests extends ParallelTesting { "../scala-scala/src/library/scala/collection/mutable/SetLike.scala" ), scala2Mode - ) - }.checkCompile() - - @Test def compileCoreNoCheck: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).checkCompile() - - @Test def compileDotcInternals: Unit = { - compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes) + - compileDir("../compiler/src/dotty/tools/dotc/transform", allowDeepSubtypes) + - compileDir("../compiler/src/dotty/tools/dotc/parsing", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/printing", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/reporting", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions) + - compileDir("../compiler/src/dotty/tools/io", defaultOptions) + ) + + compileFilesInDir("../tests/new", defaultOptions) + + compileFilesInDir("../tests/pos-scala2", scala2Mode) + compileFilesInDir("../tests/pos", defaultOptions) }.checkCompile() @Test def posTwice: Unit = { @@ -140,17 +131,10 @@ class CompilationTests extends ParallelTesting { ) }.times(2).checkCompile() - // New tests ----------------------------------------------------------------- - - @Test def compileNew: Unit = - compileFilesInDir("../tests/new", defaultOptions).checkCompile() - // Negative tests ------------------------------------------------------------ - @Test def compileNeg: Unit = - compileShallowFilesInDir("../tests/neg", defaultOptions).checkExpectedErrors() - - @Test def compileNegCustomFlags: Unit = { + @Test def compileNeg: Unit = { + compileShallowFilesInDir("../tests/neg", defaultOptions) + compileFile("../tests/neg/customArgs/typers.scala", allowDoubleBindings) + compileFile("../tests/neg/customArgs/overrideClass.scala", scala2Mode) + compileFile("../tests/neg/customArgs/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) + @@ -180,7 +164,9 @@ class CompilationTests extends ParallelTesting { // Pickling tests are very memory intensive and as such need to be run with a // lower level of concurrency as to not kill their running VMs - @Test def testPickling1: Unit = { + @Test def testPickling: Unit = { + compileDir("../compiler/src/dotty/tools", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc", picklingOptions) + compileFilesInDir("../tests/new", picklingOptions) + compileFilesInDir("../tests/pickling", picklingOptions) + compileDir("../library/src/dotty/runtime", picklingOptions) + @@ -196,23 +182,12 @@ class CompilationTests extends ParallelTesting { compileDir("../compiler/src/dotty/tools/dotc/typer", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/util", picklingOptions) + compileDir("../compiler/src/dotty/tools/io", picklingOptions) + - compileFile("../tests/pos/pickleinf.scala", picklingOptions) - }.limitThreads(4).checkCompile() - - @Test def testPickling2: Unit = { + compileFile("../tests/pos/pickleinf.scala", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/core/classfile", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/core/tasty", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/core/unpickleScala2", picklingOptions) }.limitThreads(4).checkCompile() - @Test def testPickling3: Unit = { - compileDir("../compiler/src/dotty/tools", picklingOptions) - }.limitThreads(4).checkCompile() - - @Test def testPickling4: Unit = { - compileDir("../compiler/src/dotty/tools/dotc", picklingOptions) - }.limitThreads(4).checkCompile() - /** The purpose of this test is two-fold, being able to compile dotty * bootstrapped, and making sure that TASTY can link against a compiled * version of Dotty diff --git a/sbt-bridge/src/xsbt/CompilerClassLoader.scala b/sbt-bridge/src/xsbt/CompilerClassLoader.scala index 3cb3f344f..071141dcf 100644 --- a/sbt-bridge/src/xsbt/CompilerClassLoader.scala +++ b/sbt-bridge/src/xsbt/CompilerClassLoader.scala @@ -2,6 +2,8 @@ package xsbt import java.net.{URL, URLClassLoader} +import scala.collection.mutable + /** A classloader to run the compiler * * A CompilerClassLoader is constructed from a list of `urls` that need to be on @@ -42,6 +44,14 @@ class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader) } object CompilerClassLoader { + /** Cache the result of `fixBridgeLoader`. + * + * Reusing ClassLoaders is important for warm performance since otherwise the + * JIT code cache for the compiler will be discarded between every call to + * the sbt `compile` task. + */ + private[this] val fixedLoaderCache = new mutable.WeakHashMap[ClassLoader, ClassLoader] + /** Fix the compiler bridge ClassLoader * * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs @@ -70,7 +80,11 @@ object CompilerClassLoader { * @param bridgeLoader The classloader that sbt uses to load the compiler bridge * @return A fixed classloader that works with dotty */ - def fixBridgeLoader(bridgeLoader: ClassLoader) = bridgeLoader match { + def fixBridgeLoader(bridgeLoader: ClassLoader): ClassLoader = synchronized { + fixedLoaderCache.getOrElseUpdate(bridgeLoader, computeFixedLoader(bridgeLoader)) + } + + private[this] def computeFixedLoader(bridgeLoader: ClassLoader) = bridgeLoader match { case bridgeLoader: URLClassLoader => val dualLoader = bridgeLoader.getParent val dualLoaderClass = dualLoader.getClass diff --git a/tests/run/if-with-constant-cond.check b/tests/run/if-with-constant-cond.check new file mode 100644 index 000000000..2e31d4f23 --- /dev/null +++ b/tests/run/if-with-constant-cond.check @@ -0,0 +1,4 @@ +cond1 +then1 +cond2 +else2 diff --git a/tests/run/if-with-constant-cond.scala b/tests/run/if-with-constant-cond.scala new file mode 100644 index 000000000..db5959c84 --- /dev/null +++ b/tests/run/if-with-constant-cond.scala @@ -0,0 +1,9 @@ + +object Test { + + def main(args: Array[String]): Unit = { + if ({ println("cond1"); true }: true) println("then1") else println("else1") + if ({ println("cond2"); false }: false) println("then2") else println("else2") + } + +}
\ No newline at end of file diff --git a/tests/run/inline-constant-in-constructor-1.check b/tests/run/inline-constant-in-constructor-1.check new file mode 100644 index 000000000..05035c26d --- /dev/null +++ b/tests/run/inline-constant-in-constructor-1.check @@ -0,0 +1,3 @@ +assert +s +r init diff --git a/tests/run/inline-constant-in-constructor-1.scala b/tests/run/inline-constant-in-constructor-1.scala new file mode 100644 index 000000000..11a727fd6 --- /dev/null +++ b/tests/run/inline-constant-in-constructor-1.scala @@ -0,0 +1,15 @@ + + +abstract class A { + def s: Boolean = { println("s"); r } + def r: Boolean +} + +object Test extends A { + assert({ println("assert"); r == s }) // r constant type not replaced by true, r not initialized yet + override val r: true = { + println("r init") + true + } + def main(args: Array[String]): Unit = {} +} diff --git a/tests/run/inline-constant-in-constructor-2.check b/tests/run/inline-constant-in-constructor-2.check new file mode 100644 index 000000000..05035c26d --- /dev/null +++ b/tests/run/inline-constant-in-constructor-2.check @@ -0,0 +1,3 @@ +assert +s +r init diff --git a/tests/run/inline-constant-in-constructor-2.scala b/tests/run/inline-constant-in-constructor-2.scala new file mode 100644 index 000000000..a8d351ab6 --- /dev/null +++ b/tests/run/inline-constant-in-constructor-2.scala @@ -0,0 +1,14 @@ + +abstract class A { + def s: Boolean = { println("s"); r } + def r: Boolean +} + +object Test extends A { + assert({ println("assert"); r == s }) // r constant type replaced by false + override val r: false = { + println("r init") + false + } + def main(args: Array[String]): Unit = {} +} diff --git a/tests/run/inline-constant-in-constructor-3.check b/tests/run/inline-constant-in-constructor-3.check new file mode 100644 index 000000000..b0171e3d9 --- /dev/null +++ b/tests/run/inline-constant-in-constructor-3.check @@ -0,0 +1,4 @@ +assert +r2 +s +r init diff --git a/tests/run/inline-constant-in-constructor-3.scala b/tests/run/inline-constant-in-constructor-3.scala new file mode 100644 index 000000000..621ace231 --- /dev/null +++ b/tests/run/inline-constant-in-constructor-3.scala @@ -0,0 +1,19 @@ + + +abstract class A { + def s: Boolean = { println("s"); r } + def r: Boolean +} + +object Test extends A { + assert({ println("assert"); r2 != s }) // s not initialized yet + def r2: true = { + println("r2") + true + } + override val r: true = { + println("r init") + true + } + def main(args: Array[String]): Unit = {} +} diff --git a/tests/run/inline-constant-lazy-val.check b/tests/run/inline-constant-lazy-val.check new file mode 100644 index 000000000..16858db7a --- /dev/null +++ b/tests/run/inline-constant-lazy-val.check @@ -0,0 +1,2 @@ +X +Y diff --git a/tests/run/inline-constant-lazy-val.scala b/tests/run/inline-constant-lazy-val.scala new file mode 100644 index 000000000..5f4aaa12b --- /dev/null +++ b/tests/run/inline-constant-lazy-val.scala @@ -0,0 +1,10 @@ + +object Test { + lazy val x: true = { println("X"); true } + + def main(args: Array[String]): Unit = { + lazy val y: true = { println("Y"); true } + x + y + } +}
\ No newline at end of file |