diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala new file mode 100644 index 0000000000..e027b065ac --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -0,0 +1,403 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL and Lightbend, Inc + */ + +package scala.tools.nsc +package transform + +import symtab._ +import Flags._ +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 transform? + 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: global.Symbol, lazyAccessor: global.Symbol, transformedRhs: global.Tree): Tree = { + 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, fields.castHack(rhsAtSlowDef, lazyVar.info)) + + def needsInit = mkTest(lazyAccessor) + val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor)) + // the slow part of double-checked locking (TODO: is this the most efficient pattern? https://github.come/scala/scala-dev/issues/204) + val slowPathRhs = Block(gen.mkSynchronized(thisRef)(If(needsInit, doInit, EmptyTree)) :: Nil, 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(needsInit, 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)) + } + } + } + + 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)) + } + } + } +} |