Skip to content

Commit a3d4d17

Browse files
committed
Part III of the Lazy Vals Saga: Inliner strikes back. Or at least let's hope it will.
1 parent 3631f1d commit a3d4d17

File tree

5 files changed

+77
-33
lines changed

5 files changed

+77
-33
lines changed

src/compiler/scala/reflect/internal/StdNames.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ trait StdNames {
293293
val FAKE_LOCAL_THIS: NameType = "this$"
294294
val INITIALIZER: NameType = CONSTRUCTOR // Is this buying us something?
295295
val LAZY_LOCAL: NameType = "$lzy"
296+
val LAZY_SLOW_SUFFIX: NameType = "$lzycompute"
296297
val LOCAL_SUFFIX_STRING = " "
297298
val MIRROR_FREE_PREFIX: NameType = "free$"
298299
val MIRROR_FREE_THIS_SUFFIX: NameType = "$this"
@@ -810,6 +811,8 @@ trait StdNames {
810811
val BITMAP_CHECKINIT: NameType = BITMAP_PREFIX + "init$" // initialization bitmap for checkinit values
811812
val BITMAP_CHECKINIT_TRANSIENT: NameType = BITMAP_PREFIX + "inittrans$" // initialization bitmap for transient checkinit values
812813

814+
def newLazyValSlowComputeName(lzyValName: Name) = lzyValName append LAZY_SLOW_SUFFIX
815+
813816
def isModuleVarName(name: Name): Boolean =
814817
stripAnonNumberSuffix(name) endsWith MODULE_VAR_SUFFIX
815818

src/compiler/scala/reflect/internal/Trees.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ trait Trees extends api.Trees { self: SymbolTable =>
412412
tree match {
413413
case Ident(name0) if tree.symbol != NoSymbol =>
414414
treeCopy.Ident(tree, tree.symbol.name)
415-
case Select(qual, name0) =>
415+
case Select(qual, name0) if tree.symbol != NoSymbol =>
416416
treeCopy.Select(tree, transform(qual), tree.symbol.name)
417417
case _ =>
418418
super.transform(tree)

src/compiler/scala/tools/nsc/ast/TreeGen.scala

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -362,13 +362,12 @@ abstract class TreeGen extends reflect.internal.TreeGen with TreeDSL {
362362
def mkDoubleCheckedLocking(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
363363
mkDoubleCheckedLocking(mkAttributedThis(clazz), cond, syncBody, stats)
364364

365-
def mkDoubleCheckedLocking(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = {
366-
If(cond,
367-
Block(
368-
mkSynchronized(
369-
attrThis,
370-
If(cond, Block(syncBody: _*), EmptyTree)) ::
371-
stats: _*),
372-
EmptyTree)
373-
}
365+
def mkDoubleCheckedLocking(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
366+
If(cond, mkSynchronizedCheck(attrThis, cond, syncBody, stats), EmptyTree)
367+
368+
def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
369+
Block(mkSynchronized(
370+
attrThis,
371+
If(cond, Block(syncBody: _*), EmptyTree)) ::
372+
stats: _*)
374373
}

src/compiler/scala/tools/nsc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ abstract class Erasure extends AddInterfaces
643643
else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) {
644644
adaptToType(box(tree, pt.toString), pt)
645645
} else if (tree.tpe.isInstanceOf[MethodType] && tree.tpe.params.isEmpty) {
646-
// [H] this assert fails when try to call !(Some.this.bitmap) for single lazy val
646+
// [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val
647647
//assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt)
648648
adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt)
649649
// } else if (pt <:< tree.tpe)

src/compiler/scala/tools/nsc/transform/Mixin.scala

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -775,32 +775,76 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
775775
else lhs GEN_!= (ZERO, kind)
776776
}
777777
}
778+
779+
def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
780+
stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Symbol = {
781+
val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name), lzyVal.pos, PRIVATE)
782+
val params = defSym newSyntheticValueParams args.map(_.symbol.tpe)
783+
defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType)
784+
val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym)
785+
val strictSubst = new SlowPathTreeSymSubstituter(args.map(_.symbol), params)
786+
addDef(position(defSym), DEF(defSym).mkTree(strictSubst(BLOCK(rhs, retVal))) setSymbol defSym)
787+
defSym
788+
}
789+
790+
// Always copy the tree if we are going to perform sym substitution,
791+
// otherwise we will side-effect on the tree that is used in the fast path
792+
class SlowPathTreeSymSubstituter(from: List[Symbol], to: List[Symbol]) extends TreeSymSubstituter(from, to) {
793+
override def transform(tree: Tree): Tree = {
794+
if (tree.hasSymbol && from.contains(tree.symbol)) {
795+
super.transform(tree.duplicate)
796+
} else super.transform(tree.duplicate)
797+
}
798+
override def apply[T <: Tree](tree: T): T = if (from.isEmpty) tree else super.apply(tree)
799+
}
800+
801+
def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
802+
stats: List[Tree], retVal: Tree): Tree = {
803+
mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List())
804+
}
805+
806+
def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
807+
stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = {
808+
val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args)
809+
If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal)
810+
}
778811

