Skip to content

Commit d46d7a3

Browse files
Merge pull request #2910 from dotty-staging/link-from-tasty
Link from tasty
2 parents 7141ded + 4f0b070 commit d46d7a3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+488
-107
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import dotty.tools.dotc.core.Types.Type
55
import dotty.tools.dotc.core.tasty.{TastyUnpickler, TastyBuffer, TastyPickler}
66
import util.SourceFile
77
import ast.{tpd, untpd}
8+
import dotty.tools.dotc.ast.tpd.{ Tree, TreeTraverser }
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
811
import dotty.tools.dotc.core.Symbols._
912

1013
class CompilationUnit(val source: SourceFile) {
@@ -20,3 +23,20 @@ class CompilationUnit(val source: SourceFile) {
2023
/** Pickled TASTY binaries, indexed by class. */
2124
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
2225
}
26+
27+
object CompilationUnit {
28+
29+
/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
30+
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
31+
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq()))
32+
unit1.tpdTree = unpickled
33+
if (forceTrees)
34+
force.traverse(unit1.tpdTree)
35+
unit1
36+
}
37+
38+
/** Force the tree to be loaded */
39+
private object force extends TreeTraverser {
40+
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
41+
}
42+
}

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Compiler {
4747
List(new PostTyper), // Additional checks and cleanups after type checking
4848
List(new sbt.ExtractAPI), // Sends a representation of the API of classes to sbt via callbacks
4949
List(new Pickler), // Generate TASTY info
50+
List(new LinkAll), // Reload compilation units from TASTY for library code (if needed)
5051
List(new FirstTransform, // Some transformations to put trees into a canonical form
5152
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
5253
new ElimJavaPackages), // Eliminate syntactic references to Java packages

compiler/src/dotty/tools/dotc/FromTasty.scala

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ object FromTasty extends Driver {
6060
override def toString = s"class file $className"
6161
}
6262

63-
object force extends TreeTraverser {
64-
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
65-
}
66-
6763
class ReadTastyTreesFromClasses extends FrontEnd {
6864

6965
override def isTyper = false
@@ -85,13 +81,8 @@ object FromTasty extends Driver {
8581
case info: ClassfileLoader =>
8682
info.load(clsd)
8783
val unpickled = clsd.symbol.asClass.tree
88-
if (unpickled != null) {
89-
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq()))
90-
unit1.tpdTree = unpickled
91-
force.traverse(unit1.tpdTree)
92-
unit1
93-
} else
94-
cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute")
84+
if (unpickled != null) CompilationUnit.mkCompilationUnit(clsd, unpickled, forceTrees = true)
85+
else cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute")
9586
case info =>
9687
cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader")
9788
}

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class ScalaSettings extends Settings.SettingGroup {
113113
val YoptPhases = PhasesSetting("-Yopt-phases", "Restrict the optimisation phases to execute under -optimise.")
114114
val YoptFuel = IntSetting("-Yopt-fuel", "Maximum number of optimisations performed under -optimise.", -1)
115115
val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize"
116+
val XlinkOptimise = BooleanSetting("-Xlink-optimise", "Recompile library code with the application.").withAbbreviation("-Xlink-optimize")
116117

117118
/** Dottydoc specific settings */
118119
val siteRoot = StringSetting(

compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,15 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
323323

324324
def load(root: SymDenotation)(implicit ctx: Context): Unit = {
325325
val (classRoot, moduleRoot) = rootDenots(root.asClass)
326-
(new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)).run() match {
327-
case Some(unpickler: tasty.DottyUnpickler) if ctx.settings.YretainTrees.value =>
328-
classRoot.symbol.asClass.unpickler = unpickler
329-
moduleRoot.symbol.asClass.unpickler = unpickler
330-
case _ =>
326+
val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)
327+
val result = classfileParser.run()
328+
if (ctx.settings.YretainTrees.value || ctx.settings.XlinkOptimise.value) {
329+
result match {
330+
case Some(unpickler: tasty.DottyUnpickler) =>
331+
classRoot.symbol.asClass.unpickler = unpickler
332+
moduleRoot.symbol.asClass.unpickler = unpickler
333+
case _ =>
334+
}
331335
}
332336
}
333337
}

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -555,35 +555,31 @@ object Symbols {
555555

556556
type ThisName = TypeName
557557

558-
/** If this is a top-level class, and if `-Yretain-trees` is set, return the TypeDef tree
559-
* for this class, otherwise EmptyTree. This will force the info of the class.
560-
*/
561-
def tree(implicit ctx: Context): tpd.Tree /* tpd.TypeDef | tpd.EmptyTree */ = {
558+
/** If this is either:
559+
* - a top-level class and `-Yretain-trees` is set
560+
* - a top-level class loaded from TASTY and `-Xlink-optimise` is set
561+
* then return the TypeDef tree (possibly wrapped inside PackageDefs) for this class, otherwise EmptyTree.
562+
* This will force the info of the class.
563+
*/
564+
def tree(implicit ctx: Context): tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = {
562565
denot.info
563566
// TODO: Consider storing this tree like we store lazy trees for inline functions
564567
if (unpickler != null && !denot.isAbsent) {
565568
assert(myTree.isEmpty)
566-
567-
import ast.Trees._
568-
569-
def findTree(tree: tpd.Tree): Option[tpd.TypeDef] = tree match {
570-
case PackageDef(_, stats) =>
571-
stats.flatMap(findTree).headOption
572-
case tree: tpd.TypeDef if tree.symbol == this =>
573-
Some(tree)
574-
case _ =>
575-
None
576-
}
577-
val List(unpickledTree) = unpickler.body(ctx.addMode(Mode.ReadPositions))
569+
val body = unpickler.body(ctx.addMode(Mode.ReadPositions))
570+
myTree = body.headOption.getOrElse(tpd.EmptyTree)
578571
unpickler = null
579-
580-
myTree = findTree(unpickledTree).get
581572
}
582573
myTree
583574
}
584-
private[dotc] var myTree: tpd.Tree = tpd.EmptyTree
575+
private[this] var myTree: tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = tpd.EmptyTree
585576
private[dotc] var unpickler: tasty.DottyUnpickler = _
586577

578+
private[dotc] def registerTree(tree: tpd.TypeDef)(implicit ctx: Context): Unit = {
579+
if (ctx.settings.YretainTrees.value)
580+
myTree = tree
581+
}
582+
587583
/** The source or class file from which this class was generated, null if not applicable. */
588584
override def associatedFile(implicit ctx: Context): AbstractFile =
589585
if (assocFile != null || (this.owner is PackageClass) || this.isEffectiveRoot) assocFile

compiler/src/dotty/tools/dotc/interactive/SourceTree.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ object SourceTree {
4343
sym.sourceFile == null) // FIXME: We cannot deal with external projects yet
4444
None
4545
else {
46-
sym.tree match {
47-
case tree: tpd.TypeDef =>
46+
import ast.Trees._
47+
def sourceTreeOfClass(tree: tpd.Tree): Option[SourceTree] = tree match {
48+
case PackageDef(_, stats) => stats.flatMap(sourceTreeOfClass).headOption
49+
case tree: tpd.TypeDef if tree.symbol == sym =>
4850
val sourceFile = new SourceFile(sym.sourceFile, Codec.UTF8)
4951
Some(SourceTree(tree, sourceFile))
50-
case _ =>
51-
None
52+
case _ => None
5253
}
54+
sourceTreeOfClass(sym.tree)
5355
}
5456
}
5557
}

compiler/src/dotty/tools/dotc/transform/FirstTransform.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota
161161
cpy.Template(impl)(self = EmptyValDef)
162162
}
163163

