Skip to content

Commit 76e3292

Browse files
authored
Merge pull request #4988 from sjrd/webassembly
Initial implementation of the WebAssembly backend.
2 parents 53da391 + e89e1e4 commit 76e3292

File tree

49 files changed

+13299
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+13299
-38
lines changed

Jenkinsfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,41 @@ def Tasks = [
396396
++$scala $testSuite$v/test
397397
''',
398398

399+
"test-suite-webassembly": '''
400+
setJavaVersion $java
401+
npm install &&
402+
sbtretry ++$scala \
403+
'set Global/enableWasmEverywhere := true' \
404+
helloworld$v/run &&
405+
sbtretry ++$scala \
406+
'set Global/enableWasmEverywhere := true' \
407+
'set scalaJSStage in Global := FullOptStage' \
408+
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withPrettyPrint(true))' \
409+
helloworld$v/run &&
410+
sbtretry ++$scala \
411+
'set Global/enableWasmEverywhere := true' \
412+
reversi$v/fastLinkJS \
413+
reversi$v/fullLinkJS &&
414+
sbtretry ++$scala \
415+
'set Global/enableWasmEverywhere := true' \
416+
jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \
417+
'set scalaJSStage in Global := FullOptStage' jUnitTestOutputsJS$v/test testBridge$v/test &&
418+
sbtretry ++$scala \
419+
'set Global/enableWasmEverywhere := true' \
420+
$testSuite$v/test &&
421+
sbtretry ++$scala \
422+
'set Global/enableWasmEverywhere := true' \
423+
'set scalaJSStage in Global := FullOptStage' \
424+
$testSuite$v/test &&
425+
sbtretry ++$scala \
426+
'set Global/enableWasmEverywhere := true' \
427+
testingExample$v/testHtml &&
428+
sbtretry ++$scala \
429+
'set Global/enableWasmEverywhere := true' \
430+
'set scalaJSStage in Global := FullOptStage' \
431+
testingExample$v/testHtml
432+
''',
433+
399434
/* For the bootstrap tests to be able to call
400435
* `testSuite/test:fastOptJS`, `scalaJSStage in testSuite` must be
401436
* `FastOptStage`, even when `scalaJSStage in Global` is `FullOptStage`.
@@ -539,8 +574,11 @@ mainScalaVersions.each { scalaVersion ->
539574
quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuite"])
540575
quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "true", testSuite: "testSuite"])
541576
quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "testSuite"])
577+
quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuite"])
578+
quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuiteEx"])
542579
quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "scalaTestSuite"])
543580
quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "scalaTestSuite"])
581+
quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "scalaTestSuite"])
544582
quickMatrix.add([task: "bootstrap", scala: scalaVersion, java: mainJavaVersion])
545583
quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion])
546584
}

TESTING.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ $ python3 -m http.server
2525
// Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html
2626
```
2727

28+
## HTML-Test Runner with WebAssembly
29+
30+
WebAssembly requires modules, so this is manual as well.
31+
32+
This test currently requires Chrome (or another V8-based browser) with `--wasm-experimental-exnref` enabled.
33+
That option can be configured as "Experimental WebAssembly" at [chrome://flags/#enable-experimental-webassembly-features](chrome://flags/#enable-experimental-webassembly-features).
34+
35+
```
36+
$ sbt
37+
> set Global/enableWasmEverywhere := true
38+
> testingExample2_12/testHtml
39+
> testSuite2_12/testHtml
40+
> exit
41+
$ python3 -m http.server
42+
43+
// Open http://localhost:8000/examples/testing/.2.12/target/scala-2.12/testing-fastopt-test-html/index.html
44+
// Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html
45+
```
46+
2847
## Sourcemaps
2948

3049
To test source maps, do the following on:

ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap
1717
import scala.util.matching.Regex
1818

1919
object ScalaJSVersions extends VersionChecks(
20-
current = "1.16.1-SNAPSHOT",
20+
current = "1.17.0-SNAPSHOT",
2121
binaryEmitted = "1.16"
2222
)
2323

ir/shared/src/main/scala/org/scalajs/ir/UTF8String.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
package org.scalajs.ir
1414

15-
import java.nio.CharBuffer
15+
import java.nio.{ByteBuffer, CharBuffer}
1616
import java.nio.charset.CharacterCodingException
1717
import java.nio.charset.CodingErrorAction
1818
import java.nio.charset.StandardCharsets.UTF_8
@@ -48,6 +48,9 @@ final class UTF8String private (private[ir] val bytes: Array[Byte])
4848
System.arraycopy(that.bytes, 0, result, thisLen, thatLen)
4949
new UTF8String(result)
5050
}
51+
52+
def writeTo(buffer: ByteBuffer): Unit =
53+
buffer.put(bytes)
5154
}
5255

5356
object UTF8String {

javalib/src/main/scala/java/lang/StackTrace.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ private[lang] object StackTrace {
6161
* prototypes.
6262
*/
6363
reference
64-
} else if (js.constructorOf[js.Error].captureStackTrace eq ().asInstanceOf[AnyRef]) {
65-
// Create a JS Error with the current stack trace.
64+
} else if ((js.constructorOf[js.Error].captureStackTrace eq ().asInstanceOf[AnyRef]) ||
65+
js.Object.isSealed(throwable.asInstanceOf[js.Object])) {
66+
/* If `captureStackTrace` is not available, or if the `throwable` instance
67+
* is sealed (which notably happens on Wasm), create a JS `Error` with the
68+
* current stack trace.
69+
*/
6670
new js.Error()
6771
} else {
6872
/* V8-specific.

linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ final class StandardConfig private (
6363
* On the JavaScript platform, this does not have any effect.
6464
*/
6565
val closureCompilerIfAvailable: Boolean,
66-
/** Pretty-print the output. */
66+
/** Pretty-print the output, for debugging purposes.
67+
*
68+
* For the WebAssembly backend, this results in an additional `.wat` file
69+
* next to each produced `.wasm` file with the WebAssembly text format
70+
* representation of the latter. This file is never subsequently used,
71+
* but may be inspected for debugging pruposes.
72+
*/
6773
val prettyPrint: Boolean,
6874
/** Whether the linker should run in batch mode.
6975
*
@@ -78,7 +84,9 @@ final class StandardConfig private (
7884
*/
7985
val batchMode: Boolean,
8086
/** The maximum number of (file) writes executed concurrently. */
81-
val maxConcurrentWrites: Int
87+
val maxConcurrentWrites: Int,
88+
/** If true, use the experimental WebAssembly backend. */
89+
val experimentalUseWebAssembly: Boolean
8290
) {
8391
private def this() = {
8492
this(
@@ -97,7 +105,8 @@ final class StandardConfig private (
97105
closureCompilerIfAvailable = false,
98106
prettyPrint = false,
99107
batchMode = false,
100-
maxConcurrentWrites = 50
108+
maxConcurrentWrites = 50,
109+
experimentalUseWebAssembly = false
101110
)
102111
}
103112

@@ -177,6 +186,40 @@ final class StandardConfig private (
177186
def withMaxConcurrentWrites(maxConcurrentWrites: Int): StandardConfig =
178187
copy(maxConcurrentWrites = maxConcurrentWrites)
179188

189+
/** Specifies whether to use the experimental WebAssembly backend.
190+
*
191+
* When using this setting, the following settings must also be set:
192+
*
193+
* - `withSemantics(sems)` such that the behaviors of `sems` are all set to
194+
* `CheckedBehavior.Unchecked`
195+
* - `withModuleKind(ModuleKind.ESModule)`
196+
* - `withOptimizer(false)`
197+
* - `withStrictFloats(true)` (this is the default)
198+
*
199+
* These restrictions will be lifted in the future, except for the
200+
* `ModuleKind`.
201+
*
202+
* If any of these restrictions are not met, linking will eventually throw
203+
* an `IllegalArgumentException`.
204+
*
205+
* @note
206+
* Currently, the WebAssembly backend silently ignores `@JSExport` and
207+
* `@JSExportAll` annotations. This behavior may change in the future,
208+
* either by making them warnings or errors, or by adding support for them.
209+
* All other language features are supported.
210+
*
211+
* @note
212+
* This setting is experimental. It may be removed in an upcoming *minor*
213+
* version of Scala.js. Future minor versions may also produce code that
214+
* requires more recent versions of JS engines supporting newer WebAssembly
215+
* standards.
216+
*
217+
* @throws java.lang.UnsupportedOperationException
218+
* In the future, if the feature gets removed.
219+
*/
220+
def withExperimentalUseWebAssembly(experimentalUseWebAssembly: Boolean): StandardConfig =
221+
copy(experimentalUseWebAssembly = experimentalUseWebAssembly)
222+
180223
override def toString(): String = {
181224
s"""StandardConfig(
182225
| semantics = $semantics,
@@ -195,6 +238,7 @@ final class StandardConfig private (
195238
| prettyPrint = $prettyPrint,
196239
| batchMode = $batchMode,
197240
| maxConcurrentWrites = $maxConcurrentWrites,
241+
| experimentalUseWebAssembly = $experimentalUseWebAssembly,
198242
|)""".stripMargin
199243
}
200244

@@ -214,7 +258,8 @@ final class StandardConfig private (
214258
closureCompilerIfAvailable: Boolean = closureCompilerIfAvailable,
215259
prettyPrint: Boolean = prettyPrint,
216260
batchMode: Boolean = batchMode,
217-
maxConcurrentWrites: Int = maxConcurrentWrites
261+
maxConcurrentWrites: Int = maxConcurrentWrites,
262+
experimentalUseWebAssembly: Boolean = experimentalUseWebAssembly
218263
): StandardConfig = {
219264
new StandardConfig(
220265
semantics,
@@ -232,7 +277,8 @@ final class StandardConfig private (
232277
closureCompilerIfAvailable,
233278
prettyPrint,
234279
batchMode,
235-
maxConcurrentWrites
280+
maxConcurrentWrites,
281+
experimentalUseWebAssembly
236282
)
237283
}
238284
}
@@ -263,6 +309,7 @@ object StandardConfig {
263309
.addField("prettyPrint", config.prettyPrint)
264310
.addField("batchMode", config.batchMode)
265311
.addField("maxConcurrentWrites", config.maxConcurrentWrites)
312+
.addField("experimentalUseWebAssembly", config.experimentalUseWebAssembly)
266313
.build()
267314
}
268315
}
@@ -290,6 +337,7 @@ object StandardConfig {
290337
* - `prettyPrint`: `false`
291338
* - `batchMode`: `false`
292339
* - `maxConcurrentWrites`: `50`
340+
* - `experimentalUseWebAssembly`: `false`
293341
*/
294342
def apply(): StandardConfig = new StandardConfig()
295343

linker/js/src/main/scala/org/scalajs/linker/backend/LinkerBackendImplPlatform.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ package org.scalajs.linker.backend
1515
private[backend] object LinkerBackendImplPlatform {
1616
import LinkerBackendImpl.Config
1717

18-
def createLinkerBackend(config: Config): LinkerBackendImpl =
18+
def createJSLinkerBackend(config: Config): LinkerBackendImpl =
1919
new BasicLinkerBackend(config)
2020
}

linker/jvm/src/main/scala/org/scalajs/linker/backend/LinkerBackendImplPlatform.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import org.scalajs.linker.backend.closure.ClosureLinkerBackend
1717
private[backend] object LinkerBackendImplPlatform {
1818
import LinkerBackendImpl.Config
1919

20-
def createLinkerBackend(config: Config): LinkerBackendImpl = {
20+
def createJSLinkerBackend(config: Config): LinkerBackendImpl = {
2121
if (config.closureCompiler)
2222
new ClosureLinkerBackend(config)
2323
else

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ abstract class LinkerBackendImpl(
3838
}
3939

4040
object LinkerBackendImpl {
41-
def apply(config: Config): LinkerBackendImpl =
42-
LinkerBackendImplPlatform.createLinkerBackend(config)
41+
def apply(config: Config): LinkerBackendImpl = {
42+
if (config.experimentalUseWebAssembly)
43+
new WebAssemblyLinkerBackend(config)
44+
else
45+
LinkerBackendImplPlatform.createJSLinkerBackend(config)
46+
}
4347

4448
/** Configurations relevant to the backend */
4549
final class Config private (
@@ -62,7 +66,9 @@ object LinkerBackendImpl {
6266
/** Pretty-print the output. */
6367
val prettyPrint: Boolean,
6468
/** The maximum number of (file) writes executed concurrently. */
65-
val maxConcurrentWrites: Int
69+
val maxConcurrentWrites: Int,
70+
/** If true, use the experimental WebAssembly backend. */
71+
val experimentalUseWebAssembly: Boolean
6672
) {
6773
private def this() = {
6874
this(
@@ -74,7 +80,9 @@ object LinkerBackendImpl {
7480
minify = false,
7581
closureCompilerIfAvailable = false,
7682
prettyPrint = false,
77-
maxConcurrentWrites = 50)
83+
maxConcurrentWrites = 50,
84+
experimentalUseWebAssembly = false
85+
)
7886
}
7987

8088
def withCommonConfig(commonConfig: CommonPhaseConfig): Config =
@@ -106,6 +114,9 @@ object LinkerBackendImpl {
106114
def withMaxConcurrentWrites(maxConcurrentWrites: Int): Config =
107115
copy(maxConcurrentWrites = maxConcurrentWrites)
108116

117+
def withExperimentalUseWebAssembly(experimentalUseWebAssembly: Boolean): Config =
118+
copy(experimentalUseWebAssembly = experimentalUseWebAssembly)
119+
109120
private def copy(
110121
commonConfig: CommonPhaseConfig = commonConfig,
111122
jsHeader: String = jsHeader,
@@ -115,7 +126,9 @@ object LinkerBackendImpl {
115126
minify: Boolean = minify,
116127
closureCompilerIfAvailable: Boolean = closureCompilerIfAvailable,
117128
prettyPrint: Boolean = prettyPrint,
118-
maxConcurrentWrites: Int = maxConcurrentWrites): Config = {
129+
maxConcurrentWrites: Int = maxConcurrentWrites,
130+
experimentalUseWebAssembly: Boolean = experimentalUseWebAssembly
131+
): Config = {
119132
new Config(
120133
commonConfig,
121134
jsHeader,
@@ -125,7 +138,8 @@ object LinkerBackendImpl {
125138
minify,
126139
closureCompilerIfAvailable,
127140
prettyPrint,
128-
maxConcurrentWrites
141+
maxConcurrentWrites,
142+
experimentalUseWebAssembly
129143
)
130144
}
131145
}

0 commit comments

Comments
 (0)