From fac3b52d908e771fa41671eef761bf845db8891e Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Mar 2024 14:53:11 +0100 Subject: [PATCH 1/4] Remove `force` parameter from OutputWriter methods TODO: Explain --- .../linker/backend/closure/ClosureLinkerBackend.scala | 4 ++-- .../scalajs/linker/backend/BasicLinkerBackend.scala | 8 ++++---- .../org/scalajs/linker/backend/OutputWriter.scala | 10 ++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index 7532e0be47..13299f672b 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -216,7 +216,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) writer.write(footer) } - protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] = { + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] = { val jsFileWriter = new ByteArrayOutputStream() val jsFileStrWriter = new java.io.OutputStreamWriter(jsFileWriter, StandardCharsets.UTF_8) writeCode(jsFileStrWriter) @@ -224,7 +224,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) Some(ByteBuffer.wrap(jsFileWriter.toByteArray())) } - protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] = { + protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] = { val jsFileURI = OutputPatternsImpl.jsFileURI(config.outputPatterns, moduleID.id) val sourceMapURI = OutputPatternsImpl.sourceMapURI(config.outputPatterns, moduleID.id) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index fa7e616880..53be7cd359 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -90,11 +90,11 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) val allChanged = allChanged0 || config.minify val writer = new OutputWriter(output, config, skipContentCheck) { - protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] = { + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] = { val cache = printedModuleSetCache.getModuleCache(moduleID) val (trees, changed) = emitterResult.body(moduleID) - if (force || changed || allChanged) { + if (changed || allChanged) { rewrittenModules.incrementAndGet() val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) @@ -113,11 +113,11 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) } } - protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] = { + protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] = { val cache = printedModuleSetCache.getModuleCache(moduleID) val (trees, changed) = emitterResult.body(moduleID) - if (force || changed || allChanged) { + if (changed || allChanged) { rewrittenModules.incrementAndGet() val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala index 49113d8e41..3174338ed7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala @@ -30,9 +30,9 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, private val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) private val moduleKind = config.commonConfig.coreSpec.moduleKind - protected def writeModuleWithoutSourceMap(moduleID: ModuleID, force: Boolean): Option[ByteBuffer] + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] - protected def writeModuleWithSourceMap(moduleID: ModuleID, force: Boolean): Option[(ByteBuffer, ByteBuffer)] + protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] def write(moduleSet: ModuleSet)(implicit ec: ExecutionContext): Future[Report] = { val ioThrottler = new IOThrottler(config.maxConcurrentWrites) @@ -68,9 +68,8 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, if (config.sourceMap) { val sourceMapFileName = OutputPatternsImpl.sourceMapFile(config.outputPatterns, moduleID.id) val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, Some(sourceMapFileName), moduleKind) - val force = !existingFiles.contains(jsFileName) || !existingFiles.contains(sourceMapFileName) - writeModuleWithSourceMap(moduleID, force) match { + writeModuleWithSourceMap(moduleID) match { case Some((code, sourceMap)) => for { _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) @@ -83,9 +82,8 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, } } else { val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, None, moduleKind) - val force = !existingFiles.contains(jsFileName) - writeModuleWithoutSourceMap(moduleID, force) match { + writeModuleWithoutSourceMap(moduleID) match { case Some(code) => for { _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) From 788b7b5ddbe6ce4048032aa24a88f0606c1c3a11 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Mar 2024 15:03:40 +0100 Subject: [PATCH 2/4] Move module changed check to OutputWriter --- .../closure/ClosureLinkerBackend.scala | 10 +-- .../linker/backend/BasicLinkerBackend.scala | 71 +++++++++---------- .../scalajs/linker/backend/OutputWriter.scala | 44 ++++++------ 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index 13299f672b..b59d981b50 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -216,15 +216,17 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) writer.write(footer) } - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] = { + protected def moduleChanged(moduleID: ModuleID): Boolean = true // no incremental support + + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer = { val jsFileWriter = new ByteArrayOutputStream() val jsFileStrWriter = new java.io.OutputStreamWriter(jsFileWriter, StandardCharsets.UTF_8) writeCode(jsFileStrWriter) jsFileStrWriter.flush() - Some(ByteBuffer.wrap(jsFileWriter.toByteArray())) + ByteBuffer.wrap(jsFileWriter.toByteArray()) } - protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] = { + protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) = { val jsFileURI = OutputPatternsImpl.jsFileURI(config.outputPatterns, moduleID.id) val sourceMapURI = OutputPatternsImpl.sourceMapURI(config.outputPatterns, moduleID.id) @@ -241,7 +243,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) sourceMap.appendTo(sourceMapStrWriter, jsFileURI) sourceMapStrWriter.flush() - Some((ByteBuffer.wrap(jsFileWriter.toByteArray()), ByteBuffer.wrap(sourceMapWriter.toByteArray()))) + (ByteBuffer.wrap(jsFileWriter.toByteArray()), ByteBuffer.wrap(sourceMapWriter.toByteArray())) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index 53be7cd359..b3a729c410 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -90,64 +90,59 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) val allChanged = allChanged0 || config.minify val writer = new OutputWriter(output, config, skipContentCheck) { - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] = { + protected def moduleChanged(moduleID: ModuleID): Boolean = + allChanged || emitterResult.body(moduleID)._2 + + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val (trees, changed) = emitterResult.body(moduleID) + val (trees, _) = emitterResult.body(moduleID) - if (changed || allChanged) { - rewrittenModules.incrementAndGet() + rewrittenModules.incrementAndGet() - val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) + val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) - jsFileWriter.write(printedModuleSetCache.headerBytes) - jsFileWriter.writeASCIIString("'use strict';\n") + jsFileWriter.write(printedModuleSetCache.headerBytes) + jsFileWriter.writeASCIIString("'use strict';\n") - bodyPrinter.printWithoutSourceMap(trees, jsFileWriter) + bodyPrinter.printWithoutSourceMap(trees, jsFileWriter) - jsFileWriter.write(printedModuleSetCache.footerBytes) + jsFileWriter.write(printedModuleSetCache.footerBytes) - cache.recordFinalSizes(jsFileWriter.currentSize, 0) - Some(jsFileWriter.toByteBuffer()) - } else { - None - } + cache.recordFinalSizes(jsFileWriter.currentSize, 0) + jsFileWriter.toByteBuffer() } - protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] = { + protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) = { val cache = printedModuleSetCache.getModuleCache(moduleID) - val (trees, changed) = emitterResult.body(moduleID) - - if (changed || allChanged) { - rewrittenModules.incrementAndGet() + val (trees, _) = emitterResult.body(moduleID) - val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) - val sourceMapWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalSourceMapSize())) + rewrittenModules.incrementAndGet() - val jsFileURI = OutputPatternsImpl.jsFileURI(config.outputPatterns, moduleID.id) - val sourceMapURI = OutputPatternsImpl.sourceMapURI(config.outputPatterns, moduleID.id) + val jsFileWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalJSFileSize())) + val sourceMapWriter = new ByteArrayWriter(sizeHintFor(cache.getPreviousFinalSourceMapSize())) - val smWriter = new SourceMapWriter(sourceMapWriter, jsFileURI, - config.relativizeSourceMapBase) + val jsFileURI = OutputPatternsImpl.jsFileURI(config.outputPatterns, moduleID.id) + val sourceMapURI = OutputPatternsImpl.sourceMapURI(config.outputPatterns, moduleID.id) - jsFileWriter.write(printedModuleSetCache.headerBytes) - for (_ <- 0 until printedModuleSetCache.headerNewLineCount) - smWriter.nextLine() + val smWriter = new SourceMapWriter(sourceMapWriter, jsFileURI, + config.relativizeSourceMapBase) - jsFileWriter.writeASCIIString("'use strict';\n") + jsFileWriter.write(printedModuleSetCache.headerBytes) + for (_ <- 0 until printedModuleSetCache.headerNewLineCount) smWriter.nextLine() - bodyPrinter.printWithSourceMap(trees, jsFileWriter, smWriter) + jsFileWriter.writeASCIIString("'use strict';\n") + smWriter.nextLine() - jsFileWriter.write(printedModuleSetCache.footerBytes) - jsFileWriter.write(("//# sourceMappingURL=" + sourceMapURI + "\n").getBytes(StandardCharsets.UTF_8)) + bodyPrinter.printWithSourceMap(trees, jsFileWriter, smWriter) - smWriter.complete() + jsFileWriter.write(printedModuleSetCache.footerBytes) + jsFileWriter.write(("//# sourceMappingURL=" + sourceMapURI + "\n").getBytes(StandardCharsets.UTF_8)) - cache.recordFinalSizes(jsFileWriter.currentSize, sourceMapWriter.currentSize) - Some((jsFileWriter.toByteBuffer(), sourceMapWriter.toByteBuffer())) - } else { - None - } + smWriter.complete() + + cache.recordFinalSizes(jsFileWriter.currentSize, sourceMapWriter.currentSize) + (jsFileWriter.toByteBuffer(), sourceMapWriter.toByteBuffer()) } private def sizeHintFor(previousSize: Int): Int = diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala index 3174338ed7..602c482d30 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala @@ -30,9 +30,11 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, private val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) private val moduleKind = config.commonConfig.coreSpec.moduleKind - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): Option[ByteBuffer] + protected def moduleChanged(moduleID: ModuleID): Boolean - protected def writeModuleWithSourceMap(moduleID: ModuleID): Option[(ByteBuffer, ByteBuffer)] + protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer + + protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) def write(moduleSet: ModuleSet)(implicit ec: ExecutionContext): Future[Report] = { val ioThrottler = new IOThrottler(config.maxConcurrentWrites) @@ -69,29 +71,29 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, val sourceMapFileName = OutputPatternsImpl.sourceMapFile(config.outputPatterns, moduleID.id) val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, Some(sourceMapFileName), moduleKind) - writeModuleWithSourceMap(moduleID) match { - case Some((code, sourceMap)) => - for { - _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) - _ <- outputImpl.writeFull(sourceMapFileName, sourceMap, skipContentCheck) - } yield { - report - } - case None => - Future.successful(report) + if (moduleChanged(moduleID)) { + val (code, sourceMap) = writeModuleWithSourceMap(moduleID) + for { + _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) + _ <- outputImpl.writeFull(sourceMapFileName, sourceMap, skipContentCheck) + } yield { + report + } + } else { + Future.successful(report) } } else { val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, None, moduleKind) - writeModuleWithoutSourceMap(moduleID) match { - case Some(code) => - for { - _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) - } yield { - report - } - case None => - Future.successful(report) + if (moduleChanged(moduleID)) { + val code = writeModuleWithoutSourceMap(moduleID) + for { + _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) + } yield { + report + } + } else { + Future.successful(report) } } } From 143a24d3f5cd825bef43581a3188d4f1e38f220c Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Mar 2024 16:17:23 +0100 Subject: [PATCH 3/4] No nested post transforms They are not useful. --- .../linker/backend/BasicLinkerBackend.scala | 75 ++--------- .../linker/backend/emitter/Emitter.scala | 127 ++++++++---------- .../org/scalajs/linker/EmitterTest.scala | 11 +- 3 files changed, 74 insertions(+), 139 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index b3a729c410..5e27f74d7d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -41,19 +41,19 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) private[this] var totalModules = 0 private[this] val rewrittenModules = new AtomicInteger(0) - private[this] val bodyPrinter: BodyPrinter = { - if (config.minify) IdentityPostTransformerBasedBodyPrinter - else if (config.sourceMap) PrintedTreeWithSourceMapBodyPrinter - else PrintedTreeWithoutSourceMapBodyPrinter + private[this] val postTransformer: Emitter.PostTransformer = { + if (config.minify) Emitter.PostTransformer.Identity + else if (config.sourceMap) PostTransformerWithSourceMap + else PostTransformerWithoutSourceMap } - private[this] val emitter: Emitter[bodyPrinter.TreeType] = { + private[this] val emitter: Emitter = { val emitterConfig = Emitter.Config(config.commonConfig.coreSpec) .withJSHeader(config.jsHeader) .withInternalModulePattern(m => OutputPatternsImpl.moduleName(config.outputPatterns, m.id)) .withMinify(config.minify) - new Emitter(emitterConfig, bodyPrinter.postTransformer) + new Emitter(emitterConfig, postTransformer) } val symbolRequirements: SymbolRequirement = emitter.symbolRequirements @@ -104,7 +104,9 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.write(printedModuleSetCache.headerBytes) jsFileWriter.writeASCIIString("'use strict';\n") - bodyPrinter.printWithoutSourceMap(trees, jsFileWriter) + val printer = new Printers.JSTreePrinter(jsFileWriter) + for (tree <- trees) + printer.printStat(tree) jsFileWriter.write(printedModuleSetCache.footerBytes) @@ -134,7 +136,9 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.writeASCIIString("'use strict';\n") smWriter.nextLine() - bodyPrinter.printWithSourceMap(trees, jsFileWriter, smWriter) + val printer = new Printers.JSTreePrinterWithSourceMap(jsFileWriter, smWriter, initIndent = 0) + for (tree <- trees) + printer.printStat(tree) jsFileWriter.write(printedModuleSetCache.footerBytes) jsFileWriter.write(("//# sourceMappingURL=" + sourceMapURI + "\n").getBytes(StandardCharsets.UTF_8)) @@ -235,58 +239,7 @@ private object BasicLinkerBackend { } } - private abstract class BodyPrinter { - type TreeType >: Null <: js.Tree - - val postTransformer: Emitter.PostTransformer[TreeType] - - def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit - def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit - } - - private object IdentityPostTransformerBasedBodyPrinter extends BodyPrinter { - type TreeType = js.Tree - - val postTransformer: Emitter.PostTransformer[TreeType] = Emitter.PostTransformer.Identity - - def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit = { - val printer = new Printers.JSTreePrinter(jsFileWriter) - for (tree <- trees) - printer.printStat(tree) - } - - def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit = { - val printer = new Printers.JSTreePrinterWithSourceMap(jsFileWriter, smWriter, initIndent = 0) - for (tree <- trees) - printer.printStat(tree) - } - } - - private abstract class PrintedTreeBasedBodyPrinter( - val postTransformer: Emitter.PostTransformer[js.PrintedTree] - ) extends BodyPrinter { - type TreeType = js.PrintedTree - - def printWithoutSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter): Unit = { - for (tree <- trees) - jsFileWriter.write(tree.jsCode) - } - - def printWithSourceMap(trees: List[TreeType], jsFileWriter: ByteArrayWriter, smWriter: SourceMapWriter): Unit = { - for (tree <- trees) { - jsFileWriter.write(tree.jsCode) - smWriter.insertFragment(tree.sourceMapFragment) - } - } - } - - private object PrintedTreeWithoutSourceMapBodyPrinter - extends PrintedTreeBasedBodyPrinter(PostTransformerWithoutSourceMap) - - private object PrintedTreeWithSourceMapBodyPrinter - extends PrintedTreeBasedBodyPrinter(PostTransformerWithSourceMap) - - private object PostTransformerWithoutSourceMap extends Emitter.PostTransformer[js.PrintedTree] { + private object PostTransformerWithoutSourceMap extends Emitter.PostTransformer { def transformStats(trees: List[js.Tree], indent: Int): List[js.PrintedTree] = { if (trees.isEmpty) { Nil // Fast path @@ -301,7 +254,7 @@ private object BasicLinkerBackend { } } - private object PostTransformerWithSourceMap extends Emitter.PostTransformer[js.PrintedTree] { + private object PostTransformerWithSourceMap extends Emitter.PostTransformer { def transformStats(trees: List[js.Tree], indent: Int): List[js.PrintedTree] = { if (trees.isEmpty) { Nil // Fast path diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 506dec4d4a..28af02b61b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -33,8 +33,7 @@ import EmitterNames._ import GlobalRefUtils._ /** Emits a desugared JS tree to a builder */ -final class Emitter[E >: Null <: js.Tree]( - config: Emitter.Config, postTransformer: Emitter.PostTransformer[E]) { +final class Emitter(config: Emitter.Config, postTransformer: Emitter.PostTransformer) { import Emitter._ import config._ @@ -83,15 +82,13 @@ final class Emitter[E >: Null <: js.Tree]( private[this] var statsMethodsReused: Int = 0 private[this] var statsMethodsInvalidated: Int = 0 private[this] var statsPostTransforms: Int = 0 - private[this] var statsNestedPostTransforms: Int = 0 - private[this] var statsNestedPostTransformsAvoided: Int = 0 val symbolRequirements: SymbolRequirement = Emitter.symbolRequirements(config) val injectedIRFiles: Seq[IRFile] = PrivateLibHolder.files - def emit(moduleSet: ModuleSet, logger: Logger): Result[E] = { + def emit(moduleSet: ModuleSet, logger: Logger): Result = { val WithGlobals(body, globalRefs) = emitInternal(moduleSet, logger) val result = moduleKind match { @@ -135,15 +132,13 @@ final class Emitter[E >: Null <: js.Tree]( } private def emitInternal(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { + logger: Logger): WithGlobals[Map[ModuleID, (List[js.Tree], Boolean)]] = { // Reset caching stats. statsClassesReused = 0 statsClassesInvalidated = 0 statsMethodsReused = 0 statsMethodsInvalidated = 0 statsPostTransforms = 0 - statsNestedPostTransforms = 0 - statsNestedPostTransformsAvoided = 0 // Update GlobalKnowledge. val invalidateAll = knowledgeGuardian.update(moduleSet) @@ -165,10 +160,7 @@ final class Emitter[E >: Null <: js.Tree]( logger.debug( s"Emitter: Method tree cache stats: reused: $statsMethodsReused -- "+ s"invalidated: $statsMethodsInvalidated") - logger.debug( - s"Emitter: Post transforms: total: $statsPostTransforms -- " + - s"nested: $statsNestedPostTransforms -- " + - s"nested avoided: $statsNestedPostTransformsAvoided") + logger.debug(s"Emitter: Post transforms: total: $statsPostTransforms") // Inform caches about run completion. state.moduleCaches.filterInPlace((_, c) => c.cleanAfterRun()) @@ -176,12 +168,12 @@ final class Emitter[E >: Null <: js.Tree]( } } - private def postTransform(trees: List[js.Tree], indent: Int): List[E] = { + private def postTransform(trees: List[js.Tree], indent: Int): List[js.Tree] = { statsPostTransforms += 1 postTransformer.transformStats(trees, indent) } - private def postTransform(tree: js.Tree, indent: Int): List[E] = + private def postTransform(tree: js.Tree, indent: Int): List[js.Tree] = postTransform(tree :: Nil, indent) /** Emits all JavaScript code avoiding clashes with global refs. @@ -192,7 +184,7 @@ final class Emitter[E >: Null <: js.Tree]( */ @tailrec private def emitAvoidGlobalClash(moduleSet: ModuleSet, - logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { + logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, (List[js.Tree], Boolean)]] = { val result = emitOnce(moduleSet, logger) val mentionedDangerousGlobalRefs = @@ -218,7 +210,7 @@ final class Emitter[E >: Null <: js.Tree]( } private def emitOnce(moduleSet: ModuleSet, - logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = { + logger: Logger): WithGlobals[Map[ModuleID, (List[js.Tree], Boolean)]] = { // Genreate classes first so we can measure time separately. val generatedClasses = logger.time("Emitter: Generate Classes") { moduleSet.modules.map { module => @@ -292,7 +284,7 @@ final class Emitter[E >: Null <: js.Tree]( * requires consistency between the Analyzer and the Emitter. As such, * it is crucial that we verify it. */ - val defTrees: List[E] = ( + val defTrees: List[js.Tree] = ( /* The definitions of the CoreJSLib that come before the definition * of `j.l.Object`. They depend on nothing else. */ @@ -408,7 +400,7 @@ final class Emitter[E >: Null <: js.Tree]( } private def genClass(linkedClass: LinkedClass, - moduleContext: ModuleContext): GeneratedClass[E] = { + moduleContext: ModuleContext): GeneratedClass = { val className = linkedClass.className val classCache = classCaches.getOrElseUpdate( @@ -439,7 +431,7 @@ final class Emitter[E >: Null <: js.Tree]( // Main part - val main = List.newBuilder[E] + val main = List.newBuilder[js.Tree] val (linkedInlineableInit, linkedMethods) = classEmitter.extractInlineableInit(linkedClass)(classCache) @@ -669,14 +661,7 @@ final class Emitter[E >: Null <: js.Tree]( allMembers // invalidated directly )(moduleContext, fullClassChangeTracker, linkedClass.pos) // pos invalidated by class version } yield { - // Avoid a nested post transform if we just got the original members back. - if (clazz eq allMembers) { - statsNestedPostTransformsAvoided += 1 - allMembers - } else { - statsNestedPostTransforms += 1 - postTransform(clazz, 0) - } + clazz } } @@ -769,14 +754,14 @@ final class Emitter[E >: Null <: js.Tree]( private final class ModuleCache extends knowledgeGuardian.KnowledgeAccessor { private[this] var _cacheUsed: Boolean = false - private[this] var _importsCache: WithGlobals[List[E]] = WithGlobals.nil + private[this] var _importsCache: WithGlobals[List[js.Tree]] = WithGlobals.nil private[this] var _lastExternalDependencies: Set[String] = Set.empty private[this] var _lastInternalDependencies: Set[ModuleID] = Set.empty - private[this] var _topLevelExportsCache: WithGlobals[List[E]] = WithGlobals.nil + private[this] var _topLevelExportsCache: WithGlobals[List[js.Tree]] = WithGlobals.nil private[this] var _lastTopLevelExports: List[LinkedTopLevelExport] = Nil - private[this] var _initializersCache: WithGlobals[List[E]] = WithGlobals.nil + private[this] var _initializersCache: WithGlobals[List[js.Tree]] = WithGlobals.nil private[this] var _lastInitializers: List[ModuleInitializer.Initializer] = Nil override def invalidate(): Unit = { @@ -797,7 +782,7 @@ final class Emitter[E >: Null <: js.Tree]( } def getOrComputeImports(externalDependencies: Set[String], internalDependencies: Set[ModuleID])( - compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { + compute: => WithGlobals[List[js.Tree]]): (WithGlobals[List[js.Tree]], Boolean) = { _cacheUsed = true @@ -813,7 +798,7 @@ final class Emitter[E >: Null <: js.Tree]( } def getOrComputeTopLevelExports(topLevelExports: List[LinkedTopLevelExport])( - compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { + compute: => WithGlobals[List[js.Tree]]): (WithGlobals[List[js.Tree]], Boolean) = { _cacheUsed = true @@ -854,7 +839,7 @@ final class Emitter[E >: Null <: js.Tree]( } def getOrComputeInitializers(initializers: List[ModuleInitializer.Initializer])( - compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = { + compute: => WithGlobals[List[js.Tree]]): (WithGlobals[List[js.Tree]], Boolean) = { _cacheUsed = true @@ -875,20 +860,20 @@ final class Emitter[E >: Null <: js.Tree]( } private final class ClassCache extends knowledgeGuardian.KnowledgeAccessor { - private[this] var _cache: DesugaredClassCache[List[E]] = null + private[this] var _cache: DesugaredClassCache = null private[this] var _lastVersion: Version = Version.Unversioned private[this] var _cacheUsed = false private[this] val _methodCaches = - Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[E]]]) + Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[js.Tree]]]) private[this] val _memberMethodCache = - mutable.Map.empty[MethodName, MethodCache[List[E]]] + mutable.Map.empty[MethodName, MethodCache[List[js.Tree]]] - private[this] var _constructorCache: Option[MethodCache[List[E]]] = None + private[this] var _constructorCache: Option[MethodCache[List[js.Tree]]] = None private[this] val _exportedMembersCache = - mutable.Map.empty[Int, MethodCache[List[E]]] + mutable.Map.empty[Int, MethodCache[List[js.Tree]]] private[this] var _fullClassChangeTracker: Option[FullClassChangeTracker] = None @@ -909,13 +894,13 @@ final class Emitter[E >: Null <: js.Tree]( _fullClassChangeTracker.foreach(_.startRun()) } - def getCache(version: Version): (DesugaredClassCache[List[E]], Boolean) = { + def getCache(version: Version): (DesugaredClassCache, Boolean) = { _cacheUsed = true if (_cache == null || !_lastVersion.sameVersion(version)) { invalidate() statsClassesInvalidated += 1 _lastVersion = version - _cache = new DesugaredClassCache[List[E]] + _cache = new DesugaredClassCache (_cache, true) } else { statsClassesReused += 1 @@ -924,25 +909,25 @@ final class Emitter[E >: Null <: js.Tree]( } def getMemberMethodCache( - methodName: MethodName): MethodCache[List[E]] = { + methodName: MethodName): MethodCache[List[js.Tree]] = { _memberMethodCache.getOrElseUpdate(methodName, new MethodCache) } def getStaticLikeMethodCache(namespace: MemberNamespace, - methodName: MethodName): MethodCache[List[E]] = { + methodName: MethodName): MethodCache[List[js.Tree]] = { _methodCaches(namespace.ordinal) .getOrElseUpdate(methodName, new MethodCache) } - def getConstructorCache(): MethodCache[List[E]] = { + def getConstructorCache(): MethodCache[List[js.Tree]] = { _constructorCache.getOrElse { - val cache = new MethodCache[List[E]] + val cache = new MethodCache[List[js.Tree]] _constructorCache = Some(cache) cache } } - def getExportedMemberCache(idx: Int): MethodCache[List[E]] = + def getExportedMemberCache(idx: Int): MethodCache[List[js.Tree]] = _exportedMembersCache.getOrElseUpdate(idx, new MethodCache) def getFullClassChangeTracker(): FullClassChangeTracker = { @@ -1010,9 +995,9 @@ final class Emitter[E >: Null <: js.Tree]( private class FullClassChangeTracker extends knowledgeGuardian.KnowledgeAccessor { private[this] var _lastVersion: Version = Version.Unversioned - private[this] var _lastCtor: WithGlobals[List[E]] = null - private[this] var _lastMemberMethods: List[WithGlobals[List[E]]] = null - private[this] var _lastExportedMembers: List[WithGlobals[List[E]]] = null + private[this] var _lastCtor: WithGlobals[List[js.Tree]] = null + private[this] var _lastMemberMethods: List[WithGlobals[List[js.Tree]]] = null + private[this] var _lastExportedMembers: List[WithGlobals[List[js.Tree]]] = null private[this] var _trackerUsed = false override def invalidate(): Unit = { @@ -1025,9 +1010,9 @@ final class Emitter[E >: Null <: js.Tree]( def startRun(): Unit = _trackerUsed = false - def trackChanged(version: Version, ctor: WithGlobals[List[E]], - memberMethods: List[WithGlobals[List[E]]], - exportedMembers: List[WithGlobals[List[E]]]): Boolean = { + def trackChanged(version: Version, ctor: WithGlobals[List[js.Tree]], + memberMethods: List[WithGlobals[List[js.Tree]]], + exportedMembers: List[WithGlobals[List[js.Tree]]]): Boolean = { @tailrec def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = { @@ -1069,9 +1054,9 @@ final class Emitter[E >: Null <: js.Tree]( private class CoreJSLibCache extends knowledgeGuardian.KnowledgeAccessor { private[this] var _lastModuleContext: ModuleContext = _ - private[this] var _lib: WithGlobals[CoreJSLib.Lib[List[E]]] = _ + private[this] var _lib: WithGlobals[CoreJSLib.Lib[List[js.Tree]]] = _ - def build(moduleContext: ModuleContext): WithGlobals[CoreJSLib.Lib[List[E]]] = { + def build(moduleContext: ModuleContext): WithGlobals[CoreJSLib.Lib[List[js.Tree]]] = { if (_lib == null || _lastModuleContext != moduleContext) { _lib = CoreJSLib.build(sjsGen, postTransform(_, 0), moduleContext, this) _lastModuleContext = moduleContext @@ -1088,9 +1073,9 @@ final class Emitter[E >: Null <: js.Tree]( object Emitter { /** Result of an emitter run. */ - final class Result[E] private[Emitter]( + final class Result private[Emitter]( val header: String, - val body: Map[ModuleID, (List[E], Boolean)], + val body: Map[ModuleID, (List[js.Tree], Boolean)], val footer: String, val topLevelVarDecls: List[String], val globalRefs: Set[String] @@ -1174,32 +1159,32 @@ object Emitter { new Config(coreSpec.semantics, coreSpec.moduleKind, coreSpec.esFeatures) } - trait PostTransformer[E] { - def transformStats(trees: List[js.Tree], indent: Int): List[E] + trait PostTransformer { + def transformStats(trees: List[js.Tree], indent: Int): List[js.Tree] } object PostTransformer { - object Identity extends PostTransformer[js.Tree] { + object Identity extends PostTransformer { def transformStats(trees: List[js.Tree], indent: Int): List[js.Tree] = trees } } - private final class DesugaredClassCache[E >: Null] { - val privateJSFields = new OneTimeCache[WithGlobals[E]] - val storeJSSuperClass = new OneTimeCache[WithGlobals[E]] - val instanceTests = new OneTimeCache[WithGlobals[E]] - val typeData = new OneTimeCache[WithGlobals[E]] - val setTypeData = new OneTimeCache[E] - val moduleAccessor = new OneTimeCache[WithGlobals[E]] - val staticInitialization = new OneTimeCache[E] - val staticFields = new OneTimeCache[WithGlobals[E]] + private final class DesugaredClassCache { + val privateJSFields = new OneTimeCache[WithGlobals[List[js.Tree]]] + val storeJSSuperClass = new OneTimeCache[WithGlobals[List[js.Tree]]] + val instanceTests = new OneTimeCache[WithGlobals[List[js.Tree]]] + val typeData = new OneTimeCache[WithGlobals[List[js.Tree]]] + val setTypeData = new OneTimeCache[List[js.Tree]] + val moduleAccessor = new OneTimeCache[WithGlobals[List[js.Tree]]] + val staticInitialization = new OneTimeCache[List[js.Tree]] + val staticFields = new OneTimeCache[WithGlobals[List[js.Tree]]] } - private final class GeneratedClass[E]( + private final class GeneratedClass( val className: ClassName, - val main: List[E], - val staticFields: List[E], - val staticInitialization: List[E], + val main: List[js.Tree], + val staticFields: List[js.Tree], + val staticInitialization: List[js.Tree], val trackedGlobalRefs: Set[String], val changed: Boolean ) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala index 50e726106e..0501f99ebe 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala @@ -137,7 +137,7 @@ class EmitterTest { raw"""Emitter: Method tree cache stats: reused: (\d+) -- invalidated: (\d+)""".r private val EmitterPostTransformStatsMessage = - raw"""Emitter: Post transforms: total: (\d+) -- nested: (\d+) -- nested avoided: (\d+)""".r + raw"""Emitter: Post transforms: total: (\d+)""".r /** Makes sure that linking a "substantial" program (using `println`) twice * does not invalidate any cache or top-level tree in the second run. @@ -208,10 +208,10 @@ class EmitterTest { // Post transforms - val Seq(postTransforms1, nestedPostTransforms1, _) = + val Seq(postTransforms1) = lines1.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) - val Seq(postTransforms2, nestedPostTransforms2, _) = + val Seq(postTransforms2) = lines2.assertContainsMatch(EmitterPostTransformStatsMessage).map(_.toInt) // At the time of writing this test, postTransforms1 reports 216 @@ -219,10 +219,7 @@ class EmitterTest { s"Not enough post transforms (got $postTransforms1); extraction must have gone wrong", postTransforms1 > 200) - assertEquals("Second run must only have nested post transforms", - nestedPostTransforms2, postTransforms2) - assertEquals("Both runs must have the same number of nested post transforms", - nestedPostTransforms1, nestedPostTransforms2) + assertEquals("Second run may not have post transforms", 0, postTransforms2) } } } From 639d93b45097927ccf1db369c5534f3e49873477 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 3 Mar 2024 15:44:15 +0100 Subject: [PATCH 4/4] wip --- .../closure/ClosureLinkerBackend.scala | 4 +-- .../linker/backend/BasicLinkerBackend.scala | 12 +++---- .../scalajs/linker/backend/OutputWriter.scala | 16 ++++++--- .../backend/javascript/ByteArrayWriter.scala | 8 +++++ .../linker/backend/javascript/Printers.scala | 21 +++++++---- .../linker/backend/javascript/Trees.scala | 36 +++++++++++++++++-- .../backend/javascript/PrintersTest.scala | 9 ++++- 7 files changed, 84 insertions(+), 22 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index b59d981b50..95d9fa3965 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -218,7 +218,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) protected def moduleChanged(moduleID: ModuleID): Boolean = true // no incremental support - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer = { + protected def writeModuleWithoutSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): ByteBuffer = { val jsFileWriter = new ByteArrayOutputStream() val jsFileStrWriter = new java.io.OutputStreamWriter(jsFileWriter, StandardCharsets.UTF_8) writeCode(jsFileStrWriter) @@ -226,7 +226,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) ByteBuffer.wrap(jsFileWriter.toByteArray()) } - protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) = { + protected def writeModuleWithSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): (ByteBuffer, ByteBuffer) = { val jsFileURI = OutputPatternsImpl.jsFileURI(config.outputPatterns, moduleID.id) val sourceMapURI = OutputPatternsImpl.sourceMapURI(config.outputPatterns, moduleID.id) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index 5e27f74d7d..3798b52623 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -93,7 +93,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) protected def moduleChanged(moduleID: ModuleID): Boolean = allChanged || emitterResult.body(moduleID)._2 - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer = { + protected def writeModuleWithoutSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): ByteBuffer = { val cache = printedModuleSetCache.getModuleCache(moduleID) val (trees, _) = emitterResult.body(moduleID) @@ -104,7 +104,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.write(printedModuleSetCache.headerBytes) jsFileWriter.writeASCIIString("'use strict';\n") - val printer = new Printers.JSTreePrinter(jsFileWriter) + val printer = new Printers.JSTreePrinter(prevFile, jsFileWriter) for (tree <- trees) printer.printStat(tree) @@ -114,7 +114,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.toByteBuffer() } - protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) = { + protected def writeModuleWithSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): (ByteBuffer, ByteBuffer) = { val cache = printedModuleSetCache.getModuleCache(moduleID) val (trees, _) = emitterResult.body(moduleID) @@ -136,7 +136,7 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) jsFileWriter.writeASCIIString("'use strict';\n") smWriter.nextLine() - val printer = new Printers.JSTreePrinterWithSourceMap(jsFileWriter, smWriter, initIndent = 0) + val printer = new Printers.JSTreePrinterWithSourceMap(prevFile, jsFileWriter, smWriter, initIndent = 0) for (tree <- trees) printer.printStat(tree) @@ -245,7 +245,7 @@ private object BasicLinkerBackend { Nil // Fast path } else { val jsCodeWriter = new ByteArrayWriter() - val printer = new Printers.JSTreePrinter(jsCodeWriter, indent) + val printer = new Printers.JSTreePrinter(None, jsCodeWriter, indent) trees.foreach(printer.printStat(_)) @@ -261,7 +261,7 @@ private object BasicLinkerBackend { } else { val jsCodeWriter = new ByteArrayWriter() val smFragmentBuilder = new SourceMapWriter.FragmentBuilder() - val printer = new Printers.JSTreePrinterWithSourceMap(jsCodeWriter, smFragmentBuilder, indent) + val printer = new Printers.JSTreePrinterWithSourceMap(None, jsCodeWriter, smFragmentBuilder, indent) trees.foreach(printer.printStat(_)) smFragmentBuilder.complete() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala index 602c482d30..8d1187362a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/OutputWriter.scala @@ -32,9 +32,9 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, protected def moduleChanged(moduleID: ModuleID): Boolean - protected def writeModuleWithoutSourceMap(moduleID: ModuleID): ByteBuffer + protected def writeModuleWithoutSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): ByteBuffer - protected def writeModuleWithSourceMap(moduleID: ModuleID): (ByteBuffer, ByteBuffer) + protected def writeModuleWithSourceMap(moduleID: ModuleID, prevFile: Option[ByteBuffer]): (ByteBuffer, ByteBuffer) def write(moduleSet: ModuleSet)(implicit ec: ExecutionContext): Future[Report] = { val ioThrottler = new IOThrottler(config.maxConcurrentWrites) @@ -67,13 +67,20 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, implicit ec: ExecutionContext): Future[Report.Module] = { val jsFileName = OutputPatternsImpl.jsFile(config.outputPatterns, moduleID.id) + val prevFileFuture = + if (existingFiles.contains(jsFileName)) outputImpl.readFull(jsFileName).map(Some(_)) + else Future.successful(None) + if (config.sourceMap) { val sourceMapFileName = OutputPatternsImpl.sourceMapFile(config.outputPatterns, moduleID.id) val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, Some(sourceMapFileName), moduleKind) if (moduleChanged(moduleID)) { - val (code, sourceMap) = writeModuleWithSourceMap(moduleID) for { + prevFile <- prevFileFuture + (code, sourceMap) = writeModuleWithSourceMap(moduleID, prevFile) + + // TODO: We should not read the file again, but use the existing buffer to compare if a write is required. _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) _ <- outputImpl.writeFull(sourceMapFileName, sourceMap, skipContentCheck) } yield { @@ -86,8 +93,9 @@ private[backend] abstract class OutputWriter(output: OutputDirectory, val report = new ReportImpl.ModuleImpl(moduleID.id, jsFileName, None, moduleKind) if (moduleChanged(moduleID)) { - val code = writeModuleWithoutSourceMap(moduleID) for { + prevFile <- prevFileFuture + code = writeModuleWithoutSourceMap(moduleID, prevFile) _ <- outputImpl.writeFull(jsFileName, code, skipContentCheck) } yield { report diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/ByteArrayWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/ByteArrayWriter.scala index 7ac9d914af..3f9b152be9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/ByteArrayWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/ByteArrayWriter.scala @@ -44,6 +44,14 @@ private[backend] final class ByteArrayWriter(originalCapacity: Int) extends Outp size += 1 } + def write(bs: ByteBuffer): Unit = { + val len = bs.remaining() + val newSize = size + len + ensureCapacity(newSize) + bs.get(buffer, size, len) + size = newSize + } + override def write(bs: Array[Byte]): Unit = write(bs, 0, bs.length) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala index 33ec9cc020..df87330f53 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala @@ -12,6 +12,7 @@ package org.scalajs.linker.backend.javascript +import java.nio.ByteBuffer import java.nio.charset.StandardCharsets import scala.annotation.switch @@ -33,7 +34,7 @@ import Trees._ object Printers { private val ReusableIndentArray = Array.fill(128)(' '.toByte) - class JSTreePrinter(protected val out: ByteArrayWriter, initIndent: Int = 0) { + class JSTreePrinter(prevFile: Option[ByteBuffer], protected val out: ByteArrayWriter, initIndent: Int = 0) { private final val IndentStep = 2 private var indentMargin = initIndent * IndentStep @@ -762,8 +763,12 @@ object Printers { print("]") } - protected def print(printedTree: PrintedTree): Unit = - out.write(printedTree.jsCode) + protected def print(printedTree: PrintedTree): Unit = { + val startPos = out.currentSize + out.write(printedTree.jsCode(prevFile)) + val endPos = out.currentSize + printedTree.written(startPos, endPos) + } private def print(exportName: ExportName): Unit = printEscapeJS(exportName.name) @@ -776,9 +781,9 @@ object Printers { out.write(c) } - class JSTreePrinterWithSourceMap(_out: ByteArrayWriter, + class JSTreePrinterWithSourceMap(prevFile: Option[ByteBuffer], _out: ByteArrayWriter, sourceMap: SourceMapWriter.Builder, initIndent: Int) - extends JSTreePrinter(_out, initIndent) { + extends JSTreePrinter(prevFile, _out, initIndent) { private var column = 0 @@ -853,7 +858,7 @@ object Printers { /** A printer that shows `Tree`s for debugging, not for pretty-printing. */ private class JSTreeShowPrinter(_out: ByteArrayWriter, initIndent: Int = 0) - extends JSTreePrinter(_out, initIndent) { + extends JSTreePrinter(None, _out, initIndent) { def printTreeForShow(tree: Tree): Unit = printTree(tree, isStat = true) @@ -862,5 +867,9 @@ object Printers { print(ident.resolver.debugString) print(">") } + + override protected def print(printedTree: PrintedTree): Unit = { + print(printedTree.show) + } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala index 0c0b820e82..2a4ed8fcb4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala @@ -13,8 +13,10 @@ package org.scalajs.linker.backend.javascript import scala.annotation.switch +import scala.concurrent.Future import java.nio.charset.StandardCharsets +import java.nio.ByteBuffer import org.scalajs.ir import org.scalajs.ir.{OriginalName, Position} @@ -548,14 +550,42 @@ object Trees { * A cleaner abstraction would be to have something like ir.Tree.Transient * (for different output formats), but for now, we do not need this. */ - sealed case class PrintedTree(jsCode: Array[Byte], - sourceMapFragment: SourceMapWriter.Fragment) extends Tree { + sealed class PrintedTree private ( + private[this] var state: PrintedTree.State, + val sourceMapFragment: SourceMapWriter.Fragment + ) extends Tree { val pos: Position = Position.NoPosition - override def show: String = new String(jsCode, StandardCharsets.UTF_8) + override def show: String = state match { + case PrintedTree.InMemory(jsCode) => + new String(jsCode, StandardCharsets.UTF_8) + + case PrintedTree.OnDisk(startPos, endPos) => + f"" + } + + def jsCode(prevFile: Option[ByteBuffer]): ByteBuffer = state match { + case PrintedTree.InMemory(jsCode) => + ByteBuffer.wrap(jsCode).asReadOnlyBuffer() + + case PrintedTree.OnDisk(startPos, endPos) => + prevFile.get.slice().position(startPos).limit(endPos) + } + + def written(startPos: Int, endPos: Int): Unit = + this.state = PrintedTree.OnDisk(startPos, endPos) } object PrintedTree { def empty: PrintedTree = PrintedTree(Array(), SourceMapWriter.Fragment.Empty) + + def apply(jsCode: Array[Byte], + sourceMapFragment: SourceMapWriter.Fragment): PrintedTree = { + new PrintedTree(InMemory(jsCode), sourceMapFragment) + } + + sealed trait State + final case class InMemory(jsCode: Array[Byte]) extends State + final case class OnDisk(startPos: Int, endPos: Int) extends State } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala index 2c5de62dd1..2b91a081e2 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/backend/javascript/PrintersTest.scala @@ -32,7 +32,7 @@ class PrintersTest { private def printTree(tree: Tree): String = { val out = new ByteArrayWriter - val printer = new Printers.JSTreePrinter(out) + val printer = new Printers.JSTreePrinter(None, out) printer.printStat(tree) new String(out.toByteArray(), UTF_8) } @@ -224,6 +224,13 @@ class PrintersTest { assertEquals("test", tree.show) } + @Test def showWrittenPrintedTree(): Unit = { + val tree = PrintedTree("test".getBytes(UTF_8), SourceMapWriter.Fragment.Empty) + tree.written(100, 2000) + + assertEquals("", tree.show) + } + @Test def showNestedPrintedTree(): Unit = { val tree = PrintedTree(" test\n".getBytes(UTF_8), SourceMapWriter.Fragment.Empty)