164+
/** Eliminate empty package definitions that may have been stored in the TASTY trees */
165+
override def transformPackageDef(tree: PackageDef)(implicit ctx: Context, info: TransformerInfo): Tree =
166+
if (tree.stats.isEmpty) EmptyTree else tree
167+
164168
override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = {
165169
if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) {
166170
ddef.symbol.resetFlag(Deferred)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.CompilationUnit
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.ast.tpd
6+
import dotty.tools.dotc.core.Contexts._
7+
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
8+
import dotty.tools.dotc.core.Symbols._
9+
import dotty.tools.dotc.core.Flags._
10+
import dotty.tools.dotc.transform.TreeTransforms._
11+
12+
/** Loads all potentially reachable trees from tasty. ▲
13+
* Only performed on whole world optimization mode. ▲ ▲
14+
*
15+
* TODO: Next step is to only load compilation units reachable in the call graph
16+
*/
17+
class LinkAll extends MiniPhaseTransform {
18+
import tpd._
19+
import LinkAll._
20+
21+
override def phaseName = "linkAll"
22+
23+
/** Do not transform the any tree, runOn will traverse the trees and reload compilation units if needed */
24+
override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = NoTransform
25+
26+
override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
27+
/** Loads and processes new compilation units, possibly loading more units. */
28+
def allUnits(processed: Set[CompilationUnit], unprocessed: Set[CompilationUnit], loadedClasses: Set[ClassDenotation])(implicit ctx: Context): List[CompilationUnit] = {
29+
if (unprocessed.isEmpty) processed.toList
30+
else {
31+
val accum = new ClassesToLoadAccumulator
32+
val classesToLoad = unprocessed.foldLeft(Set.empty[ClassDenotation])((acc, unit) => accum.apply(acc, unit.tpdTree)) -- loadedClasses
33+
val loadedUnits = classesToLoad.flatMap(cls => loadCompilationUnit(cls))
34+
allUnits(processed ++ unprocessed, loadedUnits, loadedClasses ++ classesToLoad)
35+
}
36+
}
37+
38+
if (ctx.settings.XlinkOptimise.value) super.runOn(allUnits(Set.empty, units.toSet, Set.empty))
39+
else super.runOn(units)
40+
}
41+
42+
/** Collects all class denotations that may need to be loaded. */
43+
private class ClassesToLoadAccumulator extends TreeAccumulator[Set[ClassDenotation]] {
44+
private var inParents = false
45+
override def apply(acc: Set[ClassDenotation], tree: tpd.Tree)(implicit ctx: Context): Set[ClassDenotation] = tree match {
46+
case New(tpt) => accum(acc, tpt.tpe.classSymbol)
47+
case AppliedTypeTree(tpt, _) if inParents => accum(acc, tpt.symbol)
48+
case tree: RefTree if inParents || tree.symbol.is(Module) =>
49+
foldOver(accum(acc, tree.symbol), tree)
50+
case tree @ Template(constr, parents, self, _) =>
51+
val acc1 = this(acc, constr)
52+
inParents = true
53+
val acc2 = this(acc1, parents)
54+
inParents = false
55+
this(this(acc2, self), tree.body)
56+
case _ => foldOver(acc, tree)
57+
}
58+
59+
/** Accumulate class denotation for `sym` if needed */
60+
private def accum(acc: Set[ClassDenotation], sym: Symbol)(implicit ctx: Context): Set[ClassDenotation] = {
61+
val topClass = sym.topLevelClass.denot.asClass
62+
if (topClass.is(JavaDefined) || topClass.is(Scala2x) || topClass.symbol == defn.ObjectClass) acc
63+
else acc + topClass
64+
}
65+
}
66+
}
67+
68+
object LinkAll {
69+
70+
private[LinkAll] def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = {
71+
assert(ctx.settings.XlinkOptimise.value)
72+
val tree = clsd.symbol.asClass.tree
73+
if (tree.isEmpty) None
74+
else {
75+
ctx.log("Loading compilation unit for: " + clsd)
76+
Some(CompilationUnit.mkCompilationUnit(clsd, tree, forceTrees = false))
77+
}
78+
}
79+
80+
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,9 +1427,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
14271427
// check value class constraints
14281428
checkDerivedValueClass(cls, body1)
14291429

1430-
if (ctx.settings.YretainTrees.value) {
1431-
cls.myTree = cdef1
1432-
}
1430+
cls.registerTree(cdef1)
1431+
14331432
cdef1
14341433

14351434
// todo later: check that

0 commit comments

Comments
 (0)