From df3689139c4d4bcd2933364d13b8195c3433eb43 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 14 Jul 2016 16:52:20 -0700 Subject: Fields phase expands lazy vals like modules They remain ValDefs until then. - remove lazy accessor logic now that we have a single ValDef for lazy vals, with the underlying machinery being hidden until the fields phase leave a `@deprecated def lazyAccessor` for scala-refactoring - don't skolemize in purely synthetic getters, but *do* skolemize in lazy accessor during typers Lazy accessors have arbitrary user code, so have to skolemize. We exempt the purely synthetic accessors (`isSyntheticAccessor`) for strict vals, and lazy accessors emitted by the fields phase to avoid spurious type mismatches due to issues with existentials (That bug is tracked as https://github.com/scala/scala-dev/issues/165) When we're past typer, lazy accessors are synthetic, but before they are user-defined to make this hack less hacky, we could rework our flag usage to allow for requiring both the ACCESSOR and the SYNTHETIC bits to identify synthetic accessors and trigger the exemption. see also https://github.com/scala/scala-dev/issues/165 ok 7 - pos/existentials-harmful.scala ok 8 - pos/t2435.scala ok 9 - pos/existentials.scala previous attempt: skolemize type of val inside the private[this] val because its type is only observed from inside the accessor methods (inside the method scope its existentials are skolemized) - bean accessors have regular method types, not nullary method types - must re-infer type for param accessor some weirdness with scoping of param accessor vals and defs? - tailcalls detect lazy vals, which are defdefs after fields - can inline constant lazy val from trait - don't mix in fields etc for an overridden lazy val - need try-lift in lazy vals: the assign is not seen in uncurry because fields does the transform (see run/t2333.scala) - ensure field members end up final in bytecode - implicit class companion method: annot filter in completer - update check: previous error message was tangled up with unrelated field definitions (`var s` and `val s_scope`), now it behaves consistently whether those are val/vars or defs - analyzer plugin check update seems benign, but no way to know... - error message gen: there is no underlying symbol for a deferred var look for missing getter/setter instead - avoid retypechecking valdefs while duplicating for specialize see pos/spec-private - Scaladoc uniformly looks to field/accessor symbol - test updates to innerClassAttribute by Lukas --- .../scala/tools/nsc/typechecker/Namers.scala | 149 +++++++++++---------- 1 file changed, 81 insertions(+), 68 deletions(-) (limited to 'src/compiler/scala/tools/nsc/typechecker/Namers.scala') diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 99c1b6991e..033b6cc0cd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -129,6 +129,7 @@ trait Namers extends MethodSynthesis { !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter !isEnumConstant(vd) // enums can only occur in classes, so only check here + /** Determines whether this field holds an enum constant. * To qualify, the following conditions must be met: * - The field's class has the ENUM flag set @@ -803,86 +804,89 @@ trait Namers extends MethodSynthesis { import AnnotationInfo.{mkFilter => annotationFilter} - def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { sym => - val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter annotationFilter(FieldTargetClass, !tree.mods.isParamAccessor) + def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - sym setInfo typeSig(tree, annots) + val annotations = classSym.initialize.annotations - validate(sym) + methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - // println(s"triaging for ${sym.debugFlagString} $sym from $valAnnots to $annots") - - // typeSig calls valDefSig (because tree: ValDef) - // sym is an accessor, while tree is the field (which may have the same symbol as the getter, or maybe it's the field) - // TODO: can we make this work? typeSig is called on same tree (valdef) to complete info for field and all its accessors - // reuse work done in valTypeCompleter if we already computed the type signature of the val - // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) -// val valSig = -// if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info -// else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig - - val valSig = typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig - - val sig = accessorSigFromFieldTp(sym, isSetter, valSig) + // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, + // as specified by Field.noFieldFor) + def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => val mods = tree.mods - if (mods.annotations.nonEmpty) { - val annotSigs = annotSig(mods.annotations) - - // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis - // (TODO: can we look at symbols earlier?) - if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) - && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) - BeanPropertyAnnotationLimitationError(tree) + val isGetter = fieldOrGetterSym.isMethod + val annots = + if (mods.annotations.isEmpty) Nil + else { + val annotSigs = annotSig(mods.annotations) + if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter + else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor) + } - sym setAnnotations (annotSigs filter filterAccessorAnnotations(isSetter)) - } + // must use typeSig, not memberSig (TODO: when do we need to switch namers?) + val sig = typeSig(tree, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) - validate(sym) + validate(fieldOrGetterSym) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def beanAccessorTypeCompleter(tree: ValDef, missingTpt: Boolean, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - context.unit.synthetics get sym match { + // knowing `isBean`, we could derive `isSetter` from `valDef.name` + def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = mkTypeCompleter(valDef) { accessorSym => + context.unit.synthetics get accessorSym match { case Some(ddef: DefDef) => - // sym is an accessor, while tree is the field (for traits it's actually the getter, and we're completing the setter) + // `accessorSym` is the accessor for which we're completing the info (tree == ddef), + // while `valDef` is the field definition that spawned the accessor + // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol + // reuse work done in valTypeCompleter if we already computed the type signature of the val // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) val valSig = - if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info - else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info + else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized - if (missingTpt) { // can't look at tree.tpt here because it may have been completed by now + // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) + // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... + // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) + if (missingTpt || accessorSym.isParamAccessor) { if (!isSetter) ddef.tpt setType valSig else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig - else throw new TypeError(tree.pos, s"Internal error: could not complete parameter/return type for $ddef from $sym") + else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") } + val mods = valDef.mods val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter filterBeanAccessorAnnotations(isSetter) + if (mods.annotations.isEmpty) Nil + else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) - val sig = typeSig(ddef, annots) + // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) + // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) + val sig = + if (isSetter || isBean) typeSig(ddef, annots) + else { + if (annots.nonEmpty) annotate(accessorSym, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + NullaryMethodType(valSig) + } + + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - validate(sym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) + + validate(accessorSym) case _ => - throw new TypeError(tree.pos, s"Internal error: no synthetic tree found for bean accessor $sym") + throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") } - } - // see scala.annotation.meta's package class for more info // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. // The defaults are: @@ -893,24 +897,33 @@ trait Namers extends MethodSynthesis { // // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) - private def filterAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) - - private def filterBeanAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) - - - private def accessorSigFromFieldTp(sym: Symbol, isSetter: Boolean, tp: Type): Type = - if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) - else NullaryMethodType(tp) + private def filterAccessorAnnots(annotSigs: List[global.AnnotationInfo], tree: global.ValDef, isSetter: Boolean = false, isBean: Boolean = false): List[AnnotationInfo] = { + val mods = tree.mods + if (!isBean) { + // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis + // (TODO: can we look at symbols earlier?) + if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) + && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) + BeanPropertyAnnotationLimitationError(tree) + } + + def filterAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) + + def filterBeanAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + + annotSigs filter (if (isBean) filterBeanAccessorAnnotations else filterAccessorAnnotations) + } + def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe -- cgit v1.2.3 From 63f7b357f787b96ddf0440c779b1937844cbbb52 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 12 Aug 2016 15:50:57 -0700 Subject: SI-8873 don't propagate primary ctor arg to case class's apply Final implementation based on feedback by Jason --- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 11 +++++++++++ src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 1 + test/files/pos/t8873.scala | 1 + 3 files changed, 13 insertions(+) create mode 100644 test/files/pos/t8873.scala (limited to 'src/compiler/scala/tools/nsc/typechecker/Namers.scala') diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 033b6cc0cd..fe7a4d5fef 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -686,6 +686,8 @@ trait Namers extends MethodSynthesis { if (name == nme.copy && sym.isSynthetic) enterCopyMethod(tree) + else if (name == nme.apply && sym.hasAllFlags(SYNTHETIC | CASE)) + sym setInfo caseApplyMethodCompleter(tree, completerOf(tree).asInstanceOf[LockingTypeCompleter]) else sym setInfo completerOf(tree) } @@ -813,6 +815,15 @@ trait Namers extends MethodSynthesis { classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } + def caseApplyMethodCompleter(tree: DefDef, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) + + // don't propagate e.g. @volatile annot to apply's argument + def retainOnlyParamAnnots(param: Symbol) = + param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false)) + + methSym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) + } // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, // as specified by Field.noFieldFor) diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 22fb0728e6..f2e9b260b0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -128,6 +128,7 @@ trait Unapplies extends ast.TreeDSL { */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { val tparams = constrTparamsInvariant(cdef) + val cparamss = constrParamss(cdef) def classtpe = classType(cdef, tparams) atPos(cdef.pos.focus)( diff --git a/test/files/pos/t8873.scala b/test/files/pos/t8873.scala new file mode 100644 index 0000000000..e2f0a5fad2 --- /dev/null +++ b/test/files/pos/t8873.scala @@ -0,0 +1 @@ +case class X(@volatile var x:Int) -- cgit v1.2.3 From b2e0911ded57f33e9600c6337a307c0fe1877eff Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 19 Aug 2016 11:07:00 -0700 Subject: Fields does bitmaps & synch for lazy vals & modules Essentially, we fuse mixin and lazyvals into the fields phase. With fields mixing in trait members into subclasses, we have all info needed to compute bitmaps, and thus we can synthesize the synchronisation logic as well. By doing this before erasure we get better signatures, and before specialized means specialized lazy vals work now. Mixins is now almost reduced to its essence: implementing super accessors and forwarders. It still synthesizes accessors for param accessors and early init trait vals. Concretely, trait lazy vals are mixed into subclasses with the needed synchronization logic in place, as do lazy vals in classes and methods. Similarly, modules are initialized using double checked locking. Since the code to initialize a module is short, we do not emit compute methods for modules (anymore). For simplicity, local lazy vals do not get a compute method either. The strange corner case of constant-typed final lazy vals is resolved in favor of laziness, by no longer assigning a constant type to a lazy val (see widenIfNecessary in namers). If you explicitly ask for something lazy, you get laziness; with the constant-typedness implicit, it yields to the conflicting `lazy` modifier because it is explicit. Co-Authored-By: Lukas Rytz Fixes scala/scala-dev#133 Inspired by dotc, desugar a local `lazy val x = rhs` into ``` val x$lzy = new scala.runtime.LazyInt() def x(): Int = { x$lzy.synchronized { if (!x$lzy.initialized) { x$lzy.initialized = true x$lzy.value = rhs } x$lzy.value } } ``` Note that the 2.11 decoding (into a local variable and a bitmap) also creates boxes for local lazy vals, in fact two for each lazy val: ``` def f = { lazy val x = 0 x } ``` desugars to ``` public int f() { IntRef x$lzy = IntRef.zero(); VolatileByteRef bitmap$0 = VolatileByteRef.create((byte)0); return this.x$1(x$lzy, bitmap$0); } private final int x$lzycompute$1(IntRef x$lzy$1, VolatileByteRef bitmap$0$1) { C c = this; synchronized (c) { if ((byte)(bitmap$0$1.elem & 1) == 0) { x$lzy$1.elem = 0; bitmap$0$1.elem = (byte)(bitmap$0$1.elem | 1); } return x$lzy$1.elem; } } private final int x$1(IntRef x$lzy$1, VolatileByteRef bitmap$0$1) { return (byte)(bitmap$0$1.elem & 1) == 0 ? this.x$lzycompute$1(x$lzy$1, bitmap$0$1) : x$lzy$1.elem; } ``` An additional problem with the above encoding is that the `lzycompute` method synchronizes on `this`. In connection with the new lambda encoding that no longer generates anonymous classes, captured lazy vals no longer synchronize on the lambda object. The new encoding solves this problem (scala/scala-dev#133) by synchronizing on the lazy holder. Currently, we don't exploit the fact that the initialized field is `@volatile`, because it's not clear the performance is needed for local lazy vals (as they are not contended, and as soon as the VM warms up, biased locking should deal with that) Note, be very very careful when moving to double-checked locking, as this needs a different variation than the one we use for class-member lazy vals. A read of a volatile field of a class does not necessarily impart any knowledge about a "subsequent" read of another non-volatile field of the same object. A pair of volatile reads and write can be used to implement a lock, but it's not clear if the complexity is worth an unproven performance gain. (Once the performance gain is proven, let's change the encoding.) - don't explicitly init bitmap in bytecode - must apply method to () explicitly after uncurry --- project/ScalaOptionParser.scala | 2 +- src/compiler/scala/tools/ant/Scalac.scala | 2 +- src/compiler/scala/tools/nsc/Global.scala | 12 +- .../scala/tools/nsc/transform/Constructors.scala | 19 +- .../scala/tools/nsc/transform/Fields.scala | 246 +++++---- .../scala/tools/nsc/transform/LazyVals.scala | 310 ------------ src/compiler/scala/tools/nsc/transform/Mixin.scala | 561 ++++++++++----------- .../tools/nsc/transform/SpecializeTypes.scala | 11 +- .../scala/tools/nsc/typechecker/Namers.scala | 2 +- src/library/scala/runtime/LazyRef.scala | 52 ++ src/manual/scala/man1/scalac.scala | 4 +- .../scala/reflect/internal/Definitions.scala | 5 +- src/reflect/scala/reflect/internal/StdNames.scala | 1 + src/reflect/scala/reflect/internal/Symbols.scala | 2 +- .../scala/reflect/runtime/JavaUniverseForce.scala | 3 + src/repl/scala/tools/nsc/interpreter/Phased.scala | 4 +- test/files/neg/t6446-additional.check | 21 +- test/files/neg/t6446-missing.check | 19 +- test/files/neg/t6446-show-phases.check | 19 +- test/files/neg/t7494-no-options.check | 21 +- test/files/run/analyzerPlugins.check | 4 +- test/files/run/delambdafy_t6028.check | 6 +- test/files/run/lazy-locals-2.scala | 322 ++++++++++++ test/files/run/lazy_local_labels.check | 9 + test/files/run/lazy_local_labels.scala | 28 + test/files/run/programmatic-main.check | 19 +- test/files/run/t3569.check | 2 + test/files/run/t3569.scala | 8 +- test/files/run/t5552.check | 4 + test/files/run/t5552.scala | 10 +- test/files/run/t6028.check | 6 +- .../nsc/backend/jvm/opt/ScalaInlineInfoTest.scala | 3 - 32 files changed, 940 insertions(+), 797 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/LazyVals.scala create mode 100644 src/library/scala/runtime/LazyRef.scala create mode 100644 test/files/run/lazy-locals-2.scala create mode 100644 test/files/run/lazy_local_labels.check create mode 100644 test/files/run/lazy_local_labels.scala (limited to 'src/compiler/scala/tools/nsc/typechecker/Namers.scala') diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala index 77c9d765e9..af82f8fce5 100644 --- a/project/ScalaOptionParser.scala +++ b/project/ScalaOptionParser.scala @@ -94,7 +94,7 @@ object ScalaOptionParser { private def stringSettingNames = List("-Xgenerate-phase-graph", "-Xmain-class", "-Xpluginsdir", "-Xshow-class", "-Xshow-object", "-Xsource-reader", "-Ydump-classes", "-Ygen-asmp", "-Ypresentation-log", "-Ypresentation-replay", "-Yrepl-outdir", "-d", "-dependencyfile", "-encoding", "-Xscript") private def pathSettingNames = List("-bootclasspath", "-classpath", "-extdirs", "-javabootclasspath", "-javaextdirs", "-sourcepath", "-toolcp") - private val phases = List("all", "parser", "namer", "packageobjects", "typer", "patmat", "superaccessors", "extmethods", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "posterasure", "lazyvals", "lambdalift", "constructors", "flatten", "mixin", "cleanup", "delambdafy", "icode", "jvm", "terminal") + private val phases = List("all", "parser", "namer", "packageobjects", "typer", "patmat", "superaccessors", "extmethods", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "posterasure", "fields", "lambdalift", "constructors", "flatten", "mixin", "cleanup", "delambdafy", "icode", "jvm", "terminal") private val phaseSettings = List("-Xprint-icode", "-Ystop-after", "-Yskip", "-Yshow", "-Ystop-before", "-Ybrowse", "-Ylog", "-Ycheck", "-Xprint") private def multiStringSettingNames = List("-Xmacro-settings", "-Xplugin", "-Xplugin-disable", "-Xplugin-require") private def intSettingNames = List("-Xmax-classfile-name", "-Xelide-below", "-Ypatmat-exhaust-depth", "-Ypresentation-delay", "-Yrecursion") diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index e9d1dfe4d2..511572f6f3 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -88,7 +88,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { object CompilerPhase extends PermissibleValue { val values = List("namer", "typer", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", - "erasure", "lazyvals", "lambdalift", "constructors", + "erasure", "fields", "lambdalift", "constructors", "flatten", "mixin", "delambdafy", "cleanup", "jvm", "terminal") } diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index af866e1a6f..32c446e16a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -516,17 +516,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = Some("erasure") } with PostErasure - // phaseName = "lazyvals" - object lazyVals extends { - val global: Global.this.type = Global.this - val runsAfter = List("erasure") - val runsRightAfter = None - } with LazyVals // phaseName = "lambdalift" object lambdaLift extends { val global: Global.this.type = Global.this - val runsAfter = List("lazyvals") + val runsAfter = List("erasure") val runsRightAfter = None } with LambdaLift @@ -620,13 +614,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter) pickler -> "serialize symbol tables", refChecks -> "reference/override checking, translate nested objects", uncurry -> "uncurry, translate function values to anonymous classes", - fields -> "synthesize accessors and fields", + fields -> "synthesize accessors and fields, including bitmaps for lazy vals", tailCalls -> "replace tail calls by jumps", specializeTypes -> "@specialized-driven class and method specialization", explicitOuter -> "this refs to outer pointers", erasure -> "erase types, add interfaces for traits", postErasure -> "clean up erased inline classes", - lazyVals -> "allocate bitmaps, translate lazy vals into lazified defs", lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", @@ -1258,7 +1251,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val explicitouterPhase = phaseNamed("explicitouter") val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") - // val lazyvalsPhase = phaseNamed("lazyvals") val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 0a87e358b4..8d362f13dd 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -450,7 +450,9 @@ abstract class Constructors extends Statics with Transform with TypingTransforme with DelayedInitHelper with OmittablesHelper with GuardianOfCtorStmts - { + with fields.CheckedAccessorTreeSynthesis + { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) val clazz = impl.symbol.owner // the transformed class @@ -770,11 +772,20 @@ abstract class Constructors extends Statics with Transform with TypingTransforme // We never eliminate delayed hooks or the constructors, so, only filter `defs`. val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors + val statsWithInitChecks = + if (settings.checkInit) { + val addChecks = new SynthInitCheckedAccessorsIn(currentOwner) + prunedStats mapConserve { + case dd: DefDef => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol)) + case stat => stat + } + } else prunedStats + // Add the static initializers - if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats) + if (classInitStats.isEmpty) deriveTemplate(impl)(_ => statsWithInitChecks) else { - val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats) - deriveTemplate(impl)(_ => staticCtor :: prunedStats) + val staticCtor = staticConstructor(statsWithInitChecks, localTyper, impl.pos)(classInitStats) + deriveTemplate(impl)(_ => staticCtor :: statsWithInitChecks) } } } // TemplateTransformer diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 6339f0002d..0b8705948c 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -56,8 +56,7 @@ import symtab.Flags._ * * TODO: check init support (or drop the -Xcheck-init flag??) */ -abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers { - +abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers with AccessorSynthesis { import global._ import definitions._ @@ -139,13 +138,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor class FieldMemoization(accessorOrField: Symbol, site: Symbol) { val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType) - // not stored, no side-effect - val pureConstant = tp.isInstanceOf[ConstantType] - - // if !stored, may still have a side-effect - // (currently not distinguished -- used to think we could drop unit-typed vals, - // but the memory model cares about writes to unit-typed fields) - val stored = !pureConstant // || isUnitType(tp)) + // We can only omit strict vals of ConstantType. Lazy vals do not receive constant types (anymore). + // (See note at widenIfNecessary -- for example, the REPL breaks when we omit constant lazy vals) + // Note that a strict unit-typed val does receive a field, because we cannot omit the write to the field + // (well, we could emit it for non-@volatile ones, if I understand the memory model correctly, + // but that seems pretty edge-casey) + val constantTyped = tp.isInstanceOf[ConstantType] } private def fieldTypeForGetterIn(getter: Symbol, pre: Type): Type = getter.info.finalResultType.asSeenFrom(pre, getter.owner) @@ -187,31 +185,29 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor private def moduleInit(module: Symbol) = { // println(s"moduleInit for $module in ${module.ownerChain} --> ${moduleVarOf.get(module)}") val moduleVar = moduleVarOf(module) - gen.mkAssignAndReturn(moduleVar, gen.newModule(module, moduleVar.info)) + def moduleVarRef = gen.mkAttributedRef(moduleVar) + + // for local modules, we synchronize on the owner of the method that owns the module + val monitorHolder = This(moduleVar.owner.enclClass) + val cond = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) + val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info)) + + Block(List(gen.mkSynchronizedCheck(monitorHolder, cond, List(init), Nil)), moduleVarRef) } + // NoSymbol for lazy accessor sym with unit result type + def lazyVarOf(sym: Symbol) = moduleVarOf.getOrElse(sym, NoSymbol) - private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0, localLazyVal: Boolean = false): TermSymbol = { + private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0): TermSymbol = { val flags = member.flags | extraFlags - val name = member.name.toTermName - val pos = member.pos - - // If the owner is not a class, this is a lazy val from a method, - // with no associated field. It has an accessor with $lzy appended to its name and - // its flags are set differently. The implicit flag is reset because otherwise - // a local implicit "lazy val x" will create an ambiguity with itself - // via "x$lzy" as can be seen in test #3927. - val nameSuffix = - if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING - else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - // TODO: should end up final in bytecode - val fieldFlags = - if (!localLazyVal) flags & FieldFlags | PrivateLocal | MUTABLE - else (flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) - -// println(s"new lazy var sym in $owner for $member ${symtab.Flags.flagsToString(fieldFlags)}") - val sym = owner.newValue(name.append(nameSuffix), pos.focus, fieldFlags | extraFlags) setInfo tp + val pos = member.pos + val name = member.name.toTermName.append(reflect.NameTransformer.LOCAL_SUFFIX_STRING) + + // the underlying field for a lazy val should not be final because we write to it outside of a constructor, + // so, set the MUTABLE flag + val fieldFlags = flags & FieldFlags | PrivateLocal | MUTABLE + + val sym = owner.newValue(name, pos.focus, fieldFlags | extraFlags) setInfo tp moduleVarOf(member) = sym sym } @@ -292,7 +288,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags // this accessor has to be implemented in a subclass -- can't be private - if ((member hasFlag PRIVATE) && fieldMemoization.stored) member makeNotPrivate clazz + if ((member hasFlag PRIVATE) && !fieldMemoization.constantTyped) member makeNotPrivate clazz // This must remain in synch with publicizeTraitMethod in Mixins, so that the // synthesized member in a subclass and the trait member remain in synch regarding access. @@ -304,7 +300,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // (not sure why this only problem only arose when we started setting the notPROTECTED flag) // derive trait setter after calling makeNotPrivate (so that names are mangled consistently) - if (accessorUnderConsideration && fieldMemoization.stored) { + if (accessorUnderConsideration && !fieldMemoization.constantTyped) { synthesizeImplInSubclasses(member) if ((member hasFlag STABLE) && !(member hasFlag LAZY)) @@ -347,19 +343,18 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // a module does not need treatment here if it's static, unless it has a matching member in a superclass // a non-static method needs a module var val modulesAndLazyValsNeedingExpansion = - oldDecls.toList.filter(m => - (m.isModule && (!m.isStatic || m.isOverridingSymbol)) - || (m.isLazy && !(m.info.isInstanceOf[ConstantType] || isUnitType(m.info))) // no need for ASF since we're in the defining class - ) + oldDecls.toList.filter(m => (m.isModule && (!m.isStatic || m.isOverridingSymbol)) || m.isLazy) + + val accessorSymbolSynth = checkedAccessorSymbolSynth(tp.typeSymbol) // expand module def in class/object (if they need it -- see modulesNeedingExpansion above) - val expandedModulesAndLazyVals = - modulesAndLazyValsNeedingExpansion map { member => + val expandedModulesAndLazyVals = ( + modulesAndLazyValsNeedingExpansion flatMap { member => if (member.isLazy) { - newLazyVarMember(member) + List(newLazyVarMember(member), accessorSymbolSynth.newSlowPathSymbol(member)) } // expanding module def (top-level or nested in static module) - else if (member.isStatic) { // implies m.isOverridingSymbol as per above filter + else List(if (member.isStatic) { // implies m.isOverridingSymbol as per above filter // Need a module accessor, to implement/override a matching member in a superclass. // Never a need for a module var if the module is static. newMatchingModuleAccessor(clazz, member) @@ -368,8 +363,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // must reuse symbol instead of creating an accessor member setFlag NEEDS_TREES newModuleVarMember(member) - } - } + }) + }) // println(s"expanded modules for $clazz: $expandedModules") @@ -400,7 +395,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (nme.isTraitSetterName(member.name)) { val getter = member.getterIn(member.owner) val clone = cloneAccessor() - + setClonedTraitSetterFlags(clazz, getter, clone) // println(s"mixed in trait setter ${clone.defString}") @@ -416,9 +411,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else if (member hasFlag LAZY) { val mixedinLazy = cloneAccessor() val lazyVar = newLazyVarMember(mixedinLazy) - List(lazyVar, newSuperLazy(mixedinLazy, site, lazyVar)) + // println(s"mixing in lazy var: $lazyVar for $member") + List(lazyVar, accessorSymbolSynth.newSlowPathSymbol(mixedinLazy), newSuperLazy(mixedinLazy, site, lazyVar)) } - else if (member.isGetter && fieldMemoizationIn(member, clazz).stored) { + else if (member.isGetter && !fieldMemoizationIn(member, clazz).constantTyped) { // add field if needed val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType) @@ -432,13 +428,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } else List(cloneAccessor()) // no field needed (constant-typed getter has constant as its RHS) } -// println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") + // println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") // omit fields that are not memoized, retain all other members - def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && !fieldMemoizationIn(sym, clazz).stored + def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && fieldMemoizationIn(sym, clazz).constantTyped val newDecls = - if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + // under -Xcheckinit we generate all kinds of bitmaps, even when there are no lazy vals + if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty && !settings.checkInit) + oldDecls.filterNot(omittableField) else { // must not alter `decls` directly val newDecls = newScope @@ -449,10 +447,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor oldDecls foreach { d => if (!omittableField(d)) enter(d) } mixedInAccessorAndFields foreach enterAll + // both oldDecls and mixedInAccessorAndFields (a list of lists) contribute + val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.toList) + + bitmapSyms foreach enter + newDecls } -// println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") + // println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") if (newDecls eq oldDecls) tp else ClassInfoType(parents, newDecls, clazz) @@ -468,50 +471,109 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (!module.isStatic) module setFlag METHOD | STABLE } - class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - def mkTypedUnit(pos: Position) = localTyper.typedPos(pos)(CODE.UNIT) - def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) + class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with CheckedAccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) - def mkAccessor(accessor: Symbol)(body: Tree) = localTyper.typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + def mkTypedUnit(pos: Position) = typedPos(pos)(CODE.UNIT) + // TODO: clean up. this method is not used + def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) - def mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + def mkAccessor(accessor: Symbol)(body: Tree) = typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + + // this makes trees for mixed in fields, as well as for bitmap fields (their RHS will be EmptyTree because they are initialized implicitly) + // if we decide to explicitly initialize, use this RHS: if (symbol.info.typeSymbol.asClass == BooleanClass) FALSE else ZERO) + // could detect it's a bitmap field with something like `sym.name.startsWith(nme.BITMAP_PREFIX)` (or perhaps something more robust...) + def mkField(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef] + + /** + * Desugar a local `lazy val x: Int = rhs` into + * ``` + * val x$lzy = new scala.runtime.LazyInt() + * def x(): Int = + * x$lzy.synchronized { + * if (!x$lzy.initialized) { + * x$lzy.initialized = true + * x$lzy.value = rhs + * } + * x$lzy.value + * } + * ``` + */ + private def mkLazyLocalDef(lazyVal: Symbol, rhs: Tree): Tree = { + val lazyValType = lazyVal.tpe.resultType + val refClass = lazyHolders.getOrElse(lazyValType.typeSymbol, LazyRefClass) + val refTpe = if (refClass != LazyRefClass) refClass.tpe else appliedType(refClass.typeConstructor, List(lazyValType)) + + val flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE??? + val name = lazyVal.name.toTermName.append(nme.LAZY_LOCAL_SUFFIX_STRING) + val holderSym = + lazyVal.owner.newValue(name, lazyVal.pos, flags) setInfo refTpe + + val accessor = mkAccessor(lazyVal) { + import CODE._ + val initializedGetter = refTpe.member(nme.initialized) + val setInitialized = Apply(Select(Ident(holderSym), initializedGetter.setterIn(refClass)), TRUE :: Nil) + val isUnit = refClass == LazyUnitClass + val valueGetter = if (isUnit) NoSymbol else refTpe.member(nme.value) + val valueSetter = if (isUnit) NoSymbol else valueGetter.setterIn(refClass) + val setValue = if (isUnit) rhs else Apply(Select(Ident(holderSym), valueSetter), rhs :: Nil) + val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) + gen.mkSynchronized(Ident(holderSym), + Block(List( + If(NOT(Ident(holderSym) DOT initializedGetter), + Block(List( + setInitialized), + setValue), + EmptyTree)), + getValue)) // must read the value within the synchronized block since it's not volatile + // (there's no happens-before relation with the read of the volatile initialized field) + // TODO: double-checked locking + } + // do last! + // remove LAZY: prevent lazy expansion in mixin + // remove STABLE: prevent replacing accessor call of type Unit by BoxedUnit.UNIT in erasure + // remove ACCESSOR: prevent constructors from eliminating the method body if the lazy val is + // lifted into a trait (TODO: not sure about the details here) + lazyVal.resetFlag(LAZY | STABLE | ACCESSOR) + Thicket(mkField(holderSym, New(refTpe)) :: accessor :: Nil) + } // synth trees for accessors/fields and trait setters when they are mixed into a class def fieldsAndAccessors(clazz: Symbol): List[ValOrDefDef] = { - def fieldAccess(accessor: Symbol): Option[Tree] = { + def fieldAccess(accessor: Symbol): List[Tree] = { val fieldName = accessor.localName val field = clazz.info.decl(fieldName) // The `None` result denotes an error, but it's refchecks' job to report it (this fallback is for robustness). // This is the result of overriding a val with a def, so that no field is found in the subclass. - if (field.exists) Some(Select(This(clazz), field)) - else None + if (field.exists) List(Select(This(clazz), field)) + else Nil } - def getterBody(getter: Symbol): Option[Tree] = { + def getterBody(getter: Symbol): List[Tree] = { // accessor created by newMatchingModuleAccessor for a static module that does need an accessor // (because there's a matching member in a super class) if (getter.asTerm.referenced.isModule) { - Some(gen.mkAttributedRef(clazz.thisType, getter.asTerm.referenced)) + List(gen.mkAttributedRef(clazz.thisType, getter.asTerm.referenced)) } else { val fieldMemoization = fieldMemoizationIn(getter, clazz) - if (fieldMemoization.pureConstant) Some(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode + if (fieldMemoization.constantTyped) List(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode else fieldAccess(getter) } } // println(s"accessorsAndFieldsNeedingTrees for $templateSym: $accessorsAndFieldsNeedingTrees") - def setterBody(setter: Symbol): Option[Tree] = { + def setterBody(setter: Symbol): List[Tree] = { // trait setter in trait - if (clazz.isTrait) Some(EmptyTree) + if (clazz.isTrait) List(EmptyTree) // trait setter for overridden val in class - else if (checkAndClearOverriddenTraitSetter(setter)) Some(mkTypedUnit(setter.pos)) + else if (checkAndClearOverriddenTraitSetter(setter)) List(mkTypedUnit(setter.pos)) // trait val/var setter mixed into class else fieldAccess(setter) map (fieldSel => Assign(fieldSel, Ident(setter.firstParam))) } - def moduleAccessorBody(module: Symbol): Some[Tree] = Some( + def moduleAccessorBody(module: Symbol): List[Tree] = List( // added during synthFieldsAndAccessors using newModuleAccessor // a module defined in a trait by definition can't be static (it's a member of the trait and thus gets a new instance for every outer instance) if (clazz.isTrait) EmptyTree @@ -519,15 +581,17 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else moduleInit(module) ) - def superLazy(getter: Symbol): Some[Tree] = { + val synthAccessorInClass = new SynthLazyAccessorsIn(clazz) + def superLazy(getter: Symbol): List[ValOrDefDef] = { assert(!clazz.isTrait) // this contortion was the only way I can get the super select to be type checked correctly.. TODO: why does SelectSuper not work? - Some(gen.mkAssignAndReturn(moduleVarOf(getter), Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil))) + val rhs = Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil) + explodeThicket(synthAccessorInClass.expandLazyClassMember(lazyVarOf(getter), getter, rhs, Map.empty)).asInstanceOf[List[ValOrDefDef]] } clazz.info.decls.toList.filter(checkAndClearNeedsTrees) flatMap { case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module) map mkAccessor(module) - case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter) map mkAccessor(getter) + case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter) case setter if setter.isSetter => setterBody(setter) map mkAccessor(setter) case getter if getter.hasFlag(ACCESSOR) => getterBody(getter) map mkAccessor(getter) case field if !(field hasFlag METHOD) => Some(mkField(field)) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) @@ -538,7 +602,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree = atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) - private def Thicket(trees: List[Tree]) = Block(trees, EmptyTree) override def transform(stat: Tree): Tree = { val clazz = currentOwner val statSym = stat.symbol @@ -564,37 +627,28 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor case DefDef(_, _, _, _, _, rhs) if (statSym hasFlag ACCESSOR) && (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) && !clazz.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo - && fieldMemoizationIn(statSym, clazz).pureConstant => + && fieldMemoizationIn(statSym, clazz).constantTyped => deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) - /** Normalize ValDefs to corresponding accessors + field - * - * ValDef in trait --> getter DefDef - * Lazy val receives field with a new symbol (if stored) and the ValDef's symbol is moved to a DefDef (the lazy accessor): - * - for lazy values of type Unit and all lazy fields inside traits, - * the rhs is the initializer itself, because we'll just "compute" the result on every access - * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) - * - for all other lazy values z the accessor is a block of this form: - * { z = ; z } where z can be an identifier or a field. - */ + // deferred val, trait val, lazy val (local or in class) case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, clazz) => - def notStored = {val resultType = statSym.info.resultType ; (resultType.isInstanceOf[ConstantType] || isUnitType(resultType))} val transformedRhs = atOwner(statSym)(transform(rhs)) if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) - else if (clazz.isTrait || notStored) mkAccessor(statSym)(transformedRhs) - else if (clazz.isClass) mkAccessor(statSym)(gen.mkAssignAndReturn(moduleVarOf(vd.symbol), transformedRhs)) + else if (clazz.isTrait) mkAccessor(statSym)(transformedRhs) + else if (!clazz.isClass) mkLazyLocalDef(vd.symbol, transformedRhs) else { - // local lazy val (same story as modules: info transformer doesn't get here, so can't drive tree synthesis) - val lazyVar = newLazyVarSymbol(currentOwner, statSym, statSym.info.resultType, extraFlags = 0, localLazyVal = true) - val lazyValInit = gen.mkAssignAndReturn(lazyVar, transformedRhs) - Thicket(mkField(lazyVar) :: mkAccessor(statSym)(lazyValInit) :: Nil) + // TODO: make `synthAccessorInClass` a field and update it in atOwner? + // note that `LazyAccessorTreeSynth` is pretty lightweight + // (it's just a bunch of methods that all take a `clazz` parameter, which is thus stored as a field) + val synthAccessorInClass = new SynthLazyAccessorsIn(clazz) + synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs, nullables.getOrElse(clazz, Map.empty)) } // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) - && fieldMemoizationIn(statSym, clazz).pureConstant => - EmptyTree + && fieldMemoizationIn(statSym, clazz).constantTyped => + EmptyThicket case ModuleDef(_, _, impl) => // ??? The typer doesn't take kindly to seeing this ClassDef; we have to set NoType so it will be ignored. @@ -616,22 +670,24 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (stat.isTerm) atOwner(exprOwner)(transform(stat)) else transform(stat) + private val nullables = perRunCaches.newMap[Symbol, Map[Symbol, List[Symbol]]] + override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { val addedStats = - if (!currentOwner.isClass) Nil + if (!currentOwner.isClass) Nil // TODO: || currentOwner.isPackageClass else afterOwnPhase { fieldsAndAccessors(currentOwner) } + val inRealClass = currentOwner.isClass && !(currentOwner.isPackageClass || currentOwner.isTrait) + if (inRealClass) + nullables(currentOwner) = lazyValNullables(currentOwner, stats) + val newStats = stats mapConserve (if (exprOwner != currentOwner) transformTermsAtExprOwner(exprOwner) else transform) addedStats ::: (if (newStats eq stats) stats else { // check whether we need to flatten thickets and drop empty ones - if (newStats exists { case EmptyTree => true case Block(_, EmptyTree) => true case _ => false }) - newStats flatMap { - case EmptyTree => Nil - case Block(thicket, EmptyTree) => thicket - case stat => stat :: Nil - } + if (newStats exists mustExplodeThicket) + newStats flatMap explodeThicket else newStats }) } diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala deleted file mode 100644 index ba2f6082e0..0000000000 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ /dev/null @@ -1,310 +0,0 @@ -package scala.tools.nsc -package transform - -import scala.collection.mutable - -abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL { - // inherits abstract value `global` and class `Phase` from Transform - - import global._ // the global environment - import definitions._ // standard classes and methods - import typer.typed // methods to type trees - import CODE._ - - val phaseName: String = "lazyvals" - private val FLAGS_PER_BYTE: Int = 8 // Byte - private def bitmapKind = ByteClass - - def newTransformer(unit: CompilationUnit): Transformer = - new LazyValues(unit) - - private def lazyUnit(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass - - object LocalLazyValFinder extends Traverser { - var result: Boolean = _ - - def find(t: Tree) = {result = false; traverse(t); result} - def find(ts: List[Tree]) = {result = false; traverseTrees(ts); result} - - override def traverse(t: Tree) { - if (!result) - t match { - case v@ValDef(_, _, _, _) if v.symbol.isLazy => - result = true - - case d@DefDef(_, _, _, _, _, _) if d.symbol.isLazy && lazyUnit(d.symbol) => - d.symbol.resetFlag(symtab.Flags.LAZY) - result = true - - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) => - - // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops - case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) => - - case _ => - super.traverse(t) - } - } - } - - /** - * Transform local lazy accessors to check for the initialized bit. - */ - class LazyValues(unit: CompilationUnit) extends TypingTransformer(unit) { - /** map from method symbols to the number of lazy values it defines. */ - private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 - - import symtab.Flags._ - private def flattenThickets(stats: List[Tree]): List[Tree] = stats.flatMap(_ match { - case b @ Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if b.tpe == null && n1.endsWith(nme.LAZY_SLOW_SUFFIX) => - List(d1, d2) - case stat => - List(stat) - }) - - /** Perform the following transformations: - * - for a lazy accessor inside a method, make it check the initialization bitmap - * - implement double checked locking of member modules for non-trait owners (trait just have the abstract accessor) - * ``` - * // typer - * class C { object x } - * // fields - * class C { var x$module; def x() = { x$module = new x; x$module } - * // lazyvals - * class C { - * var x$module // module var - * def x() = { if (x$module == null) x$lzycompute() else x$module // fast path - * def x$lzycompute() = { synchronized { if (x$module == null) x$module = new x }; x$module } // slow path - * } - * ``` - * - for all methods, add enough int vars to allow one flag per lazy local value - * - blocks in template bodies behave almost like methods. A single bitmaps section is - * added in the first block, for all lazy values defined in such blocks. - * - remove ACCESSOR flags: accessors in traits are not statically implemented, - * but moved to the host class. local lazy values should be statically implemented. - */ - override def transform(tree: Tree): Tree = { - val sym = tree.symbol - curTree = tree - - tree match { - - case Block(_, _) => - val block1 = super.transform(tree) - val Block(stats, expr) = block1 - treeCopy.Block(block1, flattenThickets(stats), expr) - - case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) { - val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) { - val enclosingClassOrDummyOrMethod = { - val enclMethod = sym.enclMethod - - if (enclMethod != NoSymbol) { - val enclClass = sym.enclClass - if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod) - enclClass - else - enclMethod - } else - sym.owner - } - debuglog(s"determined enclosing class/dummy/method for lazy val as $enclosingClassOrDummyOrMethod given symbol $sym") - val idx = lazyVals(enclosingClassOrDummyOrMethod) - lazyVals(enclosingClassOrDummyOrMethod) = idx + 1 - val (rhs1, sDef) = mkLazyLocalDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym) - sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR) - (rhs1, sDef) - } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) { - rhs match { - case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) => - def cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null)))) - val (fastPath, slowPath) = mkDoubleCheckedLocking(sym.owner.enclClass, moduleRef.symbol, cond, transform(assign) :: Nil, Nil, transform(expr)) - (localTyper.typedPos(tree.pos)(fastPath), localTyper.typedPos(tree.pos)(slowPath)) - case rhs => - global.reporter.error(tree.pos, "Unexpected tree on the RHS of a module accessor: " + rhs) - (rhs, EmptyTree) - } - } else { - (transform(rhs), EmptyTree) - } - - val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) - if (slowPathDef != EmptyTree) { - // The contents of this block are flattened into the enclosing statement sequence, see flattenThickets - // This is a poor man's version of dotty's Thicket: https://github.com/lampepfl/dotty/blob/d5280358d1/src/dotty/tools/dotc/ast/Trees.scala#L707 - Block(slowPathDef, ddef1) - } else ddef1 - } - - case Template(_, _, body) => atOwner(currentOwner) { - // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder? - var added = false - val stats = super.transformTrees(body) mapConserve { - case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) - case stat: TermTree if !added && (LocalLazyValFinder find stat) => - added = true - typed(addBitmapDefs(sym, stat)) - case stat => stat - } - - val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) { - // add bitmap to inner class if necessary - val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO))) - toAdd0.foreach(t => { - if (currentOwner.info.decl(t.symbol.name) == NoSymbol) { - t.symbol.setFlag(PROTECTED) - currentOwner.info.decls.enter(t.symbol) - } - }) - toAdd0 - } else List() - deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats)) - } - - case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass => - deriveValDef(tree) { rhs0 => - val rhs = transform(rhs0) - if (LocalLazyValFinder.find(rhs)) typed(addBitmapDefs(sym, rhs)) else rhs - } - - case l@LabelDef(name0, params0, ifp0@If(_, _, _)) if name0.startsWith(nme.WHILE_PREFIX) => - val ifp1 = super.transform(ifp0) - val If(cond0, thenp0, elsep0) = ifp1 - - if (LocalLazyValFinder.find(thenp0)) - deriveLabelDef(l)(_ => treeCopy.If(ifp1, cond0, typed(addBitmapDefs(sym.owner, thenp0)), elsep0)) - else - l - - case l@LabelDef(name0, params0, block@Block(stats0, expr)) - if name0.startsWith(nme.WHILE_PREFIX) || name0.startsWith(nme.DO_WHILE_PREFIX) => - val stats1 = super.transformTrees(stats0) - if (LocalLazyValFinder.find(stats1)) - deriveLabelDef(l)(_ => treeCopy.Block(block, typed(addBitmapDefs(sym.owner, stats1.head))::stats1.tail, expr)) - else - l - - case _ => super.transform(tree) - } - } - - /** Add the bitmap definitions to the rhs of a method definition. - * If the rhs has been tail-call transformed, insert the bitmap - * definitions inside the top-level label definition, so that each - * iteration has the lazy values uninitialized. Otherwise add them - * at the very beginning of the method. - */ - private def addBitmapDefs(methSym: Symbol, rhs: Tree): Tree = { - def prependStats(stats: List[Tree], tree: Tree): Block = tree match { - case Block(stats1, res) => Block(stats ::: stats1, res) - case _ => Block(stats, tree) - } - - val bmps = bitmaps(methSym) map (ValDef(_, ZERO)) - - def isMatch(params: List[Ident]) = (params.tail corresponds methSym.tpe.params)(_.tpe == _.tpe) - - if (bmps.isEmpty) rhs else rhs match { - case Block(assign, l @ LabelDef(name, params, _)) - if (name string_== "_" + methSym.name) && isMatch(params) => - Block(assign, deriveLabelDef(l)(rhs => typed(prependStats(bmps, rhs)))) - - case _ => prependStats(bmps, rhs) - } - } - - def mkDoubleCheckedLocking(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): (Tree, Tree) = { - val owner = lzyVal.owner - val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) - defSym setInfo MethodType(List(), lzyVal.tpe.resultType) - if (owner.isClass) owner.info.decls.enter(defSym) - - val slowPathDef: Tree = { - debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") - // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d, - // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } } - // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym. - // now we set bitmap$0.owner = b$lzycomputeMethodSym. - for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym - - val rhs: Tree = gen.mkSynchronizedCheck(gen.mkAttributedThis(clazz), cond, syncBody, stats).changeOwner(currentOwner -> defSym) - - DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) - } - - - (If(cond, Apply(Ident(defSym), Nil), retVal), slowPathDef) - } - - /** return a 'lazified' version of rhs. Rhs should conform to the - * following schema: - * { - * l$ = - * l$ - * } or - * when the lazy value has type Unit (for which there is no field - * to cache its value. - * - * Similarly as for normal lazy val members (see Mixin), the result will be a tree of the form - * { if ((bitmap&n & MASK) == 0) this.l$compute() - * else l$ - * - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * l$ = - * bitmap$n = bimap$n | MASK - * }} - * l$ - * } - * } - * where bitmap$n is a byte value acting as a bitmap of initialized values. It is - * the 'n' is (offset / 8), the MASK is (1 << (offset % 8)). If the value has type - * unit, no field is used to cache the value, so the l$compute will now look as following: - * { - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * ; - * bitmap$n = bimap$n | MASK - * }} - * () - * } - * } - */ - val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil - private def mkLazyLocalDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { - /** Return the symbol corresponding of the right bitmap int inside meth, - * given offset. - */ - val bitmapSym = { - val n = offset / FLAGS_PER_BYTE - val bmps = bitmaps(methOrClass) - if (bmps.length > n) - bmps(n) - else { - val sym = methOrClass.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), methOrClass.pos).setInfo(ByteTpe) - enteringTyper { - sym addAnnotation VolatileAttr - } - - bitmaps(methOrClass) = (sym :: bmps).reverse - sym - } - } - - val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) - val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) - - debuglog(s"create complete lazy def in $methOrClass for $lazyVal") - - val (stmt, res) = tree match { - case Block(List(assignment), res) if !lazyUnit(lazyVal) => (assignment, res) - case rhs => (rhs, UNIT) - } - val block = Block(List(stmt, bitmapRef === (bitmapRef GEN_| (mask, bitmapKind))), UNIT) - - def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) - val lazyDefs = mkDoubleCheckedLocking(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) - (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) - } - } -} diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index b3558d6281..190755ff53 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -11,33 +11,34 @@ import Flags._ import scala.annotation.tailrec import scala.collection.mutable -// TODO: move lazy vals bitmap creation to lazy vals phase now that lazy vals are mixed in during fields -trait InitBitmaps extends Transform with ast.TreeDSL { +trait AccessorSynthesis extends Transform with ast.TreeDSL { import global._ import definitions._ import CODE._ - /** Map a field symbol to a unique integer denoting its position in the class layout. - * For each class, fields defined by the class come after inherited fields. - * Mixed-in fields count as fields defined by the class itself. - * - * DO NOT USE THIS MAP DIRECTLY, go through convenience methods - */ - private val _fieldOffset = perRunCaches.newMap[Symbol, Int]() + val EmptyThicket = EmptyTree + def Thicket(trees: List[Tree]) = if (trees.isEmpty) EmptyTree else Block(trees, EmptyTree) + def mustExplodeThicket(tree: Tree): Boolean = + tree match { + case EmptyTree => true + case Block(_, EmptyTree) => true + case _ => false + } + def explodeThicket(tree: Tree): List[Tree] = tree match { + case EmptyTree => Nil + case Block(thicket, EmptyTree) => thicket + case stat => stat :: Nil + } - private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() - trait InitializationTransformation { + trait AccessorTreeSynthesis { protected def typedPos(pos: Position)(tree: Tree): Tree - def accessorInitialization(clazz: Symbol, templStats: List[Tree]): AccessorInitialization = - if (settings.checkInit) new CheckInitAccessorInitialization(clazz, templStats) - else new LazyAccessorInitialization(clazz, templStats) - - class AccessorInitialization(protected val clazz: Symbol){ - private val _newDefs = mutable.ListBuffer[Tree]() + // used while we still need to synthesize some accessors in mixins: paramaccessors and presupers + class UncheckedAccessorSynth(protected val clazz: Symbol){ + protected val _newDefs = mutable.ListBuffer[Tree]() - def deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = stats + def newDefs = _newDefs.toList /** Add tree at given position as new definition */ protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree) @@ -93,196 +94,208 @@ trait InitBitmaps extends Transform with ast.TreeDSL { Select(This(clazz), accessor.accessed) } + } + case class BitmapInfo(symbol: Symbol, mask: Literal) { + def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass + } - protected class LazyAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends AccessorInitialization(clazz) { - private def bitmapNumber(sym: Symbol): Int = _fieldOffset(sym) / flagsPerBitmap(sym) - private def offsetInBitmap(sym: Symbol) = _fieldOffset(sym) % flagsPerBitmap(sym) - protected def hasBitmap(sym: Symbol) = _fieldOffset isDefinedAt sym - private def flagsPerBitmap(field: Symbol): Int = - bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 - } + // TODO: better way to communicate from info transform to tree transfor? + private[this] val _bitmapInfo = perRunCaches.newMap[Symbol, BitmapInfo] + private[this] val _slowPathFor = perRunCaches.newMap[Symbol, Symbol]() - protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) - protected def bitmapType(field: Symbol): Type = bitmapKind(field).tpe - protected def bitmapName(field: Symbol): TermName = nme.newBitmapName(bitmapCategory(field), bitmapNumber(field)).toTermName + def checkedAccessorSymbolSynth(clz: Symbol) = + if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz } + else new CheckedAccessorSymbolSynth { val clazz = clz } - protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr -// protected def isFieldWithTransientBitmap(field: Symbol) = isTransientField(field) && isFieldWithBitmap(field) + // base trait, with enough functionality for lazy vals -- CheckInitAccessorSymbolSynth adds logic for -Xcheckinit + trait CheckedAccessorSymbolSynth { + protected val clazz: Symbol + protected def defaultPos = clazz.pos.focus + protected def isTrait = clazz.isTrait + protected def hasTransientAnnot(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr - /** Examines the symbol and returns a name indicating what brand of - * bitmap it requires. The possibilities are the BITMAP_* vals - * defined in StdNames. If it needs no bitmap, nme.NO_NAME. - * - * bitmaps for checkinit fields are not inherited - */ - protected def bitmapCategory(field: Symbol): Name = { - def isFieldWithBitmap(field: Symbol) = { - field.info // ensure that nested objects are transformed - // For checkinit consider normal value getters - // but for lazy values only take into account lazy getters - field.isLazy && field.isMethod && !field.isDeferred - } + def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized - import nme._ - if (isFieldWithBitmap(field)) - if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL - else NO_NAME - } + /** Examines the symbol and returns a name indicating what brand of + * bitmap it requires. The possibilities are the BITMAP_* vals + * defined in StdNames. If it needs no bitmap, nme.NO_NAME. + * + * bitmaps for checkinit fields are not inherited + */ + protected def bitmapCategory(sym: Symbol): Name = { + // ensure that nested objects are transformed TODO: still needed? + sym.initialize - /** Fill the map from fields to offset numbers. And from bitmap category to the type used for the bitmap field (its "kind"). - * - * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. - */ - def bitmapsFor(decls: List[Symbol]): List[Symbol] = { -// val bitmaps = mutable.ListBuffer.empty[Symbol] - def fold(fields: List[Symbol], category: Name) = { - var idx = 0 - fields foreach { f => - _fieldOffset(f) = idx -// bitmaps += newBitmapFor(idx, f) - - idx += 1 - } + import nme._ - if (idx == 0) () - else if (idx == 1) bitmapKindForCategory(category) = BooleanClass - else if (idx < 9) bitmapKindForCategory(category) = ByteClass - else if (idx < 33) bitmapKindForCategory(category) = IntClass - else bitmapKindForCategory(category) = LongClass - } - decls groupBy bitmapCategory foreach { - case (nme.NO_NAME, _) => () - case (category, fields) => fold(fields, category) - } -// bitmaps.toList - Nil - } + if (needsBitmap(sym) && sym.isLazy) + if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL + else NO_NAME + } - /* - * Return the bitmap field for 'offset'. Depending on the hierarchy it is possible to reuse - * the bitmap of its parents. If that does not exist yet we create one. - */ - def bitmapFor(field: Symbol): Symbol = { - val sym = clazz.info.decl(bitmapName(field)) + def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym) + protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym - assert(!sym.isOverloaded, sym) - sym orElse newBitmapFor(field) - } + /** Fill the map from fields to bitmap infos. + * + * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. + */ + def computeBitmapInfos(decls: List[Symbol]): List[Symbol] = { + def doCategory(fields: List[Symbol], category: Name) = { + val nbFields = fields.length // we know it's > 0 + val (bitmapClass, bitmapCapacity) = + if (nbFields == 1) (BooleanClass, 1) + else if (nbFields <= 8) (ByteClass, 8) + else if (nbFields <= 32) (IntClass, 32) + else (LongClass, 64) + + // 0-based index of highest bit, divided by bits per bitmap + // note that this is only ever > 0 when bitmapClass == LongClass + val maxBitmapNumber = (nbFields - 1) / bitmapCapacity + + // transient fields get their own category + val isTransientCategory = fields.head hasAnnotation TransientAttr + + val bitmapSyms = + (0 to maxBitmapNumber).toArray map { bitmapNumber => + val bitmapSym = ( + clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, defaultPos) + setInfo bitmapClass.tpe + setFlag PrivateLocal | NEEDS_TREES + ) - private def newBitmapFor(field: Symbol): Symbol = { - val bitmapSym = - clazz.newVariable(bitmapName(field), clazz.pos) setInfo bitmapType(field) setFlag PrivateLocal setPos clazz.pos + bitmapSym addAnnotation VolatileAttr - bitmapSym addAnnotation VolatileAttr - if (isTransientField(field)) bitmapSym addAnnotation TransientAttr + if (isTransientCategory) bitmapSym addAnnotation TransientAttr - clazz.info.decls.enter(bitmapSym) - addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) + bitmapSym + } - bitmapSym - } + fields.zipWithIndex foreach { case (f, idx) => + val bitmapIdx = idx / bitmapCapacity + val offsetInBitmap = idx % bitmapCapacity + val mask = + if (bitmapClass == LongClass) Constant(1L << offsetInBitmap) + else Constant(1 << offsetInBitmap) - protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask)) + } - // overridden in CheckInitAccessorInitialization - def rhsWithOtherInitCheck(sym: Symbol)(rhs: Tree): Tree = rhs + bitmapSyms + } - /** Complete lazy field accessors. Applies only to classes, - * for its own (non inherited) lazy fields. - */ - def rhsWithInitCheck(sym: Symbol)(rhs: Tree): Tree = { - if (!clazz.isTrait && sym.isLazy && rhs != EmptyTree) { - rhs match { - case rhs if isUnitGetter(sym) => - mkLazyMemberDef(sym, List(rhs), UNIT) + decls groupBy bitmapCategory flatMap { + case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category) + case _ => Nil + } toList + } - case Block(stats, res) => - mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol)) + def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal) - case rhs => rhs - } - } - else rhsWithOtherInitCheck(sym)(rhs) - } + def newSlowPathSymbol(lzyVal: Symbol): Symbol = { + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else defaultPos // TODO: is the else branch ever taken? + val sym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfo MethodType(Nil, lzyVal.tpe.resultType) + _slowPathFor(lzyVal) = sym + sym + } - override def deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = { - bitmapsFor(clazz.info.decls.toList) + } -// foreach { bitmapSym => -// clazz.info.decls.enter(bitmapSym) -// addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) -// } + trait CheckInitAccessorSymbolSynth extends CheckedAccessorSymbolSynth { + /** Does this field require an initialized bit? + * Note: fields of classes inheriting DelayedInit are not checked. + * This is because they are neither initialized in the constructor + * nor do they have a setter (not if they are vals anyway). The usual + * logic for setting bitmaps does therefore not work for such fields. + * That's why they are excluded. + * Note: The `checkinit` option does not check if transient fields are initialized. + */ + protected def needsInitFlag(sym: Symbol): Boolean = + sym.isGetter && + !( sym.isInitializedToDefault + || isConstantType(sym.info.finalResultType) // SI-4742 + || sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) + || sym.accessed.hasFlag(PRESUPER) + || sym.isOuterAccessor + || (sym.owner isSubClass DelayedInitClass) + || (sym.accessed hasAnnotation TransientAttr)) + + /** Examines the symbol and returns a name indicating what brand of + * bitmap it requires. The possibilities are the BITMAP_* vals + * defined in StdNames. If it needs no bitmap, nme.NO_NAME. + * + * bitmaps for checkinit fields are not inherited + */ + override protected def bitmapCategory(sym: Symbol): Name = { + import nme._ - stats mapConserve { - case dd: DefDef => deriveDefDef(dd)(rhsWithInitCheck(dd.symbol)) - case stat => stat - } + super.bitmapCategory(sym) match { + case NO_NAME if needsInitFlag(sym) && !sym.isDeferred => + if (hasTransientAnnot(sym)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + case category => category } + } + + override def needsBitmap(sym: Symbol): Boolean = super.needsBitmap(sym) || !(isTrait || sym.isDeferred) && needsInitFlag(sym) + } - private def maskForOffset(sym: Symbol, kind: ClassSymbol): Constant = - if (kind == LongClass) Constant(1L << offsetInBitmap(sym)) - else Constant(1 << offsetInBitmap(sym)) + // synthesize trees based on info gathered during info transform + // (which are known to have been run because the tree transform runs afterOwnPhase) + // since we can't easily share all info via symbols and flags, we have two maps above + // (they are persisted even between phases because the -Xcheckinit logic runs during constructors) + // TODO: can we use attachments instead of _bitmapInfo and _slowPathFor? + trait CheckedAccessorTreeSynthesis extends AccessorTreeSynthesis { + // note: we deal in getters here, not field symbols + trait SynthCheckedAccessorsTreesInClass extends CheckedAccessorSymbolSynth { + def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + def thisRef = gen.mkAttributedThis(clazz) /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the * precise comparison operator depending on the value of 'equalToZero'. */ - protected def mkTest(field: Symbol, equalToZero: Boolean): Tree = { - val bitmapSym = bitmapFor(field) - val bitmapTree = This(clazz) DOT bitmapSym - val bitmapClass = bitmapSym.info.typeSymbol.asClass + def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = { + val bitmap = bitmapFor(field) + val bitmapTree = thisRef DOT bitmap.symbol - if (bitmapClass == BooleanClass) { + if (bitmap.storageClass == BooleanClass) { if (equalToZero) NOT(bitmapTree) else bitmapTree } else { - val mask = Literal(maskForOffset(field, bitmapClass)) - val lhs = bitmapTree GEN_&(mask, bitmapClass) - if (equalToZero) lhs GEN_==(ZERO, bitmapClass) - else lhs GEN_!=(ZERO, bitmapClass) + val lhs = bitmapTree GEN_&(bitmap.mask, bitmap.storageClass) + if (equalToZero) lhs GEN_==(ZERO, bitmap.storageClass) + else lhs GEN_!=(ZERO, bitmap.storageClass) } } - - /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ + /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ def mkSetFlag(valSym: Symbol): Tree = { - val bitmapSym = bitmapFor(valSym) - val bitmapClass = bitmapSym.info.typeSymbol.asClass - def x = This(clazz) DOT bitmapSym + val bitmap = bitmapFor(valSym) + def x = thisRef DOT bitmap.symbol - // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) Assign(x, - if (bitmapClass == BooleanClass) TRUE - else Apply(Select(x, getMember(bitmapClass, nme.OR)), List(Literal(maskForOffset(valSym, bitmapClass)))) + if (bitmap.storageClass == BooleanClass) TRUE + else { + val or = Apply(Select(x, getMember(bitmap.storageClass, nme.OR)), List(bitmap.mask)) + // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) + // TODO: need this to make it type check -- is there another way?? + if (bitmap.storageClass != LongClass) Apply(Select(or, newTermName("to" + bitmap.storageClass.name)), Nil) + else or + } ) - } + } - - /** return a 'lazified' version of rhs. It uses double-checked locking to ensure - * initialization is performed at most once. For performance reasons the double-checked - * locking is split into two parts, the first (fast) path checks the bitmap without - * synchronizing, and if that fails it initializes the lazy val within the - * synchronization block (slow path). This way the inliner should optimize - * the fast path because the method body is small enough. - * Private fields used only in this initializer are subsequently set to null. - * - * @param lzyVal The symbol of this lazy field - * @param init The tree which initializes the field ( f = ) - * @param offset The offset of this field in the flags bitmap + class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass { + /** + * The compute method (slow path) looks like: * - * The result will be a tree of the form `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` - * A synthetic compute method will also be added to the current class, of the following shape: * ``` * def l$compute() = { * synchronized(this) { @@ -306,134 +319,106 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * * If the class contains only a single lazy val then the bitmap is * represented as a Boolean and the condition checking is a simple bool test. + * + * Private fields used only in this initializer are subsequently set to null. + * + * For performance reasons the double-checked locking is split into two parts, + * the first (fast) path checks the bitmap without synchronizing, and if that + * fails it initializes the lazy val within the synchronization block (slow path). + * + * This way the inliner should optimize the fast path because the method body is small enough. */ - private def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree): Tree = { - def thisRef = gen.mkAttributedThis(clazz) - def cond = mkTest(lzyVal, equalToZero = true) + def expandLazyClassMember(lazyVar: Symbol, lazyAccessor: Symbol, transformedRhs: Tree, nullables: Map[Symbol, List[Symbol]]): Tree = { + // use cast so that specialization can turn null.asInstanceOf[T] into null.asInstanceOf[Long] + def nullify(sym: Symbol) = + Select(thisRef, sym.accessedOrSelf) === gen.mkAsInstanceOf(NULL, sym.info.resultType) - val slowPathDef = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) - val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify + val nulls = nullables.getOrElse(lazyAccessor, Nil) map nullify - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) + if (nulls.nonEmpty) + log("nulling fields inside " + lazyAccessor + ": " + nulls) - val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? - val slowPathSym = - clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) + val slowPathSym = slowPathFor(lazyAccessor) + val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym) - val statsToSynch = init ::: List(mkSetFlag(lzyVal), UNIT) - val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) + val isUnit = isUnitGetter(lazyAccessor) + val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) + val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) - // TODO: this code used to run after classes were flattened -- do we have all the needed owner changes? - DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(lzyVal -> slowPathSym)), retVal)) - } + val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil + val slowPathRhs = + Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, nulls)), selectVar) - addDef(slowPathDef) + // The lazy accessor delegates to the compute method if needed, otherwise just accesses the var (it was initialized previously) + // `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` + val accessorRhs = If(mkTest(lazyAccessor), Apply(Select(thisRef, slowPathSym), Nil), selectVar) - typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathDef.symbol), Nil), retVal)) + afterOwnPhase { // so that we can assign to vals + Thicket(List((DefDef(slowPathSym, slowPathRhs)), DefDef(lazyAccessor, accessorRhs)) map typedPos(lazyAccessor.pos.focus)) + } } + } - /** Map lazy values to the fields they should null after initialization. */ - private lazy val lazyValNullables: Map[Symbol, List[Symbol]] = - // if there are no lazy fields, take the fast path and save a traversal of the whole AST - if (!clazz.info.decls.exists(_.isLazy)) Map() - else { - // A map of single-use fields to the lazy value that uses them during initialization. - // Each field has to be private and defined in the enclosing class, and there must - // be exactly one lazy value using it. - // - // Such fields will be nulled after the initializer has memoized the lazy value. - val singleUseFields: Map[Symbol, List[Symbol]] = { - val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil - - object SingleUseTraverser extends Traverser { - override def traverse(tree: Tree) { - tree match { - case Assign(lhs, rhs) => traverse(rhs) // assignments don't count - case _ => - if (tree.hasSymbolField && tree.symbol != NoSymbol) { - val sym = tree.symbol - if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) - && sym.isPrivate - && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter - && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) - && sym.owner == clazz - && !sym.isLazy - && !tree.isDef) { - debuglog("added use in: " + currentOwner + " -- " + tree) - usedIn(sym) ::= currentOwner - } - } - super.traverse(tree) - } + /** Map lazy values to the fields they should null after initialization. */ + // TODO: fix + def lazyValNullables(clazz: Symbol, templStats: List[Tree]): Map[Symbol, List[Symbol]] = { + // if there are no lazy fields, take the fast path and save a traversal of the whole AST + if (!clazz.info.decls.exists(_.isLazy)) Map() + else { + // A map of single-use fields to the lazy value that uses them during initialization. + // Each field has to be private and defined in the enclosing class, and there must + // be exactly one lazy value using it. + // + // Such fields will be nulled after the initializer has memoized the lazy value. + val singleUseFields: Map[Symbol, List[Symbol]] = { + val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil + + object SingleUseTraverser extends Traverser { + override def traverse(tree: Tree) { + tree match { + // assignment targets don't count as a dereference -- only check the rhs + case Assign(_, rhs) => traverse(rhs) + case tree: RefTree if tree.symbol != NoSymbol => + val sym = tree.symbol + // println(s"$sym in ${sym.owner} from $currentOwner ($tree)") + if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) && sym.isPrivate && !sym.isLazy // non-lazy private field or its accessor + && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) // primitives don't hang on to significant amounts of heap + && sym.owner == currentOwner.enclClass && !(currentOwner.isGetter && currentOwner.accessed == sym)) { + + // println("added use in: " + currentOwner + " -- " + tree) + usedIn(sym) ::= currentOwner + } + super.traverse(tree) + case _ => super.traverse(tree) } } - templStats foreach SingleUseTraverser.apply - debuglog("usedIn: " + usedIn) - usedIn filter { - case (_, member :: Nil) => member.isValue && member.isLazy - case _ => false - } toMap } + templStats foreach SingleUseTraverser.apply + // println("usedIn: " + usedIn) - val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() - // check what fields can be nulled for - for ((field, users) <- singleUseFields; lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) - map(lazyFld) += field + // only consider usages from non-transient lazy vals (SI-9365) + val singlyUsedIn = usedIn filter { case (_, member :: Nil) => member.isLazy && !member.accessed.hasAnnotation(TransientAttr) case _ => false } toMap - map.mapValues(_.toList sortBy (_.id)).toMap + // println("singlyUsedIn: " + singlyUsedIn) + singlyUsedIn } - } - - - protected class CheckInitAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends LazyAccessorInitialization(clazz, templStats) { - /** Does this field require an initialized bit? - * Note: fields of classes inheriting DelayedInit are not checked. - * This is because they are neither initialized in the constructor - * nor do they have a setter (not if they are vals anyway). The usual - * logic for setting bitmaps does therefore not work for such fields. - * That's why they are excluded. - * Note: The `checkinit` option does not check if transient fields are initialized. - */ - private def needsInitFlag(sym: Symbol): Boolean = ( - sym.isGetter - && !sym.isInitializedToDefault - && !isConstantType(sym.info.finalResultType) // SI-4742 - && !sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) - && !sym.accessed.hasFlag(PRESUPER) - && !sym.isOuterAccessor - && !(sym.owner isSubClass DelayedInitClass) - && !(sym.accessed hasAnnotation TransientAttr) - ) - - private def needsInitFlagAndHasBitmap(sym: Symbol) = hasBitmap(sym) && needsInitFlag(sym) - -// override protected def isFieldWithTransientBitmap(field: Symbol) = -// super.isFieldWithTransientBitmap(field) || needsInitFlag(field) && !field.isDeferred && isTransientField(field) - /** Examines the symbol and returns a name indicating what brand of - * bitmap it requires. The possibilities are the BITMAP_* vals - * defined in StdNames. If it needs no bitmap, nme.NO_NAME. - * - * bitmaps for checkinit fields are not inherited - */ - override protected def bitmapCategory(field: Symbol): Name = { - import nme._ + val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() + // invert the map to see which fields can be nulled for each non-transient lazy val + for ((field, users) <- singleUseFields; lazyFld <- users) map(lazyFld) += field - super.bitmapCategory(field) match { - case NO_NAME if needsInitFlag(field) && !field.isDeferred => - if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT - case category => category - } + map.mapValues(_.toList sortBy (_.id)).toMap } + } + - object addInitBitsTransformer extends Transformer { + class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth { + private object addInitBitsTransformer extends Transformer { private def checkedGetter(lhs: Tree)(pos: Position) = { - val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitFlagAndHasBitmap(sym)) { - debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(typedPos(pos)(mkSetFlag(sym))) + val getter = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) + if (hasBitmap(getter) && needsInitFlag(getter)) { + debuglog("adding checked getter for: " + getter + " " + lhs.symbol.flagString) + List(typedPos(pos)(mkSetFlag(getter))) } else Nil } @@ -453,23 +438,21 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ - override def rhsWithOtherInitCheck(sym: Symbol)(rhs: Tree): Tree = { + def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree = { // Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor if (sym.isConstructor) addInitBitsTransformer transform rhs - else if (clazz.hasFlag(TRAIT) || rhs == EmptyTree) rhs - else if (needsInitFlag(sym)) { // implies sym.isGetter - mkCheckedAccessor(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) - } + else if (isTrait || rhs == EmptyTree) rhs + else if (needsInitFlag(sym)) // getter + mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) else if (sym.isSetter) { val getter = sym.getterIn(clazz) - if (needsInitFlagAndHasBitmap(getter)) - Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) + if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) else rhs } else rhs } - def mkCheckedAccessor(retVal: Tree, pos: Position, getter: Symbol): Tree = { + private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, getter: Symbol): Tree = { val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" val result = IF(mkTest(getter, equalToZero = false)). @@ -478,23 +461,11 @@ trait InitBitmaps extends Transform with ast.TreeDSL { typedPos(pos)(BLOCK(result, retVal)) } - - // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) - override def getterBody(getter: Symbol) = { - if (!needsInitFlag(getter)) super.getterBody(getter) - else mkCheckedAccessor(super.getterBody(getter), getter.pos, getter) - } - - override def setterBody(setter: Symbol, getter: Symbol) = { - if (!needsInitFlag(getter)) super.setterBody(setter, getter) - else Block(List(super.setterBody(setter, getter)), mkSetFlag(getter)) - } } } } - -abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { +abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ import definitions._ import CODE._ @@ -830,7 +801,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends Transformer with InitializationTransformation { + class MixinTransformer(unit : CompilationUnit) extends Transformer with AccessorTreeSynthesis { /** The typer */ private var localTyper: erasure.Typer = _ protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) @@ -879,10 +850,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { * @param clazz The class to which definitions are added */ private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { - val accessorInit = accessorInitialization(clazz, stats) - import accessorInit._ - - val statsWithInitChecks = deriveStatsWithInitChecks(stats) + val accessorSynth = new UncheckedAccessorSynth(clazz) + import accessorSynth._ // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { @@ -904,10 +873,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { } } - val addedStatsWithInitBitsAndChecks = implementWithNewDefs(statsWithInitChecks) + val implementedAccessors = implementWithNewDefs(stats) if (clazz.isTrait) - addedStatsWithInitBitsAndChecks filter { + implementedAccessors filter { case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false case _ => true } @@ -929,7 +898,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { stat } - addedStatsWithInitBitsAndChecks map completeSuperAccessor + implementedAccessors map completeSuperAccessor } } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 87c14eb3a1..c171050bbd 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -723,7 +723,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) { forwardToOverload(m) - } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition + } else if (m.isValue && !m.isMethod) { // concrete value definition def mkAccessor(field: Symbol, name: Name) = { val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) // we rely on the super class to initialize param accessors @@ -744,7 +744,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { enterMember(specVal) // create accessors - if (nme.isLocalName(m.name)) { + if (m.isLazy) { + // no getters needed (we'll specialize the compute method and accessor separately), can stay private + // m.setFlag(PRIVATE) -- TODO: figure out how to leave the non-specialized lazy var private + // (the implementation needs it to be visible while duplicating and retypechecking, + // but it really could be private in bytecode) + specVal.setFlag(PRIVATE) + } + else if (nme.isLocalName(m.name)) { val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info) val origGetter = overrideIn(sClass, m.getterIn(clazz)) info(origGetter) = Forward(specGetter) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index fe7a4d5fef..3d2b689e4c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -982,7 +982,7 @@ trait Namers extends MethodSynthesis { ) dropIllegalStarTypes( if (shouldWiden) tpe.widen - else if (sym.isFinal) tpe // "final val" allowed to retain constant type + else if (sym.isFinal && !sym.isLazy) tpe // "final val" allowed to retain constant type else tpe.deconst ) } diff --git a/src/library/scala/runtime/LazyRef.scala b/src/library/scala/runtime/LazyRef.scala new file mode 100644 index 0000000000..3cd2d2d06b --- /dev/null +++ b/src/library/scala/runtime/LazyRef.scala @@ -0,0 +1,52 @@ +package scala.runtime + +/* Classes used as holders for local lazy vals */ + +class LazyRef[T] { + var value: T = _ + @volatile var initialized: Boolean = false +} + +class LazyBoolean { + var value: Boolean = _ + @volatile var initialized: Boolean = false +} + +class LazyByte { + var value: Byte = _ + @volatile var initialized: Boolean = false +} + +class LazyChar { + var value: Char = _ + @volatile var initialized: Boolean = false +} + +class LazyShort { + var value: Short = _ + @volatile var initialized: Boolean = false +} + +class LazyInt { + var value: Int = _ + @volatile var initialized: Boolean = false +} + +class LazyLong { + var value: Long = _ + @volatile var initialized: Boolean = false +} + +class LazyFloat { + var value: Float = _ + @volatile var initialized: Boolean = false +} + +class LazyDouble { + var value: Double = _ + @volatile var initialized: Boolean = false +} + +class LazyUnit { + @volatile var initialized: Boolean = false +} diff --git a/src/manual/scala/man1/scalac.scala b/src/manual/scala/man1/scalac.scala index 6ffcccea25..79c175e0f0 100644 --- a/src/manual/scala/man1/scalac.scala +++ b/src/manual/scala/man1/scalac.scala @@ -379,8 +379,8 @@ object scalac extends Command { MItalic("posterasure"), "clean up erased inline classes"), Definition( - MItalic("lazyvals"), - "allocate bitmaps, translate lazy vals into lazified defs"), + MItalic("fields"), + "synthesize accessors and fields, including bitmaps for lazy vals"), Definition( MItalic("lambdalift"), "move nested functions to top level"), diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index d0539dfd42..89d1b0637a 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -81,7 +81,7 @@ trait Definitions extends api.StandardDefinitions { } } - private[Definitions] def classesMap[T](f: Name => T) = symbolsMap(ScalaValueClassesNoUnit, f) + private[Definitions] def classesMap[T](f: Name => T): Map[Symbol, T] = symbolsMap(ScalaValueClassesNoUnit, f) private def symbolsMap[T](syms: List[Symbol], f: Name => T): Map[Symbol, T] = mapFrom(syms)(x => f(x.name)) private def symbolsMapFilt[T](syms: List[Symbol], p: Name => Boolean, f: Name => T) = symbolsMap(syms filter (x => p(x.name)), f) @@ -93,6 +93,9 @@ trait Definitions extends api.StandardDefinitions { lazy val boxedClass = classesMap(x => getClassByName(boxedName(x))) lazy val refClass = classesMap(x => getRequiredClass("scala.runtime." + x + "Ref")) lazy val volatileRefClass = classesMap(x => getRequiredClass("scala.runtime.Volatile" + x + "Ref")) + lazy val lazyHolders = symbolsMap(ScalaValueClasses, x => getClassIfDefined("scala.runtime.Lazy" + x)) + lazy val LazyRefClass = getClassIfDefined("scala.runtime.LazyRef") + lazy val LazyUnitClass = getClassIfDefined("scala.runtime.LazyUnit") lazy val allRefClasses: Set[Symbol] = { refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 925018d3a6..c8e4d3d1d5 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -704,6 +704,7 @@ trait StdNames { val immutable: NameType = "immutable" val implicitly: NameType = "implicitly" val in: NameType = "in" + val initialized : NameType = "initialized" val internal: NameType = "internal" val inlinedEquals: NameType = "inlinedEquals" val isArray: NameType = "isArray" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 10ae68cdd1..ac025e50ae 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1682,7 +1682,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * * - packageobjects (follows namer) * - superaccessors (follows typer) - * - lazyvals (follows erasure) + * - lambdaLift (follows erasure) * - null */ private def unsafeTypeParamPhase = { diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index f55b33959a..d56cecc965 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -432,6 +432,9 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.boxedClass definitions.refClass definitions.volatileRefClass + definitions.lazyHolders + definitions.LazyRefClass + definitions.LazyUnitClass definitions.allRefClasses definitions.UnitClass definitions.ByteClass diff --git a/src/repl/scala/tools/nsc/interpreter/Phased.scala b/src/repl/scala/tools/nsc/interpreter/Phased.scala index d1d422ce3e..dd327a13d4 100644 --- a/src/repl/scala/tools/nsc/interpreter/Phased.scala +++ b/src/repl/scala/tools/nsc/interpreter/Phased.scala @@ -88,7 +88,7 @@ trait Phased { lazy val all = List( Parser, Namer, Packageobjects, Typer, Superaccessors, Pickler, Refchecks, - Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Lazyvals, Lambdalift, + Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Fields, Lambdalift, Constructors, Flatten, Mixin, Cleanup, Delambdafy, Jvm, Terminal ) lazy val nameMap = all.map(x => x.name -> x).toMap withDefaultValue NoPhaseName @@ -114,12 +114,12 @@ trait Phased { case object Pickler extends PhaseName case object Refchecks extends PhaseName case object Uncurry extends PhaseName + case object Fields extends PhaseName case object Tailcalls extends PhaseName case object Specialize extends PhaseName case object Explicitouter extends PhaseName case object Erasure extends PhaseName case object PostErasure extends PhaseName - case object Lazyvals extends PhaseName case object Lambdalift extends PhaseName case object Constructors extends PhaseName case object Flatten extends PhaseName diff --git a/test/files/neg/t6446-additional.check b/test/files/neg/t6446-additional.check index 45db63317c..23df978cd9 100644 --- a/test/files/neg/t6446-additional.check +++ b/test/files/neg/t6446-additional.check @@ -10,19 +10,18 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - ploogin 25 A sample phase that does so many things it's kind of hard... - terminal 26 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + ploogin 24 A sample phase that does so many things it's kind of hard... + terminal 25 the last phase during a compilation run diff --git a/test/files/neg/t6446-missing.check b/test/files/neg/t6446-missing.check index 04523d18e6..c0a8fea140 100644 --- a/test/files/neg/t6446-missing.check +++ b/test/files/neg/t6446-missing.check @@ -11,18 +11,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/neg/t6446-show-phases.check b/test/files/neg/t6446-show-phases.check index 03f8273c17..cf8595db5d 100644 --- a/test/files/neg/t6446-show-phases.check +++ b/test/files/neg/t6446-show-phases.check @@ -10,18 +10,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/neg/t7494-no-options.check b/test/files/neg/t7494-no-options.check index bb143e8644..138d2fe9a3 100644 --- a/test/files/neg/t7494-no-options.check +++ b/test/files/neg/t7494-no-options.check @@ -11,19 +11,18 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - ploogin 25 A sample phase that does so many things it's kind of hard... - terminal 26 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + ploogin 24 A sample phase that does so many things it's kind of hard... + terminal 25 the last phase during a compilation run diff --git a/test/files/run/analyzerPlugins.check b/test/files/run/analyzerPlugins.check index 9643079b83..64b68db242 100644 --- a/test/files/run/analyzerPlugins.check +++ b/test/files/run/analyzerPlugins.check @@ -39,7 +39,7 @@ pluginsPt(?, Trees$TypeBoundsTree) [2] pluginsPt(?, Trees$TypeDef) [1] pluginsPt(?, Trees$TypeTree) [32] pluginsPt(?, Trees$Typed) [1] -pluginsPt(?, Trees$ValDef) [21] +pluginsPt(?, Trees$ValDef) [13] pluginsPt(Any, Trees$Literal) [2] pluginsPt(Any, Trees$Typed) [1] pluginsPt(Array[Any], Trees$ArrayValue) [1] @@ -109,7 +109,7 @@ pluginsTyped(, Trees$ClassDef) [2] pluginsTyped(, Trees$DefDef) [14] pluginsTyped(, Trees$PackageDef) [1] pluginsTyped(, Trees$TypeDef) [1] -pluginsTyped(, Trees$ValDef) [21] +pluginsTyped(, Trees$ValDef) [13] pluginsTyped(=> Boolean @testAnn, Trees$Select) [1] pluginsTyped(=> Double, Trees$Select) [1] pluginsTyped(=> Int, Trees$Select) [5] diff --git a/test/files/run/delambdafy_t6028.check b/test/files/run/delambdafy_t6028.check index 8b0ae7e9b9..b188928c09 100644 --- a/test/files/run/delambdafy_t6028.check +++ b/test/files/run/delambdafy_t6028.check @@ -42,7 +42,7 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer; def $outer(): T = MethodLocalObject$2.this.$outer }; - final private[this] def MethodLocalObject$lzycompute$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { + final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { T.this.synchronized({ if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); @@ -50,10 +50,6 @@ package { }); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; - final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) - else - MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); final private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try { tryyLocal$1.elem = tryyParam$1 } finally () diff --git a/test/files/run/lazy-locals-2.scala b/test/files/run/lazy-locals-2.scala new file mode 100644 index 0000000000..cb905d3bef --- /dev/null +++ b/test/files/run/lazy-locals-2.scala @@ -0,0 +1,322 @@ +object Logs { + val logBuf = new collection.mutable.StringBuilder() + def log(m: Any): Unit = { if (logBuf.nonEmpty) logBuf.append(":"); logBuf.append(m) } + def checkLog(expected: String): Unit = { + val res = logBuf.toString + assert(res == expected, s"expected:\n$expected\nfound:\n$res") + logBuf.clear() + } +} + +import Logs._ + +class C { + def getInt : Int = { log("getInt"); 1 } + def getString: String = { log("getString"); "s" } + def getUnit : Unit = { log("getUnit") } + + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + checkLog("") + + def m1 = { + lazy val t1 = getInt + t1 + t1 + } + def m2 = { + lazy val t1 = getString + t1 + t1 + } + def m3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + val vl1 = { + lazy val t1 = getInt + t1 + t1 + } + val vl2 = { + lazy val t1 = getString + t1 + t1 + } + val vl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + var vr1 = { + lazy val t1 = getInt + t1 + t1 + } + var vr2 = { + lazy val t1 = getString + t1 + t1 + } + var vr3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + lazy val lvl1 = { + lazy val t1 = getInt + t1 + t1 + } + lazy val lvl2 = { + lazy val t1 = getString + t1 + t1 + } + lazy val lvl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + { + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + + log(t1 + t1) + log(t2 + t2) + log(t3); log(t3) + } + checkLog("getInt:2:getString:ss:getUnit:():()") + + + def run(): Unit = { + log(t1); log(t1); + log(t2); log(t2); + log(t3); log(t3); + checkLog("getInt:1:1:getString:s:s:getUnit:():()") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("getInt:2:getInt:2:getString:ss:getString:ss:getUnit:():():():getUnit:():():()") + + log(vl1); log(vl1) + log(vl2); log(vl2) + log(vl3); log(vl3) + checkLog("2:2:ss:ss:():()") + + log(vr1); log(vr1); vr1 = 393; log(vr1) + log(vr2); log(vr2); vr2 = "h"; log(vr2) + log(vr3); log(vr3); vr3 = () ; log(vr3) + checkLog("2:2:393:ss:ss:h:():():()") + + log(lvl1); log(lvl1) + log(lvl2); log(lvl2) + log(lvl3); log(lvl3) + checkLog("getInt:2:2:getString:ss:ss:getUnit:():():():()") + } +} + +trait T { + def getInt : Int = { log("getInt"); 1 } + def getString: String = { log("getString"); "s" } + def getUnit : Unit = { log("getUnit") } + + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + checkLog("") + + def m1 = { + lazy val t1 = getInt + t1 + t1 + } + def m2 = { + lazy val t1 = getString + t1 + t1 + } + def m3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + val vl1 = { + lazy val t1 = getInt + t1 + t1 + } + val vl2 = { + lazy val t1 = getString + t1 + t1 + } + val vl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + var vr1 = { + lazy val t1 = getInt + t1 + t1 + } + var vr2 = { + lazy val t1 = getString + t1 + t1 + } + var vr3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + lazy val lvl1 = { + lazy val t1 = getInt + t1 + t1 + } + lazy val lvl2 = { + lazy val t1 = getString + t1 + t1 + } + lazy val lvl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + { + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + + log(t1 + t1) + log(t2 + t2) + log(t3); log(t3) + } + checkLog("getInt:2:getString:ss:getUnit:():()") + + + def run(): Unit = { + log(t1); log(t1); + log(t2); log(t2); + log(t3); log(t3); + checkLog("getInt:1:1:getString:s:s:getUnit:():()") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("getInt:2:getInt:2:getString:ss:getString:ss:getUnit:():():():getUnit:():():()") + + log(vl1); log(vl1) + log(vl2); log(vl2) + log(vl3); log(vl3) + checkLog("2:2:ss:ss:():()") + + log(vr1); log(vr1); vr1 = 393; log(vr1) + log(vr2); log(vr2); vr2 = "h"; log(vr2) + log(vr3); log(vr3); vr3 = () ; log(vr3) + checkLog("2:2:393:ss:ss:h:():():()") + + log(lvl1); log(lvl1) + log(lvl2); log(lvl2) + log(lvl3); log(lvl3) + checkLog("getInt:2:2:getString:ss:ss:getUnit:():():():()") + } +} + +class D extends T + +class D1 extends T { + override lazy val t1 = { log("o-t1"); -1 } + checkLog("") + + override def m1 = { log("o-m1"); -2 } + override val m2 = { log("o-m2"); "n" } + override lazy val m3 = { log("o-m3") } + checkLog("o-m2") + + override val vl1 = { log("o-vl1"); -3 } + checkLog("o-vl1") + + override lazy val lvl1 = { log("o-lvl1"); -4 } + checkLog("") + + override def run(): Unit = { + log(t1); log(t1) + checkLog("o-t1:-1:-1") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("o-m1:-2:o-m1:-2:n:n:o-m3:():()") + + log(vl1); log(vl1) + checkLog("-3:-3") + + log(lvl1); log(lvl1) + checkLog("o-lvl1:-4:-4") + } +} + +class E { + object T { log("init T"); override def toString = "T" } + def m = { object T { log("init T"); val x = 1 }; T.x } + checkLog("") +} + +object Test { + def main(args: Array[String]): Unit = { + val c = new C + c.run() + + val lzyComputeMethods = c.getClass.getDeclaredMethods.filter(_.getName contains "lzycompute").map(_.getName).toList.sorted + val expComputeMethods = List("lvl1$lzycompute", "lvl2$lzycompute", "lvl3$lzycompute", "t1$lzycompute", "t2$lzycompute", "t3$lzycompute") + assert( + lzyComputeMethods == expComputeMethods, + s"wrong lzycompute methods. expected:\n$expComputeMethods\nfound:\n$lzyComputeMethods") + + val fields = c.getClass.getDeclaredFields.toList.sortBy(_.getName).map(_.toString) + val expFields = List( + "private volatile byte C.bitmap$0", + "private int C.lvl1", + "private java.lang.String C.lvl2", + "private scala.runtime.BoxedUnit C.lvl3", + "private int C.t1", + "private java.lang.String C.t2", + "private scala.runtime.BoxedUnit C.t3", + "private final int C.vl1", + "private final java.lang.String C.vl2", + "private final scala.runtime.BoxedUnit C.vl3", + "private int C.vr1", + "private java.lang.String C.vr2", + "private scala.runtime.BoxedUnit C.vr3") + assert( + fields == expFields, + s"wrong fields. expected:\n$expFields\nfound:\n$fields") + + + val d = new D + d.run() + + val dFields = d.getClass.getDeclaredFields.toList.sortBy(_.getName).map(_.toString) + assert( + dFields == expFields.map(_.replaceAll(" C.", " D.")), + s"wrong fields. expected:\n$expFields\nfound:\n$fields") + + + val d1 = new D1 + d1.run() + + val e = new E + log(e.T); log(e.T) + checkLog("init T:T:T") + log(e.m); log(e.m) + checkLog("init T:1:init T:1") + } +} diff --git a/test/files/run/lazy_local_labels.check b/test/files/run/lazy_local_labels.check new file mode 100644 index 0000000000..e42c8fb8ce --- /dev/null +++ b/test/files/run/lazy_local_labels.check @@ -0,0 +1,9 @@ +HI +HI +HI +HI +HI +HI +HI +HI +HI diff --git a/test/files/run/lazy_local_labels.scala b/test/files/run/lazy_local_labels.scala new file mode 100644 index 0000000000..f4a1cdf689 --- /dev/null +++ b/test/files/run/lazy_local_labels.scala @@ -0,0 +1,28 @@ +// should print HI nine times to indicate the lazy val has been re-initialized on every iteration +object Test extends App { + def fooDo: Unit = { + var i = 3 + do { + lazy val x = { println("HI"); 1 } + i -= x + } while(i > 0) + } + + def fooWhile: Unit = { + var i = 3 + while(i > 0) { + lazy val x = { println("HI"); 1 } + i -= x + } + } + + @annotation.tailrec def fooTail(i: Int): Unit = { + lazy val x = { println("HI"); 1 } + if (i > 0) fooTail(i - x) + } + + + fooWhile + fooDo + fooTail(3) +} diff --git a/test/files/run/programmatic-main.check b/test/files/run/programmatic-main.check index 03f8273c17..cf8595db5d 100644 --- a/test/files/run/programmatic-main.check +++ b/test/files/run/programmatic-main.check @@ -10,18 +10,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/run/t3569.check b/test/files/run/t3569.check index a9fb5ff32e..e0e1d6c405 100644 --- a/test/files/run/t3569.check +++ b/test/files/run/t3569.check @@ -2,6 +2,8 @@ private final int Test$X.val1 private final int Test$X.val2 private final int Test$X.val3 +private int Test$X.const1 +private int Test$X.const2 private int Test$X.lval1 private int Test$X.lval2 private int Test$X.lval3 diff --git a/test/files/run/t3569.scala b/test/files/run/t3569.scala index eb3b424439..7da4de9e95 100644 --- a/test/files/run/t3569.scala +++ b/test/files/run/t3569.scala @@ -4,7 +4,13 @@ object Test { lazy val lv = scala.util.Random.nextInt() - class X(final var x: Int) { + trait T { final lazy val const1 = 1 } // no fields + + class X(final var x: Int) extends T { + // a lazy val does not receive a constant type, for backwards compat (e.g. for the repl) + // besides, since you explicitly wanted something lazy, we'll give you something lazy! (a field and a bitmap) + final lazy val const2 = 2 + final var var1: Int = 0 final private var var2: Int = 0 final private[this] var var3: Int = 0 diff --git a/test/files/run/t5552.check b/test/files/run/t5552.check index a19a60840e..73ad9cf824 100644 --- a/test/files/run/t5552.check +++ b/test/files/run/t5552.check @@ -1,2 +1,6 @@ +lazy: 3 (3,3) +(3,3) +lazy: 3.0 +(3.0,3.0) (3.0,3.0) diff --git a/test/files/run/t5552.scala b/test/files/run/t5552.scala index afb8a1f0be..5b717f9e13 100644 --- a/test/files/run/t5552.scala +++ b/test/files/run/t5552.scala @@ -1,10 +1,14 @@ class C[@specialized(Int) A](a:A) { - lazy val b = (a, a) + lazy val b = {println(s"lazy: $a"); (a, a)} // there should only be two instances of "lazy" in the output def c = b } object Test { def main(args:Array[String]) { - println(new C(3).c) - println(new C(3.0).c) + val cInt = new C(3) + println(cInt.c) + println(cInt.c) + val cFloat = new C(3.0) + println(cFloat.c) + println(cFloat.c) } } diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index 532d177300..c2e3ca58d8 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -54,7 +54,7 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer; def $outer(): T = MethodLocalObject$2.this.$outer }; - final private[this] def MethodLocalObject$lzycompute$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { + final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { T.this.synchronized({ if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); @@ -62,10 +62,6 @@ package { }); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; - final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) - else - MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { def ($outer: T, tryyParam$1: Int, tryyLocal$1: runtime.IntRef): <$anon: Function0> = { $anonfun$tryy$1.super.(); diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 56da0e2493..eae5385147 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -105,7 +105,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { ("x4$(LT;)I", MethodInlineInfo(true ,false,false)), ("x5()I", MethodInlineInfo(true, false,false)), ("x5$(LT;)I", MethodInlineInfo(true ,false,false)), - ("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), ("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), ("nest$1()I", MethodInlineInfo(true, false,false)), ("$init$(LT;)V", MethodInlineInfo(true,false,false))), @@ -118,7 +117,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { val infoC = inlineInfo(c) val expectC = InlineInfo(false, None, Map( "O()LT$O$;" -> MethodInlineInfo(true ,false,false), - "O$lzycompute()LT$O$;" -> MethodInlineInfo(true, false,false), "f6()I" -> MethodInlineInfo(false,false,false), "x1()I" -> MethodInlineInfo(false,false,false), "T$_setter_$x1_$eq(I)V" -> MethodInlineInfo(false,false,false), @@ -181,7 +179,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { val infoC = inlineInfo(c) val expected = Map( "()V" -> MethodInlineInfo(false,false,false), - "O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false), "O()LC$O$;" -> MethodInlineInfo(true,false,false)) assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected)) assertSameMethods(c, expected.keySet) -- cgit v1.2.3