summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala95
-rw-r--r--test/files/run/lazy-leaks.scala18
3 files changed, 103 insertions, 12 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index da067ae1d9..b9d74ab686 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -69,7 +69,7 @@ abstract class GenICode extends SubComponent {
override def apply(unit: CompilationUnit): Unit = {
this.unit = unit
unit.icode.clear
- log("Generating icode for " + unit)
+ informProgress("Generating icode for " + unit)
gen(unit.body)
this.unit = null
}
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 71caf89010..d3c3a86f4c 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -9,9 +9,8 @@ package transform
import symtab._
import Flags._
-import scala.collection.mutable.ListBuffer
import scala.tools.nsc.util.{Position,NoPosition}
-import scala.collection.mutable.HashMap
+import collection.mutable.{ListBuffer, HashMap}
abstract class Mixin extends InfoTransform with ast.TreeDSL {
import global._
@@ -364,6 +363,50 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
tp
}
+ import scala.collection._
+
+ /** Return 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.
+ */
+ def singleUseFields(templ: Template): collection.Map[Symbol, List[Symbol]] = {
+ val usedIn = new mutable.HashMap[Symbol, List[Symbol]] {
+ override def default(key: Symbol) = 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.hasSymbol && tree.symbol != NoSymbol) {
+ val sym = tree.symbol
+ if ((sym.hasFlag(ACCESSOR) || (sym.isTerm && !sym.isMethod))
+ && sym.hasFlag(PRIVATE)
+ && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter
+ && !definitions.isValueClass(sym.tpe.resultType.typeSymbol)
+ && sym.owner == templ.symbol.owner
+ && !tree.isDef) {
+ log("added use in: " + currentOwner + " -- " + tree)
+ usedIn(sym) ::= currentOwner
+
+ }
+ }
+ super.traverse(tree)
+ }
+ }
+ }
+ SingleUseTraverser(templ)
+ log("usedIn: " + usedIn)
+ usedIn filter { pair =>
+ val member = pair._2.head
+ (member.isValue
+ && member.hasFlag(LAZY)
+ && pair._2.tail.isEmpty) }
+ }
+
// --------- term transformation -----------------------------------------------
protected def newTransformer(unit: CompilationUnit): Transformer =
@@ -384,6 +427,9 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
private var localTyper: erasure.Typer = _
private def typedPos(pos: Position)(tree: Tree) = localTyper typed { atPos(pos)(tree) }
+ /** Map lazy values to the fields they should null after initialization. */
+ private var lazyValNullables: mutable.MultiMap[Symbol, Symbol] = _
+
import scala.collection._
/** Map a field symbol to a unique integer denoting its position in the class layout.
@@ -612,17 +658,27 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* where bitmap$n is an int value acting as a bitmap of initialized values. It is
* the 'n' is (offset / 32), the MASK is (1 << (offset % 32)).
*/
- def mkLazyDef(clazz: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = {
+ def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = {
+ def nullify(sym: Symbol): Tree = {
+ val sym1 = if (sym.hasFlag(ACCESSOR)) sym.accessed else sym
+ Select(This(clazz), sym1) === LIT(null)
+ }
+
+
val bitmapSym = bitmapFor(clazz, offset)
val mask = LIT(1 << (offset % FLAGS_PER_WORD))
def cond = mkTest(clazz, mask, bitmapSym, true)
+ val nulls = (lazyValNullables(lzyVal).toList.sort(_.id < _.id) map nullify)
def syncBody = init ::: List(mkSetFlag(clazz, offset), UNIT)
+ log("nulling fields inside " + lzyVal + ": " + nulls)
val result =
- IF (cond) THEN gen.mkSynchronized(
- gen mkAttributedThis clazz,
- IF (cond) THEN BLOCK(syncBody: _*) ENDIF
- ) ENDIF
+ IF (cond) THEN BLOCK(
+ (gen.mkSynchronized(
+ gen mkAttributedThis clazz,
+ IF (cond) THEN BLOCK(syncBody: _*) ENDIF
+ )
+ :: nulls): _*) ENDIF
typedPos(init.head.pos)(BLOCK(result, retVal))
}
@@ -653,10 +709,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
if sym.hasFlag(LAZY) && rhs != EmptyTree && !clazz.isImplClass =>
assert(fieldOffset.isDefinedAt(sym))
val rhs1 = if (sym.tpe.resultType.typeSymbol == UnitClass)
- mkLazyDef(clazz, List(rhs), UNIT, fieldOffset(sym))
+ mkLazyDef(clazz, sym, List(rhs), UNIT, fieldOffset(sym))
else {
val Block(stats, res) = rhs
- mkLazyDef(clazz, stats, Select(This(clazz), res.symbol), fieldOffset(sym))
+ mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym))
}
treeCopy.DefDef(stat, mods, name, tp, vp, tpt, rhs1)
@@ -790,13 +846,13 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
if (sym.hasFlag(LAZY) && sym.isGetter) {
val rhs1 =
if (sym.tpe.resultType.typeSymbol == UnitClass)
- mkLazyDef(clazz, List(Apply(staticRef(initializer(sym)), List(gen.mkAttributedThis(clazz)))), UNIT, fieldOffset(sym))
+ mkLazyDef(clazz, sym, List(Apply(staticRef(initializer(sym)), List(gen.mkAttributedThis(clazz)))), UNIT, fieldOffset(sym))
else {
val assign = atPos(sym.pos) {
Assign(Select(This(sym.accessed.owner), sym.accessed) /*gen.mkAttributedRef(sym.accessed)*/ ,
Apply(staticRef(initializer(sym)), gen.mkAttributedThis(clazz) :: Nil))
}
- mkLazyDef(clazz, List(assign), Select(This(clazz), sym.accessed), fieldOffset(sym))
+ mkLazyDef(clazz, sym, List(assign), Select(This(clazz), sym.accessed), fieldOffset(sym))
}
rhs1
} else if (sym.getter(sym.owner).tpe.resultType.typeSymbol == UnitClass) {
@@ -855,6 +911,22 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
stats1
}
+ private def nullableFields(templ: Template) = {
+ val nullables = new mutable.HashMap[Symbol, mutable.Set[Symbol]] with mutable.MultiMap[Symbol, Symbol] {
+ override def default(key: Symbol) = mutable.Set.empty
+ }
+
+ // if there are no lazy fields, take the fast path and save a traversal of the whole AST
+ if (templ.symbol.owner.info.decls.exists(_.hasFlag(LAZY))) {
+ // check what fields can be nulled for
+ val uses = singleUseFields(templ)
+ for ((field, users) <- uses; lazyFld <- users) {
+ nullables.addBinding(lazyFld, field)
+ }
+ }
+ nullables
+ }
+
/** The transform that gets applied to a tree after it has been completely
* traversed and possible modified by a preTransform.
* This step will
@@ -884,6 +956,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// change parents of templates to conform to parents in the symbol info
val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos)
+ lazyValNullables = nullableFields(tree.asInstanceOf[Template])
// add all new definitions to current class or interface
val body1 = addNewDefs(currentOwner, body)
diff --git a/test/files/run/lazy-leaks.scala b/test/files/run/lazy-leaks.scala
new file mode 100644
index 0000000000..e7073b5b60
--- /dev/null
+++ b/test/files/run/lazy-leaks.scala
@@ -0,0 +1,18 @@
+class Lazy(f: => Int) {
+ lazy val get: Int = f
+}
+
+object Test extends Application
+{
+ val buffer = new scala.collection.mutable.ListBuffer[Lazy]
+
+ // This test requires 4 Mb of RAM if Lazy is discarding thunks
+ // It consumes 4 Gb of RAM if Lazy is not discarding thunks
+
+ for (val idx <- Iterator.range(0, 1024)) {
+ val data = new Array[Int](1024*1024)
+ val lz: Lazy = new Lazy(data.length)
+ buffer += lz
+ lz.get
+ }
+}