8000 Fuse emitting and printing of trees in the backend · scala-js/scala-js@82a25db · GitHub
[go: up one dir, main page]

Skip to content

Commit 82a25db

Browse files
committed
Fuse emitting and printing of trees in the backend
This allows us to use the Emitter's powerful caching mechanism to directly cache printed trees (as byte buffers) and not cache JavaScript trees anymore at all. This reduces in-between run memory usage on the test suite from 1.12 GB (not GiB) to 1.00 GB on my machine (roughly 10%). Runtime performance (both batch and incremental is unaffected). It is worth pointing out, that due to how the Emitter caches trees, classes that end up being ES6 classes is performed will be held twice in memory (once the individual methods, once the entire class). On the test suite, this is the case for 710 cases out of 6538.
1 parent 6af57c9 commit 82a25db

File tree

10 files changed

+419
-301
lines changed

10 files changed

+419
-301
lines changed

linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala

Lines changed: 74 additions & 64 deletions
EED3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.scalajs.ir
1818
import ir.Position
1919
import ir.Position.NoPosition
2020

21+
import org.scalajs.linker.backend.emitter.Emitter
2122
import org.scalajs.linker.backend.javascript.Trees._
2223
import org.scalajs.linker.backend.javascript.SourceFileUtil
2324

@@ -30,30 +31,65 @@ import java.lang.{Double => JDouble}
3031
import java.net.URI
3132

3233
private[closure] object ClosureAstTransformer {
33-
def transformScript(topLevelTrees: List[Tree], featureSet: FeatureSet,
34-
relativizeBaseURI: Option[URI]): Node = {
35-
val transformer = new ClosureAstTransformer(featureSet, relativizeBaseURI)
36-
transformer.transformScript(topLevelTrees)
37-
}
34+
val emptyChunk = new Chunk(Nil)
35+
36+
final class Chunk private[ClosureAstTransformer] (
37+
private[ClosureAstTransformer] val nodes: List[Node]
38+
) extends Transformed.Value
3839
}
3940

