Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize ScalaJSModule and cache IRFileCache #2783

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions scalajslib/src/mill/scalajslib/ScalaJSModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
// we need to use the scala-library of the currently running mill
resolveDependencies(
repositoriesTask(),
(commonDeps ++ envDeps).map(Lib.depToBoundDep(_, mill.main.BuildInfo.scalaVersion, "")),
(commonDeps.iterator ++ envDeps)
.map(Lib.depToBoundDep(_, mill.main.BuildInfo.scalaVersion, "")),
ctx = Some(T.log)
)
}
Expand Down Expand Up @@ -119,7 +120,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
worker = ScalaJSWorkerExternalModule.scalaJSWorker(),
toolsClasspath = scalaJSToolsClasspath(),
runClasspath = runClasspath(),
mainClass = finalMainClassOpt().toOption,
mainClass = finalMainClassOpt(),
forceOutJs = forceOutJs,
testBridgeInit = false,
isFullLinkJS = isFullLinkJS,
Expand Down Expand Up @@ -160,7 +161,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
worker: ScalaJSWorker,
toolsClasspath: Agg[PathRef],
runClasspath: Agg[PathRef],
mainClass: Option[String],
mainClass: Either[String, String],
forceOutJs: Boolean,
testBridgeInit: Boolean,
isFullLinkJS: Boolean,
Expand All @@ -175,16 +176,9 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>

os.makeDir.all(ctx.dest)

val classpath = runClasspath.map(_.path)
val sjsirFiles = classpath
.filter(path => os.exists(path) && os.isDir(path))
.flatMap(os.walk(_))
.filter(_.ext == "sjsir")
val libraries = classpath.filter(_.ext == "jar")
worker.link(
toolsClasspath = toolsClasspath,
sources = sjsirFiles,
libraries = libraries,
runClasspath = runClasspath,
dest = outputPath.toIO,
main = mainClass,
forceOutJs = forceOutJs,
Expand Down Expand Up @@ -279,7 +273,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
scalaVersion = scalaVersion(),
scalaBinaryVersion = ZincWorkerUtil.scalaBinaryVersion(scalaVersion()),
platform = ScalaPlatform.JS,
jars = scalaCompilerClasspath().map(_.path.toNIO.toUri.toString).iterator.toSeq,
jars = scalaCompilerClasspath().iterator.map(_.path.toNIO.toUri.toString).toSeq,
jvmBuildTarget = None
)
))
Expand All @@ -296,8 +290,7 @@ trait TestScalaJSModule extends ScalaJSModule with TestModule {
ivy"org.scala-js::scalajs-library:${scalaJSVersion()}",
ivy"org.scala-js::scalajs-test-bridge:${scalaJSVersion()}"
)
.map(_.withDottyCompat(scalaVersion()))
.map(bind)
.map(dep => bind(dep.withDottyCompat(scalaVersion())))
})
}

