diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-11-02 11:08:28 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-11-22 01:35:07 +0100 |
commit | 8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch) | |
tree | a8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/dotc/transform/LambdaLift.scala | |
parent | 6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff) | |
download | dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2 dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip |
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/LambdaLift.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/transform/LambdaLift.scala | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala new file mode 100644 index 000000000..19fb3dd0c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -0,0 +1,548 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import core.Phases._ +import ast.Trees._ +import SymUtils._ +import ExplicitOuter.outer +import util.Attachment +import util.NameTransformer +import util.Positions._ +import collection.{ mutable, immutable } +import collection.mutable.{ HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeSet } + +object LambdaLift { + private val NJ = NameTransformer.NAME_JOIN_STRING + private class NoPath extends Exception +} + +/** This phase performs the necessary rewritings to eliminate classes and methods + * nested in other methods. In detail: + * 1. It adds all free variables of local functions as additional parameters (proxies). + * 2. It rebinds references to free variables to the corresponding proxies, + * 3. It lifts all local functions and classes out as far as possible, but at least + * to the enclosing class. + * 4. It stores free variables of non-trait classes as additional fields of the class. + * The fields serve as proxies for methods in the class, which avoids the need + * of passing additional parameters to these methods. + * + * A particularly tricky case are local traits. These cannot store free variables + * as field proxies, because LambdaLift runs after Mixin, so the fields cannot be + * expanded anymore. Instead, methods of local traits get free variables of + * the trait as additional proxy parameters. The difference between local classes + * and local traits is illustrated by the two rewritings below. + * + * def f(x: Int) = { def f(x: Int) = new C(x).f2 + * class C { ==> class C(x$1: Int) { + * def f2 = x def f2 = x$1 + * } } + * new C().f2 + * } + * + * def f(x: Int) = { def f(x: Int) = new C().f2(x) + * trait T { ==> trait T + * def f2 = x def f2(x$1: Int) = x$1 + * } } + * class C extends T class C extends T + * new C().f2 + * } + */ +class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform => + import LambdaLift._ + import ast.tpd._ + + /** the following two members override abstract members in Transform */ + val phaseName: String = "lambdaLift" + val treeTransform = new LambdaLifter + + override def relaxedTyping = true + + override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Constructors]) + // Constructors has to happen before LambdaLift because the lambda lift logic + // becomes simpler if it can assume that parameter accessors have already been + // converted to parameters in super calls. Without this it is very hard to get + // lambda lift for super calls right. Witness the implementation restrictions to + // this effect in scalac. + + class LambdaLifter extends TreeTransform { + override def phase = thisTransform + + private type SymSet = TreeSet[Symbol] + + /** A map storing free variables of functions and classes */ + private val free = new LinkedHashMap[Symbol, SymSet] + + /** A map storing the free variable proxies of functions and classes. + * For every function and class, this is a map from the free variables + * of that function or class to the proxy symbols accessing them. + */ + private val proxyMap = new LinkedHashMap[Symbol, Map[Symbol, Symbol]] + + /** A hashtable storing calls between functions */ + private val called = new LinkedHashMap[Symbol, SymSet] + + /** Symbols that are called from an inner class. */ + private val calledFromInner = new HashSet[Symbol] + + /** A map from local methods and classes to the owners to which they will be lifted as members. + * For methods and classes that do not have any dependencies this will be the enclosing package. + * symbols with packages as lifted owners will subsequently represented as static + * members of their toplevel class, unless their enclosing class was already static. + * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner + * is also used to decide whether a method had a term owner before. + */ + private val liftedOwner = new HashMap[Symbol, Symbol] + + /** The outer parameter of a constructor */ + private val outerParam = new HashMap[Symbol, Symbol] + + /** Buffers for lifted out classes and methods, indexed by owner */ + private val liftedDefs = new HashMap[Symbol, mutable.ListBuffer[Tree]] + + /** A flag to indicate whether new free variables have been found */ + private var changedFreeVars: Boolean = _ + + /** A flag to indicate whether lifted owners have changed */ + private var changedLiftedOwner: Boolean = _ + + private val ord: Ordering[Symbol] = Ordering.by((_: Symbol).id) // Dotty deviation: Type annotation needed. TODO: figure out why + private def newSymSet = TreeSet.empty[Symbol](ord) + + private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = + f.getOrElseUpdate(sym, newSymSet) + + def freeVars(sym: Symbol): List[Symbol] = free get sym match { + case Some(set) => set.toList + case None => Nil + } + + def proxyOf(sym: Symbol, fv: Symbol) = proxyMap.getOrElse(sym, Map.empty)(fv) + + def proxies(sym: Symbol): List[Symbol] = freeVars(sym).map(proxyOf(sym, _)) + + /** A symbol is local if it is owned by a term or a local trait, + * or if it is a constructor of a local symbol. + */ + def isLocal(sym: Symbol)(implicit ctx: Context): Boolean = { + val owner = sym.maybeOwner + owner.isTerm || + owner.is(Trait) && isLocal(owner) || + sym.isConstructor && isLocal(owner) + } + + /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested + * than the previous value of `liftedowner(sym)`. + */ + def narrowLiftedOwner(sym: Symbol, owner: Symbol)(implicit ctx: Context) = + if (sym.maybeOwner.isTerm && + owner.isProperlyContainedIn(liftedOwner(sym)) && + owner != sym) { + ctx.log(i"narrow lifted $sym to $owner") + changedLiftedOwner = true + liftedOwner(sym) = owner + } + + /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined + * in `enclosure` or there is an intermediate class properly containing `enclosure` + * in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so + * that `enclosure` can access `sym`, or its proxy in an intermediate class. + * This means: + * + * 1. If there is an intermediate class in which `sym` is free, `enclosure` + * must be contained in that class (in order to access the `sym proxy stored + * in the class). + * + * 2. If there is no intermediate class, `enclosure` must be contained + * in the class enclosing `sym`. + * + * @return If there is a non-trait class between `enclosure` and + * the owner of `sym`, the largest such class. + * Otherwise, if there is a trait between `enclosure` and + * the owner of `sym`, the largest such trait. + * Otherwise, NoSymbol. + * + * @pre sym.owner.isTerm, (enclosure.isMethod || enclosure.isClass) + * + * The idea of `markFree` is illustrated with an example: + * + * def f(x: int) = { + * class C { + * class D { + * val y = x + * } + * } + * } + * + * In this case `x` is free in the primary constructor of class `C`. + * but it is not free in `D`, because after lambda lift the code would be transformed + * as follows: + * + * def f(x$0: int) { + * class C(x$0: int) { + * val x$1 = x$0 + * class D { + * val y = outer.x$1 + * } + * } + * } + */ + private def markFree(sym: Symbol, enclosure: Symbol)(implicit ctx: Context): Symbol = try { + if (!enclosure.exists) throw new NoPath + if (enclosure == sym.enclosure) NoSymbol + else { + ctx.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + val intermediate = + if (enclosure.is(PackageClass)) enclosure + else markFree(sym, enclosure.enclosure) + narrowLiftedOwner(enclosure, intermediate orElse sym.enclosingClass) + if (!intermediate.isRealClass || enclosure.isConstructor) { + // Constructors and methods nested inside traits get the free variables + // of the enclosing trait or class. + // Conversely, local traits do not get free variables. + if (!enclosure.is(Trait)) + if (symSet(free, enclosure).add(sym)) { + changedFreeVars = true + ctx.log(i"$sym is free in $enclosure") + } + } + if (intermediate.isRealClass) intermediate + else if (enclosure.isRealClass) enclosure + else if (intermediate.isClass) intermediate + else if (enclosure.isClass) enclosure + else NoSymbol + } + } catch { + case ex: NoPath => + println(i"error lambda lifting ${ctx.compilationUnit}: $sym is not visible from $enclosure") + throw ex + } + + private def markCalled(callee: Symbol, caller: Symbol)(implicit ctx: Context): Unit = { + ctx.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") + assert(isLocal(callee)) + symSet(called, caller) += callee + if (callee.enclosingClass != caller.enclosingClass) calledFromInner += callee + } + + private class CollectDependencies extends EnclosingMethodTraverser { + def traverse(enclosure: Symbol, tree: Tree)(implicit ctx: Context) = try { //debug + val sym = tree.symbol + def narrowTo(thisClass: ClassSymbol) = { + val enclClass = enclosure.enclosingClass + narrowLiftedOwner(enclosure, + if (enclClass.isContainedIn(thisClass)) thisClass + else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner + } + tree match { + case tree: Ident => + if (isLocal(sym)) { + if (sym is Label) + assert(enclosure == sym.enclosure, + i"attempt to refer to label $sym from nested $enclosure") + else if (sym is Method) markCalled(sym, enclosure) + else if (sym.isTerm) markFree(sym, enclosure) + } + def captureImplicitThis(x: Type): Unit = { + x match { + case tr@TermRef(x, _) if (!tr.termSymbol.isStatic) => captureImplicitThis(x) + case x: ThisType if (!x.tref.typeSymbol.isStaticOwner) => narrowTo(x.tref.typeSymbol.asClass) + case _ => + } + } + captureImplicitThis(tree.tpe) + case tree: Select => + if (sym.is(Method) && isLocal(sym)) markCalled(sym, enclosure) + case tree: This => + narrowTo(tree.symbol.asClass) + case tree: DefDef => + if (sym.owner.isTerm && !sym.is(Label)) + liftedOwner(sym) = sym.enclosingPackageClass + // this will make methods in supercall constructors of top-level classes owned + // by the enclosing package, which means they will be static. + // On the other hand, all other methods will be indirectly owned by their + // top-level class. This avoids possible deadlocks when a static method + // has to access its enclosing object from the outside. + else if (sym.isConstructor) { + if (sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait)) + // add a call edge from the constructor of a local non-trait class to + // the class itself. This is done so that the constructor inherits + // the free variables of the class. + symSet(called, sym) += sym.owner + + tree.vparamss.head.find(_.name == nme.OUTER) match { + case Some(vdef) => outerParam(sym) = vdef.symbol + case _ => + } + } + case tree: TypeDef => + if (sym.owner.isTerm) liftedOwner(sym) = sym.topLevelClass.owner + case tree: Template => + liftedDefs(tree.symbol.owner) = new mutable.ListBuffer + case _ => + } + foldOver(enclosure, tree) + } catch { //debug + case ex: Exception => + println(i"$ex while traversing $tree") + throw ex + } + } + + /** Compute final free variables map `fvs by closing over caller dependencies. */ + private def computeFreeVars()(implicit ctx: Context): Unit = + do { + changedFreeVars = false + for { + caller <- called.keys + callee <- called(caller) + fvs <- free get callee + fv <- fvs + } markFree(fv, caller) + } while (changedFreeVars) + + /** Compute final liftedOwner map by closing over caller dependencies */ + private def computeLiftedOwners()(implicit ctx: Context): Unit = + do { + changedLiftedOwner = false + for { + caller <- called.keys + callee <- called(caller) + } { + val normalizedCallee = callee.skipConstructor + val calleeOwner = normalizedCallee.owner + if (calleeOwner.isTerm) narrowLiftedOwner(caller, liftedOwner(normalizedCallee)) + else { + assert(calleeOwner.is(Trait)) + // methods nested inside local trait methods cannot be lifted out + // beyond the trait. Note that we can also call a trait method through + // a qualifier; in that case no restriction to lifted owner arises. + if (caller.isContainedIn(calleeOwner)) + narrowLiftedOwner(caller, calleeOwner) + } + } + } while (changedLiftedOwner) + + private def newName(sym: Symbol)(implicit ctx: Context): Name = + if (sym.isAnonymousFunction && sym.owner.is(Method, butNot = Label)) + (sym.name ++ NJ ++ sym.owner.name).freshened + else sym.name.freshened + + private def generateProxies()(implicit ctx: Context): Unit = + for ((owner, freeValues) <- free.toIterator) { + val newFlags = Synthetic | (if (owner.isClass) ParamAccessor | Private else Param) + ctx.debuglog(i"free var proxy: ${owner.showLocated}, ${freeValues.toList}%, %") + proxyMap(owner) = { + for (fv <- freeValues.toList) yield { + val proxyName = newName(fv) + val proxy = ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord) + if (owner.isClass) proxy.enteredAfter(thisTransform) + (fv, proxy) + } + }.toMap + } + + private def liftedInfo(local: Symbol)(implicit ctx: Context): Type = local.info match { + case mt @ MethodType(pnames, ptypes) => + val ps = proxies(local) + MethodType( + ps.map(_.name.asTermName) ++ pnames, + ps.map(_.info) ++ ptypes, + mt.resultType) + case info => info + } + + private def liftLocals()(implicit ctx: Context): Unit = { + for ((local, lOwner) <- liftedOwner) { + val (newOwner, maybeStatic) = + if (lOwner is Package) { + val encClass = local.enclosingClass + val topClass = local.topLevelClass + val preferEncClass = + encClass.isStatic && + // non-static classes can capture owners, so should be avoided + (encClass.isProperlyContainedIn(topClass) || + // can be false for symbols which are defined in some weird combination of supercalls. + encClass.is(ModuleClass, butNot = Package) + // needed to not cause deadlocks in classloader. see t5375.scala + ) + if (preferEncClass) (encClass, EmptyFlags) + else (topClass, JavaStatic) + } + else (lOwner, EmptyFlags) + local.copySymDenotation( + owner = newOwner, + name = newName(local), + initFlags = local.flags &~ (InSuperCall | Module) | Private | maybeStatic, + // drop Module because class is no longer a singleton in the lifted context. + info = liftedInfo(local)).installAfter(thisTransform) + } + for (local <- free.keys) + if (!liftedOwner.contains(local)) + local.copySymDenotation(info = liftedInfo(local)).installAfter(thisTransform) + } + + private def init(implicit ctx: Context) = { + (new CollectDependencies).traverse(NoSymbol, ctx.compilationUnit.tpdTree) + computeFreeVars() + computeLiftedOwners() + generateProxies()(ctx.withPhase(thisTransform.next)) + liftLocals()(ctx.withPhase(thisTransform.next)) + } + + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { + val lifter = new LambdaLifter + lifter.init(ctx.withPhase(thisTransform)) + lifter + } + + private def currentEnclosure(implicit ctx: Context) = + ctx.owner.enclosingMethodOrClass + + private def inCurrentOwner(sym: Symbol)(implicit ctx: Context) = + sym.enclosure == currentEnclosure + + private def proxy(sym: Symbol)(implicit ctx: Context): Symbol = { + def liftedEnclosure(sym: Symbol) = liftedOwner.getOrElse(sym, sym.enclosure) + def searchIn(enclosure: Symbol): Symbol = { + if (!enclosure.exists) { + def enclosures(encl: Symbol): List[Symbol] = + if (encl.exists) encl :: enclosures(liftedEnclosure(encl)) else Nil + throw new IllegalArgumentException(i"Could not find proxy for ${sym.showDcl} in ${sym.ownersIterator.toList}, encl = $currentEnclosure, owners = ${currentEnclosure.ownersIterator.toList}%, %; enclosures = ${enclosures(currentEnclosure)}%, %") + } + ctx.debuglog(i"searching for $sym(${sym.owner}) in $enclosure") + proxyMap get enclosure match { + case Some(pmap) => + pmap get sym match { + case Some(proxy) => return proxy + case none => + } + case none => + } + searchIn(liftedEnclosure(enclosure)) + } + if (inCurrentOwner(sym)) sym else searchIn(currentEnclosure) + } + + private def memberRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val clazz = sym.enclosingClass + val qual = + if (clazz.isStaticOwner || ctx.owner.enclosingClass == clazz) + singleton(clazz.thisType) + else if (ctx.owner.isConstructor) + outerParam.get(ctx.owner) match { + case Some(param) => outer.path(clazz, Ident(param.termRef)) + case _ => outer.path(clazz) + } + else outer.path(clazz) + transformFollowingDeep(qual.select(sym)) + } + + private def proxyRef(sym: Symbol)(implicit ctx: Context, info: TransformerInfo): Tree = { + val psym = proxy(sym)(ctx.withPhase(thisTransform)) + transformFollowingDeep(if (psym.owner.isTerm) ref(psym) else memberRef(psym)) + } + + private def addFreeArgs(sym: Symbol, args: List[Tree])(implicit ctx: Context, info: TransformerInfo) = + free get sym match { + case Some(fvs) => fvs.toList.map(proxyRef(_)) ++ args + case _ => args + } + + private def addFreeParams(tree: Tree, proxies: List[Symbol])(implicit ctx: Context, info: TransformerInfo): Tree = proxies match { + case Nil => tree + case proxies => + val sym = tree.symbol + val freeParamDefs = proxies.map(proxy => + transformFollowingDeep(ValDef(proxy.asTerm).withPos(tree.pos)).asInstanceOf[ValDef]) + def proxyInit(field: Symbol, param: Symbol) = + transformFollowingDeep(memberRef(field).becomes(ref(param))) + + /** Initialize proxy fields from proxy parameters and map `rhs` from fields to parameters */ + def copyParams(rhs: Tree) = { + val fvs = freeVars(sym.owner) + val classProxies = fvs.map(proxyOf(sym.owner, _)) + val constrProxies = fvs.map(proxyOf(sym, _)) + ctx.debuglog(i"copy params ${constrProxies.map(_.showLocated)}%, % to ${classProxies.map(_.showLocated)}%, %}") + seq((classProxies, constrProxies).zipped.map(proxyInit), rhs) + } + + tree match { + case tree: DefDef => + cpy.DefDef(tree)( + vparamss = tree.vparamss.map(freeParamDefs ++ _), + rhs = + if (sym.isPrimaryConstructor && !sym.owner.is(Trait)) copyParams(tree.rhs) + else tree.rhs) + case tree: Template => + cpy.Template(tree)(body = freeParamDefs ++ tree.body) + } + } + + private def liftDef(tree: MemberDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val buf = liftedDefs(tree.symbol.owner) + transformFollowing(rename(tree, tree.symbol.name)).foreachInThicket(buf += _) + EmptyTree + } + + private def needsLifting(sym: Symbol) = liftedOwner contains sym + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = { + val sym = tree.symbol + tree.tpe match { + case tpe @ TermRef(prefix, _) => + if (prefix eq NoPrefix) + if (sym.enclosure != currentEnclosure && !sym.isStatic) + (if (sym is Method) memberRef(sym) else proxyRef(sym)).withPos(tree.pos) + else if (sym.owner.isClass) // sym was lifted out + ref(sym).withPos(tree.pos) + else + tree + else if (!prefixIsElidable(tpe)) ref(tpe) + else tree + case _ => + tree + } + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + cpy.Apply(tree)(tree.fun, addFreeArgs(tree.symbol, tree.args)).withPos(tree.pos) + + override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo) = + cpy.Closure(tree)(env = addFreeArgs(tree.meth.symbol, tree.env)) + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + val sym = tree.symbol + val paramsAdded = + if (free.contains(sym)) addFreeParams(tree, proxies(sym)).asInstanceOf[DefDef] + else tree + if (needsLifting(sym)) liftDef(paramsAdded) + else paramsAdded + } + + override def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo) = tree.expr match { + case Block(stats, value) => + Block(stats, Return(value, tree.from)).withPos(tree.pos) + case _ => + tree + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + val cls = ctx.owner + val impl = addFreeParams(tree, proxies(cls)).asInstanceOf[Template] + cpy.Template(impl)(body = impl.body ++ liftedDefs.remove(cls).get) + } + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context, info: TransformerInfo) = + if (needsLifting(tree.symbol)) liftDef(tree) else tree + } +} |