From f70157f46ebd683c4532f142c21775ad1dce0f56 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 11:47:49 +0200 Subject: [PATCH 1/5] Refactor LambdaLift Break out the dependency collection into a separate class. That makes it more re-usable and makes LambdaLift itself easier to understand. --- .../dotc/transform/DependencyCollector.scala | 266 +++++++++++++++++ .../tools/dotc/transform/LambdaLift.scala | 270 +----------------- 2 files changed, 281 insertions(+), 255 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala new file mode 100644 index 000000000000..150d248af6ed --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala @@ -0,0 +1,266 @@ +package dotty.tools.dotc +package transform + +import MegaPhase._ +import core.Denotations.NonSymSingleDenotation +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.NameKinds.ExpandPrefixName +import ast.Trees._ +import SymUtils._ +import ExplicitOuter.outer +import util.Store +import collection.mutable +import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } + +abstract class DependencyCollector: + import ast.tpd._ + + def enclosure(using Context): Symbol + def isExpr(sym: Symbol)(using Context): Boolean + + protected type SymSet = TreeSet[Symbol] + + /** A map storing free variables of functions and classes */ + val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap + + /** A hashtable storing calls between functions */ + val called = new LinkedHashMap[Symbol, SymSet] + + /** 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. + */ + val liftedOwner = new LinkedHashMap[Symbol, Symbol] + + /** 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(_.id) + 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 + + /** 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. + * Note: we count members of local traits as local since their free variables + * have to be passed on from their callers. By contrast, class members get their + * free variable proxies from their enclosing class. + */ + def isLocal(sym: Symbol)(using 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)(using Context): Unit = + if sym.maybeOwner.isTerm + && owner.isProperlyContainedIn(liftedOwner(sym)) + && owner != sym + then + report.log(i"narrow lifted $sym to $owner") + changedLiftedOwner = true + liftedOwner(sym) = owner + + private class NoPath extends Exception + + /** 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)(using Context): Symbol = try { + if (!enclosure.exists) throw new NoPath + if (enclosure == sym.enclosure) NoSymbol + else { + def nestedInConstructor(sym: Symbol): Boolean = + sym.isConstructor || + sym.isTerm && nestedInConstructor(sym.enclosure) + report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + val intermediate = + if (enclosure.is(PackageClass)) enclosure + else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) + else markFree(sym, enclosure.enclosure) + if (intermediate.exists) narrowLiftedOwner(enclosure, intermediate) + if !intermediate.isRealClass || nestedInConstructor(enclosure) then + // Constructors and methods nested inside traits get the free variables + // of the enclosing trait or class. + // Conversely, local traits do not get free variables. + // Methods inside constructors also don't have intermediates, + // need to get all their free variables passed directly. + if (!enclosure.is(Trait)) + if (symSet(free, enclosure).add(sym)) { + changedFreeVars = true + report.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)(using Context): Unit = { + report.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") + assert(isLocal(callee)) + symSet(called, caller) += callee + } + + def process(tree: Tree)(using Context) = + val sym = tree.symbol + + def narrowTo(thisClass: ClassSymbol) = + val enclMethod = enclosure + val enclClass = enclMethod.enclosingClass + narrowLiftedOwner(enclMethod, + if enclClass.isContainedIn(thisClass) then thisClass + else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner + + tree match + case tree: Ident => + if isLocal(sym) then + if isExpr(sym) then markCalled(sym, enclosure) + else if sym.isTerm then 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 isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) + case tree: This => + narrowTo(tree.symbol.asClass) + case tree: DefDef => + if sym.owner.isTerm then + 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 then + if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) then + // 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 + case tree: TypeDef => + if sym.owner.isTerm then liftedOwner(sym) = sym.topLevelClass.owner + case _ => + end process + + private class CollectDependencies extends TreeTraverser: + def traverse(tree: Tree)(using Context) = + try + process(tree) + traverseChildren(tree) + catch 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()(using Context): Unit = + while + changedFreeVars = false + for + caller <- called.keys + callee <- called(caller) + fvs <- free get callee + fv <- fvs + do + markFree(fv, caller) + changedFreeVars + do () + + /** Compute final liftedOwner map by closing over caller dependencies */ + private def computeLiftedOwners()(using Context): Unit = + while + changedLiftedOwner = false + for + caller <- called.keys + callee <- called(caller) + do + val normalizedCallee = callee.skipConstructor + val calleeOwner = normalizedCallee.owner + if calleeOwner.isTerm then 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) then + narrowLiftedOwner(caller, calleeOwner) + changedLiftedOwner + do () + + def collectDependencies()(using Context): Unit = + CollectDependencies().traverse(ctx.compilationUnit.tpdTree) + computeFreeVars() + computeLiftedOwners() +end DependencyCollector diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 58d4d7083d32..3ec2a29ac9ee 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -27,12 +27,7 @@ object LambdaLift { val name: String = "lambdaLift" /** The core lambda lift functionality. */ - class Lifter(thisPhase: MiniPhase with DenotTransformer)(using Context) { - - private type SymSet = TreeSet[Symbol] - - /** A map storing free variables of functions and classes */ - val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap + class Lifter(thisPhase: MiniPhase & DenotTransformer)(using Context) extends DependencyCollector: /** A map storing the free variable proxies of functions and classes. * For every function and class, this is a map from the free variables @@ -40,262 +35,29 @@ object LambdaLift { */ 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 LinkedHashMap[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 */ val liftedDefs: mutable.HashMap[Symbol, mutable.ListBuffer[Tree]] = new HashMap - /** 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(_.id) - 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): 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)(using 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)(using Context): Unit = - if (sym.maybeOwner.isTerm && - owner.isProperlyContainedIn(liftedOwner(sym)) && - owner != sym) { - report.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)(using Context): Symbol = try { - if (!enclosure.exists) throw new NoPath - if (enclosure == sym.enclosure) NoSymbol - else { - def nestedInConstructor(sym: Symbol): Boolean = - sym.isConstructor || - sym.isTerm && nestedInConstructor(sym.enclosure) - report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") - val intermediate = - if (enclosure.is(PackageClass)) enclosure - else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) - else markFree(sym, enclosure.enclosure) - if (intermediate.exists) narrowLiftedOwner(enclosure, intermediate) - if !intermediate.isRealClass || nestedInConstructor(enclosure) then - // Constructors and methods nested inside traits get the free variables - // of the enclosing trait or class. - // Conversely, local traits do not get free variables. - // Methods inside constructors also don't have intermediates, - // need to get all their free variables passed directly. - if (!enclosure.is(Trait)) - if (symSet(free, enclosure).add(sym)) { - changedFreeVars = true - report.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)(using Context): Unit = { - report.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 TreeTraverser { - def traverse(tree: Tree)(using Context) = try { //debug - val sym = tree.symbol - - def enclosure = ctx.owner.enclosingMethod - - def narrowTo(thisClass: ClassSymbol) = { - val enclMethod = enclosure - val enclClass = enclMethod.enclosingClass - narrowLiftedOwner(enclMethod, - 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 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) - 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.termParamss.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 _ => - } - traverseChildren(tree) - } - catch { //debug - case ex: Exception => - println(i"$ex while traversing $tree") - throw ex - } - } + def enclosure(using Context) = ctx.owner.enclosingMethod + def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) - /** Compute final free variables map `fvs by closing over caller dependencies. */ - private def computeFreeVars()(using Context): Unit = - while ({ - changedFreeVars = false - for { - caller <- called.keys - callee <- called(caller) - fvs <- free get callee - fv <- fvs - } - markFree(fv, caller) - changedFreeVars - }) - () - - /** Compute final liftedOwner map by closing over caller dependencies */ - private def computeLiftedOwners()(using Context): Unit = - while ({ - 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) - } - } - changedLiftedOwner - }) - () + override def process(tree: Tree)(using Context): Unit = + super.process(tree) + tree match + case tree: DefDef if tree.symbol.isConstructor => + tree.termParamss.head.find(_.name == nme.OUTER) match + case Some(vdef) => outerParam(tree.symbol) = vdef.symbol + case _ => + case tree: Template => + liftedDefs(tree.symbol.owner) = new mutable.ListBuffer + case _ => private def newName(sym: Symbol)(using Context): Name = if (sym.isAnonymousFunction && sym.owner.is(Method)) @@ -372,9 +134,7 @@ object LambdaLift { // initialization atPhase(thisPhase) { - (new CollectDependencies).traverse(ctx.compilationUnit.tpdTree) - computeFreeVars() - computeLiftedOwners() + collectDependencies() } atPhase(thisPhase.next) { generateProxies() @@ -471,7 +231,7 @@ object LambdaLift { } def needsLifting(sym: Symbol): Boolean = liftedOwner contains sym - } + end Lifter } /** This phase performs the necessary rewritings to eliminate classes and methods From 097cae60eceb155df6fd4b57dc950d95546a16b3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 12:36:17 +0200 Subject: [PATCH 2/5] Use aggregation instead of inheritance for dependency collection --- .../dotc/transform/DependencyCollector.scala | 27 +++++----- .../tools/dotc/transform/LambdaLift.scala | 51 ++++++++++--------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala index 150d248af6ed..a1f554e03676 100644 --- a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala +++ b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala @@ -19,20 +19,24 @@ import ExplicitOuter.outer import util.Store import collection.mutable import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } +import annotation.constructorOnly -abstract class DependencyCollector: +/** Exposes the dependencies of the typed tree in the current compilation unit + * in sets `freeVars`, `liftedOwner`. + */ +abstract class Dependencies(@constructorOnly rootContext: Context): import ast.tpd._ def enclosure(using Context): Symbol def isExpr(sym: Symbol)(using Context): Boolean - protected type SymSet = TreeSet[Symbol] + type SymSet = TreeSet[Symbol] /** A map storing free variables of functions and classes */ val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap /** A hashtable storing calls between functions */ - val called = new LinkedHashMap[Symbol, SymSet] + private val called = new LinkedHashMap[Symbol, SymSet] /** 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. @@ -55,9 +59,7 @@ abstract class DependencyCollector: 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 freeVars(sym: Symbol): collection.Set[Symbol] = free.getOrElse(sym, Set.empty) /** 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. @@ -65,7 +67,7 @@ abstract class DependencyCollector: * have to be passed on from their callers. By contrast, class members get their * free variable proxies from their enclosing class. */ - def isLocal(sym: Symbol)(using Context): Boolean = + private def isLocal(sym: Symbol)(using Context): Boolean = val owner = sym.maybeOwner owner.isTerm || owner.is(Trait) && isLocal(owner) @@ -74,7 +76,7 @@ abstract class DependencyCollector: /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested * than the previous value of `liftedowner(sym)`. */ - def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = + private def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = if sym.maybeOwner.isTerm && owner.isProperlyContainedIn(liftedOwner(sym)) && owner != sym @@ -172,7 +174,7 @@ abstract class DependencyCollector: symSet(called, caller) += callee } - def process(tree: Tree)(using Context) = + protected def process(tree: Tree)(using Context) = val sym = tree.symbol def narrowTo(thisClass: ClassSymbol) = @@ -259,8 +261,9 @@ abstract class DependencyCollector: changedLiftedOwner do () - def collectDependencies()(using Context): Unit = - CollectDependencies().traverse(ctx.compilationUnit.tpdTree) + inContext(rootContext) { + CollectDependencies().traverse(rootContext.compilationUnit.tpdTree) computeFreeVars() computeLiftedOwners() -end DependencyCollector + } +end Dependencies diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 3ec2a29ac9ee..e0cd76a2b4af 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -27,13 +27,7 @@ object LambdaLift { val name: String = "lambdaLift" /** The core lambda lift functionality. */ - class Lifter(thisPhase: MiniPhase & DenotTransformer)(using Context) extends DependencyCollector: - - /** 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]] + class Lifter(thisPhase: MiniPhase & DenotTransformer)(using Context): /** The outer parameter of a constructor */ private val outerParam = new HashMap[Symbol, Symbol] @@ -41,23 +35,33 @@ object LambdaLift { /** Buffers for lifted out classes and methods, indexed by owner */ val liftedDefs: mutable.HashMap[Symbol, mutable.ListBuffer[Tree]] = new HashMap - def proxyOf(sym: Symbol, fv: Symbol): Symbol = proxyMap.getOrElse(sym, Map.empty)(fv) + val deps = new Dependencies(ctx.withPhase(thisPhase)): + def enclosure(using Context) = ctx.owner.enclosingMethod + def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) + + override def process(tree: Tree)(using Context): Unit = + super.process(tree) + tree match + case tree: DefDef if tree.symbol.isConstructor => + tree.termParamss.head.find(_.name == nme.OUTER) match + case Some(vdef) => outerParam(tree.symbol) = vdef.symbol + case _ => + case tree: Template => + liftedDefs(tree.symbol.owner) = new mutable.ListBuffer + case _ => + end deps + export deps.{liftedOwner, free} - def proxies(sym: Symbol): List[Symbol] = freeVars(sym).map(proxyOf(sym, _)) + /** 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]] - def enclosure(using Context) = ctx.owner.enclosingMethod - def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) + def proxyOf(sym: Symbol, fv: Symbol): Symbol = proxyMap.getOrElse(sym, Map.empty)(fv) - override def process(tree: Tree)(using Context): Unit = - super.process(tree) - tree match - case tree: DefDef if tree.symbol.isConstructor => - tree.termParamss.head.find(_.name == nme.OUTER) match - case Some(vdef) => outerParam(tree.symbol) = vdef.symbol - case _ => - case tree: Template => - liftedDefs(tree.symbol.owner) = new mutable.ListBuffer - case _ => + def proxies(sym: Symbol): List[Symbol] = + deps.freeVars(sym).toList.map(proxyOf(sym, _)) private def newName(sym: Symbol)(using Context): Name = if (sym.isAnonymousFunction && sym.owner.is(Method)) @@ -133,9 +137,6 @@ object LambdaLift { } // initialization - atPhase(thisPhase) { - collectDependencies() - } atPhase(thisPhase.next) { generateProxies() liftLocals() @@ -205,7 +206,7 @@ object LambdaLift { /** Initialize proxy fields from proxy parameters and map `rhs` from fields to parameters */ def copyParams(rhs: Tree) = { - val fvs = freeVars(sym.owner) + val fvs = deps.freeVars(sym.owner).toList val classProxies = fvs.map(proxyOf(sym.owner, _)) val constrProxies = fvs.map(proxyOf(sym, _)) report.debuglog(i"copy params ${constrProxies.map(_.showLocated)}%, % to ${classProxies.map(_.showLocated)}%, %}") From 1d55a774c2f7b1d4d583106323ebf2cf2c425777 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 12:48:13 +0200 Subject: [PATCH 3/5] Hide free --- .../dotc/transform/DependencyCollector.scala | 4 ++- .../tools/dotc/transform/LambdaLift.scala | 27 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala index a1f554e03676..3816918f3e18 100644 --- a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala +++ b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala @@ -33,7 +33,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): type SymSet = TreeSet[Symbol] /** A map storing free variables of functions and classes */ - val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap + private val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap /** A hashtable storing calls between functions */ private val called = new LinkedHashMap[Symbol, SymSet] @@ -61,6 +61,8 @@ abstract class Dependencies(@constructorOnly rootContext: Context): def freeVars(sym: Symbol): collection.Set[Symbol] = free.getOrElse(sym, Set.empty) + def tracked: Iterable[Symbol] = free.keys + /** 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. * Note: we count members of local traits as local since their free variables diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index e0cd76a2b4af..1ae75bb5c3c2 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -50,7 +50,7 @@ object LambdaLift { liftedDefs(tree.symbol.owner) = new mutable.ListBuffer case _ => end deps - export deps.{liftedOwner, free} + export deps.{liftedOwner} /** A map storing the free variable proxies of functions and classes. * For every function and class, this is a map from the free variables @@ -71,19 +71,18 @@ object LambdaLift { else sym.name.freshened private def generateProxies()(using Context): Unit = - for ((owner, freeValues) <- free.iterator) { + for owner <- deps.tracked do + val fvs = deps.freeVars(owner).toList val newFlags = Synthetic | (if (owner.isClass) ParamAccessor | Private else Param) - report.debuglog(i"free var proxy of ${owner.showLocated}: ${freeValues.toList}%, %") - proxyMap(owner) = { - for (fv <- freeValues.toList) yield { + report.debuglog(i"free var proxy of ${owner.showLocated}: $fvs%, %") + val freeProxyPairs = + for fv <- fvs yield val proxyName = newName(fv) val proxy = newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord) .enteredAfter(thisPhase) (fv, proxy) - } - }.toMap - } + proxyMap(owner) = freeProxyPairs.toMap private def liftedInfo(local: Symbol)(using Context): Type = local.info match { case MethodTpe(pnames, ptypes, restpe) => @@ -131,7 +130,7 @@ object LambdaLift { initFlags = initFlags, info = liftedInfo(local)).installAfter(thisPhase) } - for (local <- free.keys) + for (local <- deps.tracked) if (!liftedOwner.contains(local)) local.copySymDenotation(info = liftedInfo(local)).installAfter(thisPhase) } @@ -190,10 +189,8 @@ object LambdaLift { } def addFreeArgs(sym: Symbol, args: List[Tree])(using Context): List[Tree] = - free get sym match { - case Some(fvs) => fvs.toList.map(proxyRef(_)) ++ args - case _ => args - } + val fvs = deps.freeVars(sym) + if fvs.nonEmpty then fvs.toList.map(proxyRef(_)) ++ args else args def addFreeParams(tree: Tree, proxies: List[Symbol])(using Context): Tree = proxies match { case Nil => tree @@ -319,7 +316,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisPhase => // reload them manually here. // Note: If you tweak this code, make sure to test your changes with // `Config.reuseSymDenotations` set to false to exercise this path more. - if denot.isInstanceOf[NonSymSingleDenotation] && lifter.free.contains(sym) then + if denot.isInstanceOf[NonSymSingleDenotation] && lifter.deps.freeVars(sym).nonEmpty then tree.qualifier.select(sym).withSpan(tree.span) else tree @@ -333,7 +330,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisPhase => val sym = tree.symbol val lft = lifter val paramsAdded = - if (lft.free.contains(sym)) lft.addFreeParams(tree, lft.proxies(sym)).asInstanceOf[DefDef] + if lft.deps.freeVars(sym).nonEmpty then lft.addFreeParams(tree, lft.proxies(sym)).asInstanceOf[DefDef] else tree if (lft.needsLifting(sym)) lft.liftDef(paramsAdded) else paramsAdded From 88887676c0d2574fbef522afe74e54f0e05d84eb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 13:52:28 +0200 Subject: [PATCH 4/5] Hide liftedOwner --- .../dotc/transform/DependencyCollector.scala | 18 ++++++++++-------- .../tools/dotc/transform/LambdaLift.scala | 10 +++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala index 3816918f3e18..37b47e5c34bc 100644 --- a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala +++ b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala @@ -27,8 +27,8 @@ import annotation.constructorOnly abstract class Dependencies(@constructorOnly rootContext: Context): import ast.tpd._ - def enclosure(using Context): Symbol - def isExpr(sym: Symbol)(using Context): Boolean + protected def enclosure(using Context): Symbol + protected def isExpr(sym: Symbol)(using Context): Boolean type SymSet = TreeSet[Symbol] @@ -45,7 +45,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner * is also used to decide whether a method had a term owner before. */ - val liftedOwner = new LinkedHashMap[Symbol, Symbol] + private val depOwner = new LinkedHashMap[Symbol, Symbol] /** A flag to indicate whether new free variables have been found */ private var changedFreeVars: Boolean = _ @@ -63,6 +63,8 @@ abstract class Dependencies(@constructorOnly rootContext: Context): def tracked: Iterable[Symbol] = free.keys + def dependentOwner: collection.Map[Symbol, Symbol] = depOwner + /** 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. * Note: we count members of local traits as local since their free variables @@ -80,12 +82,12 @@ abstract class Dependencies(@constructorOnly rootContext: Context): */ private def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = if sym.maybeOwner.isTerm - && owner.isProperlyContainedIn(liftedOwner(sym)) + && owner.isProperlyContainedIn(depOwner(sym)) && owner != sym then report.log(i"narrow lifted $sym to $owner") changedLiftedOwner = true - liftedOwner(sym) = owner + depOwner(sym) = owner private class NoPath extends Exception @@ -202,7 +204,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): narrowTo(tree.symbol.asClass) case tree: DefDef => if sym.owner.isTerm then - liftedOwner(sym) = sym.enclosingPackageClass + depOwner(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 @@ -215,7 +217,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): // the free variables of the class. symSet(called, sym) += sym.owner case tree: TypeDef => - if sym.owner.isTerm then liftedOwner(sym) = sym.topLevelClass.owner + if sym.owner.isTerm then depOwner(sym) = sym.topLevelClass.owner case _ => end process @@ -252,7 +254,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): do val normalizedCallee = callee.skipConstructor val calleeOwner = normalizedCallee.owner - if calleeOwner.isTerm then narrowLiftedOwner(caller, liftedOwner(normalizedCallee)) + if calleeOwner.isTerm then narrowLiftedOwner(caller, depOwner(normalizedCallee)) else assert(calleeOwner.is(Trait)) // methods nested inside local trait methods cannot be lifted out diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 1ae75bb5c3c2..d6a88956c4eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -50,7 +50,6 @@ object LambdaLift { liftedDefs(tree.symbol.owner) = new mutable.ListBuffer case _ => end deps - export deps.{liftedOwner} /** A map storing the free variable proxies of functions and classes. * For every function and class, this is a map from the free variables @@ -95,7 +94,7 @@ object LambdaLift { } private def liftLocals()(using Context): Unit = { - for ((local, lOwner) <- liftedOwner) { + for ((local, lOwner) <- deps.dependentOwner) { val (newOwner, maybeStatic) = if (lOwner is Package) { val encClass = local.enclosingClass @@ -131,7 +130,7 @@ object LambdaLift { info = liftedInfo(local)).installAfter(thisPhase) } for (local <- deps.tracked) - if (!liftedOwner.contains(local)) + if (!deps.dependentOwner.contains(local)) local.copySymDenotation(info = liftedInfo(local)).installAfter(thisPhase) } @@ -148,7 +147,8 @@ object LambdaLift { sym.enclosure == currentEnclosure private def proxy(sym: Symbol)(using Context): Symbol = { - def liftedEnclosure(sym: Symbol) = liftedOwner.getOrElse(sym, sym.enclosure) + def liftedEnclosure(sym: Symbol) = + deps.dependentOwner.getOrElse(sym, sym.enclosure) def searchIn(enclosure: Symbol): Symbol = { if (!enclosure.exists) { def enclosures(encl: Symbol): List[Symbol] = @@ -228,7 +228,7 @@ object LambdaLift { EmptyTree } - def needsLifting(sym: Symbol): Boolean = liftedOwner contains sym + def needsLifting(sym: Symbol): Boolean = deps.dependentOwner.contains(sym) end Lifter } From 1c2a6fbb70765fa07297d92dcd3e41245d17b60b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 15:43:22 +0200 Subject: [PATCH 5/5] Cleanups --- ...encyCollector.scala => Dependencies.scala} | 159 +++++++++--------- .../tools/dotc/transform/LambdaLift.scala | 36 ++-- 2 files changed, 94 insertions(+), 101 deletions(-) rename compiler/src/dotty/tools/dotc/transform/{DependencyCollector.scala => Dependencies.scala} (67%) diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala similarity index 67% rename from compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala rename to compiler/src/dotty/tools/dotc/transform/Dependencies.scala index 37b47e5c34bc..0503dd71601c 100644 --- a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala +++ b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala @@ -1,39 +1,41 @@ package dotty.tools.dotc package transform -import MegaPhase._ -import core.Denotations.NonSymSingleDenotation -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.NameKinds.ExpandPrefixName -import ast.Trees._ -import SymUtils._ -import ExplicitOuter.outer -import util.Store -import collection.mutable -import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } +import core.* +import Symbols.*, Contexts.*, Types.*, Flags.*, Decorators.* +import ast.Trees.* +import SymUtils.* +import collection.mutable.{LinkedHashMap, TreeSet} import annotation.constructorOnly -/** Exposes the dependencies of the typed tree in the current compilation unit - * in sets `freeVars`, `liftedOwner`. +/** Exposes the dependencies of the `root` tree in three functions or maps: + * `freeVars`, `tracked`, and `logicalOwner`. */ -abstract class Dependencies(@constructorOnly rootContext: Context): +abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Context): import ast.tpd._ - protected def enclosure(using Context): Symbol + /** The symbol is a method or a lazy val that will be mapped to a method */ protected def isExpr(sym: Symbol)(using Context): Boolean - type SymSet = TreeSet[Symbol] + /** The closest enclosing symbol in the current context for which `isExpr` is true */ + protected def enclosure(using Context): Symbol + + /** The set of free variables of a function, including free variables of its callees */ + def freeVars(sym: Symbol): collection.Set[Symbol] = free.getOrElse(sym, Set.empty) + + /** The set of functions that have free variables, i.e for which `freeVars` is non-empty */ + def tracked: Iterable[Symbol] = free.keys + + /** The outermost class that captures all free variables of a function + * that are captured by enclosinh classes (this means that the function could + * be placed in that class without having to add more environment parameters) + */ + def logicalOwner: collection.Map[Symbol, Symbol] = logicOwner + + private type SymSet = TreeSet[Symbol] /** A map storing free variables of functions and classes */ - private val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap + private val free: LinkedHashMap[Symbol, SymSet] = new LinkedHashMap /** A hashtable storing calls between functions */ private val called = new LinkedHashMap[Symbol, SymSet] @@ -45,13 +47,13 @@ abstract class Dependencies(@constructorOnly rootContext: Context): * 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 depOwner = new LinkedHashMap[Symbol, Symbol] + private val logicOwner = new LinkedHashMap[Symbol, Symbol] /** 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 var changedLogicOwner: Boolean = _ private val ord: Ordering[Symbol] = Ordering.by(_.id) private def newSymSet = TreeSet.empty[Symbol](ord) @@ -59,12 +61,6 @@ abstract class Dependencies(@constructorOnly rootContext: Context): private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = f.getOrElseUpdate(sym, newSymSet) - def freeVars(sym: Symbol): collection.Set[Symbol] = free.getOrElse(sym, Set.empty) - - def tracked: Iterable[Symbol] = free.keys - - def dependentOwner: collection.Map[Symbol, Symbol] = depOwner - /** 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. * Note: we count members of local traits as local since their free variables @@ -80,16 +76,14 @@ abstract class Dependencies(@constructorOnly rootContext: Context): /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested * than the previous value of `liftedowner(sym)`. */ - private def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = + private def narrowLogicOwner(sym: Symbol, owner: Symbol)(using Context): Unit = if sym.maybeOwner.isTerm - && owner.isProperlyContainedIn(depOwner(sym)) + && owner.isProperlyContainedIn(logicOwner(sym)) && owner != sym then report.log(i"narrow lifted $sym to $owner") - changedLiftedOwner = true - depOwner(sym) = owner - - private class NoPath extends Exception + changedLogicOwner = true + logicOwner(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` @@ -135,42 +129,40 @@ abstract class Dependencies(@constructorOnly rootContext: Context): * } * } */ - private def markFree(sym: Symbol, enclosure: Symbol)(using Context): Symbol = try { - if (!enclosure.exists) throw new NoPath - if (enclosure == sym.enclosure) NoSymbol - else { - def nestedInConstructor(sym: Symbol): Boolean = - sym.isConstructor || - sym.isTerm && nestedInConstructor(sym.enclosure) - report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") - val intermediate = - if (enclosure.is(PackageClass)) enclosure - else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) - else markFree(sym, enclosure.enclosure) - if (intermediate.exists) narrowLiftedOwner(enclosure, intermediate) - if !intermediate.isRealClass || nestedInConstructor(enclosure) then - // Constructors and methods nested inside traits get the free variables - // of the enclosing trait or class. - // Conversely, local traits do not get free variables. - // Methods inside constructors also don't have intermediates, - // need to get all their free variables passed directly. - if (!enclosure.is(Trait)) - if (symSet(free, enclosure).add(sym)) { - changedFreeVars = true - report.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 => + private def markFree(sym: Symbol, enclosure: Symbol)(using Context): Symbol = + import Dependencies.NoPath + try + if !enclosure.exists then throw NoPath() + if enclosure == sym.enclosure then NoSymbol + else + def nestedInConstructor(sym: Symbol): Boolean = + sym.isConstructor + || sym.isTerm && nestedInConstructor(sym.enclosure) + report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + val intermediate = + if enclosure.is(PackageClass) then enclosure + else if enclosure.isConstructor then markFree(sym, enclosure.owner.enclosure) + else markFree(sym, enclosure.enclosure) + if intermediate.exists then + narrowLogicOwner(enclosure, intermediate) + if !intermediate.isRealClass || nestedInConstructor(enclosure) then + // Constructors and methods nested inside traits get the free variables + // of the enclosing trait or class. + // Conversely, local traits do not get free variables. + // Methods inside constructors also don't have intermediates, + // need to get all their free variables passed directly. + if !enclosure.is(Trait) then + if symSet(free, enclosure).add(sym) then + changedFreeVars = true + report.log(i"$sym is free in $enclosure") + if intermediate.isRealClass then intermediate + else if enclosure.isRealClass then enclosure + else if intermediate.isClass then intermediate + else if enclosure.isClass then 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)(using Context): Unit = { report.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") @@ -184,7 +176,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): def narrowTo(thisClass: ClassSymbol) = val enclMethod = enclosure val enclClass = enclMethod.enclosingClass - narrowLiftedOwner(enclMethod, + narrowLogicOwner(enclMethod, if enclClass.isContainedIn(thisClass) then thisClass else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner @@ -204,7 +196,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): narrowTo(tree.symbol.asClass) case tree: DefDef => if sym.owner.isTerm then - depOwner(sym) = sym.enclosingPackageClass + logicOwner(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 @@ -217,7 +209,7 @@ abstract class Dependencies(@constructorOnly rootContext: Context): // the free variables of the class. symSet(called, sym) += sym.owner case tree: TypeDef => - if sym.owner.isTerm then depOwner(sym) = sym.topLevelClass.owner + if sym.owner.isTerm then logicOwner(sym) = sym.topLevelClass.owner case _ => end process @@ -245,29 +237,32 @@ abstract class Dependencies(@constructorOnly rootContext: Context): do () /** Compute final liftedOwner map by closing over caller dependencies */ - private def computeLiftedOwners()(using Context): Unit = + private def computeLogicOwners()(using Context): Unit = while - changedLiftedOwner = false + changedLogicOwner = false for caller <- called.keys callee <- called(caller) do val normalizedCallee = callee.skipConstructor val calleeOwner = normalizedCallee.owner - if calleeOwner.isTerm then narrowLiftedOwner(caller, depOwner(normalizedCallee)) + if calleeOwner.isTerm then narrowLogicOwner(caller, logicOwner(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) then - narrowLiftedOwner(caller, calleeOwner) - changedLiftedOwner + narrowLogicOwner(caller, calleeOwner) + changedLogicOwner do () + // initialization inContext(rootContext) { - CollectDependencies().traverse(rootContext.compilationUnit.tpdTree) + CollectDependencies().traverse(root) computeFreeVars() - computeLiftedOwners() + computeLogicOwners() } +object Dependencies: + private class NoPath extends Exception end Dependencies diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index d6a88956c4eb..86972605c3eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -17,12 +17,10 @@ import ast.Trees._ import SymUtils._ import ExplicitOuter.outer import util.Store -import collection.mutable -import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } +import collection.mutable.{HashMap, LinkedHashMap, ListBuffer} -object LambdaLift { +object LambdaLift: import ast.tpd._ - private class NoPath extends Exception val name: String = "lambdaLift" @@ -33,11 +31,11 @@ object LambdaLift { private val outerParam = new HashMap[Symbol, Symbol] /** Buffers for lifted out classes and methods, indexed by owner */ - val liftedDefs: mutable.HashMap[Symbol, mutable.ListBuffer[Tree]] = new HashMap + val liftedDefs: HashMap[Symbol, ListBuffer[Tree]] = new HashMap - val deps = new Dependencies(ctx.withPhase(thisPhase)): - def enclosure(using Context) = ctx.owner.enclosingMethod + val deps = new Dependencies(ctx.compilationUnit.tpdTree, ctx.withPhase(thisPhase)): def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) + def enclosure(using Context) = ctx.owner.enclosingMethod override def process(tree: Tree)(using Context): Unit = super.process(tree) @@ -47,7 +45,7 @@ object LambdaLift { case Some(vdef) => outerParam(tree.symbol) = vdef.symbol case _ => case tree: Template => - liftedDefs(tree.symbol.owner) = new mutable.ListBuffer + liftedDefs(tree.symbol.owner) = new ListBuffer case _ => end deps @@ -94,7 +92,7 @@ object LambdaLift { } private def liftLocals()(using Context): Unit = { - for ((local, lOwner) <- deps.dependentOwner) { + for ((local, lOwner) <- deps.logicalOwner) { val (newOwner, maybeStatic) = if (lOwner is Package) { val encClass = local.enclosingClass @@ -130,16 +128,10 @@ object LambdaLift { info = liftedInfo(local)).installAfter(thisPhase) } for (local <- deps.tracked) - if (!deps.dependentOwner.contains(local)) + if (!deps.logicalOwner.contains(local)) local.copySymDenotation(info = liftedInfo(local)).installAfter(thisPhase) } - // initialization - atPhase(thisPhase.next) { - generateProxies() - liftLocals() - } - def currentEnclosure(using Context): Symbol = ctx.owner.enclosingMethodOrClass @@ -148,7 +140,7 @@ object LambdaLift { private def proxy(sym: Symbol)(using Context): Symbol = { def liftedEnclosure(sym: Symbol) = - deps.dependentOwner.getOrElse(sym, sym.enclosure) + deps.logicalOwner.getOrElse(sym, sym.enclosure) def searchIn(enclosure: Symbol): Symbol = { if (!enclosure.exists) { def enclosures(encl: Symbol): List[Symbol] = @@ -228,9 +220,15 @@ object LambdaLift { EmptyTree } - def needsLifting(sym: Symbol): Boolean = deps.dependentOwner.contains(sym) + def needsLifting(sym: Symbol): Boolean = deps.logicalOwner.contains(sym) + + // initialization + atPhase(thisPhase.next) { + generateProxies() + liftLocals() + } end Lifter -} +end LambdaLift /** This phase performs the necessary rewritings to eliminate classes and methods * nested in other methods. In detail: