diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/ant/Scalac.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/Global.scala | 12 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Constructors.scala | 19 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Fields.scala | 246 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/LazyVals.scala | 310 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Mixin.scala | 561 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala | 11 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Namers.scala | 2 | ||||
-rw-r--r-- | src/library/scala/runtime/LazyRef.scala | 52 | ||||
-rw-r--r-- | src/manual/scala/man1/scalac.scala | 4 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Definitions.scala | 5 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/StdNames.scala | 1 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Symbols.scala | 2 | ||||
-rw-r--r-- | src/reflect/scala/reflect/runtime/JavaUniverseForce.scala | 3 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/Phased.scala | 4 |
15 files changed, 509 insertions, 725 deletions
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 = <rhs>; 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$ = <rhs> - * l$ - * } or - * <rhs> 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$ = <rhs> - * 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) { - * <rhs>; - * 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 = <rhs> ) - * @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 |