Expand All @@ -306,7 +299,7 @@ trait TestScalaJSModule extends ScalaJSModule with TestModule {
worker = ScalaJSWorkerExternalModule.scalaJSWorker(),
toolsClasspath = scalaJSToolsClasspath(),
runClasspath = scalaJSTestDeps() ++ runClasspath(),
mainClass = None,
mainClass = Left("No main class specified or found"),
forceOutJs = false,
testBridgeInit = true,
isFullLinkJS = false,
Expand Down
13 changes: 5 additions & 8 deletions scalajslib/src/mill/scalajslib/worker/ScalaJSWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,9 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {

def link(
toolsClasspath: Agg[mill.PathRef],
sources: Agg[os.Path],
libraries: Agg[os.Path],
runClasspath: Agg[mill.PathRef],
dest: File,
main: Option[String],
main: Either[String, String],
forceOutJs: Boolean,
testBridgeInit: Boolean,
isFullLinkJS: Boolean,
Expand All @@ -164,10 +163,9 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {
outputPatterns: api.OutputPatterns
)(implicit ctx: Ctx.Home): Result[api.Report] = {
bridge(toolsClasspath).link(
sources = sources.items.map(_.toIO).toArray,
libraries = libraries.items.map(_.toIO).toArray,
runClasspath = runClasspath.iterator.map(_.path.toNIO).toSeq,
dest = dest,
main = main.orNull,
main = main,
forceOutJs = forceOutJs,
testBridgeInit = testBridgeInit,
isFullLinkJS = isFullLinkJS,
Expand All @@ -186,8 +184,7 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {
def run(toolsClasspath: Agg[mill.PathRef], config: api.JsEnvConfig, report: api.Report)(
implicit ctx: Ctx.Home
): Unit = {
val dest =
bridge(toolsClasspath).run(toWorkerApi(config), toWorkerApi(report))
bridge(toolsClasspath).run(toWorkerApi(config), toWorkerApi(report))
}

def getFramework(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package mill.scalajslib.worker.api

import java.io.File
import java.nio.file.Path

private[scalajslib] trait ScalaJSWorkerApi {
def link(
sources: Array[File],
libraries: Array[File],
runClasspath: Seq[Path],
dest: File,
main: String,
main: Either[String, String],
forceOutJs: Boolean,
testBridgeInit: Boolean,
isFullLinkJS: Boolean,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package mill.scalajslib.worker

import scala.concurrent.{Await, Future}
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import java.io.File
import java.nio.file.Path
import mill.scalajslib.worker.api._
import mill.scalajslib.worker.jsenv._
import org.scalajs.ir.ScalaJSVersions
import org.scalajs.linker.{
PathIRContainer,
PathIRFile,
PathOutputDirectory,
PathOutputFile,
StandardImpl
}
import org.scalajs.linker.{PathIRContainer, PathOutputDirectory, PathOutputFile, StandardImpl}
import org.scalajs.linker.interface.{
ESFeatures => ScalaJSESFeatures,
ESVersion => ScalaJSESVersion,
ModuleKind => ScalaJSModuleKind,
OutputPatterns => ScalaJSOutputPatterns,
Report => ScalaJSReport,
Report => _,
ModuleSplitStyle => _,
_
}
Expand Down Expand Up @@ -46,15 +41,16 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
case _ => true
}
private object ScalaJSLinker {
private val cache = mutable.Map.empty[LinkerInput, SoftReference[Linker]]
def reuseOrCreate(input: LinkerInput): Linker = cache.get(input) match {
case Some(SoftReference(linker)) => linker
private val irFileCache = StandardImpl.irFileCache()
private val cache = mutable.Map.empty[LinkerInput, SoftReference[(Linker, IRFileCache.Cache)]]
def reuseOrCreate(input: LinkerInput): (Linker, IRFileCache.Cache) = cache.get(input) match {
case Some(SoftReference((linker, irFileCacheCache))) => (linker, irFileCacheCache)
case _ =>
val newLinker = createLinker(input)
cache.update(input, SoftReference(newLinker))
newLinker
val newResult = createLinker(input)
cache.update(input, SoftReference(newResult))
newResult
}
private def createLinker(input: LinkerInput): Linker = {
private def createLinker(input: LinkerInput): (Linker, IRFileCache.Cache) = {
val semantics = input.isFullLinkJS match {
case true => Semantics.Defaults.optimized
case false => Semantics.Defaults
Expand Down Expand Up @@ -101,7 +97,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
else withESVersion_1_5_minus(scalaJSESFeatures)

val useClosure = input.isFullLinkJS && input.moduleKind != ModuleKind.ESModule
var partialConfig = StandardConfig()
val partialConfig = StandardConfig()
.withOptimizer(input.optimizer)
.withClosureCompilerIfAvailable(useClosure)
.withSemantics(semantics)
Expand Down Expand Up @@ -150,14 +146,15 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
)
else withModuleSplitStyle

StandardImpl.clearableLinker(withOutputPatterns)
val linker = StandardImpl.clearableLinker(withOutputPatterns)
val irFileCacheCache = irFileCache.newCache
(linker, irFileCacheCache)
}
}
def link(
sources: Array[File],
libraries: Array[File],
runClasspath: Seq[Path],
dest: File,
main: String,
main: Either[String, String],
forceOutJs: Boolean,
testBridgeInit: Boolean,
isFullLinkJS: Boolean,
Expand All @@ -172,7 +169,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
// the new mode is not supported and in tests we always use legacy = false
val useLegacy = forceOutJs || !minorIsGreaterThanOrEqual(3)
import scala.concurrent.ExecutionContext.Implicits.global
val linker = ScalaJSLinker.reuseOrCreate(LinkerInput(
val (linker, irFileCacheCache) = ScalaJSLinker.reuseOrCreate(LinkerInput(
isFullLinkJS = isFullLinkJS,
optimizer = optimizer,
sourceMap = sourceMap,
Expand All @@ -182,23 +179,23 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
outputPatterns = outputPatterns,
dest = dest
))
val cache = StandardImpl.irFileCache().newCache
val sourceIRsFuture = Future.sequence(sources.toSeq.map(f => PathIRFile(f.toPath())))
val irContainersPairs = PathIRContainer.fromClasspath(libraries.toIndexedSeq.map(_.toPath()))
val libraryIRsFuture = irContainersPairs.flatMap(pair => cache.cached(pair._1))
val irContainersAndPathsFuture = PathIRContainer.fromClasspath(runClasspath)
val logger = new ScalaConsoleLogger
val mainInitializer = Option(main).map { cls =>
ModuleInitializer.mainMethodWithArgs(cls, "main")
}
val testInitializer =
if (testBridgeInit)
Some(ModuleInitializer.mainMethod(TAI.ModuleClassName, TAI.MainMethodName))
else None
val moduleInitializers = mainInitializer.toList ::: testInitializer.toList
ModuleInitializer.mainMethod(TAI.ModuleClassName, TAI.MainMethodName) :: Nil
else Nil
val moduleInitializers = main match {
case Right(main) =>
ModuleInitializer.mainMethodWithArgs(main, "main") ::
testInitializer
case _ =>
testInitializer
}

val resultFuture = (for {
sourceIRs <- sourceIRsFuture
libraryIRs <- libraryIRsFuture
(irContainers, _) <- irContainersAndPathsFuture
irFiles <- irFileCacheCache.cached(irContainers)
report <-
if (useLegacy) {
val jsFileName = "out.js"
Expand All @@ -212,7 +209,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
.withSourceMap(PathOutputFile(sourceMapFile))
.withSourceMapURI(java.net.URI.create(sourceMapFile.getFileName.toString))
}
linker.link(sourceIRs ++ libraryIRs, moduleInitializers, linkerOutput, logger).map {
linker.link(irFiles, moduleInitializers, linkerOutput, logger).map {
file =>
Report(
publicModules = Seq(Report.Module(
Expand All @@ -227,7 +224,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
} else {
val linkerOutput = PathOutputDirectory(dest.toPath())
linker.link(
sourceIRs ++ libraryIRs,
irFiles,
moduleInitializers,
linkerOutput,
logger
Expand Down