summaryrefslogtreecommitdiff
path: root/src/compiler/scala
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2015-08-21 14:00:46 +1000
committerJason Zaugg <jzaugg@gmail.com>2015-10-08 14:09:59 +1000
commit571ed0312031a0826f65d40b27933d16b9617fbe (patch)
treedcd841aaa63b1b97d47eab76a5d3e2afc53ac75d /src/compiler/scala
parent5614baf08b2da56532b580c7e2f70cf357832970 (diff)
downloadscala-571ed0312031a0826f65d40b27933d16b9617fbe.tar.gz
scala-571ed0312031a0826f65d40b27933d16b9617fbe.tar.bz2
scala-571ed0312031a0826f65d40b27933d16b9617fbe.zip
Desugar module var and accessor in refchecks/lazyvals
Rather than leaving it until mixin. The broader motivation is to simplify the mixin phase of the compiler before we get rid of implementatation classes in favour of using JDK8 default interface methods. The current code in mixin is used for both lazy val and modules, and puts the "slow path" code that uses the monitor into a dedicated method (`moduleName$lzyCompute`). I tracked this back to a3d4d17b77. I can't tell from that commit whether the performance sensititivity was related to modules or lazy vals, from the commit message I'd say the latter. As the initialization code for a module is just a constructor call, rather than an arbitraryly large chunk of code for a lazy initializer, this commit opts to inline the `lzycompute` method. During refchecks, mixin module accessors are added to classes, so that mixed in and defined modules are translated uniformly. Trait owned modules get an accessor method with an empty body (that shares the module symbol), but no module var. Defer synthesis of the double checked locking idiom to the lazyvals phase, which gets us a step closer to a unified translation of modules and lazy vals. I had to change the `atOwner` methods to to avoid using the non-existent module class of a module accessor method as the current owner. This fixes a latent bug. Without this change, retypechecking of the module accessor method during erasure crashes with an accessibility error selecting the module var. In the process, I've tweaked a tree generation utility method to wvoid synthesizing redundant blocks in module desugaring.
Diffstat (limited to 'src/compiler/scala')
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala13
-rw-r--r--src/compiler/scala/tools/nsc/transform/LazyVals.scala52
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala35
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypingTransformers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala39
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala2
6 files changed, 81 insertions, 62 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index 332acf4a26..3167f87383 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -242,11 +242,14 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats)
- def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
- Block(mkSynchronized(
- attrThis,
- If(cond, Block(syncBody: _*), EmptyTree)) ::
- stats: _*)
+ def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = {
+ def blockOrStat(stats: List[Tree]): Tree = stats match {
+ case head :: Nil => head
+ case _ => Block(stats : _*)
+ }
+ val sync = mkSynchronized(attrThis, If(cond, blockOrStat(syncBody), EmptyTree))
+ blockOrStat(sync :: stats)
+ }
/** Creates a tree representing new Object { stats }.
* To make sure an anonymous subclass of Object is created,
diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala
index b6695efb0b..73c15c1f53 100644
--- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala
+++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala
@@ -54,14 +54,35 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
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 }
+ * // refchecks
+ * 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.
+ *
+ * The general pattern is
*/
override def transform(tree: Tree): Tree = {
val sym = tree.symbol
@@ -72,20 +93,14 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
case Block(_, _) =>
val block1 = super.transform(tree)
val Block(stats, expr) = block1
- val stats1 = stats.flatMap(_ match {
- case Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if (nme.newLazyValSlowComputeName(n2) == n1) =>
- List(d1, d2)
- case stat =>
- List(stat)
- })
- treeCopy.Block(block1, stats1, expr)
+ 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 ) {
+ if (enclMethod != NoSymbol) {
val enclClass = sym.enclClass
if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod)
enclClass
@@ -100,11 +115,26 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym)
sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR)
(rhs1, sDef)
- } else
+ } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) {
+ rhs match {
+ case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) =>
+ val cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null))))
+ val (fastPath, slowPath) = mkFastPathBody(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) Block(slowPathDef, ddef1) else ddef1
+ 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) {
@@ -135,7 +165,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
})
toAdd0
} else List()
- deriveTemplate(tree)(_ => innerClassBitmaps ++ stats)
+ deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats))
}
case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass =>
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 00a994fe87..d6e4e1a727 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -861,16 +861,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal))
}
- def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree =
- rhs match {
- case Block(List(assign), returnTree) =>
- val Assign(moduleVarRef, _) = assign
- val cond = Apply(Select(moduleVarRef, Object_eq), List(NULL))
- mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args)
- case _ =>
- abort(s"Invalid getter $rhs for module in $clazz")
- }
-
def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = {
val sym = fieldSym.getterIn(fieldSym.owner)
val bitmapSym = bitmapFor(clazz, offset, sym)
@@ -926,18 +916,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT))
else stat
}
- else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) {
- deriveDefDef(stat)(rhs =>
- typedPos(stat.pos)(
- mkInnerClassAccessorDoubleChecked(
- // Martin to Hubert: I think this can be replaced by selfRef(tree.pos)
- // @PP: It does not seem so, it crashes for me trying to bootstrap.
- if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz),
- rhs, sym, stat.vparamss.head
- )
- )
- )
- }
else stat
}
stats map {
@@ -1082,18 +1060,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
})
}
else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) {
- // add modules
- val vsym = sym.owner.newModuleVarSymbol(sym)
- addDef(position(sym), ValDef(vsym))
-
- // !!! TODO - unravel the enormous duplication between this code and
- // eliminateModuleDefs in RefChecks.
- val rhs = gen.newModule(sym, vsym.tpe)
- val assignAndRet = gen.mkAssignAndReturn(vsym, rhs)
- val attrThis = gen.mkAttributedThis(clazz)
- val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List())
-
- addDefDef(sym, rhs1)
+ // Moved to Refchecks
}
else if (!sym.isMethod) {
// add fields
diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
index dc3313e2e4..d5adfe12e9 100644
--- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
@@ -26,7 +26,7 @@ trait TypingTransformers {
def atOwner[A](tree: Tree, owner: Symbol)(trans: => A): A = {
val savedLocalTyper = localTyper
- localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner)
+ localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner)
val result = super.atOwner(owner)(trans)
localTyper = savedLocalTyper
result
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index 7252cae0b5..915ffcc7d0 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -1188,14 +1188,6 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// set NoType so it will be ignored.
val cdef = ClassDef(module.moduleClass, impl) setType NoType
- def newInnerObject() = {
- val moduleVar = site newModuleVarSymbol module
- val rhs = gen.newModule(module, moduleVar.tpe)
- val body = if (site.isTrait) rhs else gen.mkAssignAndReturn(moduleVar, rhs)
- val accessor = DefDef(module, body.changeOwner(moduleVar -> module))
-
- ValDef(moduleVar) :: accessor :: Nil
- }
def matchingInnerObject() = {
val newFlags = (module.flags | STABLE) & ~MODULE
val newInfo = NullaryMethodType(module.moduleClass.tpe)
@@ -1208,10 +1200,36 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O.
if (module.isOverridingSymbol) matchingInnerObject() else Nil
else
- newInnerObject()
+ newInnerObject(site, module)
)
transformTrees(newTrees map localTyper.typedPos(moduleDef.pos))
}
+ def newInnerObject(site: Symbol, module: Symbol): List[Tree] = {
+ if (site.isTrait)
+ DefDef(module, EmptyTree) :: Nil
+ else {
+ val moduleVar = site newModuleVarSymbol module
+ // used for the mixin case: need a new symbol owned by the subclass for the accessor, rather than repurposing the module symbol
+ def mkAccessorSymbol =
+ site.newMethod(module.name.toTermName, site.pos, STABLE | MODULE | MIXEDIN)
+ .setInfo(moduleVar.tpe)
+ .andAlso(self => if (module.isPrivate) self.expandName(module.owner))
+
+ val accessor = if (module.owner == site) module else mkAccessorSymbol
+ val accessorDef = DefDef(accessor, gen.mkAssignAndReturn(moduleVar, gen.newModule(module, moduleVar.tpe)).changeOwner(moduleVar -> accessor))
+
+ ValDef(moduleVar) :: accessorDef :: Nil
+ }
+ }
+
+ def mixinModuleDefs(clazz: Symbol): List[Tree] = {
+ val res = for {
+ mixinClass <- clazz.mixinClasses.iterator
+ module <- mixinClass.info.decls.iterator.filter(_.isModule)
+ newMember <- newInnerObject(clazz, module)
+ } yield transform(localTyper.typedPos(clazz.pos)(newMember))
+ res.toList
+ }
def transformStat(tree: Tree, index: Int): List[Tree] = tree match {
case t if treeInfo.isSelfConstrCall(t) =>
@@ -1643,11 +1661,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// SI-7870 default getters for constructors live in the companion module
checkOverloadedRestrictions(currentOwner, currentOwner.companionModule)
val bridges = addVarargBridges(currentOwner)
+ val moduleDesugared = if (currentOwner.isTrait) Nil else mixinModuleDefs(currentOwner)
checkAllOverrides(currentOwner)
checkAnyValSubclass(currentOwner)
if (currentOwner.isDerivedValueClass)
currentOwner.primaryConstructor makeNotPrivate NoSymbol // SI-6601, must be done *after* pickler!
- if (bridges.nonEmpty) deriveTemplate(tree)(_ ::: bridges) else tree
+ if (bridges.nonEmpty || moduleDesugared.nonEmpty) deriveTemplate(tree)(_ ::: bridges ::: moduleDesugared) else tree
case dc@TypeTreeWithDeferredRefCheck() => abort("adapt should have turned dc: TypeTreeWithDeferredRefCheck into tpt: TypeTree, with tpt.original == dc")
case tpt@TypeTree() =>
diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
index e0d96df062..187d2b1084 100644
--- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala
@@ -387,7 +387,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT
val savedValid = validCurrentOwner
if (owner.isClass) validCurrentOwner = true
val savedLocalTyper = localTyper
- localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner)
+ localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner)
typers = typers updated (owner, localTyper)
val result = super.atOwner(tree, owner)(trans)
localTyper = savedLocalTyper