diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/GenICode.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Mixin.scala | 95 | ||||
-rw-r--r-- | test/files/run/lazy-leaks.scala | 18 |
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 + } +} |