diff options
author | Adriaan Moors <adriaan@lightbend.com> | 2016-08-19 11:43:12 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan@lightbend.com> | 2016-08-29 09:54:08 +0200 |
commit | 0f813a5675f7f6c45acfab705b697e9cc1eceff3 (patch) | |
tree | 32e74b417f7552823167c73d9890d635620cf13a /src/compiler/scala/tools/nsc/transform/Mixin.scala | |
parent | b2e0911ded57f33e9600c6337a307c0fe1877eff (diff) | |
download | scala-0f813a5675f7f6c45acfab705b697e9cc1eceff3.tar.gz scala-0f813a5675f7f6c45acfab705b697e9cc1eceff3.tar.bz2 scala-0f813a5675f7f6c45acfab705b697e9cc1eceff3.zip |
Move AccessorSynthesis out to its own file
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/Mixin.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Mixin.scala | 457 |
1 files changed, 2 insertions, 455 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 190755ff53..21f585ef55 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -1,5 +1,6 @@ /* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL + * Copyright 2005-2016 LAMP/EPFL and Lightbend, Inc + * * @author Martin Odersky */ @@ -9,461 +10,7 @@ package transform import symtab._ import Flags._ import scala.annotation.tailrec -import scala.collection.mutable -trait AccessorSynthesis extends Transform with ast.TreeDSL { - import global._ - import definitions._ - import CODE._ - - 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 - } - - - trait AccessorTreeSynthesis { - protected def typedPos(pos: Position)(tree: Tree): 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 newDefs = _newDefs.toList - - /** Add tree at given position as new definition */ - protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree) - - /** The position of given symbol, or, if this is undefined, - * the position of the current class. - */ - private def position(sym: Symbol) = if (sym.pos == NoPosition) clazz.pos else sym.pos - - /** Add new method definition. - * - * @param sym The method symbol. - * @param rhs The method body. - */ - def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(DefDef(sym, rhs)) - def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(ValDef(sym, rhs)) - - /** Complete `stats` with init checks and bitmaps, - * removing any abstract method definitions in `stats` that are - * matched by some symbol defined by a tree previously passed to `addDef`. - */ - def implementWithNewDefs(stats: List[Tree]): List[Tree] = { - val newDefs = _newDefs.toList - val newSyms = newDefs map (_.symbol) - def isNotDuplicate(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => - val sym = tree.symbol - !(sym.isDeferred && - (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe)))) - case _ => true - } - if (newDefs.isEmpty) stats - else newDefs ::: (stats filter isNotDuplicate) - } - - def accessorBody(sym: Symbol) = - if (sym.isSetter) setterBody(sym, sym.getterIn(clazz)) else getterBody(sym) - - protected def getterBody(getter: Symbol): Tree = { - assert(getter.isGetter) - assert(getter.hasFlag(PARAMACCESSOR)) - - fieldAccess(getter) - } - - protected def setterBody(setter: Symbol, getter: Symbol): Tree = { - assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") - - Assign(fieldAccess(setter), Ident(setter.firstParam)) - } - - private def fieldAccess(accessor: Symbol) = - Select(This(clazz), accessor.accessed) - - } - } - - case class BitmapInfo(symbol: Symbol, mask: Literal) { - def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass - } - - - // 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]() - - def checkedAccessorSymbolSynth(clz: Symbol) = - if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz } - else new CheckedAccessorSymbolSynth { val clazz = clz } - - // 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 - - def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized - - - /** 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 - - import nme._ - - if (needsBitmap(sym) && sym.isLazy) - if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL - else NO_NAME - } - - - def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym) - protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym - - - /** 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 - ) - - bitmapSym addAnnotation VolatileAttr - - if (isTransientCategory) bitmapSym addAnnotation TransientAttr - - 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) - - _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask)) - } - - bitmapSyms - } - - decls groupBy bitmapCategory flatMap { - case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category) - case _ => Nil - } toList - } - - def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal) - - 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 - } - - } - - 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._ - - 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) - } - - - // 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'. - */ - def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = { - val bitmap = bitmapFor(field) - val bitmapTree = thisRef DOT bitmap.symbol - - if (bitmap.storageClass == BooleanClass) { - if (equalToZero) NOT(bitmapTree) else bitmapTree - } else { - 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'. */ - def mkSetFlag(valSym: Symbol): Tree = { - val bitmap = bitmapFor(valSym) - def x = thisRef DOT bitmap.symbol - - Assign(x, - 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 - } - ) - } - } - - class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass { - /** - * The compute method (slow path) looks like: - * - * ``` - * def l$compute() = { - * synchronized(this) { - * if ((bitmap$n & MASK) == 0) { - * init // l$ = <rhs> - * bitmap$n = bimap$n | MASK - * } - * } - * ... - * this.f1 = null - * ... - * this.fn = null - * l$ - * } - * ``` - * - * `bitmap$n` is a byte, int or long value acting as a bitmap of initialized values. - * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it. - * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32), - * the MASK is (1 << (offset % 32)). - * - * 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. - */ - 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 nulls = nullables.getOrElse(lazyAccessor, Nil) map nullify - - if (nulls.nonEmpty) - log("nulling fields inside " + lazyAccessor + ": " + nulls) - - val slowPathSym = slowPathFor(lazyAccessor) - val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym) - - val isUnit = isUnitGetter(lazyAccessor) - val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) - val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) - - val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil - val slowPathRhs = - Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, nulls)), selectVar) - - // 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) - - 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. */ - // 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 - // println("usedIn: " + usedIn) - - // 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 - - // println("singlyUsedIn: " + singlyUsedIn) - singlyUsedIn - } - - 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 - - map.mapValues(_.toList sortBy (_.id)).toMap - } - } - - - class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth { - private object addInitBitsTransformer extends Transformer { - private def checkedGetter(lhs: Tree)(pos: Position) = { - 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 - } - override def transformStats(stats: List[Tree], exprOwner: Symbol) = { - // !!! Ident(self) is never referenced, is it supposed to be confirming - // that self is anything in particular? - super.transformStats( - stats flatMap { - case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs)(stat.pos.focus) - // remove initialization for default values -- TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT - case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil - case stat => List(stat) - }, - exprOwner - ) - } - } - - /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ - 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 (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 (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) - else rhs - } - else rhs - } - - 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)). - THEN(retVal). - ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) - - typedPos(pos)(BLOCK(result, retVal)) - } - } - } -} abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ |