Skip to content

Commit 4370568

Browse files
committed
Tools refactoring
This commit substantially refactors Scala.js' linker. Notably: Linker pipeline --------------- The linker pipeline is divided into a frontend and a backend. The frontend links and optimizes IR files into a `LinkingUnit`. It paves the path for linker plugins in the future. The backend emits a linking unit into a JavaScript file. Currently there are two backends: * BasicLinkerBackend (basic JavaScript generation, aka fastOptJS) * ClosureBackend (optimization pass with the Google Closure compiler, aka fullOptJS) As a side effect of this change, Rhino now runs optimized code (unless this is explicitly disabled). This further has the side effect, that the `PreLinkStage` was entirely removed. Classpath traversal ------------------- Classpath traversal and abstraction is fully pushed to the build system. As a consequence, the whole `org.scalajs.core.tools.classpath` package is removed. Functionally is mostly replaced by a couple of lines of sbt code. This also fixes scala-js/scala-js#1635. IR caching ---------- In the past, each linker cached its own IR. This caused a lot of IR to be in memory multiple times. This commit replaces caching with a global IR cache. There are two main advantages: * IR for each class is only cached once in memory. * Linking other projects or configurations (e.g. compile v.s. test) can reuse the already cached IR. JSEnvs ------ Since classpaths have been removed, we need a way to re-enable lazy loading in Rhino. We therefore introduce the `LinkingUnitJSEnv` that can directly accept a `LinkingUnit`. Further, we enrich the `JSEnv`s with an API to load libraries. This allows for much nicer interfaces in a couple of spots, where you essentially just pass a `JSEnv` and then run your code in it, with all dependencies already present (e.g. FrameworkDetector). There are a couple of advantages: - Symbol requirements are made explicit - No duplicate code for linking in RhinoJSEnv - Rhino can now run with optimized code (and does so by default)
1 parent bcb242d commit 4370568

15 files changed

+318
-98
lines changed

js-envs/src/main/scala/org/scalajs/jsenv/AsyncJSEnv.scala

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,22 @@
99

1010
package org.scalajs.jsenv
1111

12-
import org.scalajs.core.tools.io._
13-
import org.scalajs.core.tools.classpath._
14-
import org.scalajs.core.tools.logging._
12+
import org.scalajs.core.tools.io.VirtualJSFile
13+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
1514

1615
trait AsyncJSEnv extends JSEnv {
17-
def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile,
18-
logger: Logger, console: JSConsole): AsyncJSRunner
16+
def asyncRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile): AsyncJSRunner
17+
18+
final def asyncRunner(code: VirtualJSFile): AsyncJSRunner =
19+
asyncRunner(Nil, code)
20+
21+
override def loadLibs(libs: Seq[ResolvedJSDependency]): AsyncJSEnv =
22+
new AsyncLoadedLibs { val loadedLibs = libs }
23+
24+
private[jsenv] trait AsyncLoadedLibs extends LoadedLibs with AsyncJSEnv {
25+
def asyncRunner(libs: Seq[ResolvedJSDependency],
26+
code: VirtualJSFile): AsyncJSRunner = {
27+
AsyncJSEnv.this.asyncRunner(loadedLibs ++ libs, code)
28+
}
29+
}
1930
}

js-envs/src/main/scala/org/scalajs/jsenv/AsyncJSRunner.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package org.scalajs.jsenv
33
import scala.concurrent.{Future, Await}
44
import scala.concurrent.duration.Duration
55

