Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3fbbee6

Browse files
committed
Remove JSBuilders and simply return trees from Emitter
The JSBuilders were originally created to be able to write trees as we go. This is not the case anymore since the Linker has been made asynchronous. As a result, the abstraction is complicated for little gains. Going forward, this will simplify emitting multiple files / modules.
1 parent 2ba92cf commit 3fbbee6

File tree

9 files changed

+230
-327
lines changed

9 files changed

+230
-327
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,34 @@ import scala.annotation.tailrec
3030

3131
import java.net.URI
3232

33-
private[closure] class ClosureAstTransformer(relativizeBaseURI: Option[URI]) {
33+
private[closure] object ClosureAstTransformer {
34+
def transformScript(trees: List[Tree], relativizeBaseURI: Option[URI]): Node = {
35+
val transformer = new ClosureAstTransformer(relativizeBaseURI)
36+
transformer.transformScript(trees)
37+
}
38+
}
3439

40+
private class ClosureAstTransformer(relativizeBaseURI: Option[URI]) {
3541
private val dummySourceName = new java.net.URI("virtualfile:scala.js-ir")
3642

43+
def transformScript(trees: List[Tree]): Node = {
44+
/* Top-level `js.Block`s must be explicitly flattened here.
45+
* Our `js.Block`s do not have the same semantics as GCC's `BLOCK`s: GCC's
46+
* impose strict scoping for `let`s, `const`s and `class`es, while ours are
47+
* only a means of putting together several statements in one `js.Tree`
48+
* (in fact, they automatically flatten themselves out upon construction).
49+
*/
50+
val treeBuf = mutable.ListBuffer.empty[Node]
51+
52+
trees.foreach {
53+
case Block(stats) => treeBuf ++= transformBlockStats(stats)(NoPosition)
54+
case Skip() => // ignore
55+
case tree => treeBuf += transformStat(tree)(NoPosition)
56+
}
57+
58+
setNodePosition(IR.script(treeBuf.result(): _*), NoPosition)
59+
}
60+
3761
def transformStat(tree: Tree)(implicit parentPos: Position): Node =
3862
innerTransformStat(tree, tree.pos orElse parentPos)
3963

@@ -476,7 +500,7 @@ private[closure] class ClosureAstTransformer(relativizeBaseURI: Option[URI]) {
476500
}
477501
}
478502

479-
def setNodePosition(node: Node, pos: ir.Position): node.type = {
503+
private def setNodePosition(node: Node, pos: ir.Position): node.type = {
480504
if (pos != ir.Position.NoPosition) {
481505
attachSourceFile(node, pos.source)
482506
node.setLineno(pos.line+1)

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

Lines changed: 47 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.scalajs.linker.interface._
3232
import org.scalajs.linker.interface.unstable.{IRFileImpl, OutputFileImpl}
3333
import org.scalajs.linker.backend._
3434
import org.scalajs.linker.backend.emitter.Emitter
35+
import org.scalajs.linker.backend.javascript.{Trees => js}
3536
import org.scalajs.linker.standard._
3637

3738
/** The Closure backend of the Scala.js linker.
@@ -61,57 +62,68 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
6162

6263
override def injectedIRFiles: Seq[IRFile] = emitter.injectedIRFiles
6364

64-
private val needsIIFEWrapper = moduleKind match {
65-
case ModuleKind.NoModule => true
66-
case ModuleKind.ESModule | ModuleKind.CommonJSModule => false
67-
}
68-
6965
/** Emit the given [[standard.LinkingUnit LinkingUnit]] to the target output.
7066
*
7167
* @param unit [[standard.LinkingUnit LinkingUnit]] to emit
7268
* @param output File to write to
7369
*/
7470
def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)(
7571
implicit ec: ExecutionContext): Future[Unit] = {
76-
Future(compile(unit, output, logger)).flatMap { case (topLevelVarDeclarations, code, sourceMap) =>
77-
logger.timeFuture("Closure: Write result") {
78-
writeResult(topLevelVarDeclarations, code, sourceMap, output)
79-
}
72+
verifyUnit(unit)
73+
74+
val emitterResult = logger.time("Emitter") {
75+
emitter.emit(unit, logger)
8076
}
81-
}
8277

83-
private def compile(unit: LinkingUnit, output: LinkerOutput, logger: Logger) = {
84-
verifyUnit(unit)
78+
val module = logger.time("Closure: Create trees)") {
79+
buildModule(emitterResult.body)
80+
}
8581

86-
// Build Closure IR
87-
val (topLevelVarDeclarations, globalRefs, module) = {
88-
logger.time("Closure: Emitter (create Closure trees)") {
89-
val builder = new ClosureModuleBuilder(config.relativizeSourceMapBase)
90-
val (topLevelVarDeclarations, globalRefs) =
91-
emitter.emitForClosure(unit, builder, logger)
92-
(topLevelVarDeclarations, globalRefs, builder.result())
93-
}
82+
val (code, sourceMap) = logger.time("Closure: Compiler pass") {
83+
val options = closureOptions(output)
84+
85+
val externs = java.util.Arrays.asList(
86+
ClosureSource.fromCode("ScalaJSExterns.js",
87+
ClosureLinkerBackend.ScalaJSExterns),
88+
ClosureSource.fromCode("ScalaJSGlobalRefs.js",
89+
makeExternsForGlobalRefs(emitterResult.globalRefs)),
90+
ClosureSource.fromCode("ScalaJSExportExterns.js",
91+
makeExternsForExports(emitterResult.topLevelVarDecls, unit)))
92+
93+
compile(externs, module, options, logger)
9494
}
9595

96-
// Compile the module
97-
val closureExterns = java.util.Arrays.asList(
98-
ClosureSource.fromCode("ScalaJSExterns.js", ClosureLinkerBackend.ScalaJSExterns),
99-
ClosureSource.fromCode("ScalaJSGlobalRefs.js", makeExternsForGlobalRefs(globalRefs)),
100-
ClosureSource.fromCode("ScalaJSExportExterns.js", makeExternsForExports(topLevelVarDeclarations, unit)))
101-
val options = closureOptions(output)
102-
val compiler = closureCompiler(logger)
103-
104-
val result = logger.time("Closure: Compiler pass") {
105-
compiler.compileModules(
106-
closureExterns, java.util.Arrays.asList(module), options)
96+
logger.timeFuture("Closure: Write result") {
97+
writeResult(emitterResult.header, code, emitterResult.footer, sourceMap, output)
10798
}
99+
}
100+
101+
private def buildModule(trees: List[js.Tree]): JSModule = {
102+
val root = ClosureAstTransformer.transformScript(
103+
trees, config.relativizeSourceMapBase)
104+
105+
val module = new JSModule("Scala.js")
106+
module.add(new CompilerInput(new SyntheticAst(root)))
107+
module
108+
}
109+
110+
private def compile(externs: java.util.List[ClosureSource], module: JSModule,
111+
options: ClosureOptions, logger: Logger) = {
112+
import com.google.common.collect.ImmutableSet
113+
114+
val compiler = new ClosureCompiler
115+
compiler.setErrorManager(new SortingErrorManager(ImmutableSet.of(
116+
new LoggerErrorReportGenerator(logger))))
117+
118+
val result = compiler.compileModules(externs,
119+
java.util.Arrays.asList(module), options)
108120

109121
if (!result.success) {
110122
throw new LinkingException(
111123
"There were errors when applying the Google Closure Compiler")
112124
}
113125

114-
(topLevelVarDeclarations, compiler.toSource + "\n", Option(compiler.getSourceMap()))
126+
(compiler.toSource + "\n", Option(compiler.getSourceMap()))
115127
}
116128

117129
/** Constructs an externs file listing all the global refs.
@@ -153,32 +165,9 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
153165
content.toString()
154166
}
155167

156-
private def closureCompiler(logger: Logger) = {
157-
import com.google.common.collect.ImmutableSet
158-
159-
val compiler = new ClosureCompiler
160-
compiler.setErrorManager(new SortingErrorManager(ImmutableSet.of(
161-
new LoggerErrorReportGenerator(logger))))
162-
compiler
163-
}
164-
165-
private def writeResult(topLevelVarDeclarations: List[String],
166-
outputContent: String, sourceMap: Option[SourceMap], output: LinkerOutput)(
168+
private def writeResult(header: String, body: String, footer: String,
169+
sourceMap: Option[SourceMap], output: LinkerOutput)(
167170
implicit ec: ExecutionContext): Future[Unit] = {
168-
169-
def ifIIFE(str: String): String = if (needsIIFEWrapper) str else ""
170-
171-
val header = {
172-
val maybeTopLevelVarDecls = if (topLevelVarDeclarations.nonEmpty) {
173-
val kw = if (esFeatures.useECMAScript2015) "let " else "var "
174-
topLevelVarDeclarations.mkString(kw, ",", ";\n")
175-
} else {
176-
""
177-
}
178-
maybeTopLevelVarDecls + ifIIFE("(function(){") + "'use strict';\n"
179-
}
180-
val footer = ifIIFE("}).call(this);\n")
181-
182171
def writeToFile(file: LinkerOutput.File)(content: Writer => Unit): Future[Unit] = {
183172
val out = new ByteArrayOutputStream()
184173
val writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
@@ -192,7 +181,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
192181
// Write optimized code
193182
val codeWritten = writeToFile(output.jsFile) { w =>
194183
w.write(header)
195-
w.write(outputContent)
184+
w.write(body)
196185
w.write(footer)
197186
output.sourceMapURI.foreach(uri =>
198187
w.write("//# sourceMappingURL=" + uri.toASCIIString + "\n"))

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

Lines changed: 0 additions & 65 deletions
This file was deleted.

linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@ package org.scalajs.linker.backend
1414

1515
import scala.concurrent._
1616

17+
import java.io._
18+
import java.net.URI
19+
import java.nio.ByteBuffer
20+
import java.nio.charset.StandardCharsets
21+
1722
import org.scalajs.logging.Logger
1823

1924
import org.scalajs.linker.interface.{IRFile, LinkerOutput}
2025
import org.scalajs.linker.interface.unstable.OutputFileImpl
2126
import org.scalajs.linker.standard._
2227

2328
import org.scalajs.linker.backend.emitter.Emitter
24-
import org.scalajs.linker.backend.javascript.{JSLineBuilder, JSFileBuilder, JSFileBuilderWithSourceMap}
29+
import org.scalajs.linker.backend.javascript.{Printers, SourceMapWriter}
2530

2631
/** The basic backend for the Scala.js linker.
2732
*
@@ -45,22 +50,58 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config)
4550
implicit ec: ExecutionContext): Future[Unit] = {
4651
verifyUnit(unit)
4752

48-
logger.timeFuture("Emitter") {
53+
val emitterResult = logger.time("Emitter") {
54+
emitter.emit(unit, logger)
55+
}
56+
57+
logger.timeFuture("BasicBackend: Write result") {
4958
output.sourceMap.filter(_ => config.sourceMap).fold {
50-
// Without source map.
51-
val b = new JSFileBuilder
52-
emitter.emitAll(unit, b, logger)
53-
OutputFileImpl.fromOutputFile(output.jsFile).writeFull(b.complete())
54-
} { sourceMap =>
55-
// With source map.
56-
val b = new JSFileBuilderWithSourceMap(output.jsFileURI,
57-
output.sourceMapURI, config.relativizeSourceMapBase)
58-
emitter.emitAll(unit, b, logger)
59-
val (js, sm) = b.complete()
60-
61-
OutputFileImpl.fromOutputFile(output.jsFile).writeFull(js)
62-
.flatMap(_ => OutputFileImpl.fromOutputFile(sourceMap).writeFull(sm))
59+
val code = withWriter { writer =>
60+
val printer = new Printers.JSTreePrinter(writer)
61+
writer.write(emitterResult.header)
62+
emitterResult.body.foreach(printer.printTopLevelTree _)
63+
writer.write(emitterResult.footer)
64+
}
65+
66+
write(output.jsFile, code)
67+
} { sourceMapFile =>
68+
val sourceMapWriter = new SourceMapWriter(output.jsFileURI,
69+
config.relativizeSourceMapBase)
70+
71+
val code = withWriter { writer =>
72+
val printer = new Printers.JSTreePrinterWithSourceMap(writer, sourceMapWriter)
73+
74+
writer.write(emitterResult.header)
75+
for (_ <- 0 until emitterResult.header.count(_ == '\n'))
76+
sourceMapWriter.nextLine()
77+
78+
emitterResult.body.foreach(printer.printTopLevelTree _)
79+
80+
writer.write(emitterResult.footer)
81+
82+
output.sourceMapURI.foreach { uri =>
83+
writer.write("//# sourceMappingURL=" + uri.toASCIIString() + "\n")
84+
}
85+
}
86+
87+
val sourceMap = sourceMapWriter.result()
88+
89+
write(output.jsFile, code)
90+
.flatMap(_ => write(sourceMapFile, sourceMap))
6391
}
6492
}
6593
}
94+
95+
private def withWriter(body: Writer => Unit): ByteBuffer = {
96+
val byteStream = new ByteArrayOutputStream
97+
val out = new OutputStreamWriter(byteStream, StandardCharsets.UTF_8)
98+
body(out)
99+
out.close()
100+
ByteBuffer.wrap(byteStream.toByteArray())
101+
}
102+
103+
private def write(file: LinkerOutput.File, buf: ByteBuffer)(
104+
implicit ec: ExecutionContext): Future[Unit] = {
105+
OutputFileImpl.fromOutputFile(file).writeFull(buf)
106+
}
66107
}

0 commit comments

Comments
 (0)