40-
private class ClosureAstTransformer(featureSet: FeatureSet,
41-
relativizeBaseURI: Option[URI]) {
41+
private[closure] class ClosureAstTransformer(featureSet: FeatureSet,
42+
relativizeBaseURI: Option[URI]) extends Emitter.PostTransformer[ClosureAstTransformer.Chunk] {
43+
import ClosureAstTransformer.Chunk
44+
4245
private val dummySourceName = new java.net.URI("virtualfile:scala.js-ir")
4346

44-
def transformScript(topLevelTrees: List[Tree]): Node = {
47+
def transformScript(chunks: List[Chunk]): Node = {
4548
val script = setNodePosition(new Node(Token.SCRIPT), NoPosition)
46-
transformBlockStats(topLevelTrees)(NoPosition).foreach(script.addChildToBack(_))
49+
50+
for {
51+
chunk <- chunks
52+
node <- chunk.nodes
53+
} {
54+
script.addChildToBack(node)
55+
}
56+
4757
script.putProp(Node.FEATURE_SET, featureSet)
4858
script
4959
}
5060

51-
def transformStat(tree: Tree)(implicit parentPos: Position): Node =
61+
def empty: Chunk = ClosureAstTransformer.emptyChunk
62+
63+
def transform(trees: List[Tree]): Chunk =
64+
new Chunk(transformBlockStats(trees)(NoPosition))
65+
66+
private def transformStat(tree: Tree)(implicit parentPos: Position): Node =
5267
innerTransformStat(tree, tree.pos orElse parentPos)
5368

5469
private def innerTransformStat(tree: Tree, pos_in: Position): Node = {
5570
implicit val pos = pos_in
5671

72+
def newFixedPropNode(token: Token, static: Boolean, name: Ident,
73+
function: Node): Node = {
74+
val node = Node.newString(token, name.name)
75+
node.addChildToBack(function)
76+
node.setStaticMember(static)
77+
node
78+
}
79+
80+
/* This method should take a `prop: Node.Prop` parameter to factor out
81+
* the `node.putBooleanProp()` that we find the three cases below. However,
82+
* that is not possible because `Node.Prop` is private in `Node`. Go figure
83+
* why Java allows to export as `public` the aliases
84+
* `Node.COMPUTED_PROP_METHOD` et al. with a type that is not public ...
85+
*/
86+
def newComputedPropNode(static: Boolean, nameExpr: Tree,
87+
function: Node): Node = {
88+
val node = new Node(Token.COMPUTED_PROP, transformExpr(nameExpr), function)
89+
node.setStaticMember(static)
90+
node
91+
}
92+
5793
wrapTransform(tree) {
5894
case VarDef(ident, optRhs) =>
5995
val node = transformName(ident)
@@ -172,51 +208,6 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
172208
case classDef: ClassDef =>
173209
transformClassDef(classDef)
174210

175-
case _ =>
176-
// We just assume it is an expression
177-
new Node(Token.EXPR_RESULT, transformExpr(tree))
178-
}
179-
}
180-
181-
private def transformClassDef(classDef: ClassDef)(
182-
implicit pos: Position): Node = {
183-
val ClassDef(className, parentClass, members) = classDef
184-
185-
val membersBlock = new Node(Token.CLASS_MEMBERS)
186-
for (member <- members)
187-
membersBlock.addChildToBack(transformClassMember(member))
188-
new Node(
189-
Token.CLASS,
190-
className.fold(new Node(Token.EMPTY))(transformName(_)),
191-
parentClass.fold(new Node(Token.EMPTY))(transformExpr(_)),
192-
membersBlock)
193-
}
194-
195-
private def transformClassMember(member: Tree): Node = {
196-
implicit val pos = member.pos
197-
198-
def newFixedPropNode(token: Token, static: Boolean, name: Ident,
< 10000 /td>
199-
function: Node): Node = {
200-
val node = Node.newString(token, name.name)
201-
node.addChildToBack(function)
202-
node.setStaticMember(static)
203-
node
204-
}
205-
206-
/* This method should take a `prop: Node.Prop` parameter to factor out
207-
* the `node.putBooleanProp()` that we find the three cases below. However,
208-
* that is not possible because `Node.Prop` is private in `Node`. Go figure
209-
* why Java allows to export as `public` the aliases
210-
* `Node.COMPUTED_PROP_METHOD` et al. with a type that is not public ...
211-
*/
212-
def newComputedPropNode(static: Boolean, nameExpr: Tree,
213-
function: Node): Node = {
214-
val node = new Node(Token.COMPUTED_PROP, transformExpr(nameExpr), function)
215-
node.setStaticMember(static)
216-
node
217-
}
218-
219-
wrapTransform(member) {
220211
case MethodDef(static, name, args, restParam, body) =>
221212
val function = genFunction("", args, restParam, body)
222213
name match {
@@ -280,12 +271,27 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
280271
}
281272

282273
case _ =>
283-
throw new AssertionError(
284-
s"Unexpected class member tree of class ${member.getClass.getName}")
274+
// We just assume it is an expression
275+
new Node(Token.EXPR_RESULT, transformExpr(tree))
285276
}
286277
}
287278

288-
def transformExpr(tree: Tree)(implicit parentPos: Position): Node =
279+
private def transformClassDef(classDef: ClassDef)(
280+
implicit pos: Position): Node = {
281+
val ClassDef(className, parentClass, members) = classDef
282+
283+
val membersBlock = new Node(Token.CLASS_MEMBERS)
284+
for (node <- transformBlockStats(members))
285+
membersBlock.addChildToBack(node)
286+
287+
new Node(
288+
Token.CLASS,
289+
className.fold(new Node(Token.EMPTY))(transformName(_)),
290+
parentClass.fold(new Node(Token.EMPTY))(transformExpr(_)),
291+
membersBlock)
292+
}
293+
294+
private def transformExpr(tree: Tree)(implicit parentPos: Position): Node =
289295
innerTransformExpr(tree, tree.pos orElse parentPos)
290296

291297
private def innerTransformExpr(tree: Tree, pos_in: Position): Node = {
@@ -406,15 +412,15 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
406412
new Node(Token.FUNCTION, nameNode, paramList, transformBlock(body))
407413
}
408414

409-
def transformName(ident: Ident)(implicit parentPos: Position): Node =
415+
private def transformName(ident: Ident)(implicit parentPos: Position): Node =
410416
setNodePosition(Node.newString(Token.NAME, ident.name),
411417
ident.pos orElse parentPos)
412418

413-
def transformLabel(ident: Ident)(implicit parentPos: Position): Node =
419+
private def transformLabel(ident: Ident)(implicit parentPos: Position): Node =
414420
setNodePosition(Node.newString(Token.LABEL_NAME, ident.name),
415421
ident.pos orElse parentPos)
416422

417-
def transformObjectLitField(name: PropertyName, value: Tree)(
423+
private def transformObjectLitField(name: PropertyName, value: Tree)(
418424
implicit parentPos: Position): Node = {
419425

420426
val transformedValue = transformExpr(value)
@@ -436,7 +442,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
436442
setNodePosition(node, name.pos.orElse(parentPos))
437443
}
438444

439-
def transformBlock(tree: Tree)(implicit parentPos: Position): Node = {
445+
private def transformBlock(tree: Tree)(implicit parentPos: Position): Node = {
440446
val pos = if (tree.pos.isDefined) tree.pos else parentPos
441447
wrapTransform(tree) {
442448
case Block(stats) =>
@@ -446,14 +452,14 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
446452
} (pos)
447453
}
448454

449-
def transformBlock(stats: List[Tree], blockPos: Position): Node = {
455+
private def transformBlock(stats: List[Tree], blockPos: Position): Node = {
450456
val block = new Node(Token.BLOCK)
451457
for (node <- transformBlockStats(stats)(blockPos))
452458
block.addChildToBack(node)
453459
block
454460
}
455461

456-
def transformBlockStats(stats: List[Tree])(
462+
private def transformBlockStats(stats: List[Tree])(
457463
implicit parentPos: Position): List[Node] = {
458464

459465
@inline def ctorDoc(): JSDocInfo = {
@@ -469,6 +475,10 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
469475
case DocComment(text) :: tss =>
470476
loop(tss, nextIsCtor = text.startsWith("@constructor"), acc)
471477

478+
case Transformed(chunk: Chunk) :: tss =>
479+
assert(!nextIsCtor)
480+
loop(tss, nextIsCtor = false, chunk.nodes reverse_::: acc)
481+
472482
case t :: tss =>
473483
val node = transformStat(t)
474484
if (nextIsCtor) {

linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,6 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
5353
require(moduleKind != ModuleKind.ESModule,
5454
s"Cannot use module kind $moduleKind with the Closure Compiler")
5555

56-
private[this] val emitter = {
57-
val emitterConfig = Emitter.Config(config.commonConfig.coreSpec)
58-
.withJSHeader(config.jsHeader)
59-
.withOptimizeBracketSelects(false)
60-
.withTrackAllGlobalRefs(true)
61-
.withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id))
62-
63-
new Emitter(emitterConfig)
64-
}
65-
66-
val symbolRequirements: SymbolRequirement = emitter.symbolRequirements
67-
68-
override def injectedIRFiles: Seq[IRFile] = emitter.injectedIRFiles
69-
7056
private val languageMode: ClosureOptions.LanguageMode = {
7157
import ClosureOptions.LanguageMode._
7258

@@ -85,6 +71,23 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
8571
}
8672
}
8773

74+
private[this] val transformer = new ClosureAstTransformer(
75+
languageMode.toFeatureSet(), config.relativizeSourceMapBase)
76+
77+
private[this] val emitter: Emitter[ClosureAstTransformer.Chunk] = {
78+
val emitterConfig = Emitter.Config(config.commonConfig.coreSpec)
79+
.withJSHeader(config.jsHeader)
80+
.withOptimizeBracketSelects(false)
81+
.withTrackAllGlobalRefs(true)
82+
.withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id))
83+
84+
new Emitter(emitterConfig, transformer)
85+
}
86+
87+
val symbolRequirements: SymbolRequirement = emitter.symbolRequirements
88+
89+
override def injectedIRFiles: Seq[IRFile] = emitter.injectedIRFiles
90+
8891
/** Emit the given [[standard.ModuleSet ModuleSet]] to the target output.
8992
*
9093
* @param moduleSet [[standard.ModuleSet ModuleSet]] to emit
@@ -129,9 +132,8 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
129132
}
130133
}
131134

132-
private def buildChunk(topLevelTrees: List[js.Tree]): JSChunk = {
133-
val root = ClosureAstTransformer.transformScript(topLevelTrees,
134-
languageMode.toFeatureSet(), config.relativizeSourceMapBase)
135+
private def buildChunk(topLevelTrees: List[ClosureAstTransformer.Chunk]): JSChunk = {
136+
val root = transformer.transformScript(topLevelTrees)
135137

136138
val chunk = new JSChunk("Scala.js")
137139
chunk.add(new CompilerInput(new SyntheticAst(root)))

0 commit comments

Comments
 (0)
0