6+
import org.scalajs.core.tools.logging.Logger
7+
68
trait AsyncJSRunner {
79

810
/** A future that completes when the associated run has terminated. */
@@ -13,7 +15,7 @@ trait AsyncJSRunner {
1315
* when the run terminates. The returned Future is equivalent to
1416
* the one returned by [[future]].
1517
*/
16-
def start(): Future[Unit]
18+
def start(logger: Logger, console: JSConsole): Future[Unit]
1719

1820
/** Aborts the associated run.
1921
*

js-envs/src/main/scala/org/scalajs/jsenv/ComJSEnv.scala

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99

1010
package org.scalajs.jsenv
1111

12-
import org.scalajs.core.tools.io._
13-
import org.scalajs.core.tools.classpath._
14-
import org.scalajs.core.tools.logging._
12+
import org.scalajs.core.tools.io.VirtualJSFile
13+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
1514

1615
/** An [[AsyncJSEnv]] that provides communication to and from the JS VM.
1716
*
@@ -29,8 +28,19 @@ import org.scalajs.core.tools.logging._
2928
* }}}
3029
*/
3130
trait ComJSEnv extends AsyncJSEnv {
32-
def comRunner(classpath: CompleteClasspath, code: VirtualJSFile,
33-
logger: Logger, console: JSConsole): ComJSRunner
31+
def comRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile): ComJSRunner
32+
33+
final def comRunner(code: VirtualJSFile): ComJSRunner = comRunner(Nil, code)
34+
35+
override def loadLibs(libs: Seq[ResolvedJSDependency]): ComJSEnv =
36+
new ComLoadedLibs { val loadedLibs = libs }
37+
38+
private[jsenv] trait ComLoadedLibs extends AsyncLoadedLibs with ComJSEnv {
39+
def comRunner(libs: Seq[ResolvedJSDependency],
40+
code: VirtualJSFile): ComJSRunner = {
41+
ComJSEnv.this.comRunner(loadedLibs ++ libs, code)
42+
}
43+
}
3444
}
3545

3646
object ComJSEnv {

js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package org.scalajs.jsenv
22

33
import org.scalajs.core.tools.io._
4-
import org.scalajs.core.tools.classpath._
5-
import org.scalajs.core.tools.logging._
4+
import org.scalajs.core.tools.logging.Logger
5+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
66

77
import java.io.{ Console => _, _ }
88
import scala.io.Source
@@ -16,6 +16,8 @@ abstract class ExternalJSEnv(
1616

1717
import ExternalJSEnv._
1818

19+
def name: String = s"ExternalJSEnv for $vmName"
20+
1921
/** Printable name of this VM */
2022
protected def vmName: String
2123

@@ -25,9 +27,21 @@ abstract class ExternalJSEnv(
2527
/** Custom initialization scripts. */
2628
protected def customInitFiles(): Seq[VirtualJSFile] = Nil
2729

28-
protected class AbstractExtRunner(protected val classpath: CompleteClasspath,
29-
protected val code: VirtualJSFile, protected val logger: Logger,
30-
protected val console: JSConsole) {
30+
protected class AbstractExtRunner(
31+
protected val libs: Seq[ResolvedJSDependency],
32+
protected val code: VirtualJSFile) {
33+
34+
private[this] var _logger: Logger = _
35+
private[this] var _console: JSConsole = _
36+
37+
protected def logger: Logger = _logger
38+
protected def console: JSConsole = _console
39+
40+
protected def setupLoggerAndConsole(logger: Logger, console: JSConsole) = {
41+
require(_logger == null && _console == null)
42+
_logger = logger
43+
_console = console
44+
}
3145

3246
/** JS files used to setup VM */
3347
protected def initFiles(): Seq[VirtualJSFile] = Nil
@@ -53,7 +67,7 @@ abstract class ExternalJSEnv(
5367

5468
/** Get files that are a library (i.e. that do not run anything) */
5569
protected def getLibJSFiles(): Seq[VirtualJSFile] =
56-
initFiles() ++ customInitFiles() ++ classpath.allCode
70+
initFiles() ++ customInitFiles() ++ libs.map(_.lib)
5771

5872
/** Get all files that are passed to VM (libraries and code) */
5973
protected def getJSFiles(): Seq[VirtualJSFile] =
@@ -133,23 +147,21 @@ abstract class ExternalJSEnv(
133147

134148
}
135149

136-
protected class ExtRunner(classpath: CompleteClasspath, code: VirtualJSFile,
137-
logger: Logger, console: JSConsole
138-
) extends AbstractExtRunner(classpath, code, logger, console)
139-
with JSRunner {
150+
protected class ExtRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile)
151+
extends AbstractExtRunner(libs, code) with JSRunner {
152+
153+
def run(logger: Logger, console: JSConsole): Unit = {
154+
setupLoggerAndConsole(logger, console)
140155

141-
def run(): Unit = {
142156
val vmInst = startVM()
143157

144158
pipeVMData(vmInst)
145159
waitForVM(vmInst)
146160
}
147161
}
148162

149-
protected class AsyncExtRunner(classpath: CompleteClasspath,
150-
code: VirtualJSFile, logger: Logger, console: JSConsole
151-
) extends AbstractExtRunner(classpath, code, logger, console)
152-
with AsyncJSRunner {
163+
protected class AsyncExtRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile)
164+
extends AbstractExtRunner(libs, code) with AsyncJSRunner {
153165

154166
private[this] var vmInst: Process = null
155167
private[this] var ioThreadEx: Throwable = null
@@ -173,11 +185,22 @@ abstract class ExternalJSEnv(
173185

174186
def future: Future[Unit] = promise.future
175187

176-
def start(): Future[Unit] = {
188+
def start(logger: Logger, console: JSConsole): Future[Unit] = {
189+
setupLoggerAndConsole(logger, console)
190+
startExternalJSEnv()
191+
future
192+
}
193+
194+
/** Core functionality of [[start]].
195+
*
196+
* Same as [[start]] but without a call to [[setupLoggerAndConsole]] and
197+
* not returning [[future]].
198+
* Useful to be called in overrides of [[start]].
199+
*/
200+
protected def startExternalJSEnv(): Unit = {
177201
require(vmInst == null, "start() may only be called once")
178202
vmInst = startVM()
179203
thread.start()
180-
future
181204
}
182205

183206
def stop(): Unit = {

js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,42 @@
99

1010
package org.scalajs.jsenv
1111

12-
import org.scalajs.core.tools.io._
13-
import org.scalajs.core.tools.classpath._
14-
import org.scalajs.core.tools.logging._
12+
import org.scalajs.core.tools.io.VirtualJSFile
13+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
1514

1615
trait JSEnv {
16+
/** Human-readable name for this [[JSEnv]] */
17+
def name: String
18+
1719
/** Prepare a runner for the code in the virtual file. */
18-
def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile,
19-
logger: Logger, console: JSConsole): JSRunner
20+
def jsRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile): JSRunner
21+
22+
/** Prepare a runner without any libraries.
23+
*
24+
* Strictly equivalent to:
25+
* {{{
26+
* this.jsRunner(Nil, code)
27+
* }}}
28+
*/
29+
final def jsRunner(code: VirtualJSFile): JSRunner = jsRunner(Nil, code)
30+
31+
/** Return this [[JSEnv]] with the given libraries already loaded.
32+
*
33+
* The following two are equivalent:
34+
* {{{
35+
* jsEnv.loadLibs(a).jsRunner(b, c)
36+
* jsEnv.jsRunner(a ++ b, c)
37+
* }}}
38+
*/
39+
def loadLibs(libs: Seq[ResolvedJSDependency]): JSEnv =
40+
new LoadedLibs { val loadedLibs = libs }
41+
42+
private[jsenv] trait LoadedLibs extends JSEnv {
43+
val loadedLibs: Seq[ResolvedJSDependency]
44+
45+
def name: String = JSEnv.this.name
46+
47+
def jsRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile): JSRunner =
48+
JSEnv.this.jsRunner(loadedLibs ++ libs, code)
49+
}
2050
}

js-envs/src/main/scala/org/scalajs/jsenv/JSRunner.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
package org.scalajs.jsenv
1111

12+
import org.scalajs.core.tools.logging.Logger
13+
1214
trait JSRunner {
1315
/** Run the associated JS code. Throw if an error occurs. */
14-
def run(): Unit
16+
def run(logger: Logger, console: JSConsole): Unit
1517
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* __ *\
2+
** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
3+
** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
4+
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
5+
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
6+
** |/____/ **
7+
\* */
8+
9+
10+
package org.scalajs.jsenv
11+
12+
import org.scalajs.core.tools.io.VirtualJSFile
13+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
14+
import org.scalajs.core.tools.linker.LinkingUnit
15+
16+
trait LinkingUnitAsyncJSEnv extends LinkingUnitJSEnv with AsyncJSEnv {
17+
def asyncRunner(preLibs: Seq[ResolvedJSDependency], linkingUnit: LinkingUnit,
18+
postLibs: Seq[ResolvedJSDependency], code: VirtualJSFile): AsyncJSRunner
19+
20+
override def loadLibs(libs: Seq[ResolvedJSDependency]): LinkingUnitAsyncJSEnv =
21+
new LinkingUnitAsyncLoadedLibs { val loadedLibs = libs }
22+
23+
override def loadLinkingUnit(linkingUnit: LinkingUnit): AsyncJSEnv =
24+
new AsyncLoadedUnit { val loadedUnit = linkingUnit }
25+
26+
private[jsenv] trait LinkingUnitAsyncLoadedLibs extends LinkingUnitLoadedLibs
27+
with AsyncLoadedLibs with LinkingUnitAsyncJSEnv {
28+
def asyncRunner(preLibs: Seq[ResolvedJSDependency], linkingUnit: LinkingUnit,
29+
postLibs: Seq[ResolvedJSDependency],
30+
code: VirtualJSFile): AsyncJSRunner = {
31+
LinkingUnitAsyncJSEnv.this.asyncRunner(loadedLibs ++ preLibs, linkingUnit,
32+
postLibs, code)
33+
}
34+
}
35+
36+
private[jsenv] trait AsyncLoadedUnit extends LoadedUnit with AsyncJSEnv {
37+
def asyncRunner(libs: Seq[ResolvedJSDependency],
38+
code: VirtualJSFile): AsyncJSRunner = {
39+
LinkingUnitAsyncJSEnv.this.asyncRunner(Nil, loadedUnit, libs, code)
40+
}
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* __ *\
2+
** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
3+
** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
4+
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
5+
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
6+
** |/____/ **
7+
\* */
8+
9+
10+
package org.scalajs.jsenv
11+
12+
import org.scalajs.core.tools.io.VirtualJSFile
13+
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
14+
import org.scalajs.core.tools.linker.LinkingUnit
15+
16+
trait LinkingUnitComJSEnv extends LinkingUnitAsyncJSEnv with ComJSEnv {
17+
def comRunner(preLibs: Seq[ResolvedJSDependency], linkingUnit: LinkingUnit,
18+
postLibs: Seq[ResolvedJSDependency], code: VirtualJSFile): ComJSRunner
19+
20+
override def loadLibs(libs: Seq[ResolvedJSDependency]): LinkingUnitComJSEnv =
21+
new LinkingUnitComLoadedLibs { val loadedLibs = libs }
22+
23+
override def loadLinkingUnit(linkingUnit: LinkingUnit): ComJSEnv =
24+
new ComLoadedUnit { val loadedUnit = linkingUnit }
25+
26+
private[jsenv] trait LinkingUnitComLoadedLibs
27+
extends LinkingUnitAsyncLoadedLibs with ComLoadedLibs
28+
with LinkingUnitComJSEnv {
29+
def comRunner(preLibs: Seq[ResolvedJSDependency], linkingUnit: LinkingUnit,
30+
postLibs: Seq[ResolvedJSDependency],
31+
code: VirtualJSFile): ComJSRunner = {
32+
LinkingUnitComJSEnv.this.comRunner(loadedLibs ++ preLibs, linkingUnit,
33+
postLibs, code)
34+
}
35+
}
36+
37+
private[jsenv] trait ComLoadedUnit extends AsyncLoadedUnit with ComJSEnv {
38+
def comRunner(libs: Seq[ResolvedJSDependency],
39+
code: VirtualJSFile): ComJSRunner = {
40+
LinkingUnitComJSEnv.this.comRunner(Nil, loadedUnit, libs, code)
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)