779812
/** return a 'lazified' version of rhs. It uses double-checked locking to ensure
780-
* initialization is performed at most once. Private fields used only in this
781-
* initializer are subsequently set to null.
813+
* initialization is performed at most once. For performance reasons the double-checked
814+
* locking is split into two parts, the first (fast) path checks the bitmap without
815+
* synchronizing, and if that fails it initializes the lazy val within the
816+
* synchronization block (slow path). This way the inliner should optimize
817+
* the fast path because the method body is small enough.
818+
* Private fields used only in this initializer are subsequently set to null.
782819
*
783820
* @param clazz The class symbol
784821
* @param init The tree which initializes the field ( f = <rhs> )
785822
* @param fieldSym The symbol of this lazy field
786823
* @param offset The offset of this field in the flags bitmap
787824
*
788825
* The result will be a tree of the form
789-
* {
790-
* if ((bitmap$n & MASK) == 0) {
791-
* synchronized(this) {
792-
* if ((bitmap$n & MASK) == 0) {
793-
* init // l$ = <rhs>
794-
* bitmap$n = bimap$n | MASK
795-
* }
796-
* }
797-
* this.f1 = null
798-
* ... this.fn = null
826+
* { if ((bitmap&n & MASK) == 0) this.l$compute()
827+
* else l$
828+
*
829+
* ...
830+
* def l$compute() = { synchronized(this) {
831+
* if ((bitmap$n & MASK) == 0) {
832+
* init // l$ = <rhs>
833+
* bitmap$n = bimap$n | MASK
834+
* }}
835+
* l$
799836
* }
800-
* l$
837+
*
838+
* ...
839+
* this.f1 = null
840+
* ... this.fn = null
801841
* }
802-
* where bitmap$n is an int value acting as a bitmap of initialized values. It is
803-
* the 'n' is (offset / 32), the MASK is (1 << (offset % 32)).
842+
* where bitmap$n is a byte, int or long value acting as a bitmap of initialized values.
843+
* The kind of the bitmap determines how many bit indicators for lazy vals are stored in it.
844+
* For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32),
845+
* the MASK is (1 << (offset % 32)).
846+
* If the class contains only a single lazy val then the bitmap is represented
847+
* as a Boolean and the condition checking is a simple bool test.
804848
*/
805849
def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = {
806850
def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null)
@@ -815,17 +859,15 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
815859
if (nulls.nonEmpty)
816860
log("nulling fields inside " + lzyVal + ": " + nulls)
817861

818-
val result = gen.mkDoubleCheckedLocking(clazz, cond, syncBody, nulls)
819-
typedPos(init.head.pos)(BLOCK(result, retVal))
862+
typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal))
820863
}
821864

822-
def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree): Tree =
865+
def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree =
823866
rhs match {
824867
case Block(List(assign), returnTree) =>
825868
val Assign(moduleVarRef, _) = assign
826869
val cond = Apply(Select(moduleVarRef, nme.eq), List(NULL))
827-
val doubleSynchrTree = gen.mkDoubleCheckedLocking(attrThis, cond, List(assign), Nil)
828-
Block(List(doubleSynchrTree), returnTree)
870+
mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args)
829871
case _ =>
830872
assert(false, "Invalid getter " + rhs + " for module in class " + clazz)
831873
EmptyTree
@@ -893,7 +935,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
893935
// Martin to Hubert: I think this can be replaced by selfRef(tree.pos)
894936
// @PP: It does not seem so, it crashes for me trying to bootstrap.
895937
if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz),
896-
rhs
938+
rhs, sym, stat.vparamss.head
897939
)
898940
)
899941
)
@@ -1032,7 +1074,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
10321074
val rhs = gen.newModule(sym, vdef.symbol.tpe)
10331075
val assignAndRet = gen.mkAssignAndReturn(vdef.symbol, rhs)
10341076
val attrThis = gen.mkAttributedThis(clazz)
1035-
val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet)
1077+
val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List())
10361078

10371079
addDefDef(sym, rhs1)
10381080
}

0 commit comments

Comments
 (0)