├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .sbtopts ├── .scalafmt.conf ├── README.md ├── build.sbt ├── package-lock.json ├── package.json ├── project ├── TestSuiteLinkerOptions.scala ├── WasmLinkerPlugin.scala ├── build.properties └── plugins.sbt ├── run.mjs ├── sample └── src │ ├── main │ └── scala │ │ └── Sample.scala │ └── test │ └── scala │ └── sample │ └── SampleTest.scala ├── scalajs-test-suite └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalajs │ │ └── testsuite │ │ └── utils │ │ └── BuildInfo.scala │ └── test │ └── scala │ └── org │ └── scalajs │ └── testsuite │ └── jsinterop │ ├── TopLevelExportsTest.scala │ └── WasmExportLoopback.scala ├── test-suite └── src │ └── main │ └── scala │ └── testsuite │ ├── Assert.scala │ └── core │ ├── Add.scala │ ├── ArrayReflectTest.scala │ ├── ArrayTest.scala │ ├── AsInstanceOfTest.scala │ ├── BasicListTest.scala │ ├── ClassOfTest.scala │ ├── CloneTest.scala │ ├── ClosureTest.scala │ ├── ControlStructuresTest.scala │ ├── FieldsTest.scala │ ├── FloatingPointRemTest.scala │ ├── GetClassTest.scala │ ├── HijackedClassesDispatchTest.scala │ ├── HijackedClassesMonoTest.scala │ ├── HijackedClassesUpcastTest.scala │ ├── ImportTest.scala │ ├── InterfaceCall.scala │ ├── IsInstanceOfTest.scala │ ├── JLObjectInstanceTest.scala │ ├── JSExportTopLevelTest.scala │ ├── JSForInTest.scala │ ├── JSImportCallTest.scala │ ├── JSImportMetaTest.scala │ ├── JSInteropTest.scala │ ├── MatchTest.scala │ ├── NonNativeJSClassTest.scala │ ├── Simple.scala │ ├── StaticFieldsTest.scala │ ├── StaticMethodTest.scala │ ├── StringEncodingTest.scala │ ├── ThrowAndTryTest.scala │ ├── ThrowablesTest.scala │ ├── ToStringTest.scala │ ├── TryFinallyReturnTest.scala │ ├── UnitPatMatTest.scala │ ├── VirtualDispatch.scala │ └── WrapUnwrapThrowableTest.scala ├── testrun.html ├── tests └── src │ └── test │ └── scala │ └── tests │ ├── CoreTests.scala │ └── TestSuites.scala └── wasm └── src └── main └── scala └── org └── scalajs └── linker ├── backend ├── WebAssemblyLinkerBackend.scala ├── wasmemitter │ ├── ClassEmitter.scala │ ├── CoreWasmLib.scala │ ├── EmbeddedConstants.scala │ ├── Emitter.scala │ ├── FunctionEmitter.scala │ ├── LibraryPatches.scala │ ├── LoaderContent.scala │ ├── Preprocessor.scala │ ├── SWasmGen.scala │ ├── SpecialNames.scala │ ├── TypeTransformer.scala │ ├── VarGen.scala │ └── WasmContext.scala └── webassembly │ ├── BinaryWriter.scala │ ├── FunctionBuilder.scala │ ├── Identitities.scala │ ├── Instructions.scala │ ├── ModuleBuilder.scala │ ├── Modules.scala │ ├── TextWriter.scala │ └── Types.scala └── standard ├── WebAssemblyLinkerImpl.scala └── WebAssemblyStandardLinkerImpl.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: '21' 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: '22' 23 | - name: npm install 24 | run: npm install 25 | - name: Tests 26 | run: sbt tests/test 27 | - name: Run the Sample 28 | run: sbt sample/run 29 | - name: Test the Sample 30 | run: sbt sample/test 31 | 32 | # Scala.js test suite - separated in multiple steps to see at a glance where things fail 33 | - name: Fetch Scala.js sources for the test suite 34 | run: sbt scalajs-test-suite/fetchScalaJSSource 35 | - name: Compile the Scala.js test suite 36 | run: sbt scalajs-test-suite/Test/compile 37 | - name: Link the Scala.js test suite 38 | run: sbt scalajs-test-suite/Test/fastLinkJS 39 | - name: Run the Scala.js test suite 40 | run: sbt scalajs-test-suite/test 41 | - name: Link and run the Scala.js test suite with fullLinkJS 42 | run: sbt 'set Global/scalaJSStage := FullOptStage' scalajs-test-suite/test 43 | 44 | # Make sure we can emit the .wat file without crashing 45 | - name: Link the Scala.js test suite with PrettyPrint 46 | run: sbt 'set `scalajs-test-suite`/scalaJSLinkerConfig ~= { _.withPrettyPrint(true) }' scalajs-test-suite/Test/fastLinkJS 47 | 48 | - name: Format 49 | run: sbt scalafmtCheckAll 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | project/local-plugins.sbt 12 | .history 13 | .ensime 14 | .ensime_cache/ 15 | .sbt-scripted/ 16 | local.sbt 17 | 18 | # Bloop 19 | .bsp 20 | 21 | # VS Code 22 | .vscode/ 23 | 24 | # Metals 25 | .bloop/ 26 | .metals/ 27 | metals.sbt 28 | 29 | # IDEA 30 | .idea 31 | .idea_modules 32 | /.worksheet/ 33 | 34 | node_modules 35 | 36 | /scalajs-test-suite/fetched-sources/ 37 | -------------------------------------------------------------------------------- /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xmx2G 2 | -J-Xms512M 3 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.7.15" 2 | runner.dialect = scala213 3 | maxColumn = 100 4 | literals.hexDigits = "Upper" 5 | 6 | project.excludeFilters = [ 7 | // to avoid "invalid unicode surrogate pair" 8 | "/test-suite/src/main/scala/testsuite/core/StringEncodingTest.scala" 9 | 10 | // imported sources 11 | "/scalajs-test-suite/fetched-sources/.*" 12 | ] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Experimental Scala.js linker backend for WasmGC 2 | 3 | [![CI](https://github.com/tanishiking/scala-wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/tanishiking/scala-wasm/actions/workflows/ci.yml) 4 | 5 | ### Prerequisites 6 | 7 | This project requires Node.js >= 22 to get enough support of WasmGC. 8 | 9 | If you are using NVM, you can instal Node.js 22 as follows: 10 | 11 | ```sh 12 | # Install Node.js v22 13 | $ nvm install 22 14 | # Switch to Node.js 22 15 | $ nvm use 22 16 | ``` 17 | 18 | ### Setup 19 | 20 | Before doing anything else, run `npm install`. 21 | 22 | ### Run the sample 23 | 24 | In `sample/src/main/scala/Sample.scala` you can find a sandbox to play around. 25 | 26 | You can build and run it like any other Scala.js project from sbt: 27 | 28 | - `sample/fastLinkJS` compiles and links the project with the WebAssembly backend. 29 | - `sample/run` runs the sample linked to WebAssembly with `node`. 30 | 31 | You may want to look at the output in `sample/target/scala-2.12/sample-fastopt/` to convince yourself that it was compiled to WebAssembly. 32 | 33 | In that directory, you will also find a `main.wat` file, which is not used for execution. 34 | It contains the WebAsembly Text Format representation of `main.wasm`, for exploratory and debugging purposes. 35 | This is only true by default for the `sample`. 36 | 37 | :warning: If you modify the linker code, you need to `reload` and `sample/clean` for your changes to take effect on the sample. 38 | 39 | You can also use the `run.mjs` script to play with `@JSExportTopLevel` exports. 40 | 41 | - Run from the command line with `node --experimental-wasm-exnref run.mjs`. 42 | - Run from the command line with `DENO_V8_FLAGS=--experimental-wasm-exnref deno run --allow-read run.mjs`. 43 | - Run from the browser by starting an HTTP server (e.g., `python -m http.server`) and navigate to `testrun.html`. 44 | 45 | If you encounter the `Invalid opcode 0x1f` error with Node.js, you need to use a Node.js >= 22. 46 | 47 | ### Unit test suite 48 | 49 | Run the unit test suite with `tests/test`. 50 | 51 | - `tests/test` will 52 | - Link every test suite from `testSuite` with the WebAssembly backend 53 | - Run the produced WebAssembly module and check for any uncaught error 54 | - Each Scala program in `test-suite` should have a `def main(): Unit` function. The test passes if the function successfully executes without throwing. 55 | - When you add a test, 56 | - Add a file under `test-suite` 57 | - Add a test case to `tests/src/test/scala/tests/TestSuites.scala` 58 | 59 | By default, `.wat` files are not generated, as they are quite big (several hundreds of KB for most of the tests). 60 | You can enable them by adding `withPrettyPrint(true)` to the linker configuration in `tests/src/test/scala/tests/CoreTests.scala`. 61 | 62 | ### Scala.js integration test suite 63 | 64 | Run the entire Scala.js test suite, linked with the WebAssembly backend, with: 65 | 66 | ``` 67 | > scalajs-test-suite/test 68 | ``` 69 | 70 | When you modify the linker, you need to `reload` and `scalajs-test-suite/clean` for your changes to take effect. 71 | Since recompiling the test suite from scratch every time is slow, you can replace the `clean` by manually removing only the linker output with: 72 | 73 | ``` 74 | $ rm -r scalajs-test-suite/target/scala-2.12/scalajs-test-suite-test-fastopt/ 75 | ``` 76 | 77 | By default, `.wat` files are not generated for the Scala.js test suite, as they are very big (they exceed 100 MB). 78 | It is usually easier to minimize an issue in `sample/test`, but if you really want the big `.wat` file for the Scala.js test suite, you can enable it with 79 | 80 | ```scala 81 | > set `scalajs-test-suite`/scalaJSLinkerConfig ~= { _.withPrettyPrint(true) } 82 | ``` 83 | 84 | ### Debugging tools 85 | 86 | - The WasmGC reference interpreter can be used to validate and convert between the binary and text form: 87 | - https://github.com/WebAssembly/gc/tree/main/interpreter 88 | - Use docker image for it https://github.com/tanishiking/wasmgc-docker 89 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import org.scalajs.jsenv.nodejs.NodeJSEnv 2 | 3 | import org.scalajs.linker.interface.ESVersion 4 | import org.scalajs.linker.interface.OutputPatterns 5 | 6 | val scalaV = "2.12.19" 7 | 8 | val fetchScalaJSSource = taskKey[File]("Fetches the source code of Scala.js") 9 | 10 | val writePackageJSON = taskKey[Unit]( 11 | "Write package.json to configure module type for Node.js" 12 | ) 13 | 14 | // Include wasm.jvm on the classpath used to dynamically load Scala.js linkers 15 | Global / scalaJSLinkerImpl / fullClasspath := 16 | (wasm.jvm / Compile / fullClasspath).value 17 | 18 | inThisBuild( 19 | Def.settings( 20 | scalacOptions ++= Seq( 21 | "-encoding", 22 | "utf-8", 23 | "-feature", 24 | "-deprecation", 25 | "-Xfatal-warnings" 26 | ), 27 | scalaJSLinkerConfig ~= { 28 | _.withESFeatures(_.withESVersion(ESVersion.ES2016)) 29 | }, 30 | jsEnv := { 31 | // Enable support for exnref and try_table 32 | new NodeJSEnv( 33 | NodeJSEnv.Config().withArgs(List("--enable-source-maps", "--experimental-wasm-exnref")) 34 | ) 35 | } 36 | ) 37 | ) 38 | 39 | lazy val wasm = crossProject(JVMPlatform, JSPlatform) 40 | .crossType(CrossType.Pure) 41 | .in(file("wasm")) 42 | .settings( 43 | name := "wasm", 44 | version := "0.1.0-SNAPSHOT", 45 | scalaVersion := scalaV, 46 | libraryDependencies ++= Seq( 47 | "org.scala-js" %%% "scalajs-linker" % "1.16.0" 48 | ) 49 | ) 50 | 51 | lazy val sample = project 52 | .in(file("sample")) 53 | .enablePlugins(WasmLinkerPlugin, ScalaJSJUnitPlugin) 54 | .settings( 55 | scalaVersion := scalaV, 56 | scalaJSUseMainModuleInitializer := true, 57 | // Emit .wat files for exploratory and debugging purposes 58 | scalaJSLinkerConfig ~= { _.withPrettyPrint(true) }, 59 | ) 60 | 61 | lazy val testSuite = project 62 | .in(file("test-suite")) 63 | .enablePlugins(ScalaJSPlugin) 64 | .settings( 65 | scalaVersion := scalaV, 66 | scalaJSUseMainModuleInitializer := true, 67 | scalacOptions -= "-Xfatal-warnings", // for unpaired surrogate code units in StringEncodingTest.scala 68 | ) 69 | 70 | lazy val tests = project 71 | .in(file("tests")) 72 | .enablePlugins(ScalaJSPlugin) 73 | .settings( 74 | scalaVersion := scalaV, 75 | libraryDependencies ++= Seq( 76 | "org.scalameta" %%% "munit" % "0.7.29" % Test, 77 | "org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1" % Test 78 | ), 79 | scalaJSLinkerConfig ~= { 80 | // Generate CoreTests as an ES module so that it can import the main.mjs files 81 | // Give it an `.mjs` extension so that Node.js actually interprets it as an ES module 82 | _.withModuleKind(ModuleKind.ESModule) 83 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")), 84 | }, 85 | envVars += { 86 | val testSuiteClasspath = Attributed.data((testSuite / Compile / fullClasspath).value) 87 | "SCALAJS_CLASSPATH" -> testSuiteClasspath.mkString(";") 88 | }, 89 | ) 90 | .dependsOn(wasm.js) 91 | 92 | lazy val `scalajs-test-suite` = project 93 | .in(file("scalajs-test-suite")) 94 | .enablePlugins(WasmLinkerPlugin, ScalaJSJUnitPlugin) 95 | .settings( 96 | fetchScalaJSSource / artifactPath := 97 | baseDirectory.value / "fetched-sources" / scalaJSVersion, 98 | 99 | fetchScalaJSSource := { 100 | import org.eclipse.jgit.api._ 101 | 102 | val s = streams.value 103 | val ver = scalaJSVersion 104 | val trgDir = (fetchScalaJSSource / artifactPath).value 105 | 106 | if (!trgDir.exists) { 107 | s.log.info(s"Fetching Scala.js source version $ver") 108 | 109 | // Make parent dirs 110 | IO.createDirectory(trgDir) 111 | 112 | // Clone Scala.js source code 113 | new CloneCommand() 114 | .setDirectory(trgDir) 115 | .setURI("https://github.com/scala-js/scala-js.git") 116 | .call() 117 | } 118 | 119 | // Checkout the proper ref. We do this anyway so we fail if something is wrong. 120 | val git = Git.open(trgDir) 121 | s.log.info(s"Checking out Scala.js source version $ver") 122 | git.checkout().setName(s"v$ver").call() 123 | 124 | trgDir 125 | }, 126 | 127 | // We need them in `main` as well as in `test` 128 | libraryDependencies += "org.scala-js" %% "scalajs-junit-test-runtime" % scalaJSVersion, 129 | 130 | scalacOptions --= Seq("-deprecation", "-Xfatal-warnings"), 131 | 132 | Test / unmanagedResourceDirectories ++= { 133 | val base = (fetchScalaJSSource / artifactPath).value 134 | val testDir = base / "test-suite/js/src/test" 135 | 136 | scalaJSLinkerConfig.value.moduleKind match { 137 | case ModuleKind.NoModule => Nil 138 | case ModuleKind.CommonJSModule => Seq(testDir / "resources-commonjs") 139 | case ModuleKind.ESModule => Seq(testDir / "resources-esmodule") 140 | } 141 | }, 142 | 143 | Compile / unmanagedSourceDirectories ++= { 144 | val base = (fetchScalaJSSource / artifactPath).value 145 | Seq( 146 | base / "junit-async/js/src/main/scala", 147 | base / "test-suite/shared/src/main/scala", 148 | base / "test-suite/js/src/main/scala", 149 | ) 150 | }, 151 | 152 | Compile / unmanagedSources := (Compile / unmanagedSources).dependsOn(fetchScalaJSSource).value, 153 | Test / unmanagedSources := (Test / unmanagedSources).dependsOn(fetchScalaJSSource).value, 154 | 155 | Compile / sources ~= { sources => 156 | sources 157 | .filter(_.getName != "TypecheckingMacros.scala") 158 | .filter(_.getName != "Typechecking.scala") 159 | }, 160 | 161 | Test / unmanagedSourceDirectories ++= { 162 | val base = (fetchScalaJSSource / artifactPath).value 163 | Seq( 164 | base / "test-suite/shared/src/test/scala/", 165 | base / "test-suite/shared/src/test/require-scala2/", 166 | base / "test-suite/shared/src/test/scala-old-collections", 167 | 168 | base / "test-suite/js/src/test/require-dynamic-import", 169 | base / "test-suite/js/src/test/esmodule", 170 | base / "test-suite/js/src/test/require-exponent-op", 171 | base / "test-suite/js/src/test/require-modules", 172 | base / "test-suite/js/src/test/require-new-target", 173 | base / "test-suite/js/src/test/require-scala2", 174 | base / "test-suite/js/src/test/scala", 175 | base / "test-suite/js/src/test/scala-old-collections", 176 | ) 177 | }, 178 | 179 | // Remove sources of tests that cause linking/validating to fail 180 | Test / sources := { 181 | def endsWith(f: File, suffix: String): Boolean = 182 | f.getPath().replace('\\', '/').endsWith(suffix) 183 | 184 | (Test / sources).value 185 | .filterNot(endsWith(_, "/UnionTypeTest.scala")) // requires typechecking macros 186 | .filterNot(endsWith(_, "/jsinterop/ExportsTest.scala")) // js.dynamicImport (multi-modules) 187 | .filterNot(endsWith(_, "/ExportLoopback.scala")) 188 | }, 189 | 190 | Test / scalacOptions += "-P:scalajs:genStaticForwardersForNonTopLevelObjects", 191 | Test / scalacOptions += "-P:scalajs:nowarnGlobalExecutionContext", 192 | 193 | scalaJSLinkerConfig ~= { _.withSemantics(build.TestSuiteLinkerOptions.semantics _) }, 194 | Test / scalaJSModuleInitializers ++= build.TestSuiteLinkerOptions.moduleInitializers, 195 | 196 | scalaJSLinkerConfig ~= { _.withESFeatures(_.withESVersion(ESVersion.ES2016)) }, 197 | 198 | Test / jsEnvInput := { 199 | val base = fetchScalaJSSource.value 200 | val f = (base / "test-suite/js/src/test/resources/NonNativeJSTypeTestNatives.js").toPath 201 | val resourcesInput = org.scalajs.jsenv.Input.Script(f) 202 | 203 | resourcesInput +: (Test / jsEnvInput).value 204 | }, 205 | 206 | // Write package.json inside target/ to automatically configure the right module type 207 | Compile / jsEnvInput := (Compile / jsEnvInput).dependsOn(writePackageJSON).value, 208 | Test / jsEnvInput := (Test / jsEnvInput).dependsOn(writePackageJSON).value, 209 | writePackageJSON := { 210 | val packageType = scalaJSLinkerConfig.value.moduleKind match { 211 | case ModuleKind.NoModule => "commonjs" 212 | case ModuleKind.CommonJSModule => "commonjs" 213 | case ModuleKind.ESModule => "module" 214 | } 215 | val path = target.value / "package.json" 216 | IO.write(path, s"""{"type": "$packageType"}\n""") 217 | }, 218 | 219 | Test / testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-s"), 220 | 221 | // Ignore individual tests that do not pass 222 | Test / testOptions += Tests.Filter({ testName => 223 | !IgnoredTestNames.contains(testName) 224 | }), 225 | ) 226 | 227 | lazy val IgnoredTestNames: Set[String] = { 228 | Set( 229 | // Cannot call wasmObject.toString() from JavaScript: 230 | // boxValueClassesGivenToJSInteropMethod failed: scala.scalajs.js.JavaScriptException: TypeError: vc.toString is not a function 231 | "org.scalajs.testsuite.compiler.InteroperabilityTest", 232 | // TypeError: WebAssembly objects are opaque 233 | "org.scalajs.testsuite.javalib.lang.SystemJSTest", 234 | // throwablesAreTrueErrors failed: org.junit.ComparisonFailure: expected:<[object [Error]]> but was:<[object [Object]]> 235 | // throwablesAreJSErrors failed: java.lang.AssertionError: null 236 | "org.scalajs.testsuite.javalib.lang.ThrowableJSTest", 237 | // jsError/jsObject failed: AssertionError because Wasm objects are not instanceof Error/Object 238 | "org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest", 239 | // No support for stack traces 240 | "org.scalajs.testsuite.library.StackTraceTest", 241 | ) 242 | } 243 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scala-wasm", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "fs": "^0.0.1-security", 9 | "http": "^0.0.1-security", 10 | "jszip": "^3.5.0" 11 | } 12 | }, 13 | "node_modules/core-util-is": { 14 | "version": "1.0.3", 15 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 16 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 17 | }, 18 | "node_modules/fs": { 19 | "version": "0.0.1-security", 20 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 21 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" 22 | }, 23 | "node_modules/http": { 24 | "version": "0.0.1-security", 25 | "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", 26 | "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" 27 | }, 28 | "node_modules/immediate": { 29 | "version": "3.0.6", 30 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 31 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 32 | }, 33 | "node_modules/inherits": { 34 | "version": "2.0.4", 35 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 36 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 37 | }, 38 | "node_modules/isarray": { 39 | "version": "1.0.0", 40 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 41 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 42 | }, 43 | "node_modules/jszip": { 44 | "version": "3.10.1", 45 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", 46 | "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 47 | "dependencies": { 48 | "lie": "~3.3.0", 49 | "pako": "~1.0.2", 50 | "readable-stream": "~2.3.6", 51 | "setimmediate": "^1.0.5" 52 | } 53 | }, 54 | "node_modules/lie": { 55 | "version": "3.3.0", 56 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 57 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 58 | "dependencies": { 59 | "immediate": "~3.0.5" 60 | } 61 | }, 62 | "node_modules/pako": { 63 | "version": "1.0.11", 64 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 65 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 66 | }, 67 | "node_modules/process-nextick-args": { 68 | "version": "2.0.1", 69 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 70 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 71 | }, 72 | "node_modules/readable-stream": { 73 | "version": "2.3.8", 74 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 75 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 76 | "dependencies": { 77 | "core-util-is": "~1.0.0", 78 | "inherits": "~2.0.3", 79 | "isarray": "~1.0.0", 80 | "process-nextick-args": "~2.0.0", 81 | "safe-buffer": "~5.1.1", 82 | "string_decoder": "~1.1.1", 83 | "util-deprecate": "~1.0.1" 84 | } 85 | }, 86 | "node_modules/safe-buffer": { 87 | "version": "5.1.2", 88 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 89 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 90 | }, 91 | "node_modules/setimmediate": { 92 | "version": "1.0.5", 93 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 94 | "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" 95 | }, 96 | "node_modules/string_decoder": { 97 | "version": "1.1.1", 98 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 99 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 100 | "dependencies": { 101 | "safe-buffer": "~5.1.0" 102 | } 103 | }, 104 | "node_modules/util-deprecate": { 105 | "version": "1.0.2", 106 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 107 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "fs": "^0.0.1-security", 4 | "http": "^0.0.1-security", 5 | "jszip": "^3.5.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /project/TestSuiteLinkerOptions.scala: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import org.scalajs.linker.interface._ 4 | 5 | object TestSuiteLinkerOptions { 6 | 7 | def semantics(s: Semantics): Semantics = { 8 | import Semantics.RuntimeClassNameMapper 9 | 10 | s.withRuntimeClassNameMapper( 11 | RuntimeClassNameMapper.keepAll().andThen( 12 | RuntimeClassNameMapper.regexReplace( 13 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$RenamedTestClass$$""".r, 14 | "renamed.test.Class") 15 | ).andThen( 16 | RuntimeClassNameMapper.regexReplace( 17 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$Prefix""".r, 18 | "renamed.test.byprefix.") 19 | ).andThen( 20 | RuntimeClassNameMapper.regexReplace( 21 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$OtherPrefix""".r, 22 | "renamed.test.byotherprefix.") 23 | ) 24 | ) 25 | } 26 | 27 | def moduleInitializers: List[ModuleInitializer] = { 28 | val module = "org.scalajs.testsuite.compiler.ModuleInitializers" 29 | List( 30 | ModuleInitializer.mainMethod(module, "mainNoArgs"), 31 | ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs"), 32 | ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs", List("foo", "bar")), 33 | ModuleInitializer.mainMethod(module + "$NoLinkedClass", "main"), 34 | ModuleInitializer.mainMethod(module + "$WithLinkedClass", "main") 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /project/WasmLinkerPlugin.scala: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | 6 | import org.scalajs.linker._ 7 | import org.scalajs.linker.interface.{ModuleKind, _} 8 | 9 | import org.scalajs.sbtplugin._ 10 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 11 | 12 | object WasmLinkerPlugin extends AutoPlugin { 13 | override lazy val requires = ScalaJSPlugin 14 | 15 | /** A `LinkerImpl` that reflectively loads our `WebAssemblyLinkerImpl`. */ 16 | private class WasmLinkerImpl(base: LinkerImpl.Reflect) 17 | extends LinkerImpl.Forwarding(base) { 18 | 19 | private val loader = base.loader 20 | 21 | private val clearableLinkerMethod = { 22 | Class.forName("org.scalajs.linker.standard.WebAssemblyLinkerImpl", true, loader) 23 | .getMethod("clearableLinker", classOf[StandardConfig]) 24 | } 25 | 26 | override def clearableLinker(config: StandardConfig): ClearableLinker = 27 | clearableLinkerMethod.invoke(null, config).asInstanceOf[ClearableLinker] 28 | } 29 | 30 | override def projectSettings: Seq[Setting[_]] = Def.settings( 31 | // Use a separate cache box for the LinkerImpl in this project (don't use the Global one) 32 | scalaJSLinkerImplBox := new CacheBox, 33 | 34 | // Use our custom WasmLinkerImpl as the linker implementation used by fast/fullLinkJS 35 | scalaJSLinkerImpl := { 36 | val cp = (scalaJSLinkerImpl / fullClasspath).value 37 | scalaJSLinkerImplBox.value.ensure { 38 | new WasmLinkerImpl(LinkerImpl.reflect(Attributed.data(cp))) 39 | } 40 | }, 41 | 42 | // Automatically install all the configs required by the Wasm backend 43 | scalaJSLinkerConfig ~= { prev => 44 | prev 45 | .withModuleKind(ModuleKind.ESModule) 46 | .withSemantics(_.optimized) 47 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")) 48 | .withOptimizer(false) 49 | }, 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") 2 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") 3 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 4 | 5 | libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit.pgm" % "3.2.0.201312181205-r" 6 | -------------------------------------------------------------------------------- /run.mjs: -------------------------------------------------------------------------------- 1 | import { test, field } from "./sample/target/scala-2.12/sample-fastopt/main.mjs"; 2 | 3 | console.log(field); 4 | const o = test(7); 5 | console.log(o); 6 | console.log(field); 7 | -------------------------------------------------------------------------------- /sample/src/main/scala/Sample.scala: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | object Main { 7 | @JSExportTopLevel("field") 8 | var exportedField: Int = 42 9 | 10 | @JSExportTopLevel("test") 11 | def test(i: Int): Boolean = { 12 | println("Hello") 13 | exportedField = 53 14 | println(exportedField) 15 | true 16 | } 17 | 18 | def main(args: Array[String]): Unit = { 19 | println("hello world") 20 | } 21 | 22 | // Tested in SampleTest.scala 23 | def square(x: Int): Int = x * x 24 | 25 | private def println(x: Any): Unit = 26 | js.Dynamic.global.console.log("" + x) 27 | } 28 | -------------------------------------------------------------------------------- /sample/src/test/scala/sample/SampleTest.scala: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | class SampleTest { 7 | @Test 8 | def testSquare(): Unit = { 9 | assertEquals(16, Main.square(4)) 10 | 11 | // Uncomment to get a test failure 12 | // assertEquals(16, Main.square(5)) 13 | } 14 | 15 | @Test 16 | def testException(): Unit = { 17 | assertThrows( 18 | classOf[IllegalArgumentException], 19 | { () => 20 | throwIllegalArgument() 21 | } 22 | ) 23 | 24 | // Uncomment to get a test failure 25 | /*assertThrows( 26 | classOf[UnsupportedOperationException], 27 | { () => 28 | throwIllegalArgument() 29 | } 30 | )*/ 31 | } 32 | 33 | def throwIllegalArgument(): Unit = 34 | throw new IllegalArgumentException() 35 | } 36 | -------------------------------------------------------------------------------- /scalajs-test-suite/src/main/scala/org/scalajs/testsuite/utils/BuildInfo.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.testsuite.utils 2 | 3 | private[utils] object BuildInfo { 4 | final val scalaVersion = "2.12.19" 5 | final val hasSourceMaps = false 6 | final val isNoModule = false 7 | final val isESModule = true 8 | final val isCommonJSModule = false 9 | final val usesClosureCompiler = false 10 | final val hasMinifiedNames = true 11 | final val compliantAsInstanceOfs = false 12 | final val compliantArrayIndexOutOfBounds = false 13 | final val compliantArrayStores = false 14 | final val compliantNegativeArraySizes = false 15 | final val compliantNullPointers = false 16 | final val compliantStringIndexOutOfBounds = false 17 | final val compliantModuleInit = false 18 | final val strictFloats = true 19 | final val productionMode = false 20 | final val esVersion = 6 21 | final val useECMAScript2015Semantics = true 22 | } 23 | -------------------------------------------------------------------------------- /scalajs-test-suite/src/test/scala/org/scalajs/testsuite/jsinterop/WasmExportLoopback.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala.js (https://www.scala-js.org/) 3 | * 4 | * Copyright EPFL. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (https://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package org.scalajs.testsuite.jsinterop 14 | 15 | import scala.scalajs.js 16 | import scala.scalajs.js.annotation._ 17 | 18 | import scala.concurrent.Future 19 | 20 | object ExportLoopback { 21 | val exportsNamespace: Future[js.Dynamic] = 22 | js.`import`[js.Dynamic]("./main.mjs").toFuture 23 | } 24 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/Assert.scala: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | /** Assertion helpers. */ 4 | object Assert { 5 | def ok(cond: Boolean): Unit = 6 | if (!cond) fail() 7 | 8 | def assertSame(expected: Any, actual: Any): Unit = 9 | ok(expected.asInstanceOf[AnyRef] eq actual.asInstanceOf[AnyRef]) 10 | 11 | def fail(): Unit = 12 | throw new AssertionError("assertion failed") 13 | } 14 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/Add.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object Add { 6 | def main(): Unit = { 7 | ok(1 + 1 == 2) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ArrayReflectTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object ArrayReflectTest { 6 | def main(): Unit = { 7 | testSimpleNewInstance() 8 | testMultiNewInstance() 9 | } 10 | 11 | private def testSimpleNewInstance(): Unit = { 12 | val a = java.lang.reflect.Array.newInstance(classOf[Int], 5) 13 | ok(a.isInstanceOf[Array[Int]]) 14 | assertSame(classOf[Array[Int]], a.getClass()) 15 | val a1 = a.asInstanceOf[Array[Int]] 16 | assertSame(5, a1.length) 17 | assertSame(0, a1(0)) 18 | 19 | val b = java.lang.reflect.Array.newInstance(classOf[String], 5) 20 | ok(b.isInstanceOf[Array[String]]) 21 | assertSame(classOf[Array[String]], b.getClass()) 22 | val b1 = b.asInstanceOf[Array[String]] 23 | assertSame(5, b1.length) 24 | assertSame(null, b1(0)) 25 | 26 | val c = java.lang.reflect.Array.newInstance(classOf[Array[Object]], 5) 27 | ok(c.isInstanceOf[Array[Array[Object]]]) 28 | assertSame(classOf[Array[Array[Object]]], c.getClass()) 29 | val c1 = c.asInstanceOf[Array[Array[Object]]] 30 | assertSame(5, c1.length) 31 | assertSame(null, c1(0)) 32 | } 33 | 34 | private def testMultiNewInstance(): Unit = { 35 | val a = java.lang.reflect.Array.newInstance(classOf[Int], 5, 10) 36 | ok(a.isInstanceOf[Array[Array[Int]]]) 37 | assertSame(classOf[Array[Array[Int]]], a.getClass()) 38 | val a1 = a.asInstanceOf[Array[Array[Int]]] 39 | assertSame(5, a1.length) 40 | ok(a1(3).isInstanceOf[Array[Int]]) 41 | val a2 = a1(3).asInstanceOf[Array[Int]] 42 | assertSame(10, a2.length) 43 | assertSame(0, a2(0)) 44 | ok(a1(1) ne a1(0)) 45 | 46 | val b = java.lang.reflect.Array.newInstance(classOf[String], 5, 10) 47 | ok(b.isInstanceOf[Array[Array[String]]]) 48 | assertSame(classOf[Array[Array[String]]], b.getClass()) 49 | val b1 = b.asInstanceOf[Array[Array[String]]] 50 | assertSame(5, b1.length) 51 | ok(b1(3).isInstanceOf[Array[String]]) 52 | val b2 = b1(3).asInstanceOf[Array[String]] 53 | assertSame(10, b2.length) 54 | assertSame(null, b2(0)) 55 | ok(b1(1) ne b1(0)) 56 | 57 | val c = java.lang.reflect.Array.newInstance(classOf[Array[Object]], 5, 10) 58 | ok(c.isInstanceOf[Array[Array[Array[Object]]]]) 59 | assertSame(classOf[Array[Array[Array[Object]]]], c.getClass()) 60 | val c1 = c.asInstanceOf[Array[Array[Array[Object]]]] 61 | assertSame(5, c1.length) 62 | ok(c1(3).isInstanceOf[Array[Array[Object]]]) 63 | val c2 = c1(3).asInstanceOf[Array[Array[Object]]] 64 | assertSame(10, c2.length) 65 | assertSame(null, c2(0)) 66 | ok(c1(1) ne c1(0)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ArrayTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert 4 | 5 | object ArrayTest { 6 | def main(): Unit = { 7 | Assert.ok( 8 | testLength() && testSelect() && testNew() 9 | ) 10 | } 11 | 12 | def testLength(): Boolean = { 13 | Array(1, 2, 3).length == 3 && 14 | (Array(Array(1, 2), Array(2), Array(3))).length == 3 15 | } 16 | 17 | def testSelect(): Boolean = { 18 | val a = Array(Array(1), Array(2), Array(3)) 19 | a(0)(0) == 1 && { 20 | a(0)(0) = 100 // Assign(ArraySelect(...), ...) 21 | a(0)(0) == 100 // ArraySelect(...) 22 | } && { 23 | a(1) = Array(1, 2, 3) 24 | a(1).length == 3 && a(1)(0) == 1 25 | } 26 | } 27 | 28 | def testNew(): Boolean = { 29 | (Array.emptyBooleanArray.length == 0) && 30 | (new Array[Int](10)).length == 10 && 31 | (new Array[Int](1))(0) == 0 && 32 | (new Array[Array[Array[Int]]](5))(0) == null 33 | } 34 | 35 | // TODO: Array.ofDim[T](...) 36 | } 37 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object AsInstanceOfTest { 6 | def main(): Unit = { 7 | ok( 8 | testInt(5) && 9 | testClasses(new Child()) && 10 | testString("foo", true) 11 | ) 12 | } 13 | 14 | def testClasses(c: Child): Boolean = { 15 | val c1 = c.asInstanceOf[Child] 16 | val c2 = c.asInstanceOf[Parent] 17 | c1.foo() == 5 && c2.foo() == 5 18 | } 19 | 20 | def testInt(x: Int): Boolean = { 21 | val x1 = x.asInstanceOf[Int] 22 | x1 == 5 23 | } 24 | 25 | def testString(s: String, b: Boolean): Boolean = { 26 | val s1 = s.asInstanceOf[String] 27 | val s2 = ("" + b).asInstanceOf[String] 28 | s1.length() == 3 && s2.length() == 4 29 | } 30 | 31 | class Parent { 32 | def foo(): Int = 5 33 | } 34 | class Child extends Parent 35 | } 36 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/BasicListTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object BasicListTest { 6 | def main(): Unit = { 7 | val l = List(5, 13, 29) 8 | val x = l.tail.head 9 | assertSame(13, x) 10 | 11 | val l2 = l.map(x => x * 2) 12 | assertSame(3, l2.size) 13 | assertSame(26, l2(1)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ClassOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import java.io.Serializable 4 | import java.lang.Cloneable 5 | 6 | import testsuite.Assert._ 7 | import testsuite.Assert 8 | 9 | object ClassOfTest { 10 | def main(): Unit = { 11 | testGetName() 12 | testUniqueness() 13 | testIsPrimitive() 14 | testIsInterface() 15 | testIsArray() 16 | testIsInstance() 17 | testIsAssignableFrom() 18 | testCast() 19 | testGetComponentType() 20 | } 21 | 22 | def testGetName(): Unit = { 23 | Assert.assertSame("java.lang.String", classOf[String].getName()) 24 | Assert.assertSame("java.lang.StringBuilder", classOf[java.lang.StringBuilder].getName()) 25 | Assert.assertSame("int", classOf[Int].getName()) 26 | Assert.assertSame("void", classOf[Unit].getName()) 27 | 28 | Assert.assertSame("[Ljava.lang.Object;", classOf[Array[AnyRef]].getName()) 29 | Assert.assertSame("[Ljava.lang.String;", classOf[Array[String]].getName()) 30 | Assert.assertSame("[[Ljava.lang.CharSequence;", classOf[Array[Array[CharSequence]]].getName()) 31 | 32 | Assert.assertSame("[Z", classOf[Array[Boolean]].getName()) 33 | Assert.assertSame("[C", classOf[Array[Char]].getName()) 34 | Assert.assertSame("[B", classOf[Array[Byte]].getName()) 35 | Assert.assertSame("[S", classOf[Array[Short]].getName()) 36 | Assert.assertSame("[I", classOf[Array[Int]].getName()) 37 | Assert.assertSame("[J", classOf[Array[Long]].getName()) 38 | Assert.assertSame("[F", classOf[Array[Float]].getName()) 39 | Assert.assertSame("[D", classOf[Array[Double]].getName()) 40 | } 41 | 42 | def testUniqueness(): Unit = { 43 | Assert.assertSame(classOf[String], classOf[String]) 44 | Assert.assertSame(classOf[java.lang.StringBuilder], classOf[java.lang.StringBuilder]) 45 | Assert.assertSame(classOf[CharSequence], classOf[CharSequence]) 46 | Assert.assertSame(classOf[Int], classOf[Int]) 47 | 48 | Assert.assertSame(classOf[Array[Int]], classOf[Array[Int]]) 49 | Assert.assertSame(classOf[Array[Array[java.lang.Byte]]], classOf[Array[Array[java.lang.Byte]]]) 50 | Assert.assertSame(classOf[Array[Array[Int]]], classOf[Array[Array[Int]]]) 51 | } 52 | 53 | def testIsPrimitive(): Unit = { 54 | Assert.assertSame(false, classOf[AnyRef].isPrimitive()) 55 | Assert.assertSame(false, classOf[String].isPrimitive()) 56 | Assert.assertSame(false, classOf[CharSequence].isPrimitive()) 57 | Assert.assertSame(false, classOf[Cloneable].isPrimitive()) 58 | Assert.assertSame(false, classOf[Serializable].isPrimitive()) 59 | Assert.assertSame(false, classOf[java.lang.Iterable[Any]].isPrimitive()) 60 | Assert.assertSame(false, classOf[Array[Int]].isPrimitive()) 61 | Assert.assertSame(false, classOf[Array[String]].isPrimitive()) 62 | Assert.assertSame(false, classOf[Array[CharSequence]].isPrimitive()) 63 | 64 | Assert.assertSame(true, classOf[Int].isPrimitive()) 65 | Assert.assertSame(true, classOf[Unit].isPrimitive()) 66 | } 67 | 68 | def testIsInterface(): Unit = { 69 | Assert.assertSame(false, classOf[AnyRef].isInterface()) 70 | Assert.assertSame(false, classOf[String].isInterface()) 71 | Assert.assertSame(false, classOf[Int].isInterface()) 72 | Assert.assertSame(false, classOf[Array[Int]].isInterface()) 73 | Assert.assertSame(false, classOf[Array[String]].isInterface()) 74 | Assert.assertSame(false, classOf[Array[CharSequence]].isInterface()) 75 | 76 | Assert.assertSame(true, classOf[CharSequence].isInterface()) 77 | Assert.assertSame(true, classOf[Cloneable].isInterface()) 78 | Assert.assertSame(true, classOf[Serializable].isInterface()) 79 | Assert.assertSame(true, classOf[java.lang.Iterable[Any]].isInterface()) 80 | } 81 | 82 | def testIsArray(): Unit = { 83 | Assert.assertSame(false, classOf[AnyRef].isArray()) 84 | Assert.assertSame(false, classOf[String].isArray()) 85 | Assert.assertSame(false, classOf[Int].isArray()) 86 | Assert.assertSame(false, classOf[CharSequence].isArray()) 87 | Assert.assertSame(false, classOf[Cloneable].isArray()) 88 | Assert.assertSame(false, classOf[Serializable].isArray()) 89 | Assert.assertSame(false, classOf[java.lang.Iterable[Any]].isArray()) 90 | 91 | Assert.assertSame(true, classOf[Array[Int]].isArray()) 92 | Assert.assertSame(true, classOf[Array[String]].isArray()) 93 | Assert.assertSame(true, classOf[Array[CharSequence]].isArray()) 94 | } 95 | 96 | def testIsInstance(): Unit = { 97 | def test(expected: Boolean, cls: Class[_], value: Any): Unit = 98 | assertSame(expected, cls.isInstance(value)) 99 | 100 | test(false, classOf[AnyRef], null) 101 | test(true, classOf[AnyRef], 5) 102 | test(true, classOf[AnyRef], 'A') 103 | test(true, classOf[AnyRef], "foo") 104 | test(true, classOf[AnyRef], ()) 105 | test(true, classOf[AnyRef], new AnyRef) 106 | test(true, classOf[AnyRef], new Parent) 107 | test(true, classOf[AnyRef], new Array[Int](1)) 108 | 109 | test(false, classOf[Comparable[_]], null) 110 | test(false, classOf[Comparable[_]], ()) 111 | test(true, classOf[Comparable[_]], 5) 112 | test(true, classOf[Comparable[_]], "foo") 113 | test(true, classOf[Comparable[_]], true) 114 | test(true, classOf[Comparable[_]], 6L) 115 | test(true, classOf[Comparable[_]], 'A') 116 | test(false, classOf[CharSequence], 5) 117 | test(true, classOf[CharSequence], "foo") 118 | test(false, classOf[CharSequence], 5L) 119 | test(false, classOf[CharSequence], 'A') 120 | 121 | test(false, classOf[Child], null) 122 | test(true, classOf[Parent], new Parent) 123 | test(true, classOf[Parent], new Child) 124 | test(false, classOf[Child], new Parent) 125 | test(true, classOf[Child], new Child) 126 | test(false, classOf[Child], 5) 127 | 128 | test(false, classOf[Interface], null) 129 | test(true, classOf[Interface], new Parent) 130 | test(false, classOf[OtherInterface], new Parent) 131 | test(true, classOf[Interface], new Child) 132 | test(true, classOf[OtherInterface], new Child) 133 | test(false, classOf[Interface], 5) 134 | 135 | test(false, classOf[Array[Int]], null) 136 | test(true, classOf[Array[Int]], new Array[Int](1)) 137 | test(false, classOf[Array[Int]], new Array[Byte](1)) 138 | test(true, classOf[Array[String]], new Array[String](1)) 139 | test(true, classOf[Array[Object]], new Array[String](1)) 140 | test(false, classOf[Array[Interface]], new Array[String](1)) 141 | 142 | test(true, classOf[Character], 'A') 143 | test(false, classOf[Character], 5) 144 | test(true, classOf[Integer], 5) 145 | test(false, classOf[Integer], 5.5) 146 | test(true, classOf[java.lang.Boolean], true) 147 | test(false, classOf[java.lang.Boolean], 5) 148 | test(true, classOf[java.lang.Void], ()) 149 | test(false, classOf[java.lang.Void], 5) 150 | } 151 | 152 | def testIsAssignableFrom(): Unit = { 153 | def testEquiv(cls1: Class[_], cls2: Class[_]): Unit = { 154 | assertSame(true, cls1.isAssignableFrom(cls2)) 155 | assertSame(true, cls2.isAssignableFrom(cls1)) 156 | } 157 | 158 | def testStrictSuper(cls1: Class[_], cls2: Class[_]): Unit = { 159 | assertSame(true, cls1.isAssignableFrom(cls2)) 160 | assertSame(false, cls2.isAssignableFrom(cls1)) 161 | } 162 | 163 | def testUnrelated(cls1: Class[_], cls2: Class[_]): Unit = { 164 | assertSame(false, cls1.isAssignableFrom(cls2)) 165 | assertSame(false, cls2.isAssignableFrom(cls1)) 166 | } 167 | 168 | // Same class is always assignable, including primitives and even void 169 | testEquiv(classOf[Int], classOf[Int]) 170 | testEquiv(classOf[Unit], classOf[Unit]) 171 | testEquiv(classOf[Object], classOf[Object]) 172 | testEquiv(classOf[String], classOf[String]) 173 | testEquiv(classOf[Comparable[_]], classOf[Comparable[_]]) 174 | testEquiv(classOf[Array[Int]], classOf[Array[Int]]) 175 | testEquiv(classOf[Interface], classOf[Interface]) 176 | testEquiv(classOf[OtherInterface], classOf[OtherInterface]) 177 | testEquiv(classOf[Parent], classOf[Parent]) 178 | testEquiv(classOf[Child], classOf[Child]) 179 | 180 | // Primitives are not assignable to/from anything elsething else 181 | testUnrelated(classOf[Int], classOf[Unit]) 182 | testUnrelated(classOf[Int], classOf[Double]) 183 | testUnrelated(classOf[Int], classOf[Object]) 184 | testUnrelated(classOf[Int], classOf[String]) 185 | testUnrelated(classOf[Int], classOf[Comparable[_]]) 186 | testUnrelated(classOf[Int], classOf[Cloneable]) 187 | testUnrelated(classOf[Int], classOf[Serializable]) 188 | 189 | // Everything non-primitive is assignable to Object, including arrays 190 | testStrictSuper(classOf[Object], classOf[String]) 191 | testStrictSuper(classOf[Object], classOf[Cloneable]) 192 | testStrictSuper(classOf[Object], classOf[Serializable]) 193 | testStrictSuper(classOf[Object], classOf[Comparable[_]]) 194 | testStrictSuper(classOf[Object], classOf[Array[Int]]) 195 | testStrictSuper(classOf[Object], classOf[Array[Array[String]]]) 196 | 197 | // Subclasses are assignable to superclasses 198 | testStrictSuper(classOf[Interface], classOf[OtherInterface]) 199 | testStrictSuper(classOf[Interface], classOf[Parent]) 200 | testStrictSuper(classOf[Interface], classOf[Child]) 201 | testStrictSuper(classOf[OtherInterface], classOf[Child]) 202 | testStrictSuper(classOf[Parent], classOf[Child]) 203 | 204 | // Unrelated classes are not assignable 205 | testUnrelated(classOf[String], classOf[Parent]) 206 | testUnrelated(classOf[Parent], classOf[OtherInterface]) 207 | testUnrelated(classOf[Parent], classOf[Cloneable]) 208 | testUnrelated(classOf[Parent], classOf[Serializable]) 209 | testUnrelated(classOf[Comparable[_]], classOf[Cloneable]) 210 | testUnrelated(classOf[Comparable[_]], classOf[Serializable]) 211 | testUnrelated(classOf[Comparable[_]], classOf[Parent]) 212 | 213 | // Arrays are covariant 214 | testStrictSuper(classOf[Array[Object]], classOf[Array[String]]) 215 | testStrictSuper(classOf[Array[Object]], classOf[Array[Interface]]) 216 | testStrictSuper(classOf[Array[Object]], classOf[Array[Array[Int]]]) 217 | testStrictSuper(classOf[Array[Interface]], classOf[Array[Parent]]) 218 | testStrictSuper(classOf[Array[Parent]], classOf[Array[Child]]) 219 | 220 | // Arrays are Cloneable, including covariantly 221 | testStrictSuper(classOf[Cloneable], classOf[Array[Int]]) 222 | testStrictSuper(classOf[Cloneable], classOf[Array[Object]]) 223 | testStrictSuper(classOf[Cloneable], classOf[Array[String]]) 224 | testStrictSuper(classOf[Cloneable], classOf[Array[Array[Int]]]) 225 | testStrictSuper(classOf[Cloneable], classOf[Array[Array[Object]]]) 226 | testStrictSuper(classOf[Array[Cloneable]], classOf[Array[Array[Int]]]) 227 | testStrictSuper(classOf[Array[Cloneable]], classOf[Array[Array[Array[String]]]]) 228 | testUnrelated(classOf[Array[Cloneable]], classOf[Array[Int]]) 229 | 230 | // Arrays are Serializable, including covariantly 231 | testStrictSuper(classOf[Serializable], classOf[Array[Int]]) 232 | testStrictSuper(classOf[Serializable], classOf[Array[Object]]) 233 | testStrictSuper(classOf[Serializable], classOf[Array[String]]) 234 | testStrictSuper(classOf[Serializable], classOf[Array[Array[Int]]]) 235 | testStrictSuper(classOf[Serializable], classOf[Array[Array[Object]]]) 236 | testStrictSuper(classOf[Array[Serializable]], classOf[Array[Array[Int]]]) 237 | testStrictSuper(classOf[Array[Serializable]], classOf[Array[Array[Array[String]]]]) 238 | testUnrelated(classOf[Array[Serializable]], classOf[Array[Int]]) 239 | 240 | // Arrays are unrelated to other interfaces 241 | testUnrelated(classOf[Interface], classOf[Array[Int]]) 242 | testUnrelated(classOf[Interface], classOf[Array[Object]]) 243 | testUnrelated(classOf[Interface], classOf[Array[String]]) 244 | testUnrelated(classOf[Interface], classOf[Array[Interface]]) 245 | testUnrelated(classOf[Interface], classOf[Array[Array[Int]]]) 246 | testUnrelated(classOf[Interface], classOf[Array[Array[Object]]]) 247 | } 248 | 249 | def testCast(): Unit = { 250 | /* This is always an identity, so there isn't anything to really test. 251 | * We only make sure that everything is wired up correctly. 252 | */ 253 | 254 | val child: AnyRef = new Child 255 | val child2: Parent = classOf[Parent].cast(child) 256 | assertSame(child, child2) 257 | } 258 | 259 | def testGetComponentType(): Unit = { 260 | Assert.assertSame(null, classOf[AnyRef].getComponentType()) 261 | Assert.assertSame(null, classOf[String].getComponentType()) 262 | Assert.assertSame(null, classOf[Int].getComponentType()) 263 | Assert.assertSame(null, classOf[CharSequence].getComponentType()) 264 | Assert.assertSame(null, classOf[java.lang.Iterable[Any]].getComponentType()) 265 | 266 | Assert.assertSame(classOf[Int], classOf[Array[Int]].getComponentType()) 267 | Assert.assertSame(classOf[String], classOf[Array[String]].getComponentType()) 268 | Assert.assertSame(classOf[AnyRef], classOf[Array[AnyRef]].getComponentType()) 269 | 270 | Assert.assertSame( 271 | classOf[Array[CharSequence]], 272 | classOf[Array[Array[CharSequence]]].getComponentType() 273 | ) 274 | 275 | Assert.assertSame( 276 | classOf[Array[Long]], 277 | classOf[Array[Array[Long]]].getComponentType() 278 | ) 279 | 280 | Assert.assertSame( 281 | classOf[Array[Array[java.lang.Byte]]], 282 | classOf[Array[Array[Array[java.lang.Byte]]]].getComponentType() 283 | ) 284 | 285 | Assert.assertSame( 286 | classOf[Array[ClassForUniqueGetComponentTypeTest]].getComponentType(), 287 | classOf[Array[ClassForUniqueGetComponentTypeTest]].getComponentType() 288 | ) 289 | } 290 | 291 | trait Interface 292 | trait OtherInterface extends Interface 293 | class Parent extends Interface 294 | class Child extends Parent with OtherInterface 295 | 296 | class ClassForUniqueGetComponentTypeTest 297 | } 298 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/CloneTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert 4 | 5 | object CloneTest { 6 | def main(): Unit = { 7 | testDirect() 8 | testIndirectlyImpl() 9 | testShallowCopying() 10 | } 11 | 12 | private def testDirect(): Unit = { 13 | val foo = new Foo(0, "0") 14 | val copy = foo.clone().asInstanceOf[Foo] 15 | Assert.ok(foo.x == copy.x) 16 | Assert.ok(foo.y == copy.y) 17 | } 18 | 19 | private def testIndirectlyImpl(): Unit = { 20 | val bar = new Bar(1, "1") 21 | val copy = bar.clone().asInstanceOf[Bar] 22 | Assert.ok(bar.x == copy.x) 23 | Assert.ok(bar.y == copy.y) 24 | } 25 | 26 | private def testShallowCopying(): Unit = { 27 | val foo = new Foo(0, "foo") 28 | val bar = new Bar(1, "bar") 29 | val baz = new Baz(foo, bar) 30 | val copy = baz.clone().asInstanceOf[Baz] 31 | Assert.assertSame(baz.foo, copy.foo) 32 | Assert.assertSame(baz.bar, copy.bar) 33 | } 34 | } 35 | 36 | class Foo(val x: Int, val y: String) extends Cloneable { 37 | override def clone(): Object = super.clone() 38 | } 39 | 40 | class Bar( 41 | override val x: Int, 42 | override val y: String 43 | ) extends Foo(x, y) 44 | 45 | class Baz(val foo: Foo, val bar: Bar) extends Cloneable { 46 | override def clone(): Object = super.clone() 47 | } 48 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ClosureTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert.ok 6 | 7 | object ClosureTest { 8 | def main(): Unit = { 9 | testClosure() 10 | testClosureThis() 11 | 12 | // TODO We cannot test closures with ...rest params yet because they need Seq's 13 | 14 | testGiveToActualJSCode() 15 | } 16 | 17 | def testClosure(): Unit = { 18 | def makeClosure(x: Int, y: String): js.Function2[Boolean, String, String] = 19 | (z, w) => s"$x $y $z $w" 20 | 21 | val f = makeClosure(5, "foo") 22 | ok(f(true, "bar") == "5 foo true bar") 23 | } 24 | 25 | def testClosureThis(): Unit = { 26 | def makeClosure(x: Int, y: String): js.ThisFunction2[Any, Boolean, String, String] = 27 | (ths, z, w) => s"$ths $x $y $z $w" 28 | 29 | val f = makeClosure(5, "foo") 30 | ok(f(new Obj, true, "bar") == "Obj 5 foo true bar") 31 | } 32 | 33 | def testGiveToActualJSCode(): Unit = { 34 | val arr = js.Array(2, 3, 5, 7, 11) 35 | val f: js.Function1[Int, Int] = x => x * 2 36 | val result = arr.asInstanceOf[js.Dynamic].map(f).asInstanceOf[js.Array[Int]] 37 | ok(result.length == 5) 38 | ok(result(0) == 4) 39 | ok(result(1) == 6) 40 | ok(result(2) == 10) 41 | ok(result(3) == 14) 42 | ok(result(4) == 22) 43 | } 44 | 45 | class Obj { 46 | override def toString(): String = "Obj" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ControlStructuresTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.annotation.tailrec 4 | import testsuite.Assert.ok 5 | 6 | object ControlStructuresTest { 7 | def main(): Unit = { 8 | testIf() 9 | testWhile() 10 | testFib() 11 | } 12 | 13 | private def testIf(): Unit = { 14 | def test(x: Int): String = 15 | if (x < 0) "negative" 16 | else if (x == 0) "zero" 17 | else "positive" 18 | ok(test(1) == "positive") 19 | ok(test(0) == "zero") 20 | ok(test(-1) == "negative") 21 | } 22 | 23 | private def testWhile(): Unit = { 24 | var x = 10 25 | while (x > 0) { x = x - 1 } 26 | ok(x == 0) 27 | } 28 | 29 | private def testFib(): Unit = { 30 | def testFib(i: Int, expected: Int): Unit = { 31 | ok(RecFib.fib(i) == expected) 32 | ok(LoopFib.fib(i) == expected) 33 | ok(TailRecFib.fib(i) == expected) 34 | } 35 | testFib(0, 0) 36 | testFib(1, 1) 37 | testFib(2, 1) 38 | testFib(3, 2) 39 | testFib(4, 3) 40 | testFib(5, 5) 41 | testFib(6, 8) 42 | } 43 | } 44 | 45 | object RecFib { 46 | def fib(n: Int): Int = if (n <= 1) n else fib(n - 1) + fib(n - 2) 47 | } 48 | 49 | object LoopFib { 50 | def fib(n: Int): Int = { 51 | var a = 0 52 | var b = 1 53 | var i = 0 54 | while (i < n) { 55 | val temp = b 56 | b = a + b 57 | a = temp 58 | i += 1 59 | } 60 | a 61 | } 62 | } 63 | 64 | object TailRecFib { 65 | def fib(n: Int): Int = fibLoop(n, 0, 1) 66 | @tailrec 67 | private final def fibLoop(n: Int, a: Int, b: Int): Int = 68 | if (n == 0) a 69 | else fibLoop(n - 1, b, a + b) 70 | } 71 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/FieldsTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object FieldsTest { 6 | def main(): Unit = { 7 | val parent = new Parent(5) 8 | ok(parent.x == 5) 9 | ok(parent.getX == 5) 10 | 11 | val child = new Child(6, "foo") 12 | ok(child.x == 6) 13 | ok(child.getX == 6) 14 | ok(child.foo() == 3) 15 | 16 | val child2 = new Child(-6, "foo") 17 | ok(child2.x == -6) 18 | ok(child2.getX == -6) 19 | ok(child2.foo() == -3) 20 | } 21 | 22 | class Parent(val x: Int) { 23 | def getX: Int = x 24 | } 25 | 26 | class Child(x2: Int, y: String) extends Parent(x2) { 27 | def foo(): Int = if (x >= 0) y.length() else -y.length() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/FloatingPointRemTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | /** Tests for `Float_%` and `Double_%`, which do not have Wasm equivalent. 6 | * 7 | * They are specified by https://262.ecma-international.org/#sec-numeric-types-number-remainder 8 | */ 9 | object FloatingPointRemTest { 10 | def main(): Unit = { 11 | testFloat() 12 | testDouble() 13 | } 14 | 15 | def testFloat(): Unit = { 16 | def test(expected: Float, x: Float, y: Float): Unit = 17 | assertSame(expected, x % y) 18 | 19 | // If n is NaN, return NaN 20 | test(Float.NaN, Float.NaN, Float.NaN) 21 | test(Float.NaN, Float.NaN, Float.PositiveInfinity) 22 | test(Float.NaN, Float.NaN, Float.NegativeInfinity) 23 | test(Float.NaN, Float.NaN, +0.0f) 24 | test(Float.NaN, Float.NaN, -0.0f) 25 | test(Float.NaN, Float.NaN, 2.1f) 26 | test(Float.NaN, Float.NaN, 5.5f) 27 | test(Float.NaN, Float.NaN, -151.189f) 28 | 29 | // If d is NaN, return NaN 30 | test(Float.NaN, Float.NaN, Float.NaN) 31 | test(Float.NaN, Float.PositiveInfinity, Float.NaN) 32 | test(Float.NaN, Float.NegativeInfinity, Float.NaN) 33 | test(Float.NaN, +0.0f, Float.NaN) 34 | test(Float.NaN, -0.0f, Float.NaN) 35 | test(Float.NaN, 2.1f, Float.NaN) 36 | test(Float.NaN, 5.5f, Float.NaN) 37 | test(Float.NaN, -151.189f, Float.NaN) 38 | 39 | // If n is PositiveInfinity, return NaN 40 | test(Float.NaN, Float.PositiveInfinity, Float.PositiveInfinity) 41 | test(Float.NaN, Float.PositiveInfinity, Float.NegativeInfinity) 42 | test(Float.NaN, Float.PositiveInfinity, +0.0f) 43 | test(Float.NaN, Float.PositiveInfinity, -0.0f) 44 | test(Float.NaN, Float.PositiveInfinity, 2.1f) 45 | test(Float.NaN, Float.PositiveInfinity, 5.5f) 46 | test(Float.NaN, Float.PositiveInfinity, -151.189f) 47 | 48 | // If n is NegativeInfinity, return NaN 49 | test(Float.NaN, Float.NegativeInfinity, Float.PositiveInfinity) 50 | test(Float.NaN, Float.NegativeInfinity, Float.NegativeInfinity) 51 | test(Float.NaN, Float.NegativeInfinity, +0.0f) 52 | test(Float.NaN, Float.NegativeInfinity, -0.0f) 53 | test(Float.NaN, Float.NegativeInfinity, 2.1f) 54 | test(Float.NaN, Float.NegativeInfinity, 5.5f) 55 | test(Float.NaN, Float.NegativeInfinity, -151.189f) 56 | 57 | // If d is PositiveInfinity, return n 58 | test(+0.0f, +0.0f, Float.PositiveInfinity) 59 | test(-0.0f, -0.0f, Float.PositiveInfinity) 60 | test(2.1f, 2.1f, Float.PositiveInfinity) 61 | test(5.5f, 5.5f, Float.PositiveInfinity) 62 | test(-151.189f, -151.189f, Float.PositiveInfinity) 63 | 64 | // If d is NegativeInfinity, return n 65 | test(+0.0f, +0.0f, Float.NegativeInfinity) 66 | test(-0.0f, -0.0f, Float.NegativeInfinity) 67 | test(2.1f, 2.1f, Float.NegativeInfinity) 68 | test(5.5f, 5.5f, Float.NegativeInfinity) 69 | test(-151.189f, -151.189f, Float.NegativeInfinity) 70 | 71 | // If d is +0.0, return NaN 72 | test(Float.NaN, +0.0f, +0.0f) 73 | test(Float.NaN, -0.0f, +0.0f) 74 | test(Float.NaN, 2.1f, +0.0f) 75 | test(Float.NaN, 5.5f, +0.0f) 76 | test(Float.NaN, -151.189f, +0.0f) 77 | 78 | // If d is -0.0, return NaN 79 | test(Float.NaN, +0.0f, -0.0f) 80 | test(Float.NaN, -0.0f, -0.0f) 81 | test(Float.NaN, 2.1f, -0.0f) 82 | test(Float.NaN, 5.5f, -0.0f) 83 | test(Float.NaN, -151.189f, -0.0f) 84 | 85 | // If n is +0.0, return n 86 | test(+0.0f, +0.0f, 2.1f) 87 | test(+0.0f, +0.0f, 5.5f) 88 | test(+0.0f, +0.0f, -151.189f) 89 | 90 | // If n is -0.0, return n 91 | test(-0.0f, -0.0f, 2.1f) 92 | test(-0.0f, -0.0f, 5.5f) 93 | test(-0.0f, -0.0f, -151.189f) 94 | 95 | // Non-special values 96 | // { val l = List(2.1f, 5.5f, -151.189f); for (n <- l; d <- l) println(s" test(${n % d}f, ${n}f, ${d}f)") } 97 | test(0.0f, 2.1f, 2.1f) 98 | test(2.1f, 2.1f, 5.5f) 99 | test(2.1f, 2.1f, -151.189f) 100 | test(1.3000002f, 5.5f, 2.1f) 101 | test(0.0f, 5.5f, 5.5f) 102 | test(5.5f, 5.5f, -151.189f) 103 | test(-2.0890021f, -151.189f, 2.1f) 104 | test(-2.6889954f, -151.189f, 5.5f) 105 | test(-0.0f, -151.189f, -151.189f) 106 | } 107 | 108 | def testDouble(): Unit = { 109 | def test(expected: Double, n: Double, d: Double): Unit = 110 | assertSame(expected, n % d) 111 | 112 | // If n is NaN, return NaN 113 | test(Double.NaN, Double.NaN, Double.NaN) 114 | test(Double.NaN, Double.NaN, Double.PositiveInfinity) 115 | test(Double.NaN, Double.NaN, Double.NegativeInfinity) 116 | test(Double.NaN, Double.NaN, +0.0) 117 | test(Double.NaN, Double.NaN, -0.0) 118 | test(Double.NaN, Double.NaN, 2.1) 119 | test(Double.NaN, Double.NaN, 5.5) 120 | test(Double.NaN, Double.NaN, -151.189) 121 | 122 | // If d is NaN, return NaN 123 | test(Double.NaN, Double.NaN, Double.NaN) 124 | test(Double.NaN, Double.PositiveInfinity, Double.NaN) 125 | test(Double.NaN, Double.NegativeInfinity, Double.NaN) 126 | test(Double.NaN, +0.0, Double.NaN) 127 | test(Double.NaN, -0.0, Double.NaN) 128 | test(Double.NaN, 2.1, Double.NaN) 129 | test(Double.NaN, 5.5, Double.NaN) 130 | test(Double.NaN, -151.189, Double.NaN) 131 | 132 | // If n is PositiveInfinity, return NaN 133 | test(Double.NaN, Double.PositiveInfinity, Double.PositiveInfinity) 134 | test(Double.NaN, Double.PositiveInfinity, Double.NegativeInfinity) 135 | test(Double.NaN, Double.PositiveInfinity, +0.0) 136 | test(Double.NaN, Double.PositiveInfinity, -0.0) 137 | test(Double.NaN, Double.PositiveInfinity, 2.1) 138 | test(Double.NaN, Double.PositiveInfinity, 5.5) 139 | test(Double.NaN, Double.PositiveInfinity, -151.189) 140 | 141 | // If n is NegativeInfinity, return NaN 142 | test(Double.NaN, Double.NegativeInfinity, Double.PositiveInfinity) 143 | test(Double.NaN, Double.NegativeInfinity, Double.NegativeInfinity) 144 | test(Double.NaN, Double.NegativeInfinity, +0.0) 145 | test(Double.NaN, Double.NegativeInfinity, -0.0) 146 | test(Double.NaN, Double.NegativeInfinity, 2.1) 147 | test(Double.NaN, Double.NegativeInfinity, 5.5) 148 | test(Double.NaN, Double.NegativeInfinity, -151.189) 149 | 150 | // If d is PositiveInfinity, return n 151 | test(+0.0, +0.0, Double.PositiveInfinity) 152 | test(-0.0, -0.0, Double.PositiveInfinity) 153 | test(2.1, 2.1, Double.PositiveInfinity) 154 | test(5.5, 5.5, Double.PositiveInfinity) 155 | test(-151.189, -151.189, Double.PositiveInfinity) 156 | 157 | // If d is NegativeInfinity, return n 158 | test(+0.0, +0.0, Double.NegativeInfinity) 159 | test(-0.0, -0.0, Double.NegativeInfinity) 160 | test(2.1, 2.1, Double.NegativeInfinity) 161 | test(5.5, 5.5, Double.NegativeInfinity) 162 | test(-151.189, -151.189, Double.NegativeInfinity) 163 | 164 | // If d is +0.0, return NaN 165 | test(Double.NaN, +0.0, +0.0) 166 | test(Double.NaN, -0.0, +0.0) 167 | test(Double.NaN, 2.1, +0.0) 168 | test(Double.NaN, 5.5, +0.0) 169 | test(Double.NaN, -151.189, +0.0) 170 | 171 | // If d is -0.0, return NaN 172 | test(Double.NaN, +0.0, -0.0) 173 | test(Double.NaN, -0.0, -0.0) 174 | test(Double.NaN, 2.1, -0.0) 175 | test(Double.NaN, 5.5, -0.0) 176 | test(Double.NaN, -151.189, -0.0) 177 | 178 | // If n is +0.0, return n 179 | test(+0.0, +0.0, 2.1) 180 | test(+0.0, +0.0, 5.5) 181 | test(+0.0, +0.0, -151.189) 182 | 183 | // If n is -0.0, return n 184 | test(-0.0, -0.0, 2.1) 185 | test(-0.0, -0.0, 5.5) 186 | test(-0.0, -0.0, -151.189) 187 | 188 | // Non-special values 189 | // { val l = List(2.1, 5.5, -151.189); for (n <- l; d <- l) println(s" test(${n % d}, $n, $d)") } 190 | test(0.0, 2.1, 2.1) 191 | test(2.1, 2.1, 5.5) 192 | test(2.1, 2.1, -151.189) 193 | test(1.2999999999999998, 5.5, 2.1) 194 | test(0.0, 5.5, 5.5) 195 | test(5.5, 5.5, -151.189) 196 | test(-2.0889999999999866, -151.189, 2.1) 197 | test(-2.688999999999993, -151.189, 5.5) 198 | test(-0.0, -151.189, -151.189) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/GetClassTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.assertSame 4 | 5 | object GetClassTest { 6 | def main(): Unit = { 7 | testNoHijackedDispatch() 8 | testWithHijackedDispath() 9 | } 10 | 11 | class Foo 12 | 13 | class Bar extends Foo 14 | 15 | def testNoHijackedDispatch(): Unit = { 16 | def getClassOfFoo(x: Foo): Class[_] = x.getClass() 17 | 18 | assertSame(classOf[Foo], getClassOfFoo(new Foo)) 19 | assertSame(classOf[Bar], getClassOfFoo(new Bar)) 20 | } 21 | 22 | def testWithHijackedDispath(): Unit = { 23 | def getClassOf(x: Any): Class[_] = x.getClass() 24 | 25 | assertSame(classOf[Foo], getClassOf(new Foo)) 26 | assertSame(classOf[Bar], getClassOf(new Bar)) 27 | 28 | assertSame(classOf[java.lang.Boolean], getClassOf(true)) 29 | assertSame(classOf[java.lang.Boolean], getClassOf(false)) 30 | assertSame(classOf[java.lang.Void], getClassOf(())) 31 | assertSame(classOf[java.lang.String], getClassOf("foo")) 32 | 33 | assertSame(classOf[java.lang.Byte], getClassOf(0.0)) 34 | assertSame(classOf[java.lang.Byte], getClassOf(56)) 35 | assertSame(classOf[java.lang.Byte], getClassOf(-128)) 36 | assertSame(classOf[java.lang.Short], getClassOf(200)) 37 | assertSame(classOf[java.lang.Short], getClassOf(-32000)) 38 | assertSame(classOf[java.lang.Integer], getClassOf(500000)) 39 | assertSame(classOf[java.lang.Integer], getClassOf(Int.MinValue)) 40 | 41 | assertSame(classOf[java.lang.Float], getClassOf(1.5)) 42 | assertSame(classOf[java.lang.Double], getClassOf(1.4)) 43 | assertSame(classOf[java.lang.Double], getClassOf(Float.MaxValue.toDouble * 8.0)) 44 | 45 | assertSame(classOf[java.lang.Float], getClassOf(-0.0)) 46 | assertSame(classOf[java.lang.Float], getClassOf(Double.PositiveInfinity)) 47 | assertSame(classOf[java.lang.Float], getClassOf(Double.NegativeInfinity)) 48 | assertSame(classOf[java.lang.Float], getClassOf(Double.NaN)) 49 | 50 | assertSame(classOf[Array[Int]], getClassOf(new Array[Int](3))) 51 | assertSame(classOf[Array[Int]], getClassOf(Array(1, 2, 3))) 52 | 53 | assertSame(classOf[Array[Boolean]], getClassOf(new Array[Boolean](3))) 54 | assertSame(classOf[Array[Boolean]], getClassOf(Array(false, true))) 55 | 56 | assertSame(classOf[Array[AnyRef]], getClassOf(new Array[AnyRef](3))) 57 | assertSame(classOf[Array[AnyRef]], getClassOf(Array(null): Array[AnyRef])) 58 | 59 | assertSame(classOf[Array[String]], getClassOf(new Array[String](3))) 60 | assertSame(classOf[Array[String]], getClassOf(Array("foo", "bar"))) 61 | 62 | assertSame(classOf[Array[Array[Int]]], getClassOf(new Array[Array[Int]](3))) 63 | assertSame(classOf[Array[Array[Int]]], getClassOf(Array(Array(1, 2, 3)))) 64 | 65 | assertSame(null, getClassOf(scala.scalajs.js.Math)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesDispatchTest { 6 | def main(): Unit = { 7 | val obj = new Test() 8 | val otherObj = new Test() 9 | val obj2 = new Test2() 10 | val otherObj2 = new Test2() 11 | ok( 12 | testToString(true, "true") && 13 | testToString(54321, "54321") && 14 | testToString(obj, "Test class") && 15 | testToStringStartsWith(obj2, "testsuite.core.HijackedClassesDispatchTest$Test2@") && 16 | testToString('A', "A") && 17 | testToStringStartsWith(new Array[Int](1), "[I@") && 18 | testToStringStartsWith(new Array[String](1), "[Ljava.lang.String;@") && 19 | testToStringStartsWith(new Array[Array[AnyRef]](1), "[[Ljava.lang.Object;@") && 20 | testHashCode(true, 1231) && 21 | testHashCode(54321, 54321) && 22 | testHashCode("foo", 101574) && 23 | testHashCode(obj, 123) && 24 | testHashCode(obj2, 1) && // first object for which we ask idHashCode 25 | testHashCode(obj2, 1) && // it is also 1 the second time we ask 26 | testHashCode('A', 65) && 27 | testIntValue(Int.box(5), 5) && 28 | testIntValue(Long.box(6L), 6) && 29 | testIntValue(Double.box(7.5), 7) && 30 | testIntValue(new CustomNumber(), 789) && 31 | testLength("foo", 3) && 32 | testLength(new CustomCharSeq(), 54) && 33 | testCharAt("foobar", 3, 'b') && 34 | testCharAt(new CustomCharSeq(), 3, 'A') && 35 | testEquals(true, 1, false) && 36 | testEquals(1.0, 1, true) && 37 | testEquals("foo", "foo", true) && 38 | testEquals("foo", "bar", false) && 39 | testEquals(obj, obj2, false) && 40 | testEquals(obj, otherObj, true) && 41 | testEquals(obj2, otherObj2, false) && 42 | testNotifyAll(true) && 43 | testNotifyAll(obj) 44 | ) 45 | } 46 | 47 | def testToString(x: Any, expected: String): Boolean = 48 | x.toString() == expected 49 | 50 | def testToStringStartsWith(x: Any, expectedPrefix: String): Boolean = 51 | x.toString().startsWith(expectedPrefix) 52 | 53 | def testHashCode(x: Any, expected: Int): Boolean = 54 | x.hashCode() == expected 55 | 56 | def testIntValue(x: Number, expected: Int): Boolean = 57 | x.intValue() == expected 58 | 59 | def testLength(x: CharSequence, expected: Int): Boolean = 60 | x.length() == expected 61 | 62 | def testCharAt(x: CharSequence, i: Int, expected: Char): Boolean = 63 | x.charAt(i) == expected 64 | 65 | def testEquals(x: Any, y: Any, expected: Boolean): Boolean = 66 | x.asInstanceOf[AnyRef].equals(y) == expected 67 | 68 | def testNotifyAll(x: Any): Boolean = { 69 | // This is just to test that the call validates and does not trap 70 | x.asInstanceOf[AnyRef].notifyAll() 71 | true 72 | } 73 | 74 | class Test { 75 | override def toString(): String = "Test class" 76 | 77 | override def hashCode(): Int = 123 78 | 79 | override def equals(that: Any): Boolean = 80 | that.isInstanceOf[Test] 81 | } 82 | 83 | class Test2 84 | 85 | class CustomNumber() extends Number { 86 | def value(): Int = 789 87 | def intValue(): Int = value() 88 | def longValue(): Long = 789L 89 | def floatValue(): Float = 789.0f 90 | def doubleValue(): Double = 789.0 91 | } 92 | 93 | class CustomCharSeq extends CharSequence { 94 | def length(): Int = 54 95 | override def toString(): String = "CustomCharSeq" 96 | def charAt(index: Int): Char = 'A' 97 | def subSequence(start: Int, end: Int): CharSequence = this 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesMonoTest { 6 | def main(): Unit = { 7 | ok(testInteger(5)) 8 | ok(testString("foo")) 9 | } 10 | 11 | def testInteger(x: Int): Boolean = { 12 | x.hashCode() == 5 13 | } 14 | 15 | def testString(foo: String): Boolean = { 16 | foo.length() == 3 && 17 | foo.hashCode() == 101574 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesUpcastTest { 6 | def main(): Unit = { 7 | ok( 8 | testBoolean(true) && 9 | testInteger(5) && 10 | testIntegerNull(null) && 11 | testString("foo") && 12 | testStringNull(null) && 13 | testCharacter('A') 14 | ) 15 | 16 | } 17 | 18 | def testBoolean(x: Boolean): Boolean = { 19 | val x1 = identity(x) 20 | x1 && { 21 | val x2: Any = x1 22 | x2 match { 23 | case x3: Boolean => x3 24 | case _ => false 25 | } 26 | } 27 | } 28 | 29 | def testInteger(x: Int): Boolean = { 30 | val x1 = identity(x) 31 | x1 == 5 && { 32 | val x2: Any = x1 33 | x2 match { 34 | case x3: Int => x3 + 1 == 6 35 | case _ => false 36 | } 37 | } 38 | } 39 | 40 | def testIntegerNull(x: Any): Boolean = { 41 | !x.isInstanceOf[Int] && 42 | !x.isInstanceOf[java.lang.Integer] && 43 | (x.asInstanceOf[Int] == 0) && { 44 | val x2 = x.asInstanceOf[java.lang.Integer] 45 | x2 == null 46 | } 47 | } 48 | 49 | def testString(x: String): Boolean = { 50 | val x1 = identity(x) 51 | x1.length() == 3 && { 52 | val x2: Any = x1 53 | x2 match { 54 | case x3: String => x3.length() == 3 55 | case _ => false 56 | } 57 | } 58 | } 59 | 60 | def testStringNull(x: Any): Boolean = { 61 | !x.isInstanceOf[String] && { 62 | val x2 = x.asInstanceOf[String] 63 | x2 == null 64 | } 65 | } 66 | 67 | def testCharacter(x: Char): Boolean = { 68 | val x1 = identity(x) 69 | x1 == 'A' && { 70 | val x2: Any = x1 71 | x2 match { 72 | case x3: Char => (x3 + 1).toChar == 'B' 73 | case _ => false 74 | } 75 | } 76 | } 77 | 78 | @noinline 79 | def identity[A](x: A): A = x 80 | } 81 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ImportTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | import testsuite.Assert._ 7 | 8 | object ImportTest { 9 | @js.native 10 | @JSImport("node:querystring") 11 | def escape(str: String): String = js.native 12 | 13 | def main(): Unit = { 14 | assertSame("foo%26ba%2Bbar%3Dfoobabar", escape("foo&ba+bar=foobabar")) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/InterfaceCall.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object InterfaceCall { 6 | def main(): Unit = { 7 | val c = new Concrete() 8 | val cAddSub: AddSub = c 9 | val cZero: Zero = c 10 | ok(cAddSub.plus(5, 7) == 12) 11 | ok(cAddSub.minus(5, 7) == -2) 12 | ok(cZero.zero == 0) 13 | ok(cAddSub.plus(cZero.zero, 1) == 1 && cAddSub.minus(1, cZero.zero) == 1) 14 | 15 | val o = new OtherConcrete() 16 | val oAddSub: AddSub = o 17 | val oZero: Zero = o 18 | ok(oAddSub.plus(5, 7) == 12) 19 | ok(oAddSub.minus(5, 7) == -2) 20 | ok(oZero.zero == 5) 21 | ok(oAddSub.plus(oZero.zero, 1) == 6 && oAddSub.minus(1, oZero.zero) == -4) 22 | } 23 | 24 | class Concrete extends AddSub with Zero { 25 | override def zero: Int = 0 26 | } 27 | 28 | class OtherConcrete extends Zero with Sub with AddSub { 29 | override def zero: Int = 5 30 | } 31 | 32 | trait Adder { 33 | def plus(a: Int, b: Int) = a + b 34 | } 35 | 36 | trait Sub { 37 | def minus(a: Int, b: Int): Int = a - b 38 | } 39 | 40 | trait AddSub extends Adder with Sub 41 | 42 | trait Zero { 43 | def zero: Int 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/IsInstanceOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object IsInstanceOfTest { 6 | def main(): Unit = { 7 | ok(testInheritance()) 8 | ok(testMixinAll(new Child())) 9 | ok(testMixinAll(new Parent())) 10 | ok(testMixinAll(new Base {})) 11 | ok(testMixin()) 12 | ok(testPrimitiveIsInstanceOfBase(5)) 13 | ok(testPrimitiveIsInstanceOfBase("foo")) 14 | 15 | ok(!testInt("foo")) 16 | ok(!testInt(2147483648L)) 17 | ok(testInt(3)) 18 | ok(!testInt(new Child())) 19 | ok(testString("foo")) 20 | ok(!testString(new Child())) 21 | 22 | testArrays() 23 | } 24 | 25 | private def testInheritance(): Boolean = { 26 | val child: Any = new Child() 27 | val parent: Any = new Parent() 28 | val unrelated: Any = new UnrelatedClass() 29 | child.isInstanceOf[AbstractParent] && 30 | child.isInstanceOf[Parent] && 31 | child.isInstanceOf[Child] && 32 | parent.isInstanceOf[AbstractParent] && 33 | parent.isInstanceOf[Parent] && 34 | !parent.isInstanceOf[Child] && 35 | !unrelated.isInstanceOf[AbstractParent] && 36 | !unrelated.isInstanceOf[Parent] && 37 | !unrelated.isInstanceOf[Child] 38 | } 39 | 40 | private def testMixinAll(o: Base): Boolean = { 41 | o.isInstanceOf[Base] && o.isInstanceOf[Base1] && o.isInstanceOf[Base2] 42 | } 43 | 44 | private def testMixin(): Boolean = { 45 | val base1 = new Base1 {} 46 | val base2 = new Base2 {} 47 | base1.isInstanceOf[Base1] && 48 | !base1.isInstanceOf[Base2] && 49 | !base1.isInstanceOf[Base] && 50 | !base2.isInstanceOf[Base1] && 51 | base2.isInstanceOf[Base2] && 52 | !base2.isInstanceOf[Base] 53 | } 54 | 55 | private def testArrays(): Unit = { 56 | val child: Any = new Child 57 | val booleanArray: Any = new Array[Boolean](1) 58 | val intArray: Any = new Array[Int](1) 59 | val stringArray: Any = new Array[String](1) 60 | val intArrayArray: Any = new Array[Array[Int]](1) 61 | val stringArrayArray: Any = new Array[Array[String]](1) 62 | val objectArray: Any = new Array[Object](1) 63 | 64 | // direct instances 65 | ok(booleanArray.isInstanceOf[Array[Boolean]]) 66 | ok(intArray.isInstanceOf[Array[Int]]) 67 | ok(stringArray.isInstanceOf[Array[String]]) 68 | ok(intArrayArray.isInstanceOf[Array[Array[Int]]]) 69 | ok(stringArrayArray.isInstanceOf[Array[Array[String]]]) 70 | ok(objectArray.isInstanceOf[Array[Object]]) 71 | 72 | // primitive arrays are not instances of any other type 73 | ok(!booleanArray.isInstanceOf[Array[Int]]) 74 | ok(!booleanArray.isInstanceOf[Child]) 75 | ok(!booleanArray.isInstanceOf[Array[Array[Boolean]]]) 76 | ok(!booleanArray.isInstanceOf[Array[Object]]) 77 | 78 | // different dimensions 79 | ok(!intArray.isInstanceOf[Array[Array[Int]]]) 80 | ok(!intArrayArray.isInstanceOf[Array[Int]]) 81 | ok(!intArrayArray.isInstanceOf[Array[Array[Array[Int]]]]) 82 | ok(!stringArrayArray.isInstanceOf[Array[String]]) 83 | ok(!stringArrayArray.isInstanceOf[Array[Array[Array[String]]]]) 84 | ok(!objectArray.isInstanceOf[Array[Array[Object]]]) 85 | 86 | // reference array types are covariant at run-time (IR and JVM semantics) 87 | ok(stringArray.isInstanceOf[Array[Object]]) 88 | ok(intArrayArray.isInstanceOf[Array[Object]]) 89 | ok(!objectArray.isInstanceOf[Array[String]]) 90 | 91 | // non-arrays are not instances of any array type 92 | ok(!child.isInstanceOf[Array[Boolean]]) 93 | ok(!child.isInstanceOf[Array[Int]]) 94 | ok(!child.isInstanceOf[Array[String]]) 95 | ok(!child.isInstanceOf[Array[Array[Int]]]) 96 | ok(!child.isInstanceOf[Array[Array[String]]]) 97 | ok(!child.isInstanceOf[Array[Object]]) 98 | 99 | // corner case for reachability of the typeData 100 | val arrayOfClassUsedOnlyForArrayTypeTest = new Array[ClassUsedOnlyForArrayTypeTest](1) 101 | ok(arrayOfClassUsedOnlyForArrayTypeTest.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 102 | ok(!intArray.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 103 | ok(!objectArray.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 104 | } 105 | 106 | private def testPrimitiveIsInstanceOfBase(p: Any): Boolean = 107 | !p.isInstanceOf[Base] 108 | 109 | private def testInt(e: Any): Boolean = e.isInstanceOf[Int] 110 | private def testString(e: Any): Boolean = e.isInstanceOf[String] 111 | 112 | trait InterfaceUsedOnlyForArrayTypeTest 113 | class ClassUsedOnlyForArrayTypeTest extends InterfaceUsedOnlyForArrayTypeTest 114 | } 115 | 116 | abstract class AbstractParent 117 | 118 | class Parent extends AbstractParent with Base { 119 | def foo(): Int = 5 120 | } 121 | class Child extends Parent 122 | 123 | class UnrelatedClass 124 | 125 | trait Base1 126 | trait Base2 127 | trait Base extends Base1 with Base2 128 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JLObjectInstanceTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object JLObjectInstanceTest { 6 | def main(): Unit = { 7 | val obj = new Object() 8 | val hashCode = obj.hashCode() 9 | val str = obj.toString() 10 | assertSame("java.lang.Object@" + Integer.toHexString(hashCode), str) 11 | 12 | assertSame(true, (obj: Any).isInstanceOf[Object]) 13 | assertSame(false, (obj: Any).isInstanceOf[Comparable[_]]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSExportTopLevelTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | object JSExportTopLevelTest { 7 | def main(): Unit = { 8 | /* The main() function of this test does nothing. 9 | * The real test happens after loading the module from TestSuites.scala. 10 | */ 11 | () 12 | } 13 | 14 | @JSExportTopLevel("immutableField") 15 | val immutableField: String = "my immutable field value" 16 | 17 | @JSExportTopLevel("mutableField") 18 | var mutableField: Int = 42 19 | 20 | @JSExportTopLevel("simpleFunction") 21 | def simpleFunction(x: Int): Int = 22 | x * x 23 | 24 | @JSExportTopLevel("functionWithRest") 25 | def functionWithRest(x: Int, rest: Int*): Int = 26 | x * rest.sum 27 | 28 | @JSExportTopLevel("SimpleClass") 29 | class SimpleClass(val foo: Int) extends js.Object 30 | 31 | @JSExportTopLevel("SimpleObject") 32 | object SimpleObject extends js.Object { 33 | val bar: String = "the bar field" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSForInTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSForInTest { 8 | def main(): Unit = { 9 | val obj = new js.Object().asInstanceOf[js.Dynamic] 10 | obj.foo = "foobar" 11 | obj.bar = 5 12 | 13 | var iter = 0 14 | 15 | js.special.forin(obj) { key => 16 | if (iter == 0) 17 | assertSame("foo", key) 18 | else 19 | assertSame("bar", key) 20 | iter += 1 21 | } 22 | 23 | assertSame(2, iter) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSImportCallTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSImportCallTest { 8 | def main(): Unit = { 9 | /* We cannot actually wait for the result of an asynchronous computation 10 | * in our tests, so we only test that we indeed get a Promise. 11 | */ 12 | val promise = js.`import`[js.Any]("node:fs") 13 | ok((promise: Any).isInstanceOf[js.Promise[_]]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSImportMetaTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSImportMetaTest { 8 | def main(): Unit = { 9 | val meta = js.`import`.meta 10 | assertSame("object", js.typeOf(meta)) 11 | assertSame("string", js.typeOf(meta.url)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSInteropTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import testsuite.Assert.ok 5 | 6 | object JSInteropTest { 7 | def main(): Unit = { 8 | ok( 9 | testBasicTopLevel() && 10 | testBasicStatic() && 11 | testBasicInstance() && 12 | testArray() && 13 | testObject() && 14 | testOperators() && 15 | testLinkingInfo() 16 | ) 17 | } 18 | 19 | def testBasicTopLevel(): Boolean = { 20 | ("" + js.undefined) == "undefined" && 21 | js.eval("3 + 4").asInstanceOf[Int] == 7 && 22 | js.isUndefined(()) 23 | } 24 | 25 | def testBasicStatic(): Boolean = { 26 | js.Math.PI == 3.1415926535897932 && 27 | js.Math.abs(-5.6) == 5.6 && 28 | js.Math.clz32(6548) == 19 && 29 | js.Math.min(5, 8, 2, 12, 3) == 2 30 | } 31 | 32 | def testBasicInstance(): Boolean = { 33 | val d = new js.Date(1710190169564.0) 34 | d.getTime() == 1710190169564.0 && 35 | d.getUTCFullYear() == 2024 && { 36 | d.setTime(0.0) 37 | d.getTime() == 0.0 38 | } 39 | } 40 | 41 | def testArray(): Boolean = { 42 | val a = js.Array(1, 5, 3) 43 | a.length == 3 && 44 | a(0) == 1 && 45 | a(2) == 3 && { 46 | a(0) = 65 47 | a.push(78) 48 | a.length == 4 && 49 | a(0) == 65 && 50 | a(3) == 78 51 | } 52 | } 53 | 54 | def testObject(): Boolean = { 55 | val o = new js.Object().asInstanceOf[js.Dynamic] 56 | js.isUndefined(o.foo) && { 57 | o.foo = 5 58 | o.hasOwnProperty("foo").asInstanceOf[Boolean] && 59 | o.foo.asInstanceOf[Int] == 5 && { 60 | js.special.delete(o, "foo") 61 | !o.hasOwnProperty("foo").asInstanceOf[Boolean] 62 | } 63 | } 64 | } 65 | 66 | def testOperators(): Boolean = { 67 | val x = 5.asInstanceOf[js.Dynamic] 68 | val y = 11.asInstanceOf[js.Dynamic] 69 | same(+x, 5) && 70 | same(-x, -5) && 71 | same(~x, -6) && 72 | same(!x, false) && 73 | same(js.typeOf(x), "number") && // regular typeof 74 | same(js.typeOf(js.Dynamic.global.Date), "function") && // typeof global ref 75 | same(x + y, 16) && 76 | same(x - y, -6) && 77 | same(x * y, 55) && 78 | same(x / y, 0.45454545454545453) && 79 | same(y % x, 1) && 80 | same(x << 3.asInstanceOf[js.Dynamic], 40) && 81 | same(x >> 1.asInstanceOf[js.Dynamic], 2) && 82 | same(x >>> 2.asInstanceOf[js.Dynamic], 1) && 83 | same(x & y, 1) && 84 | same(x | y, 15) && 85 | same(x ^ y, 14) && 86 | same(x < y, true) && 87 | same(x <= y, true) && 88 | same(x > y, false) && 89 | same(x >= y, false) && 90 | same(x && y, 11) && 91 | same(x || y, 5) && 92 | same(x ** 3.asInstanceOf[js.Dynamic], 125) 93 | } 94 | 95 | def testLinkingInfo(): Boolean = { 96 | val linkingInfo = scala.scalajs.runtime.linkingInfo 97 | linkingInfo.esVersion >= 6 && 98 | linkingInfo.assumingES6 == true 99 | } 100 | 101 | def same(a: js.Any, b: js.Any): Boolean = js.special.strictEquals(a, b) 102 | } 103 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/MatchTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object MatchTest { 6 | def main(): Unit = { 7 | testFib() 8 | testString() 9 | testNull() 10 | } 11 | 12 | private def testFib(): Unit = { 13 | def test(x: Int, expected: Int) = ok(fib(x) == expected) 14 | def fib(x: Int): Int = x match { 15 | case 0 => 0 16 | case 1 | 2 => 1 17 | case _ => fib(x - 1) + fib(x - 2) 18 | } 19 | test(0, 0) 20 | test(1, 1) 21 | test(2, 1) 22 | test(3, 2) 23 | test(4, 3) 24 | test(5, 5) 25 | test(6, 8) 26 | } 27 | 28 | private def testString(): Unit = { 29 | def test(x: String, expected: String) = ok(animalSound(x) == expected) 30 | def animalSound(animal: String) = animal match { 31 | case "dog" => "bow-wow" 32 | case "cat" => "meow" 33 | case _ => "unknown" 34 | } 35 | test("dog", "bow-wow") 36 | test("cat", "meow") 37 | test("???", "unknown") 38 | } 39 | 40 | private def testNull(): Unit = { 41 | def strTest(x: String) = x match { 42 | case null => "null" 43 | case "foo" => "bar" 44 | case "bar" => "babar" 45 | case x => x 46 | } 47 | ok(strTest(null) == "null") 48 | ok(strTest("a") == "a") 49 | } 50 | 51 | // TODO: Add test case that no default case _ 52 | // we can't test with node v22 at this moment due to the lack of try_table 53 | } 54 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/NonNativeJSClassTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object NonNativeJSClassTest { 8 | def main(): Unit = { 9 | testBasic() 10 | testClassCaptures() 11 | testInheritanceAndSuper() 12 | testObject() 13 | testNewTarget() 14 | } 15 | 16 | def testBasic(): Unit = { 17 | val o = new MyClass("string param") 18 | 19 | // Field access and initialization 20 | assertSame(0, o.asInstanceOf[js.Dynamic].i) 21 | assertSame(0, o.i) 22 | o.i = 42 23 | assertSame(42, o.i) 24 | 25 | // Method call 26 | assertSame(8, o.foobar(5)) 27 | 28 | // Property read/write 29 | assertSame("prop string param", o.prop) 30 | o.prop = "foobar" 31 | assertSame("prop set foobar", o.prop) 32 | 33 | // Instance test -- also ensures that we get the same MyClass class 34 | assertSame(true, (o: Any).isInstanceOf[MyClass]) 35 | } 36 | 37 | def testClassCaptures(): Unit = { 38 | def localJSClass(capture: Int): js.Dynamic = { 39 | class Local(s: String) extends MyClass(s) { 40 | def getCapture(): Int = capture 41 | } 42 | js.constructorOf[Local] 43 | } 44 | 45 | // Create classes 46 | 47 | val localClass1 = localJSClass(5) 48 | assertSame("function", js.typeOf(localClass1)) 49 | val localClass2 = localJSClass(6) 50 | assertSame(true, localClass2 ne localClass1) 51 | 52 | val obj1 = js.Dynamic.newInstance(localClass1)("foobar") 53 | assertSame("prop foobar", obj1.prop) 54 | assertSame(5, obj1.getCapture()) 55 | 56 | val obj2 = js.Dynamic.newInstance(localClass2)("babar") 57 | assertSame("prop babar", obj2.prop) 58 | assertSame(6, obj2.getCapture()) 59 | 60 | // Instance tests -- the two local classes are independent 61 | assertSame(true, (obj1: Any).isInstanceOf[MyClass]) 62 | assertSame(true, js.special.instanceof(obj1, localClass1)) 63 | assertSame(false, js.special.instanceof(obj1, localClass2)) 64 | assertSame(true, js.special.instanceof(obj2, localClass2)) 65 | } 66 | 67 | def testInheritanceAndSuper(): Unit = { 68 | val sub = new Sub() 69 | 70 | assertSame("prop subarg sub", sub.prop) 71 | sub.prop = "new value" 72 | assertSame("prop set from sub new value sub", sub.prop) 73 | 74 | assertSame(15, sub.foobar(6)) 75 | 76 | assertSame(true, (sub: Any).isInstanceOf[MyClass]) 77 | assertSame(true, (sub: Any).isInstanceOf[Sub]) 78 | } 79 | 80 | def testObject(): Unit = { 81 | assertSame(5, MyObject.foo(4)) 82 | 83 | val obj1 = MyObject 84 | val obj2 = MyObject 85 | assertSame(obj1, obj2) 86 | } 87 | 88 | def testNewTarget(): Unit = { 89 | val parent = new NewTargetParent 90 | assertSame(js.constructorOf[NewTargetParent], parent.myNewTarget) 91 | 92 | val child = new NewTargetChild 93 | assertSame(js.constructorOf[NewTargetChild], child.myNewTarget) 94 | } 95 | 96 | class MyClass(private var s: String) extends js.Object { 97 | var i: Int = _ 98 | 99 | def foobar(x: Int): Int = x + 3 100 | 101 | def prop: String = "prop " + s 102 | def prop_=(v: String): Unit = 103 | s = "set " + v 104 | } 105 | 106 | class Sub extends MyClass("subarg") { 107 | override def foobar(x: Int): Int = super.foobar(x * 2) 108 | 109 | override def prop: String = super.prop + " sub" 110 | override def prop_=(v: String): Unit = 111 | super.prop_=("from sub " + v) 112 | } 113 | 114 | object MyObject extends js.Object { 115 | def foo(x: Int): Int = x + 1 116 | } 117 | 118 | class NewTargetParent extends js.Object { 119 | val myNewTarget = js.`new`.target 120 | } 121 | 122 | class NewTargetChild extends NewTargetParent 123 | } 124 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/Simple.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object Simple { 6 | def main(): Unit = { 7 | ok(true) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StaticFieldsTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js.annotation.JSExportTopLevel 4 | 5 | import testsuite.Assert.assertSame 6 | 7 | object StaticFieldsTest { 8 | /* We use @JSExportTopLevel to force the fields to be encoded as static in, 9 | * the IR; not for the exported behavior per se. 10 | */ 11 | 12 | @JSExportTopLevel("StaticFieldsTest_valStatic") 13 | val valStatic: Int = 42 14 | 15 | @JSExportTopLevel("StaticFieldsTest_varStatic") 16 | var varStatic: String = "hello" 17 | 18 | def main(): Unit = { 19 | assertSame(42, valStatic) 20 | assertSame("hello", varStatic) 21 | varStatic = "foo" 22 | assertSame("foo", varStatic) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StaticMethodTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object StaticMethodTest { 6 | def main(): Unit = { 7 | ok(java.lang.Integer.sum(5, 65) == 70) 8 | ok(java.lang.Integer.reverseBytes(0x01020304) == 0x04030201) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StringEncodingTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object StringEncodingTest { 6 | def main(): Unit = { 7 | illformedUTF16Test() 8 | wellformedUTF16Test() 9 | } 10 | 11 | def illformedUTF16Test(): Unit = { 12 | val s = "\ud834a\udd1e" 13 | assertSame(3, s.length()) 14 | assertSame(0xd834, s.charAt(0).toInt) 15 | assertSame('a'.toInt, s.charAt(1).toInt) 16 | assertSame(0xdd1e, s.charAt(2).toInt) 17 | } 18 | 19 | def wellformedUTF16Test(): Unit = { 20 | val s = "\ud834\udd1e" 21 | assertSame(2, s.length()) 22 | assertSame(0xd834, s.charAt(0).toInt) 23 | assertSame(0xdd1e, s.charAt(1).toInt) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ThrowAndTryTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object ThrowAndTryTest { 6 | def main(): Unit = { 7 | testTryCatch() 8 | testTryCatchWithRefResultType() 9 | testTryFinally() 10 | } 11 | 12 | private def testTryCatch(): Unit = { 13 | try { 14 | doNotThrow() 15 | } catch { 16 | case e: IllegalArgumentException => 17 | fail() 18 | } 19 | 20 | try { 21 | throwIllegalArgument() 22 | fail() 23 | } catch { 24 | case e: IllegalArgumentException => 25 | assertSame("boom", e.getMessage()) 26 | } 27 | 28 | try { 29 | try { 30 | throwIllegalArgument() 31 | fail() 32 | } catch { 33 | case e: UnsupportedOperationException => 34 | // This one should not be caught 35 | fail() 36 | } 37 | fail() // the exception should have been rethrown 38 | } catch { 39 | case e: IllegalArgumentException => 40 | assertSame("boom", e.getMessage()) 41 | } 42 | } 43 | 44 | private class A(val value: Int) 45 | 46 | private def testTryCatchWithRefResultType(): Unit = { 47 | val x = 48 | try { 49 | new A(doNotThrow()) 50 | } catch { 51 | case e: IllegalArgumentException => new A(20) 52 | } 53 | assertSame(5, x.value) 54 | 55 | val y = 56 | try { 57 | new A(throwIllegalArgument()) 58 | } catch { 59 | case e: IllegalArgumentException => new A(20) 60 | } 61 | assertSame(20, y.value) 62 | } 63 | 64 | private def testTryFinally(): Unit = { 65 | var didFinally = false 66 | 67 | try { 68 | try { 69 | throwIllegalArgument() 70 | fail() 71 | } finally { 72 | didFinally = true 73 | } 74 | fail() // the exception should have been rethrown 75 | } catch { 76 | case e: IllegalArgumentException => () 77 | } 78 | ok(didFinally) 79 | 80 | didFinally = false 81 | try { 82 | doNotThrow() 83 | } finally { 84 | didFinally = true 85 | } 86 | ok(didFinally) 87 | } 88 | 89 | private def throwIllegalArgument(): Int = 90 | throw new IllegalArgumentException("boom") 91 | 92 | private def doNotThrow(): Int = 5 93 | } 94 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ThrowablesTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import testsuite.Assert.ok 5 | 6 | /** Test that we can manipulate instances of exception classes, but not throw them. */ 7 | object ThrowablesTest { 8 | def main(): Unit = { 9 | testBasicThrowable() 10 | testBasicException() 11 | 12 | testEmptyStackTrace() 13 | } 14 | 15 | def testBasicThrowable(): Unit = { 16 | val t = new Throwable("boom") 17 | ok(t.getMessage() == "boom") 18 | ok(t.getCause() == null) 19 | ok(t.toString() == "java.lang.Throwable: boom") 20 | 21 | val t2 = new Throwable("derived", t) 22 | ok(t2.getMessage() == "derived") 23 | ok(t2.getCause() eq t) 24 | ok(t2.toString() == "java.lang.Throwable: derived") 25 | } 26 | 27 | def testBasicException(): Unit = { 28 | val t = new Exception("boom") 29 | ok(t.getMessage() == "boom") 30 | ok(t.getCause() == null) 31 | ok(t.toString() == "java.lang.Exception: boom") 32 | 33 | val t2 = new Exception("derived", t) 34 | ok(t2.getMessage() == "derived") 35 | ok(t2.getCause() eq t) 36 | ok(t2.toString() == "java.lang.Exception: derived") 37 | } 38 | 39 | def testEmptyStackTrace(): Unit = { 40 | val t = new Throwable("boom") 41 | val stack = t.getStackTrace() 42 | ok(stack.length == 0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ToStringTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object ToStringTest { 6 | def main(): Unit = { 7 | ok( 8 | testBoolean(true, "true") && 9 | testBoolean(false, "false") && 10 | !testBoolean(true, "tru") && // confidence test 11 | testAny(true, "true") && 12 | testAny(false, "false") && 13 | testChar('A', "A") && 14 | testAny('A', "A") && 15 | testByte(54.toByte, "54") && 16 | testAny(54.toByte, "54") && 17 | testShort(6543.toShort, "6543") && 18 | testAny(6543.toShort, "6543") && 19 | testInt(-3423456, "-3423456") && 20 | testAny(-3423456, "-3423456") && 21 | testLong(1234567891011L, "1234567891011") && 22 | testAny(1234567891011L, "1234567891011") && 23 | testFloat(1.5f, "1.5") && 24 | testAny(1.5f, "1.5") && 25 | testDouble(1.4, "1.4") && 26 | testAny(1.4, "1.4") && 27 | testString("foo", "foo") && 28 | testAny("foo", "foo") && 29 | testString(null, "null") && 30 | testAny(null, "null") && 31 | testUndef((), "undefined") && 32 | testAny((), "undefined") && 33 | testMyToString(new MyToString(), "my toString") && 34 | testMyToString(null, "null") && 35 | testAny(new MyToString(), "my toString") && 36 | testToStringNull(new ToStringNull(), "null") && 37 | testToStringNull(null, "null") && 38 | testAny(new ToStringNull(), "null") && 39 | testConcat(1, "foo", "1foo") && 40 | testConcat(2, null, "2null") 41 | ) 42 | } 43 | 44 | def testBoolean(x: Boolean, expected: String): Boolean = 45 | "" + x == expected 46 | 47 | def testChar(x: Char, expected: String): Boolean = 48 | "" + x == expected 49 | 50 | def testByte(x: Byte, expected: String): Boolean = 51 | "" + x == expected 52 | 53 | def testShort(x: Short, expected: String): Boolean = 54 | "" + x == expected 55 | 56 | def testInt(x: Int, expected: String): Boolean = 57 | "" + x == expected 58 | 59 | def testLong(x: Long, expected: String): Boolean = 60 | "" + x == expected 61 | 62 | def testFloat(x: Float, expected: String): Boolean = 63 | "" + x == expected 64 | 65 | def testDouble(x: Double, expected: String): Boolean = 66 | "" + x == expected 67 | 68 | def testString(x: String, expected: String): Boolean = 69 | "" + x == expected 70 | 71 | def testUndef(x: Unit, expected: String): Boolean = 72 | "" + x == expected 73 | 74 | def testMyToString(x: MyToString, expected: String): Boolean = 75 | "" + x == expected 76 | 77 | def testToStringNull(x: ToStringNull, expected: String): Boolean = 78 | "" + x == expected 79 | 80 | def testAny(x: Any, expected: String): Boolean = 81 | "" + x == expected 82 | 83 | def testConcat(x: Int, y: String, expected: String): Boolean = 84 | "" + x + y == expected 85 | 86 | class MyToString { 87 | @noinline 88 | override def toString(): String = "my toString" 89 | } 90 | 91 | class ToStringNull { 92 | @noinline 93 | override def toString(): String = null // evil 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/TryFinallyReturnTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.assertSame 4 | 5 | /** Imported from the Scala test case `test/files/run/finally.scala`. */ 6 | object TryFinallyReturnTest { 7 | // Simulate a `println` API that we can check afterwards 8 | var printlnOutput: String = "" 9 | 10 | def println(x: Any): Unit = 11 | printlnOutput = printlnOutput + x + "\n" 12 | 13 | val expectedOutput = raw"""Running throwCatchFinally 14 | hi 15 | In Finally 16 | java.lang.RuntimeException: ouch 17 | ---------------------------------------- 18 | Running retCatch 19 | java.lang.Exception 20 | in finally 21 | ---------------------------------------- 22 | Running throwCatch 23 | java.lang.Exception 24 | in finally 25 | CAUGHT: java.lang.Exception 26 | ---------------------------------------- 27 | Running retBody 28 | in finally 29 | ---------------------------------------- 30 | Running throwBody 31 | java.lang.Exception 32 | in finally 33 | ---------------------------------------- 34 | Running retFinally 35 | body 36 | in finally 1 37 | in finally 2 38 | ---------------------------------------- 39 | Running throwFinally 40 | body 41 | in finally 42 | java.lang.Exception 43 | ---------------------------------------- 44 | Running nestedFinallyBlocks 45 | in finally 1 46 | in finally 2 47 | ---------------------------------------- 48 | Running nestedFinallyBlocks2 49 | in try 1 50 | in finally 1 51 | in fall-through 52 | in finally 2 53 | ---------------------------------------- 54 | """ 55 | 56 | def main(): Unit = { 57 | test(throwCatchFinally(), "throwCatchFinally") 58 | test(retCatch(), "retCatch") 59 | test(throwCatch(), "throwCatch") 60 | test(retBody(), "retBody") 61 | test(throwBody(), "throwBody") 62 | test(retFinally(), "retFinally") 63 | test(throwFinally(), "throwFinally") 64 | test(nestedFinallyBlocks(), "nestedFinallyBlocks") 65 | test(nestedFinallyBlocks2(), "nestedFinallyBlocks2") 66 | 67 | assertSame(expectedOutput, printlnOutput) 68 | } 69 | 70 | // test that finally is not covered by any exception handlers. 71 | def throwCatchFinally(): Unit = { 72 | try { 73 | bar() 74 | } catch { 75 | case e: Throwable => println(e) 76 | } 77 | } 78 | 79 | // test that finally is not covered by any exception handlers. 80 | def bar(): Unit = { 81 | try { 82 | println("hi") 83 | } catch { 84 | case e: Throwable => println("SHOULD NOT GET HERE") 85 | } finally { 86 | println("In Finally") 87 | throw new RuntimeException("ouch") 88 | } 89 | } 90 | 91 | // return in catch (finally is executed) 92 | def retCatch(): Unit = { 93 | try { 94 | throw new Exception 95 | } catch { 96 | case e: Throwable => 97 | println(e); 98 | return 99 | } finally println("in finally") 100 | } 101 | 102 | // throw in catch (finally is executed, exception propagated) 103 | def throwCatch(): Unit = { 104 | try { 105 | throw new Exception 106 | } catch { 107 | case e: Throwable => 108 | println(e); 109 | throw e 110 | } finally println("in finally") 111 | } 112 | 113 | // return inside body (finally is executed) 114 | def retBody(): Unit = { 115 | try { 116 | return 117 | } catch { 118 | case e: Throwable => 119 | println(e); 120 | throw e 121 | } finally println("in finally") 122 | } 123 | 124 | // throw inside body (finally and catch are executed) 125 | def throwBody(): Unit = { 126 | try { 127 | throw new Exception 128 | } catch { 129 | case e: Throwable => 130 | println(e); 131 | } finally println("in finally") 132 | } 133 | 134 | // return inside finally (each finally is executed once) 135 | def retFinally(): Unit = { 136 | try { 137 | try println("body") 138 | finally { 139 | println("in finally 1") 140 | return 141 | } 142 | } finally println("in finally 2") 143 | } 144 | 145 | // throw inside finally (finally is executed once, exception is propagated) 146 | def throwFinally(): Unit = { 147 | try { 148 | try println("body") 149 | finally { 150 | println("in finally") 151 | throw new Exception 152 | } 153 | } catch { 154 | case e: Throwable => println(e) 155 | } 156 | } 157 | 158 | // nested finally blocks with return value 159 | def nestedFinallyBlocks(): Int = 160 | try { 161 | try { 162 | return 10 163 | } finally { 164 | try { () } 165 | catch { case _: Throwable => () } 166 | println("in finally 1") 167 | } 168 | } finally { 169 | println("in finally 2") 170 | } 171 | 172 | def nestedFinallyBlocks2(): Int = { 173 | try { 174 | try { 175 | println("in try 1") 176 | } finally { 177 | println("in finally 1") 178 | } 179 | println("in fall-through") 180 | 0 181 | } finally { 182 | println("in finally 2") 183 | } 184 | } 185 | 186 | def test[A](m: => A, name: String): Unit = { 187 | println("Running %s".format(name)) 188 | try { 189 | m 190 | } catch { 191 | case e: Throwable => println("CAUGHT: " + e) 192 | } 193 | println("-" * 40) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/UnitPatMatTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object UnitPatMatTest { 6 | def main(): Unit = { 7 | assertEquals("failure", 5, 5) 8 | } 9 | 10 | @noinline 11 | def assertEquals(message: String, expected: Any, actual: Any): Unit = { 12 | if (!java.util.Objects.equals(expected, actual)) { 13 | (expected, actual) match { 14 | case (expectedString: String, actualString: String) => 15 | val cleanMsg: String = if (message == null) "" else message 16 | throw new AssertionError(cleanMsg) 17 | 18 | case _ => 19 | failNotEquals(message, expected, actual) 20 | } 21 | } 22 | } 23 | 24 | @noinline 25 | def failNotEquals(message: String, expected: Any, actual: Any): Unit = 26 | throw new AssertionError(message) 27 | } 28 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object VirtualDispatch { 6 | def main(): Unit = { 7 | val a = new A 8 | val b = new B 9 | 10 | ok( 11 | testA(a) && 12 | testB(a, isInstanceOfA = true) && 13 | testB(b, isInstanceOfA = false) && 14 | testC(a, isInstanceOfA = true) && 15 | testC(b, isInstanceOfA = false) 16 | ) 17 | } 18 | 19 | def testA(a: A): Boolean = { 20 | a.a == 2 && a.impl == 2 && a.b == 1 && a.c == 1 21 | } 22 | 23 | def testB(b: B, isInstanceOfA: Boolean): Boolean = { 24 | if (isInstanceOfA) { 25 | b.b == 1 && b.c == 1 && b.impl == 2 26 | } else { 27 | b.b == 1 && b.c == 1 && b.impl == 0 28 | } 29 | } 30 | 31 | def testC(c: C, isInstanceOfA: Boolean): Boolean = { 32 | if (isInstanceOfA) { 33 | c.c == 1 && c.impl == 2 34 | } else { 35 | c.c == 1 && c.impl == 0 36 | } 37 | } 38 | 39 | class A extends B { 40 | def a: Int = 2 41 | override def impl = 2 42 | } 43 | 44 | class B extends C { 45 | def b: Int = 1 46 | override def c: Int = 1 47 | } 48 | 49 | abstract class C { 50 | def c: Int 51 | def impl: Int = 0 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/WrapUnwrapThrowableTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert.{assertSame, fail} 6 | 7 | object WrapUnwrapThrowableTest { 8 | def main(): Unit = { 9 | testWrapAsThrowable() 10 | testUnwrapFromThrowable() 11 | } 12 | 13 | def testWrapAsThrowable(): Unit = { 14 | // Wraps a js.Object 15 | val obj = new js.Object 16 | val e1 = js.special.wrapAsThrowable(obj) 17 | e1 match { 18 | case e1: js.JavaScriptException => assertSame(obj, e1.exception) 19 | case _ => fail() 20 | } 21 | 22 | // Wraps null 23 | val e2 = js.special.wrapAsThrowable(null) 24 | e2 match { 25 | case e2: js.JavaScriptException => assertSame(null, e2.exception) 26 | case _ => fail() 27 | } 28 | 29 | // Does not wrap a Throwable 30 | val th = new IllegalArgumentException 31 | assertSame(th, js.special.wrapAsThrowable(th)) 32 | 33 | // Does not double-wrap 34 | assertSame(e1, js.special.wrapAsThrowable(e1)) 35 | } 36 | 37 | def testUnwrapFromThrowable(): Unit = { 38 | // Unwraps a JavaScriptException 39 | val obj = new js.Object 40 | assertSame(obj, js.special.unwrapFromThrowable(new js.JavaScriptException(obj))) 41 | 42 | // Does not unwrap a Throwable 43 | val th = new IllegalArgumentException 44 | assertSame(th, js.special.unwrapFromThrowable(th)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /testrun.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test run 4 | 5 | 6 |

Look at the console

7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/src/test/scala/tests/CoreTests.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import scala.concurrent.Future 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation._ 7 | 8 | import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._ 9 | 10 | import org.scalajs.linker._ 11 | import org.scalajs.linker.interface._ 12 | import org.scalajs.linker.standard.WebAssemblyLinkerImpl 13 | 14 | import org.scalajs.logging._ 15 | 16 | /** The `tests/test` command compiles the Scala sources under `test-suite` into Wasm with static 17 | * initializers (see `cli/src/main/scala/Main.scala`), where these static initializers should point 18 | * to each main method in the classes of test-suites. 19 | * 20 | * These static initializers will be invoked from the start function in Wasm, which in turn will be 21 | * invoked on instantiation of the Wasm module. 22 | * 23 | * Once we compile the test-suites into Wasm and put them under the `./target` directory, 24 | * `tests/test` will instantiate those Wasm modules. If the test suites have an assertion failure, 25 | * the Wasm module will execute `unreachable` and fail during instantiation. 26 | */ 27 | class CoreTests extends munit.FunSuite { 28 | import CoreTests._ 29 | 30 | private val classpath = { 31 | val cpEnvVar = js.Dynamic.global.process.env.SCALAJS_CLASSPATH 32 | 33 | (cpEnvVar: Any) match { 34 | case cpEnvVar: String if cpEnvVar != "" => 35 | cpEnvVar.split(';').toList 36 | case _ => 37 | throw new IllegalArgumentException("The classpath was not provided.") 38 | } 39 | } 40 | 41 | private val globalIRCache = StandardImpl.irFileCache() 42 | 43 | private val linkerConfig = StandardConfig() 44 | .withESFeatures(_.withESVersion(ESVersion.ES2016)) // to be able to link `**` 45 | .withSemantics(_.optimized) // because that's the only thing we actually support at the moment 46 | .withModuleKind(ModuleKind.ESModule) 47 | .withOptimizer(false) 48 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")) 49 | 50 | TestSuites.suites.foreach { suite => 51 | test(suite.className) { 52 | testOneSuite(suite) 53 | } 54 | } 55 | 56 | private def testOneSuite(suite: TestSuites.TestSuite): Future[Unit] = { 57 | val className = suite.className 58 | val methodName = suite.methodName 59 | 60 | val cache = globalIRCache.newCache 61 | val irFilesFuture = NodeIRContainer.fromClasspath(classpath).map(_._1).flatMap(cache.cached _) 62 | 63 | val logger = new ScalaConsoleLogger(Level.Error) 64 | 65 | val linker = WebAssemblyLinkerImpl.linker(linkerConfig) 66 | val moduleInitializers = List(ModuleInitializer.mainMethod(className, methodName)) 67 | 68 | val outputDirRelToPwd = s"./target/$className/" 69 | createDir(outputDirRelToPwd) 70 | val output = NodeOutputDirectory(outputDirRelToPwd) 71 | 72 | val outputDirRelToMyJSFile = s"../../../../target/$className/" 73 | 74 | for { 75 | irFiles <- irFilesFuture 76 | linkerResult <- linker.link(irFiles, moduleInitializers, output, logger) 77 | moduleExports <- js.`import`[js.Dynamic](s"$outputDirRelToMyJSFile/main.mjs").toFuture 78 | } yield { 79 | for (postLoadTests <- suite.postLoadTests) 80 | postLoadTests(moduleExports) 81 | } 82 | } 83 | } 84 | 85 | object CoreTests { 86 | def createDir(dir: String): Unit = 87 | mkdirSync(dir, js.Dynamic.literal(recursive = true)) 88 | 89 | @js.native 90 | @JSImport("node:fs") 91 | def mkdirSync(path: String, options: js.Object = js.native): Unit = js.native 92 | } 93 | -------------------------------------------------------------------------------- /tests/src/test/scala/tests/TestSuites.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import scala.scalajs.js 4 | 5 | object TestSuites { 6 | case class TestSuite( 7 | className: String, 8 | methodName: String = "main", 9 | postLoadTests: Option[js.Dynamic => Unit] = None 10 | ) 11 | 12 | val suites = List( 13 | TestSuite("testsuite.core.Simple"), 14 | TestSuite("testsuite.core.Add"), 15 | TestSuite("testsuite.core.ArrayTest"), 16 | TestSuite("testsuite.core.ArrayReflectTest"), 17 | TestSuite("testsuite.core.BasicListTest"), 18 | TestSuite("testsuite.core.CloneTest"), 19 | TestSuite("testsuite.core.ControlStructuresTest"), 20 | TestSuite("testsuite.core.VirtualDispatch"), 21 | TestSuite("testsuite.core.InterfaceCall"), 22 | TestSuite("testsuite.core.AsInstanceOfTest"), 23 | TestSuite("testsuite.core.IsInstanceOfTest"), 24 | TestSuite("testsuite.core.ClassOfTest"), 25 | TestSuite("testsuite.core.ClosureTest"), 26 | TestSuite("testsuite.core.FieldsTest"), 27 | TestSuite("testsuite.core.FloatingPointRemTest"), 28 | TestSuite("testsuite.core.GetClassTest"), 29 | TestSuite("testsuite.core.JLObjectInstanceTest"), 30 | TestSuite("testsuite.core.JSForInTest"), 31 | TestSuite("testsuite.core.JSImportCallTest"), 32 | TestSuite("testsuite.core.JSImportMetaTest"), 33 | TestSuite("testsuite.core.JSInteropTest"), 34 | TestSuite("testsuite.core.HijackedClassesDispatchTest"), 35 | TestSuite("testsuite.core.HijackedClassesMonoTest"), 36 | TestSuite("testsuite.core.HijackedClassesUpcastTest"), 37 | TestSuite("testsuite.core.ImportTest"), 38 | TestSuite("testsuite.core.NonNativeJSClassTest"), 39 | TestSuite("testsuite.core.StaticFieldsTest"), 40 | TestSuite("testsuite.core.StaticMethodTest"), 41 | TestSuite("testsuite.core.ThrowAndTryTest"), 42 | TestSuite("testsuite.core.ThrowablesTest"), 43 | TestSuite("testsuite.core.TryFinallyReturnTest"), 44 | TestSuite("testsuite.core.ToStringTest"), 45 | TestSuite("testsuite.core.UnitPatMatTest"), 46 | TestSuite("testsuite.core.MatchTest"), 47 | TestSuite("testsuite.core.WrapUnwrapThrowableTest"), 48 | TestSuite("testsuite.core.StringEncodingTest"), 49 | TestSuite( 50 | "testsuite.core.JSExportTopLevelTest", 51 | postLoadTests = Some({ moduleExports => 52 | import munit.Assertions.assert 53 | 54 | assert((moduleExports.immutableField: Any) == "my immutable field value") 55 | assert((moduleExports.mutableField: Any) == 42) 56 | assert((moduleExports.simpleFunction(5): Any) == 25) 57 | assert((moduleExports.functionWithRest(3, 5, 6, 7): Any) == 54) 58 | assert((moduleExports.SimpleObject.bar: Any) == "the bar field") 59 | 60 | val obj = js.Dynamic.newInstance(moduleExports.SimpleClass)(456) 61 | assert((obj.foo: Any) == 456) 62 | }) 63 | ) 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | import java.nio.ByteBuffer 6 | import java.nio.charset.StandardCharsets 7 | 8 | import org.scalajs.logging.Logger 9 | 10 | import org.scalajs.linker._ 11 | import org.scalajs.linker.interface._ 12 | import org.scalajs.linker.interface.unstable._ 13 | import org.scalajs.linker.standard._ 14 | 15 | import org.scalajs.linker.backend.javascript.{ByteArrayWriter, SourceMapWriter} 16 | import org.scalajs.linker.backend.webassembly._ 17 | 18 | import org.scalajs.linker.backend.wasmemitter.Emitter 19 | 20 | final class WebAssemblyLinkerBackend( 21 | linkerConfig: StandardConfig, 22 | val coreSpec: CoreSpec 23 | ) extends LinkerBackend { 24 | require( 25 | linkerConfig.moduleKind == ModuleKind.ESModule, 26 | s"The WebAssembly backend only supports ES modules; was ${linkerConfig.moduleKind}." 27 | ) 28 | require( 29 | !linkerConfig.optimizer, 30 | "The WebAssembly backend does not support the optimizer yet." 31 | ) 32 | 33 | val loaderJSFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, "__loader") 34 | 35 | private val emitter: Emitter = 36 | new Emitter(Emitter.Config(coreSpec, loaderJSFileName)) 37 | 38 | val symbolRequirements: SymbolRequirement = emitter.symbolRequirements 39 | 40 | def injectedIRFiles: Seq[IRFile] = emitter.injectedIRFiles 41 | 42 | def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)(implicit 43 | ec: ExecutionContext 44 | ): Future[Report] = { 45 | val onlyModule = moduleSet.modules match { 46 | case onlyModule :: Nil => 47 | onlyModule 48 | case modules => 49 | throw new UnsupportedOperationException( 50 | "The WebAssembly backend does not support multiple modules. Found: " + 51 | modules.map(_.id.id).mkString(", ") 52 | ) 53 | } 54 | val moduleID = onlyModule.id.id 55 | 56 | val emitterResult = emitter.emit(onlyModule, logger) 57 | val wasmModule = emitterResult.wasmModule 58 | 59 | val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) 60 | 61 | val watFileName = s"$moduleID.wat" 62 | val wasmFileName = s"$moduleID.wasm" 63 | val sourceMapFileName = s"$wasmFileName.map" 64 | val jsFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, moduleID) 65 | 66 | val filesToProduce0 = Set( 67 | wasmFileName, 68 | loaderJSFileName, 69 | jsFileName 70 | ) 71 | val filesToProduce1 = 72 | if (linkerConfig.sourceMap) filesToProduce0 + sourceMapFileName 73 | else filesToProduce0 74 | val filesToProduce = 75 | if (linkerConfig.prettyPrint) filesToProduce1 + watFileName 76 | else filesToProduce1 77 | 78 | def maybeWriteWatFile(): Future[Unit] = { 79 | if (linkerConfig.prettyPrint) { 80 | val textOutput = new TextWriter(wasmModule).write() 81 | val textOutputBytes = textOutput.getBytes(StandardCharsets.UTF_8) 82 | outputImpl.writeFull(watFileName, ByteBuffer.wrap(textOutputBytes)) 83 | } else { 84 | Future.unit 85 | } 86 | } 87 | 88 | def writeWasmFile(): Future[Unit] = { 89 | val emitDebugInfo = !linkerConfig.minify 90 | 91 | if (linkerConfig.sourceMap) { 92 | val sourceMapWriter = new ByteArrayWriter 93 | 94 | val wasmFileURI = s"./$wasmFileName" 95 | val sourceMapURI = s"./$sourceMapFileName" 96 | 97 | val smWriter = 98 | new SourceMapWriter(sourceMapWriter, wasmFileURI, linkerConfig.relativizeSourceMapBase) 99 | val binaryOutput = new BinaryWriter.WithSourceMap( 100 | wasmModule, 101 | emitDebugInfo, 102 | smWriter, 103 | sourceMapURI 104 | ).write() 105 | smWriter.complete() 106 | 107 | outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)).flatMap { _ => 108 | outputImpl.writeFull(sourceMapFileName, sourceMapWriter.toByteBuffer()) 109 | } 110 | } else { 111 | val binaryOutput = new BinaryWriter(wasmModule, emitDebugInfo).write() 112 | outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)) 113 | } 114 | } 115 | 116 | def writeLoaderFile(): Future[Unit] = 117 | outputImpl.writeFull(loaderJSFileName, ByteBuffer.wrap(emitterResult.loaderContent)) 118 | 119 | def writeJSFile(): Future[Unit] = { 120 | val jsFileOutputBytes = emitterResult.jsFileContent.getBytes(StandardCharsets.UTF_8) 121 | outputImpl.writeFull(jsFileName, ByteBuffer.wrap(jsFileOutputBytes)) 122 | } 123 | 124 | for { 125 | existingFiles <- outputImpl.listFiles() 126 | _ <- Future.sequence(existingFiles.filterNot(filesToProduce).map(outputImpl.delete(_))) 127 | _ <- maybeWriteWatFile() 128 | _ <- writeWasmFile() 129 | _ <- writeLoaderFile() 130 | _ <- writeJSFile() 131 | } yield { 132 | val reportModule = new ReportImpl.ModuleImpl( 133 | moduleID, 134 | jsFileName, 135 | None, 136 | linkerConfig.moduleKind 137 | ) 138 | new ReportImpl(List(reportModule)) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | object EmbeddedConstants { 4 | /* Values returned by the `jsValueType` helper. 5 | * 6 | * 0: false 7 | * 1: true 8 | * 2: string 9 | * 3: number 10 | * 4: undefined 11 | * 5: everything else 12 | * 13 | * This encoding has the following properties: 14 | * 15 | * - false and true also return their value as the appropriate i32. 16 | * - the types implementing `Comparable` are consecutive from 0 to 3. 17 | */ 18 | 19 | final val JSValueTypeFalse = 0 20 | final val JSValueTypeTrue = 1 21 | final val JSValueTypeString = 2 22 | final val JSValueTypeNumber = 3 23 | final val JSValueTypeUndefined = 4 24 | final val JSValueTypeBigInt = 5 25 | final val JSValueTypeSymbol = 6 26 | final val JSValueTypeOther = 7 27 | 28 | // Values for `typeData.kind` 29 | 30 | final val KindVoid = 0 31 | final val KindBoolean = 1 32 | final val KindChar = 2 33 | final val KindByte = 3 34 | final val KindShort = 4 35 | final val KindInt = 5 36 | final val KindLong = 6 37 | final val KindFloat = 7 38 | final val KindDouble = 8 39 | final val KindArray = 9 40 | final val KindObject = 10 // j.l.Object 41 | final val KindBoxedUnit = 11 42 | final val KindBoxedBoolean = 12 43 | final val KindBoxedCharacter = 13 44 | final val KindBoxedByte = 14 45 | final val KindBoxedShort = 15 46 | final val KindBoxedInteger = 16 47 | final val KindBoxedLong = 17 48 | final val KindBoxedFloat = 18 49 | final val KindBoxedDouble = 19 50 | final val KindBoxedString = 20 51 | final val KindClass = 21 52 | final val KindInterface = 22 53 | final val KindJSType = 23 54 | 55 | final val KindLastPrimitive = KindDouble 56 | } 57 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import org.scalajs.ir.Names._ 4 | import org.scalajs.ir.Types._ 5 | import org.scalajs.ir.OriginalName 6 | import org.scalajs.ir.Position 7 | 8 | import org.scalajs.linker.interface._ 9 | import org.scalajs.linker.interface.unstable._ 10 | import org.scalajs.linker.standard._ 11 | import org.scalajs.linker.standard.ModuleSet.ModuleID 12 | 13 | import org.scalajs.linker.backend.webassembly.FunctionBuilder 14 | import org.scalajs.linker.backend.webassembly.{Instructions => wa} 15 | import org.scalajs.linker.backend.webassembly.{Modules => wamod} 16 | import org.scalajs.linker.backend.webassembly.{Types => watpe} 17 | 18 | import org.scalajs.logging.Logger 19 | 20 | import SpecialNames._ 21 | import VarGen._ 22 | 23 | final class Emitter(config: Emitter.Config) { 24 | import Emitter._ 25 | 26 | private val classEmitter = new ClassEmitter(config.coreSpec) 27 | 28 | val symbolRequirements: SymbolRequirement = 29 | Emitter.symbolRequirements(config.coreSpec) 30 | 31 | // Our injected IR files are handled by WebAssemblyStandardLinkerImpl instead 32 | def injectedIRFiles: Seq[IRFile] = Nil 33 | 34 | def emit(module: ModuleSet.Module, logger: Logger): Result = { 35 | /* Sort by ancestor count so that superclasses always appear before 36 | * subclasses, then tie-break by name for stability. 37 | */ 38 | val sortedClasses = module.classDefs.sortWith { (a, b) => 39 | val cmp = Integer.compare(a.ancestors.size, b.ancestors.size) 40 | if (cmp != 0) cmp < 0 41 | else a.className.compareTo(b.className) < 0 42 | } 43 | 44 | implicit val ctx: WasmContext = 45 | Preprocessor.preprocess(sortedClasses, module.topLevelExports) 46 | 47 | // Sort for stability 48 | val allImportedModules: List[String] = module.externalDependencies.toList.sorted 49 | 50 | // Gen imports of external modules on the Wasm side 51 | for (moduleName <- allImportedModules) { 52 | val id = genGlobalID.forImportedModule(moduleName) 53 | val origName = OriginalName("import." + moduleName) 54 | ctx.moduleBuilder.addImport( 55 | wamod.Import( 56 | "__scalaJSImports", 57 | moduleName, 58 | wamod.ImportDesc.Global(id, origName, watpe.RefType.anyref, isMutable = false) 59 | ) 60 | ) 61 | } 62 | 63 | CoreWasmLib.genPreClasses() 64 | sortedClasses.foreach { clazz => 65 | classEmitter.genClassDef(clazz) 66 | } 67 | module.topLevelExports.foreach { tle => 68 | classEmitter.genTopLevelExport(tle) 69 | } 70 | CoreWasmLib.genPostClasses() 71 | 72 | complete( 73 | sortedClasses, 74 | module.initializers.toList, 75 | module.topLevelExports 76 | ) 77 | 78 | val wasmModule = ctx.moduleBuilder.build() 79 | 80 | val loaderContent = LoaderContent.bytesContent 81 | val jsFileContent = 82 | buildJSFileContent(module, module.id.id + ".wasm", allImportedModules) 83 | 84 | new Result(wasmModule, loaderContent, jsFileContent) 85 | } 86 | 87 | private def complete( 88 | sortedClasses: List[LinkedClass], 89 | moduleInitializers: List[ModuleInitializer.Initializer], 90 | topLevelExportDefs: List[LinkedTopLevelExport] 91 | )(implicit ctx: WasmContext): Unit = { 92 | /* Before generating the string globals in `genStartFunction()`, make sure 93 | * to allocate the ones that will be required by the module initializers. 94 | */ 95 | for (init <- moduleInitializers) { 96 | ModuleInitializerImpl.fromInitializer(init) match { 97 | case ModuleInitializerImpl.MainMethodWithArgs(_, _, args) => 98 | args.foreach(ctx.addConstantStringGlobal(_)) 99 | case ModuleInitializerImpl.VoidMainMethod(_, _) => 100 | () // nothing to do 101 | } 102 | } 103 | 104 | // string 105 | val (stringPool, stringPoolCount) = ctx.getFinalStringPool() 106 | ctx.moduleBuilder.addData( 107 | wamod.Data( 108 | genDataID.string, 109 | OriginalName("stringPool"), 110 | stringPool, 111 | wamod.Data.Mode.Passive 112 | ) 113 | ) 114 | ctx.addGlobal( 115 | wamod.Global( 116 | genGlobalID.stringLiteralCache, 117 | OriginalName("stringLiteralCache"), 118 | watpe.RefType(genTypeID.anyArray), 119 | wa.Expr( 120 | List( 121 | wa.I32Const(stringPoolCount), 122 | wa.ArrayNewDefault(genTypeID.anyArray) 123 | ) 124 | ), 125 | isMutable = false 126 | ) 127 | ) 128 | 129 | genStartFunction(sortedClasses, moduleInitializers, topLevelExportDefs) 130 | genDeclarativeElements() 131 | } 132 | 133 | private def genStartFunction( 134 | sortedClasses: List[LinkedClass], 135 | moduleInitializers: List[ModuleInitializer.Initializer], 136 | topLevelExportDefs: List[LinkedTopLevelExport] 137 | )(implicit ctx: WasmContext): Unit = { 138 | import org.scalajs.ir.Trees._ 139 | 140 | implicit val pos = Position.NoPosition 141 | 142 | val fb = 143 | new FunctionBuilder(ctx.moduleBuilder, genFunctionID.start, OriginalName("start"), pos) 144 | 145 | // Initialize itables 146 | for (clazz <- sortedClasses if clazz.kind.isClass && clazz.hasDirectInstances) { 147 | val className = clazz.className 148 | val classInfo = ctx.getClassInfo(className) 149 | 150 | if (classInfo.classImplementsAnyInterface) { 151 | val interfaces = clazz.ancestors.map(ctx.getClassInfo(_)).filter(_.isInterface) 152 | val resolvedMethodInfos = classInfo.resolvedMethodInfos 153 | 154 | interfaces.foreach { iface => 155 | val idx = ctx.getItableIdx(iface) 156 | fb += wa.GlobalGet(genGlobalID.forITable(className)) 157 | fb += wa.I32Const(idx) 158 | 159 | for (method <- iface.tableEntries) 160 | fb += ctx.refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryID) 161 | fb += wa.StructNew(genTypeID.forITable(iface.name)) 162 | fb += wa.ArraySet(genTypeID.itables) 163 | } 164 | } 165 | } 166 | 167 | locally { 168 | // For array classes, resolve methods in jl.Object 169 | val globalID = genGlobalID.arrayClassITable 170 | val resolvedMethodInfos = ctx.getClassInfo(ObjectClass).resolvedMethodInfos 171 | 172 | for { 173 | interfaceName <- List(SerializableClass, CloneableClass) 174 | // Use getClassInfoOption in case the reachability analysis got rid of those interfaces 175 | interfaceInfo <- ctx.getClassInfoOption(interfaceName) 176 | } { 177 | fb += wa.GlobalGet(globalID) 178 | fb += wa.I32Const(ctx.getItableIdx(interfaceInfo)) 179 | 180 | for (method <- interfaceInfo.tableEntries) 181 | fb += ctx.refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryID) 182 | fb += wa.StructNew(genTypeID.forITable(interfaceName)) 183 | fb += wa.ArraySet(genTypeID.itables) 184 | } 185 | } 186 | 187 | // Initialize the JS private field symbols 188 | 189 | for (clazz <- sortedClasses if clazz.kind.isJSClass) { 190 | for (fieldDef <- clazz.fields) { 191 | fieldDef match { 192 | case FieldDef(flags, name, _, _) if !flags.namespace.isStatic => 193 | fb += wa.Call(genFunctionID.newSymbol) 194 | fb += wa.GlobalSet(genGlobalID.forJSPrivateField(name.name)) 195 | case _ => 196 | () 197 | } 198 | } 199 | } 200 | 201 | // Emit the static initializers 202 | 203 | for (clazz <- sortedClasses if clazz.hasStaticInitializer) { 204 | val funcID = genFunctionID.forMethod( 205 | MemberNamespace.StaticConstructor, 206 | clazz.className, 207 | StaticInitializerName 208 | ) 209 | fb += wa.Call(funcID) 210 | } 211 | 212 | // Initialize the top-level exports that require it 213 | 214 | for (tle <- topLevelExportDefs) { 215 | // Load the (initial) exported value on the stack 216 | tle.tree match { 217 | case TopLevelJSClassExportDef(_, exportName) => 218 | fb += wa.Call(genFunctionID.loadJSClass(tle.owningClass)) 219 | case TopLevelModuleExportDef(_, exportName) => 220 | fb += wa.Call(genFunctionID.loadModule(tle.owningClass)) 221 | case TopLevelMethodExportDef(_, methodDef) => 222 | fb += ctx.refFuncWithDeclaration(genFunctionID.forExport(tle.exportName)) 223 | if (methodDef.restParam.isDefined) { 224 | fb += wa.I32Const(methodDef.args.size) 225 | fb += wa.Call(genFunctionID.makeExportedDefRest) 226 | } else { 227 | fb += wa.Call(genFunctionID.makeExportedDef) 228 | } 229 | case TopLevelFieldExportDef(_, _, fieldIdent) => 230 | /* Usually redundant, but necessary if the static field is never 231 | * explicitly set and keeps its default (zero) value instead. In that 232 | * case this initial call is required to publish that zero value (as 233 | * opposed to the default `undefined` value of the JS `let`). 234 | */ 235 | fb += wa.GlobalGet(genGlobalID.forStaticField(fieldIdent.name)) 236 | } 237 | 238 | // Call the export setter 239 | fb += wa.Call(genFunctionID.forTopLevelExportSetter(tle.exportName)) 240 | } 241 | 242 | // Emit the module initializers 243 | 244 | moduleInitializers.foreach { init => 245 | def genCallStatic(className: ClassName, methodName: MethodName): Unit = { 246 | val funcID = genFunctionID.forMethod(MemberNamespace.PublicStatic, className, methodName) 247 | fb += wa.Call(funcID) 248 | } 249 | 250 | ModuleInitializerImpl.fromInitializer(init) match { 251 | case ModuleInitializerImpl.MainMethodWithArgs(className, encodedMainMethodName, args) => 252 | // vtable of Array[String] 253 | fb += wa.GlobalGet(genGlobalID.forVTable(BoxedStringClass)) 254 | fb += wa.I32Const(1) 255 | fb += wa.Call(genFunctionID.arrayTypeData) 256 | 257 | // itable of Array[String] 258 | fb += wa.GlobalGet(genGlobalID.arrayClassITable) 259 | 260 | // underlying array of args 261 | args.foreach(arg => fb ++= ctx.getConstantStringInstr(arg)) 262 | fb += wa.ArrayNewFixed(genTypeID.anyArray, args.size) 263 | 264 | // array object 265 | val stringArrayTypeRef = ArrayTypeRef(ClassRef(BoxedStringClass), 1) 266 | fb += wa.StructNew(genTypeID.forArrayClass(stringArrayTypeRef)) 267 | 268 | // call 269 | genCallStatic(className, encodedMainMethodName) 270 | 271 | case ModuleInitializerImpl.VoidMainMethod(className, encodedMainMethodName) => 272 | genCallStatic(className, encodedMainMethodName) 273 | } 274 | } 275 | 276 | // Finish the start function 277 | 278 | fb.buildAndAddToModule() 279 | ctx.moduleBuilder.setStart(genFunctionID.start) 280 | } 281 | 282 | private def genDeclarativeElements()(implicit ctx: WasmContext): Unit = { 283 | // Aggregated Elements 284 | 285 | val funcDeclarations = ctx.getAllFuncDeclarations() 286 | 287 | if (funcDeclarations.nonEmpty) { 288 | /* Functions that are referred to with `ref.func` in the Code section 289 | * must be declared ahead of time in one of the earlier sections 290 | * (otherwise the module does not validate). It can be the Global section 291 | * if they are meaningful there (which is why `ref.func` in the vtables 292 | * work out of the box). In the absence of any other specific place, an 293 | * Element section with the declarative mode is the recommended way to 294 | * introduce these declarations. 295 | */ 296 | val exprs = funcDeclarations.map { funcID => 297 | wa.Expr(List(wa.RefFunc(funcID))) 298 | } 299 | ctx.moduleBuilder.addElement( 300 | wamod.Element(watpe.RefType.funcref, exprs, wamod.Element.Mode.Declarative) 301 | ) 302 | } 303 | } 304 | 305 | private def buildJSFileContent( 306 | module: ModuleSet.Module, 307 | wasmFileName: String, 308 | importedModules: List[String] 309 | ): String = { 310 | val (moduleImports, importedModulesItems) = (for { 311 | (moduleName, idx) <- importedModules.zipWithIndex 312 | } yield { 313 | val identName = s"imported$idx" 314 | val escapedModuleName = "\"" + moduleName + "\"" 315 | val moduleImport = s"import * as $identName from $escapedModuleName" 316 | val item = s" $escapedModuleName: $identName," 317 | (moduleImport, item) 318 | }).unzip 319 | 320 | val (exportDecls, exportSetters) = (for { 321 | exportName <- module.topLevelExports.map(_.exportName) 322 | } yield { 323 | val identName = s"exported$exportName" 324 | val decl = s"let $identName;\nexport { $identName as $exportName };" 325 | val setter = s" $exportName: (x) => $identName = x," 326 | (decl, setter) 327 | }).unzip 328 | 329 | s""" 330 | |${moduleImports.mkString("\n")} 331 | | 332 | |import { load as __load } from './${config.loaderModuleName}'; 333 | | 334 | |${exportDecls.mkString("\n")} 335 | | 336 | |await __load('./${wasmFileName}', { 337 | |${importedModulesItems.mkString("\n")} 338 | |}, { 339 | |${exportSetters.mkString("\n")} 340 | |}); 341 | """.stripMargin.trim() + "\n" 342 | } 343 | } 344 | 345 | object Emitter { 346 | 347 | /** Configuration for the Emitter. */ 348 | final class Config private ( 349 | val coreSpec: CoreSpec, 350 | val loaderModuleName: String 351 | ) 352 | 353 | object Config { 354 | def apply(coreSpec: CoreSpec, loaderModuleName: String): Config = 355 | new Config(coreSpec, loaderModuleName) 356 | } 357 | 358 | final class Result( 359 | val wasmModule: wamod.Module, 360 | val loaderContent: Array[Byte], 361 | val jsFileContent: String 362 | ) 363 | 364 | /** Builds the symbol requirements of our back-end. 365 | * 366 | * The symbol requirements tell the LinkerFrontend that we need these symbols to always be 367 | * reachable, even if no "user-land" IR requires them. They are roots for the reachability 368 | * analysis, together with module initializers and top-level exports. If we don't do this, the 369 | * linker frontend will dead-code eliminate our box classes. 370 | */ 371 | private def symbolRequirements(coreSpec: CoreSpec): SymbolRequirement = { 372 | val factory = SymbolRequirement.factory("wasm") 373 | 374 | factory.multiple( 375 | factory.instantiateClass(ClassClass, ClassCtor), 376 | factory.instantiateClass(CharBoxClass, CharBoxCtor), 377 | factory.instantiateClass(LongBoxClass, LongBoxCtor), 378 | 379 | // See genIdentityHashCode in HelperFunctions 380 | factory.callMethodStatically(BoxedDoubleClass, hashCodeMethodName), 381 | factory.callMethodStatically(BoxedStringClass, hashCodeMethodName) 382 | ) 383 | } 384 | 385 | } 386 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LibraryPatches.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | import org.scalajs.ir.ClassKind._ 6 | import org.scalajs.ir.Names._ 7 | import org.scalajs.ir.OriginalName 8 | import org.scalajs.ir.OriginalName.NoOriginalName 9 | import org.scalajs.ir.Position 10 | import org.scalajs.ir.Trees._ 11 | import org.scalajs.ir.Types._ 12 | import org.scalajs.ir.{EntryPointsInfo, Version} 13 | 14 | import org.scalajs.linker.interface.IRFile 15 | import org.scalajs.linker.interface.unstable.IRFileImpl 16 | 17 | import SpecialNames._ 18 | 19 | /** Patches that we apply to the standard library classes to make them wasm-friendly. */ 20 | object LibraryPatches { 21 | def patchIRFiles(irFiles: Seq[IRFile])(implicit ec: ExecutionContext): Future[Seq[IRFile]] = { 22 | val derivedIRFiles: Future[Seq[Option[IRFile]]] = Future.traverse(irFiles) { irFile => 23 | val irFileImpl = IRFileImpl.fromIRFile(irFile) 24 | irFileImpl.entryPointsInfo.flatMap { entryPointsInfo => 25 | entryPointsInfo.className match { 26 | case BoxedCharacterClass | BoxedLongClass => 27 | irFileImpl.tree.map { classDef => 28 | Some(new MemClassDefIRFile(deriveBoxClass(classDef))) 29 | } 30 | case _ => 31 | Future.successful(None) 32 | } 33 | } 34 | } 35 | 36 | val leanerJSExceptionIRFile = 37 | org.scalajs.linker.backend.emitter.PrivateLibHolder.files.find { irFile => 38 | IRFileImpl.fromIRFile(irFile).path.contains("JavaScriptException") 39 | }.get 40 | 41 | derivedIRFiles.map { derived => 42 | derived.flatten ++ Seq(StackTraceIRFile) ++ irFiles ++ Seq(leanerJSExceptionIRFile) 43 | } 44 | } 45 | 46 | private val EAF = ApplyFlags.empty 47 | private val EMF = MemberFlags.empty 48 | private val EOH = OptimizerHints.empty 49 | private val NON = NoOriginalName 50 | private val NOV = Version.Unversioned 51 | 52 | private def trivialCtor(enclosingClassName: ClassName)(implicit pos: Position): MethodDef = { 53 | val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) 54 | MethodDef( 55 | flags, 56 | MethodIdent(NoArgConstructorName), 57 | NON, 58 | Nil, 59 | NoType, 60 | Some( 61 | ApplyStatically( 62 | EAF.withConstructor(true), 63 | This()(ClassType(enclosingClassName)), 64 | ObjectClass, 65 | MethodIdent(NoArgConstructorName), 66 | Nil 67 | )(NoType) 68 | ) 69 | )(EOH, NOV) 70 | } 71 | 72 | private val StackTraceIRFile: IRFile = { 73 | implicit val noPosition: Position = Position.NoPosition 74 | 75 | val StackTraceModuleClass = ClassName("java.lang.StackTrace$") 76 | val StackTraceElementClass = ClassName("java.lang.StackTraceElement") 77 | 78 | val arrayOfSTERef = ArrayTypeRef(ClassRef(StackTraceElementClass), 1) 79 | 80 | val O = ClassRef(ObjectClass) 81 | 82 | val classDef = ClassDef( 83 | ClassIdent(StackTraceModuleClass), 84 | NON, 85 | ModuleClass, 86 | None, 87 | Some(ClassIdent(ObjectClass)), 88 | Nil, 89 | None, 90 | None, 91 | Nil, 92 | List( 93 | trivialCtor(StackTraceModuleClass), 94 | MethodDef( 95 | EMF, 96 | MethodIdent(MethodName("captureJSError", List(ClassRef(ThrowableClass)), O)), 97 | NON, 98 | List( 99 | ParamDef(LocalIdent(LocalName("th")), NON, ClassType(ThrowableClass), mutable = false) 100 | ), 101 | AnyType, 102 | Some( 103 | JSNew(JSGlobalRef("Error"), Nil) 104 | ) 105 | )(EOH.withInline(true), NOV), 106 | MethodDef( 107 | EMF, 108 | MethodIdent(MethodName("extract", List(O), arrayOfSTERef)), 109 | NON, 110 | List(ParamDef(LocalIdent(LocalName("jsError")), NON, AnyType, mutable = false)), 111 | ArrayType(arrayOfSTERef), 112 | Some( 113 | ArrayValue(arrayOfSTERef, Nil) 114 | ) 115 | )(EOH.withInline(true), NOV), 116 | MethodDef( 117 | EMF, 118 | MethodIdent(MethodName("getCurrentStackTrace", Nil, arrayOfSTERef)), 119 | NON, 120 | Nil, 121 | ArrayType(arrayOfSTERef), 122 | Some( 123 | ArrayValue(arrayOfSTERef, Nil) 124 | ) 125 | )(EOH.withInline(true), NOV) 126 | ), 127 | None, 128 | Nil, 129 | Nil, 130 | Nil 131 | )(EOH) 132 | 133 | new MemClassDefIRFile(classDef) 134 | } 135 | 136 | /** Generates the accompanying Box class of `Character` or `Long`. 137 | * 138 | * These box classes will be used as the generic representation of `char`s and `long`s when they 139 | * are upcast to `java.lang.Character`/`java.lang.Long` or any of their supertypes. 140 | * 141 | * The generated Box classes mimic the public structure of the corresponding hijacked classes. 142 | * Whereas the hijacked classes instances *are* the primitives (conceptually), the box classes 143 | * contain an explicit `value` field of the primitive type. They delegate all their instance 144 | * methods to the corresponding methods of the hijacked class, applied on the `value` primitive. 145 | * 146 | * For example, given the hijacked class 147 | * 148 | * {{{ 149 | * hijacked class Long extends java.lang.Number with Comparable { 150 | * def longValue;J(): long = this.asInstanceOf[long] 151 | * def toString;T(): string = Long$.toString(this.longValue;J()) 152 | * def compareTo;jlLong;Z(that: java.lang.Long): boolean = 153 | * Long$.compare(this.longValue;J(), that.longValue;J()) 154 | * } 155 | * }}} 156 | * 157 | * we generate 158 | * 159 | * {{{ 160 | * class LongBox extends java.lang.Number with Comparable { 161 | * val value: long 162 | * def (value: long) = { this.value = value } 163 | * def longValue;J(): long = this.value.longValue;J() 164 | * def toString;T(): string = this.value.toString;J() 165 | * def compareTo;jlLong;Z(that: jlLong): boolean = 166 | * this.value.compareTo;jlLong;Z(that) 167 | * } 168 | * }}} 169 | */ 170 | private def deriveBoxClass(classDef: ClassDef): ClassDef = { 171 | implicit val pos: Position = classDef.pos 172 | 173 | val className = classDef.className 174 | val derivedClassName = className.withSuffix("Box") 175 | val primType = BoxedClassToPrimType(className).asInstanceOf[PrimTypeWithRef] 176 | val derivedClassType = ClassType(derivedClassName) 177 | 178 | val fieldName = FieldName(derivedClassName, valueFieldSimpleName) 179 | val fieldIdent = FieldIdent(fieldName) 180 | 181 | val derivedFields: List[FieldDef] = List( 182 | FieldDef(EMF, fieldIdent, NON, primType) 183 | ) 184 | 185 | val selectField = Select(This()(derivedClassType), fieldIdent)(primType) 186 | 187 | val ctorParamDef = 188 | ParamDef(LocalIdent(fieldName.simpleName.toLocalName), NON, primType, mutable = false) 189 | val derivedCtor = MethodDef( 190 | EMF.withNamespace(MemberNamespace.Constructor), 191 | MethodIdent(MethodName.constructor(List(primType.primRef))), 192 | NON, 193 | List(ctorParamDef), 194 | NoType, 195 | Some( 196 | Assign(selectField, ctorParamDef.ref) 197 | ) 198 | )(EOH, NOV) 199 | 200 | val derivedMethods: List[MethodDef] = for { 201 | method <- classDef.methods if method.flags.namespace == MemberNamespace.Public 202 | } yield { 203 | MethodDef( 204 | method.flags, 205 | method.name, 206 | method.originalName, 207 | method.args, 208 | method.resultType, 209 | Some( 210 | Apply( 211 | EAF, 212 | selectField, 213 | method.name, 214 | method.args.map(_.ref) 215 | )(method.resultType) 216 | ) 217 | )(method.optimizerHints, method.version) 218 | } 219 | 220 | locally { 221 | import classDef.{pos => _, _} 222 | 223 | ClassDef( 224 | ClassIdent(derivedClassName), 225 | NON, 226 | Class, 227 | None, 228 | superClass, 229 | interfaces, 230 | None, 231 | None, 232 | derivedFields, 233 | derivedCtor :: derivedMethods, 234 | None, 235 | Nil, 236 | Nil, 237 | Nil 238 | )(EOH) 239 | } 240 | } 241 | 242 | /** An in-memory IRFile for a ClassDef. 243 | * 244 | * Adapted from Scala.js upstream. 245 | */ 246 | private final class MemClassDefIRFile(classDef: ClassDef) 247 | extends IRFileImpl("mem://" + classDef.name.name + ".sjsir", Version.Unversioned) { 248 | 249 | def tree(implicit ec: ExecutionContext): Future[ClassDef] = 250 | Future(classDef) 251 | 252 | def entryPointsInfo(implicit ec: ExecutionContext): Future[EntryPointsInfo] = 253 | tree.map(EntryPointsInfo.forClassDef) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import java.nio.charset.StandardCharsets 4 | 5 | import EmbeddedConstants._ 6 | 7 | /** Contents of the `__loader.js` file that we emit in every output. */ 8 | object LoaderContent { 9 | val bytesContent: Array[Byte] = 10 | stringContent.getBytes(StandardCharsets.UTF_8) 11 | 12 | private def stringContent: String = { 13 | raw""" 14 | // This implementation follows no particular specification, but is the same as the JS backend. 15 | // It happens to coincide with java.lang.Long.hashCode() for common values. 16 | function bigintHashCode(x) { 17 | var res = 0; 18 | if (x < 0n) 19 | x = ~x; 20 | while (x !== 0n) { 21 | res ^= Number(BigInt.asIntN(32, x)); 22 | x >>= 32n; 23 | } 24 | return res; 25 | } 26 | 27 | // JSSuperSelect support -- directly copied from the output of the JS backend 28 | function resolveSuperRef(superClass, propName) { 29 | var getPrototypeOf = Object.getPrototyeOf; 30 | var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 31 | var superProto = superClass.prototype; 32 | while (superProto !== null) { 33 | var desc = getOwnPropertyDescriptor(superProto, propName); 34 | if (desc !== (void 0)) { 35 | return desc; 36 | } 37 | superProto = getPrototypeOf(superProto); 38 | } 39 | } 40 | function superGet(superClass, self, propName) { 41 | var desc = resolveSuperRef(superClass, propName); 42 | if (desc !== (void 0)) { 43 | var getter = desc.get; 44 | return getter !== (void 0) ? getter.call(self) : getter.value; 45 | } 46 | } 47 | function superSet(superClass, self, propName, value) { 48 | var desc = resolveSuperRef(superClass, propName); 49 | if (desc !== (void 0)) { 50 | var setter = desc.set; 51 | if (setter !== (void 0)) { 52 | setter.call(self, value); 53 | return; 54 | } 55 | } 56 | throw new TypeError("super has no setter '" + propName + "'."); 57 | } 58 | 59 | const linkingInfo = Object.freeze({ 60 | "esVersion": 6, 61 | "assumingES6": true, 62 | "productionMode": false, 63 | "linkerVersion": "1.15.0", 64 | "fileLevelThis": this 65 | }); 66 | 67 | const scalaJSHelpers = { 68 | // JSTag 69 | JSTag: WebAssembly.JSTag, 70 | 71 | // BinaryOp.=== 72 | is: Object.is, 73 | 74 | // undefined 75 | undef: void 0, 76 | isUndef: (x) => x === (void 0), 77 | 78 | // Zero boxes 79 | bFalse: false, 80 | bZero: 0, 81 | 82 | // Boxes (upcast) -- most are identity at the JS level but with different types in Wasm 83 | bZ: (x) => x !== 0, 84 | bB: (x) => x, 85 | bS: (x) => x, 86 | bI: (x) => x, 87 | bF: (x) => x, 88 | bD: (x) => x, 89 | 90 | // Unboxes (downcast, null is converted to the zero of the type) 91 | uZ: (x) => x | 0, 92 | uB: (x) => (x << 24) >> 24, 93 | uS: (x) => (x << 16) >> 16, 94 | uI: (x) => x | 0, 95 | uF: (x) => Math.fround(x), 96 | uD: (x) => +x, 97 | 98 | // Type tests 99 | tZ: (x) => typeof x === 'boolean', 100 | tB: (x) => typeof x === 'number' && Object.is((x << 24) >> 24, x), 101 | tS: (x) => typeof x === 'number' && Object.is((x << 16) >> 16, x), 102 | tI: (x) => typeof x === 'number' && Object.is(x | 0, x), 103 | tF: (x) => typeof x === 'number' && (Math.fround(x) === x || x !== x), 104 | tD: (x) => typeof x === 'number', 105 | 106 | // fmod, to implement Float_% and Double_% (it is apparently quite hard to implement fmod otherwise) 107 | fmod: (x, y) => x % y, 108 | 109 | // Closure 110 | closure: (f, data) => f.bind(void 0, data), 111 | closureThis: (f, data) => function(...args) { return f(data, this, ...args); }, 112 | closureRest: (f, data, n) => ((...args) => f(data, ...args.slice(0, n), args.slice(n))), 113 | closureThisRest: (f, data, n) => function(...args) { return f(data, this, ...args.slice(0, n), args.slice(n)); }, 114 | 115 | // Top-level exported defs -- they must be `function`s but have no actual `this` nor `data` 116 | makeExportedDef: (f) => function(...args) { return f(...args); }, 117 | makeExportedDefRest: (f, n) => function(...args) { return f(...args.slice(0, n), args.slice(n)); }, 118 | 119 | // Strings 120 | emptyString: "", 121 | stringLength: (s) => s.length, 122 | stringCharAt: (s, i) => s.charCodeAt(i), 123 | jsValueToString: (x) => (x === void 0) ? "undefined" : x.toString(), 124 | jsValueToStringForConcat: (x) => "" + x, 125 | booleanToString: (b) => b ? "true" : "false", 126 | charToString: (c) => String.fromCharCode(c), 127 | intToString: (i) => "" + i, 128 | longToString: (l) => "" + l, // l must be a bigint here 129 | doubleToString: (d) => "" + d, 130 | stringConcat: (x, y) => ("" + x) + y, // the added "" is for the case where x === y === null 131 | isString: (x) => typeof x === 'string', 132 | 133 | // Get the type of JS value of `x` in a single JS helper call, for the purpose of dispatch. 134 | jsValueType: (x) => { 135 | if (typeof x === 'number') 136 | return $JSValueTypeNumber; 137 | if (typeof x === 'string') 138 | return $JSValueTypeString; 139 | if (typeof x === 'boolean') 140 | return x | 0; // JSValueTypeFalse or JSValueTypeTrue 141 | if (typeof x === 'undefined') 142 | return $JSValueTypeUndefined; 143 | if (typeof x === 'bigint') 144 | return $JSValueTypeBigInt; 145 | if (typeof x === 'symbol') 146 | return $JSValueTypeSymbol; 147 | return $JSValueTypeOther; 148 | }, 149 | 150 | // Identity hash code 151 | bigintHashCode: bigintHashCode, 152 | symbolDescription: (x) => { 153 | var desc = x.description; 154 | return (desc === void 0) ? null : desc; 155 | }, 156 | idHashCodeGet: (map, obj) => map.get(obj) | 0, // undefined becomes 0 157 | idHashCodeSet: (map, obj, value) => map.set(obj, value), 158 | 159 | // JS interop 160 | jsGlobalRefGet: (globalRefName) => (new Function("return " + globalRefName))(), 161 | jsGlobalRefSet: (globalRefName, v) => { 162 | var argName = globalRefName === 'v' ? 'w' : 'v'; 163 | (new Function(argName, globalRefName + " = " + argName))(v); 164 | }, 165 | jsGlobalRefTypeof: (globalRefName) => (new Function("return typeof " + globalRefName))(), 166 | jsNewArray: () => [], 167 | jsArrayPush: (a, v) => (a.push(v), a), 168 | jsArraySpreadPush: (a, vs) => (a.push(...vs), a), 169 | jsNewObject: () => ({}), 170 | jsObjectPush: (o, p, v) => (o[p] = v, o), 171 | jsSelect: (o, p) => o[p], 172 | jsSelectSet: (o, p, v) => o[p] = v, 173 | jsNew: (constr, args) => new constr(...args), 174 | jsFunctionApply: (f, args) => f(...args), 175 | jsMethodApply: (o, m, args) => o[m](...args), 176 | jsImportCall: (s) => import(s), 177 | jsImportMeta: () => import.meta, 178 | jsDelete: (o, p) => { delete o[p]; }, 179 | jsForInSimple: (o, f) => { for (var k in o) f(k); }, 180 | jsIsTruthy: (x) => !!x, 181 | jsLinkingInfo: () => linkingInfo, 182 | 183 | // Excruciating list of all the JS operators 184 | jsUnaryPlus: (a) => +a, 185 | jsUnaryMinus: (a) => -a, 186 | jsUnaryTilde: (a) => ~a, 187 | jsUnaryBang: (a) => !a, 188 | jsUnaryTypeof: (a) => typeof a, 189 | jsStrictEquals: (a, b) => a === b, 190 | jsNotStrictEquals: (a, b) => a !== b, 191 | jsPlus: (a, b) => a + b, 192 | jsMinus: (a, b) => a - b, 193 | jsTimes: (a, b) => a * b, 194 | jsDivide: (a, b) => a / b, 195 | jsModulus: (a, b) => a % b, 196 | jsBinaryOr: (a, b) => a | b, 197 | jsBinaryAnd: (a, b) => a & b, 198 | jsBinaryXor: (a, b) => a ^ b, 199 | jsShiftLeft: (a, b) => a << b, 200 | jsArithmeticShiftRight: (a, b) => a >> b, 201 | jsLogicalShiftRight: (a, b) => a >>> b, 202 | jsLessThan: (a, b) => a < b, 203 | jsLessEqual: (a, b) => a <= b, 204 | jsGreaterThan: (a, b) => a > b, 205 | jsGreaterEqual: (a, b) => a >= b, 206 | jsIn: (a, b) => a in b, 207 | jsInstanceof: (a, b) => a instanceof b, 208 | jsExponent: (a, b) => a ** b, 209 | 210 | // Non-native JS class support 211 | newSymbol: Symbol, 212 | createJSClass: (data, superClass, preSuperStats, superArgs, postSuperStats, fields) => { 213 | // fields is an array where even indices are field names and odd indices are initial values 214 | return class extends superClass { 215 | constructor(...args) { 216 | var preSuperEnv = preSuperStats(data, new.target, ...args); 217 | super(...superArgs(data, preSuperEnv, new.target, ...args)); 218 | for (var i = 0; i != fields.length; i = (i + 2) | 0) { 219 | Object.defineProperty(this, fields[i], { 220 | value: fields[(i + 1) | 0], 221 | configurable: true, 222 | enumerable: true, 223 | writable: true, 224 | }); 225 | } 226 | postSuperStats(data, preSuperEnv, new.target, this, ...args); 227 | } 228 | }; 229 | }, 230 | createJSClassRest: (data, superClass, preSuperStats, superArgs, postSuperStats, fields, n) => { 231 | // fields is an array where even indices are field names and odd indices are initial values 232 | return class extends superClass { 233 | constructor(...args) { 234 | var fixedArgs = args.slice(0, n); 235 | var restArg = args.slice(n); 236 | var preSuperEnv = preSuperStats(data, new.target, ...fixedArgs, restArg); 237 | super(...superArgs(data, preSuperEnv, new.target, ...fixedArgs, restArg)); 238 | for (var i = 0; i != fields.length; i = (i + 2) | 0) { 239 | Object.defineProperty(this, fields[i], { 240 | value: fields[(i + 1) | 0], 241 | configurable: true, 242 | enumerable: true, 243 | writable: true, 244 | }); 245 | } 246 | postSuperStats(data, preSuperEnv, new.target, this, ...fixedArgs, restArg); 247 | } 248 | }; 249 | }, 250 | installJSField: (instance, name, value) => { 251 | Object.defineProperty(instance, name, { 252 | value: value, 253 | configurable: true, 254 | enumerable: true, 255 | writable: true, 256 | }); 257 | }, 258 | installJSMethod: (data, jsClass, name, func, fixedArgCount) => { 259 | var closure = fixedArgCount < 0 260 | ? (function(...args) { return func(data, this, ...args); }) 261 | : (function(...args) { return func(data, this, ...args.slice(0, fixedArgCount), args.slice(fixedArgCount))}); 262 | jsClass.prototype[name] = closure; 263 | }, 264 | installJSStaticMethod: (data, jsClass, name, func, fixedArgCount) => { 265 | var closure = fixedArgCount < 0 266 | ? (function(...args) { return func(data, ...args); }) 267 | : (function(...args) { return func(data, ...args.slice(0, fixedArgCount), args.slice(fixedArgCount))}); 268 | jsClass[name] = closure; 269 | }, 270 | installJSProperty: (data, jsClass, name, getter, setter) => { 271 | var getterClosure = getter 272 | ? (function() { return getter(data, this) }) 273 | : (void 0); 274 | var setterClosure = setter 275 | ? (function(arg) { setter(data, this, arg) }) 276 | : (void 0); 277 | Object.defineProperty(jsClass.prototype, name, { 278 | get: getterClosure, 279 | set: setterClosure, 280 | configurable: true, 281 | }); 282 | }, 283 | installJSStaticProperty: (data, jsClass, name, getter, setter) => { 284 | var getterClosure = getter 285 | ? (function() { return getter(data) }) 286 | : (void 0); 287 | var setterClosure = setter 288 | ? (function(arg) { setter(data, arg) }) 289 | : (void 0); 290 | Object.defineProperty(jsClass, name, { 291 | get: getterClosure, 292 | set: setterClosure, 293 | configurable: true, 294 | }); 295 | }, 296 | jsSuperGet: superGet, 297 | jsSuperSet: superSet, 298 | jsSuperCall: (superClass, receiver, method, args) => { 299 | return superClass.prototype[method].apply(receiver, args); 300 | }, 301 | } 302 | 303 | export async function load(wasmFileURL, importedModules, exportSetters) { 304 | const myScalaJSHelpers = { ...scalaJSHelpers, idHashCodeMap: new WeakMap() }; 305 | const importsObj = { 306 | "__scalaJSHelpers": myScalaJSHelpers, 307 | "__scalaJSImports": importedModules, 308 | "__scalaJSExportSetters": exportSetters, 309 | }; 310 | const resolvedURL = new URL(wasmFileURL, import.meta.url); 311 | var wasmModulePromise; 312 | if (resolvedURL.protocol === 'file:') { 313 | const wasmPath = import("node:url").then((url) => url.fileURLToPath(resolvedURL)) 314 | wasmModulePromise = import("node:fs").then((fs) => { 315 | return wasmPath.then((path) => { 316 | return WebAssembly.instantiate(fs.readFileSync(path), importsObj); 317 | }); 318 | }); 319 | } else { 320 | wasmModulePromise = WebAssembly.instantiateStreaming(fetch(resolvedURL), importsObj); 321 | } 322 | await wasmModulePromise; 323 | } 324 | """ 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import org.scalajs.ir.Names._ 4 | import org.scalajs.ir.Trees.JSNativeLoadSpec 5 | import org.scalajs.ir.Types._ 6 | 7 | import org.scalajs.linker.backend.webassembly._ 8 | import org.scalajs.linker.backend.webassembly.Instructions._ 9 | 10 | import VarGen._ 11 | 12 | /** Scala.js-specific Wasm generators that are used across the board. */ 13 | object SWasmGen { 14 | 15 | def genZeroOf(tpe: Type)(implicit ctx: WasmContext): Instr = { 16 | tpe match { 17 | case BooleanType | CharType | ByteType | ShortType | IntType => 18 | I32Const(0) 19 | 20 | case LongType => I64Const(0L) 21 | case FloatType => F32Const(0.0f) 22 | case DoubleType => F64Const(0.0) 23 | case StringType => GlobalGet(genGlobalID.emptyString) 24 | case UndefType => GlobalGet(genGlobalID.undef) 25 | 26 | case AnyType | ClassType(_) | ArrayType(_) | NullType => 27 | RefNull(Types.HeapType.None) 28 | 29 | case NoType | NothingType | _: RecordType => 30 | throw new AssertionError(s"Unexpected type for field: ${tpe.show()}") 31 | } 32 | } 33 | 34 | def genBoxedZeroOf(tpe: Type)(implicit ctx: WasmContext): Instr = { 35 | tpe match { 36 | case BooleanType => 37 | GlobalGet(genGlobalID.bFalse) 38 | case CharType => 39 | GlobalGet(genGlobalID.bZeroChar) 40 | case ByteType | ShortType | IntType | FloatType | DoubleType => 41 | GlobalGet(genGlobalID.bZero) 42 | case LongType => 43 | GlobalGet(genGlobalID.bZeroLong) 44 | case AnyType | ClassType(_) | ArrayType(_) | StringType | UndefType | NullType => 45 | RefNull(Types.HeapType.None) 46 | 47 | case NoType | NothingType | _: RecordType => 48 | throw new AssertionError(s"Unexpected type for field: ${tpe.show()}") 49 | } 50 | } 51 | 52 | def genLoadJSConstructor(fb: FunctionBuilder, className: ClassName)(implicit 53 | ctx: WasmContext 54 | ): Unit = { 55 | val info = ctx.getClassInfo(className) 56 | 57 | info.jsNativeLoadSpec match { 58 | case None => 59 | // This is a non-native JS class 60 | fb += Call(genFunctionID.loadJSClass(className)) 61 | 62 | case Some(loadSpec) => 63 | genLoadJSFromSpec(fb, loadSpec) 64 | } 65 | } 66 | 67 | def genLoadJSFromSpec(fb: FunctionBuilder, loadSpec: JSNativeLoadSpec)(implicit 68 | ctx: WasmContext 69 | ): Unit = { 70 | def genFollowPath(path: List[String]): Unit = { 71 | for (prop <- path) { 72 | fb ++= ctx.getConstantStringInstr(prop) 73 | fb += Call(genFunctionID.jsSelect) 74 | } 75 | } 76 | 77 | loadSpec match { 78 | case JSNativeLoadSpec.Global(globalRef, path) => 79 | fb ++= ctx.getConstantStringInstr(globalRef) 80 | fb += Call(genFunctionID.jsGlobalRefGet) 81 | genFollowPath(path) 82 | case JSNativeLoadSpec.Import(module, path) => 83 | fb += GlobalGet(genGlobalID.forImportedModule(module)) 84 | genFollowPath(path) 85 | case JSNativeLoadSpec.ImportWithGlobalFallback(importSpec, globalSpec) => 86 | genLoadJSFromSpec(fb, importSpec) 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import org.scalajs.ir.Names._ 4 | import org.scalajs.ir.Types._ 5 | 6 | object SpecialNames { 7 | /* Our back-end-specific box classes for the generic representation of 8 | * `char` and `long`. These classes are not part of the classpath. They are 9 | * generated automatically by `LibraryPatches`. 10 | */ 11 | val CharBoxClass = BoxedCharacterClass.withSuffix("Box") 12 | val LongBoxClass = BoxedLongClass.withSuffix("Box") 13 | 14 | val CharBoxCtor = MethodName.constructor(List(CharRef)) 15 | val LongBoxCtor = MethodName.constructor(List(LongRef)) 16 | 17 | val valueFieldSimpleName = SimpleFieldName("value") 18 | 19 | // The constructor of java.lang.Class 20 | val ClassCtor = MethodName.constructor(List(ClassRef(ObjectClass))) 21 | 22 | // js.JavaScriptException, for WrapAsThrowable and UnwrapFromThrowable 23 | val JSExceptionClass = ClassName("scala.scalajs.js.JavaScriptException") 24 | val JSExceptionCtor = MethodName.constructor(List(ClassRef(ObjectClass))) 25 | val JSExceptionField = FieldName(JSExceptionClass, SimpleFieldName("exception")) 26 | 27 | val hashCodeMethodName = MethodName("hashCode", Nil, IntRef) 28 | 29 | /** A unique simple method name to map all method *signatures* into `MethodName`s. */ 30 | val normalizedSimpleMethodName = SimpleMethodName("m") 31 | } 32 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import org.scalajs.ir.Names._ 4 | import org.scalajs.ir.Types._ 5 | 6 | import org.scalajs.linker.backend.webassembly.{Types => watpe} 7 | 8 | import VarGen._ 9 | 10 | object TypeTransformer { 11 | 12 | /** This transformation should be used only for the result types of functions or blocks. 13 | * 14 | * `nothing` translates to an empty result type list, because Wasm does not have a bottom type 15 | * (at least not one that can expressed at the user level). A block or function call that returns 16 | * `nothing` should typically be followed by an extra `unreachable` statement to recover a 17 | * stack-polymorphic context. 18 | * 19 | * @see 20 | * https://webassembly.github.io/spec/core/syntax/types.html#result-types 21 | */ 22 | def transformResultType(t: Type)(implicit ctx: WasmContext): List[watpe.Type] = { 23 | t match { 24 | case NoType => Nil 25 | case NothingType => Nil 26 | case _ => List(transformType(t)) 27 | } 28 | } 29 | 30 | /** Transforms a value type to a unique Wasm type. 31 | * 32 | * This method cannot be used for `void` and `nothing`, since they have no corresponding Wasm 33 | * value type. 34 | */ 35 | def transformType(t: Type)(implicit ctx: WasmContext): watpe.Type = { 36 | t match { 37 | case AnyType => watpe.RefType.anyref 38 | 39 | case tpe: ArrayType => 40 | watpe.RefType.nullable(genTypeID.forArrayClass(tpe.arrayTypeRef)) 41 | 42 | case ClassType(className) => transformClassType(className) 43 | case RecordType(fields) => ??? 44 | case StringType | UndefType => watpe.RefType.any 45 | case p: PrimTypeWithRef => transformPrimType(p) 46 | } 47 | } 48 | 49 | def transformClassType(className: ClassName)(implicit ctx: WasmContext): watpe.RefType = { 50 | val info = ctx.getClassInfo(className) 51 | if (info.isAncestorOfHijackedClass) 52 | watpe.RefType.anyref 53 | else if (info.isInterface) 54 | watpe.RefType.nullable(genTypeID.ObjectStruct) 55 | else 56 | watpe.RefType.nullable(genTypeID.forClass(className)) 57 | } 58 | 59 | private def transformPrimType(t: PrimTypeWithRef): watpe.Type = { 60 | t match { 61 | case BooleanType => watpe.Int32 62 | case ByteType => watpe.Int32 63 | case ShortType => watpe.Int32 64 | case IntType => watpe.Int32 65 | case CharType => watpe.Int32 66 | case LongType => watpe.Int64 67 | case FloatType => watpe.Float32 68 | case DoubleType => watpe.Float64 69 | case NullType => watpe.RefType.nullref 70 | 71 | case NoType | NothingType => 72 | throw new IllegalArgumentException(s"${t.show()} does not have a corresponding Wasm type") 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import scala.annotation.tailrec 4 | 5 | import scala.collection.mutable 6 | import scala.collection.mutable.LinkedHashMap 7 | 8 | import org.scalajs.ir.ClassKind 9 | import org.scalajs.ir.Names._ 10 | import org.scalajs.ir.OriginalName.NoOriginalName 11 | import org.scalajs.ir.Trees.{FieldDef, ParamDef, JSNativeLoadSpec} 12 | import org.scalajs.ir.Types._ 13 | 14 | import org.scalajs.linker.interface.ModuleInitializer 15 | import org.scalajs.linker.interface.unstable.ModuleInitializerImpl 16 | import org.scalajs.linker.standard.LinkedTopLevelExport 17 | import org.scalajs.linker.standard.LinkedClass 18 | 19 | import org.scalajs.linker.backend.webassembly.ModuleBuilder 20 | import org.scalajs.linker.backend.webassembly.{Instructions => wa} 21 | import org.scalajs.linker.backend.webassembly.{Modules => wamod} 22 | import org.scalajs.linker.backend.webassembly.{Identitities => wanme} 23 | import org.scalajs.linker.backend.webassembly.{Types => watpe} 24 | 25 | import VarGen._ 26 | import org.scalajs.ir.OriginalName 27 | 28 | final class WasmContext( 29 | classInfo: Map[ClassName, WasmContext.ClassInfo], 30 | reflectiveProxies: Map[MethodName, Int], 31 | val itablesLength: Int 32 | ) { 33 | import WasmContext._ 34 | 35 | private val functionTypes = LinkedHashMap.empty[watpe.FunctionType, wanme.TypeID] 36 | private val tableFunctionTypes = mutable.HashMap.empty[MethodName, wanme.TypeID] 37 | private val constantStringGlobals = LinkedHashMap.empty[String, StringData] 38 | private val closureDataTypes = LinkedHashMap.empty[List[Type], wanme.TypeID] 39 | 40 | val moduleBuilder: ModuleBuilder = { 41 | new ModuleBuilder(new ModuleBuilder.FunctionTypeProvider { 42 | def functionTypeToTypeID(sig: watpe.FunctionType): wanme.TypeID = { 43 | functionTypes.getOrElseUpdate( 44 | sig, { 45 | val typeID = genTypeID.forFunction(functionTypes.size) 46 | moduleBuilder.addRecType(typeID, NoOriginalName, sig) 47 | typeID 48 | } 49 | ) 50 | } 51 | }) 52 | } 53 | 54 | private var stringPool = new mutable.ArrayBuffer[Byte]() 55 | private var nextConstantStringIndex: Int = 0 56 | private var nextClosureDataTypeIndex: Int = 1 57 | 58 | private val _funcDeclarations: mutable.LinkedHashSet[wanme.FunctionID] = 59 | new mutable.LinkedHashSet() 60 | 61 | /** The main `rectype` containing the object model types. */ 62 | val mainRecType: ModuleBuilder.RecTypeBuilder = new ModuleBuilder.RecTypeBuilder 63 | 64 | /** Get an index of the itable for the given interface. The itable instance must be placed at the 65 | * index in the array of itables (whose size is `itablesLength`). 66 | */ 67 | def getItableIdx(iface: ClassInfo): Int = { 68 | val idx = iface.itableIdx 69 | if (idx < 0) throw new IllegalArgumentException(s"Interface $iface is not registed.") 70 | idx 71 | } 72 | 73 | def getClassInfoOption(name: ClassName): Option[ClassInfo] = 74 | classInfo.get(name) 75 | 76 | def getClassInfo(name: ClassName): ClassInfo = 77 | classInfo.getOrElse(name, throw new Error(s"Class not found: $name")) 78 | 79 | def inferTypeFromTypeRef(typeRef: TypeRef): Type = typeRef match { 80 | case PrimRef(tpe) => 81 | tpe 82 | case ClassRef(className) => 83 | if (className == ObjectClass || getClassInfo(className).kind.isJSType) 84 | AnyType 85 | else 86 | ClassType(className) 87 | case typeRef: ArrayTypeRef => 88 | ArrayType(typeRef) 89 | } 90 | 91 | /** Retrieves a unique identifier for a reflective proxy with the given name. 92 | * 93 | * If no class defines a reflective proxy with the given name, returns `-1`. 94 | */ 95 | def getReflectiveProxyId(name: MethodName): Int = 96 | reflectiveProxies.getOrElse(name, -1) 97 | 98 | /** Adds or reuses a function type for a table function. 99 | * 100 | * Table function types are part of the main `rectype`, and have names derived from the 101 | * `methodName`. 102 | */ 103 | def tableFunctionType(methodName: MethodName): wanme.TypeID = { 104 | // Project all the names with the same *signatures* onto a normalized `MethodName` 105 | val normalizedName = MethodName( 106 | SpecialNames.normalizedSimpleMethodName, 107 | methodName.paramTypeRefs, 108 | methodName.resultTypeRef, 109 | methodName.isReflectiveProxy 110 | ) 111 | 112 | tableFunctionTypes.getOrElseUpdate( 113 | normalizedName, { 114 | val typeID = genTypeID.forTableFunctionType(normalizedName) 115 | val regularParamTyps = normalizedName.paramTypeRefs.map { typeRef => 116 | TypeTransformer.transformType(inferTypeFromTypeRef(typeRef))(this) 117 | } 118 | val resultType = 119 | TypeTransformer.transformResultType(inferTypeFromTypeRef(normalizedName.resultTypeRef))( 120 | this 121 | ) 122 | mainRecType.addSubType( 123 | typeID, 124 | NoOriginalName, 125 | watpe.FunctionType(watpe.RefType.any :: regularParamTyps, resultType) 126 | ) 127 | typeID 128 | } 129 | ) 130 | } 131 | 132 | def addConstantStringGlobal(str: String): StringData = { 133 | constantStringGlobals.get(str) match { 134 | case Some(data) => 135 | data 136 | 137 | case None => 138 | val bytes = str.toCharArray.flatMap { char => 139 | Array((char & 0xFF).toByte, (char >> 8).toByte) 140 | } 141 | val offset = stringPool.size 142 | val data = StringData(nextConstantStringIndex, offset) 143 | constantStringGlobals(str) = data 144 | 145 | stringPool ++= bytes 146 | nextConstantStringIndex += 1 147 | data 148 | } 149 | } 150 | 151 | def getConstantStringInstr(str: String): List[wa.Instr] = 152 | getConstantStringDataInstr(str) :+ wa.Call(genFunctionID.stringLiteral) 153 | 154 | def getConstantStringDataInstr(str: String): List[wa.I32Const] = { 155 | val data = addConstantStringGlobal(str) 156 | List( 157 | wa.I32Const(data.offset), 158 | // Assuming that the stringLiteral method will instantiate the 159 | // constant string from the data section using "array.newData $i16Array ..." 160 | // The length of the array should be equal to the length of the WTF-16 encoded string 161 | wa.I32Const(str.length()), 162 | wa.I32Const(data.constantStringIndex) 163 | ) 164 | } 165 | 166 | def getClosureDataStructType(captureParamTypes: List[Type]): wanme.TypeID = { 167 | closureDataTypes.getOrElseUpdate( 168 | captureParamTypes, { 169 | val fields: List[watpe.StructField] = 170 | for ((tpe, i) <- captureParamTypes.zipWithIndex) 171 | yield watpe.StructField( 172 | genFieldID.captureParam(i), 173 | NoOriginalName, 174 | TypeTransformer.transformType(tpe)(this), 175 | isMutable = false 176 | ) 177 | val structTypeID = genTypeID.captureData(nextClosureDataTypeIndex) 178 | nextClosureDataTypeIndex += 1 179 | val structType = watpe.StructType(fields) 180 | moduleBuilder.addRecType(structTypeID, NoOriginalName, structType) 181 | structTypeID 182 | } 183 | ) 184 | } 185 | 186 | def refFuncWithDeclaration(funcID: wanme.FunctionID): wa.RefFunc = { 187 | _funcDeclarations += funcID 188 | wa.RefFunc(funcID) 189 | } 190 | 191 | def addGlobal(g: wamod.Global): Unit = 192 | moduleBuilder.addGlobal(g) 193 | 194 | def getFinalStringPool(): (Array[Byte], Int) = 195 | (stringPool.toArray, nextConstantStringIndex) 196 | 197 | def getAllFuncDeclarations(): List[wanme.FunctionID] = 198 | _funcDeclarations.toList 199 | } 200 | 201 | object WasmContext { 202 | final case class StringData(constantStringIndex: Int, offset: Int) 203 | 204 | final class ClassInfo( 205 | val name: ClassName, 206 | val kind: ClassKind, 207 | val jsClassCaptures: Option[List[ParamDef]], 208 | classConcretePublicMethodNames: List[MethodName], 209 | val allFieldDefs: List[FieldDef], 210 | superClass: Option[ClassInfo], 211 | val classImplementsAnyInterface: Boolean, 212 | private var _hasInstances: Boolean, 213 | val isAbstract: Boolean, 214 | val hasRuntimeTypeInfo: Boolean, 215 | val jsNativeLoadSpec: Option[JSNativeLoadSpec], 216 | val jsNativeMembers: Map[MethodName, JSNativeLoadSpec], 217 | val staticFieldMirrors: Map[FieldName, List[String]], 218 | private var _itableIdx: Int 219 | ) { 220 | val resolvedMethodInfos: Map[MethodName, ConcreteMethodInfo] = { 221 | if (kind.isClass || kind == ClassKind.HijackedClass) { 222 | val inherited: Map[MethodName, ConcreteMethodInfo] = superClass match { 223 | case Some(superClass) => superClass.resolvedMethodInfos 224 | case None => Map.empty 225 | } 226 | 227 | for (methodName <- classConcretePublicMethodNames) 228 | inherited.get(methodName).foreach(_.markOverridden()) 229 | 230 | classConcretePublicMethodNames.foldLeft(inherited) { (prev, methodName) => 231 | prev.updated(methodName, new ConcreteMethodInfo(name, methodName)) 232 | } 233 | } else { 234 | Map.empty 235 | } 236 | } 237 | 238 | private val methodsCalledDynamically = mutable.HashSet.empty[MethodName] 239 | 240 | /** For a class or interface, its table entries in definition order. */ 241 | private var _tableEntries: List[MethodName] = null 242 | 243 | // See caller in Preprocessor.preprocess 244 | def setHasInstances(): Unit = 245 | _hasInstances = true 246 | 247 | def hasInstances: Boolean = _hasInstances 248 | 249 | def setItableIdx(idx: Int): Unit = _itableIdx = idx 250 | 251 | /** Returns the index of this interface's itable in the classes' interface tables. 252 | */ 253 | def itableIdx: Int = _itableIdx 254 | 255 | private var _specialInstanceTypes: Int = 0 256 | 257 | def addSpecialInstanceType(jsValueType: Int): Unit = 258 | _specialInstanceTypes |= (1 << jsValueType) 259 | 260 | /** A bitset of the `jsValueType`s corresponding to hijacked classes that extend this class. 261 | * 262 | * This value is used for instance tests against this class. A JS value `x` is an instance of 263 | * this type iff `jsValueType(x)` is a member of this bitset. Because of how a bitset works, 264 | * this means testing the following formula: 265 | * 266 | * {{{ 267 | * ((1 << jsValueType(x)) & specialInstanceTypes) != 0 268 | * }}} 269 | * 270 | * For example, if this class is `Comparable`, we want the bitset to contain the values for 271 | * `boolean`, `string` and `number` (but not `undefined`), because `jl.Boolean`, `jl.String` 272 | * and `jl.Double` implement `Comparable`. 273 | * 274 | * This field is initialized with 0, and augmented during preprocessing by calls to 275 | * `addSpecialInstanceType`. 276 | * 277 | * This technique is used both for static `isInstanceOf` tests as well as reflective tests 278 | * through `Class.isInstance`. For the latter, this value is stored in 279 | * `typeData.specialInstanceTypes`. For the former, it is embedded as a constant in the 280 | * generated code. 281 | * 282 | * See the `isInstance` and `genInstanceTest` helpers. 283 | * 284 | * Special cases: this value remains 0 for all the numeric hijacked classes except `jl.Double`, 285 | * since `jsValueType(x) == JSValueTypeNumber` is not enough to deduce that 286 | * `x.isInstanceOf[Int]`, for example. 287 | */ 288 | def specialInstanceTypes: Int = _specialInstanceTypes 289 | 290 | /** Is this class an ancestor of any hijacked class? 291 | * 292 | * This includes but is not limited to the hijacked classes themselves, as well as `jl.Object`. 293 | */ 294 | def isAncestorOfHijackedClass: Boolean = 295 | specialInstanceTypes != 0 || kind == ClassKind.HijackedClass 296 | 297 | def isInterface = kind == ClassKind.Interface 298 | 299 | def registerDynamicCall(methodName: MethodName): Unit = 300 | methodsCalledDynamically += methodName 301 | 302 | def buildMethodTable(): Unit = { 303 | if (_tableEntries != null) 304 | throw new IllegalStateException(s"Duplicate call to buildMethodTable() for $name") 305 | 306 | kind match { 307 | case ClassKind.Class | ClassKind.ModuleClass | ClassKind.HijackedClass => 308 | val superTableEntries = superClass.fold[List[MethodName]](Nil)(_.tableEntries) 309 | val superTableEntrySet = superTableEntries.toSet 310 | 311 | /* When computing the table entries to add for this class, exclude: 312 | * - methods that are already in the super class' table entries, and 313 | * - methods that are effectively final, since they will always be 314 | * statically resolved instead of using the table dispatch. 315 | */ 316 | val newTableEntries = methodsCalledDynamically.toList 317 | .filter(!superTableEntrySet.contains(_)) 318 | .filterNot(m => resolvedMethodInfos.get(m).exists(_.isEffectivelyFinal)) 319 | .sorted // for stability 320 | 321 | _tableEntries = superTableEntries ::: newTableEntries 322 | 323 | case ClassKind.Interface => 324 | _tableEntries = methodsCalledDynamically.toList.sorted // for stability 325 | 326 | case _ => 327 | _tableEntries = Nil 328 | } 329 | 330 | methodsCalledDynamically.clear() // gc 331 | } 332 | 333 | def tableEntries: List[MethodName] = { 334 | if (_tableEntries == null) 335 | throw new IllegalStateException(s"Table not yet built for $name") 336 | _tableEntries 337 | } 338 | } 339 | 340 | final class ConcreteMethodInfo( 341 | val ownerClass: ClassName, 342 | val methodName: MethodName 343 | ) { 344 | val tableEntryID = genFunctionID.forTableEntry(ownerClass, methodName) 345 | 346 | private var effectivelyFinal: Boolean = true 347 | 348 | private[WasmContext] def markOverridden(): Unit = 349 | effectivelyFinal = false 350 | 351 | def isEffectivelyFinal: Boolean = effectivelyFinal 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/webassembly/FunctionBuilder.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.webassembly 2 | 3 | import scala.collection.mutable 4 | 5 | import org.scalajs.ir.{OriginalName, Position} 6 | 7 | import Instructions._ 8 | import Identitities._ 9 | import Modules._ 10 | import Types._ 11 | 12 | final class FunctionBuilder( 13 | moduleBuilder: ModuleBuilder, 14 | val functionID: FunctionID, 15 | val functionOriginalName: OriginalName, 16 | functionPos: Position 17 | ) { 18 | import FunctionBuilder._ 19 | 20 | private var labelIdx = 0 21 | 22 | private val params = mutable.ListBuffer.empty[Local] 23 | private val locals = mutable.ListBuffer.empty[Local] 24 | private var resultTypes: List[Type] = Nil 25 | 26 | private var specialFunctionType: Option[TypeID] = None 27 | 28 | /** The instructions buffer. */ 29 | private val instrs: mutable.ListBuffer[Instr] = mutable.ListBuffer.empty 30 | 31 | def setFunctionType(typeID: TypeID): Unit = 32 | specialFunctionType = Some(typeID) 33 | 34 | def setResultTypes(tpes: List[Type]): Unit = 35 | resultTypes = tpes 36 | 37 | def setResultType(tpe: Type): Unit = 38 | setResultTypes(tpe :: Nil) 39 | 40 | def addParam(originalName: OriginalName, tpe: Type): LocalID = { 41 | val id = new ParamIDImpl(params.size, originalName) 42 | params += Local(id, originalName, tpe) 43 | id 44 | } 45 | 46 | def addParam(name: String, tpe: Type): LocalID = 47 | addParam(OriginalName(name), tpe) 48 | 49 | def genLabel(): LabelID = { 50 | val label = new LabelIDImpl(labelIdx) 51 | labelIdx += 1 52 | label 53 | } 54 | 55 | def addLocal(originalName: OriginalName, tpe: Type): LocalID = { 56 | val id = new LocalIDImpl(locals.size, originalName) 57 | locals += Local(id, originalName, tpe) 58 | id 59 | } 60 | 61 | def addLocal(name: String, tpe: Type): LocalID = 62 | addLocal(OriginalName(name), tpe) 63 | 64 | // Instructions 65 | 66 | def +=(instr: Instr): Unit = 67 | instrs += instr 68 | 69 | def ++=(instrs: Iterable[Instr]): Unit = 70 | this.instrs ++= instrs 71 | 72 | def markCurrentInstructionIndex(): InstructionIndex = 73 | new InstructionIndex(instrs.size) 74 | 75 | def insert(index: InstructionIndex, instr: Instr): Unit = 76 | instrs.insert(index.value, instr) 77 | 78 | // Helpers to build structured control flow 79 | 80 | def sigToBlockType(sig: FunctionType): BlockType = sig match { 81 | case FunctionType(Nil, Nil) => 82 | BlockType.ValueType() 83 | case FunctionType(Nil, resultType :: Nil) => 84 | BlockType.ValueType(resultType) 85 | case _ => 86 | BlockType.FunctionType(moduleBuilder.functionTypeToTypeID(sig)) 87 | } 88 | 89 | def ifThenElse(blockType: BlockType)(thenp: => Unit)(elsep: => Unit): Unit = { 90 | instrs += If(blockType) 91 | thenp 92 | instrs += Else 93 | elsep 94 | instrs += End 95 | } 96 | 97 | def ifThenElse(resultType: Type)(thenp: => Unit)(elsep: => Unit): Unit = 98 | ifThenElse(BlockType.ValueType(resultType))(thenp)(elsep) 99 | 100 | def ifThenElse(sig: FunctionType)(thenp: => Unit)(elsep: => Unit): Unit = 101 | ifThenElse(sigToBlockType(sig))(thenp)(elsep) 102 | 103 | def ifThenElse(resultTypes: List[Type])(thenp: => Unit)(elsep: => Unit): Unit = 104 | ifThenElse(FunctionType(Nil, resultTypes))(thenp)(elsep) 105 | 106 | def ifThenElse()(thenp: => Unit)(elsep: => Unit): Unit = 107 | ifThenElse(BlockType.ValueType())(thenp)(elsep) 108 | 109 | def ifThen(blockType: BlockType)(thenp: => Unit): Unit = { 110 | instrs += If(blockType) 111 | thenp 112 | instrs += End 113 | } 114 | 115 | def ifThen(sig: FunctionType)(thenp: => Unit): Unit = 116 | ifThen(sigToBlockType(sig))(thenp) 117 | 118 | def ifThen(resultTypes: List[Type])(thenp: => Unit): Unit = 119 | ifThen(FunctionType(Nil, resultTypes))(thenp) 120 | 121 | def ifThen()(thenp: => Unit): Unit = 122 | ifThen(BlockType.ValueType())(thenp) 123 | 124 | def block[A](blockType: BlockType)(body: LabelID => A): A = { 125 | val label = genLabel() 126 | instrs += Block(blockType, Some(label)) 127 | val result = body(label) 128 | instrs += End 129 | result 130 | } 131 | 132 | def block[A](resultType: Type)(body: LabelID => A): A = 133 | block(BlockType.ValueType(resultType))(body) 134 | 135 | def block[A]()(body: LabelID => A): A = 136 | block(BlockType.ValueType())(body) 137 | 138 | def block[A](sig: FunctionType)(body: LabelID => A): A = 139 | block(sigToBlockType(sig))(body) 140 | 141 | def block[A](resultTypes: List[Type])(body: LabelID => A): A = 142 | block(FunctionType(Nil, resultTypes))(body) 143 | 144 | def loop[A](blockType: BlockType)(body: LabelID => A): A = { 145 | val label = genLabel() 146 | instrs += Loop(blockType, Some(label)) 147 | val result = body(label) 148 | instrs += End 149 | result 150 | } 151 | 152 | def loop[A](resultType: Type)(body: LabelID => A): A = 153 | loop(BlockType.ValueType(resultType))(body) 154 | 155 | def loop[A]()(body: LabelID => A): A = 156 | loop(BlockType.ValueType())(body) 157 | 158 | def loop[A](sig: FunctionType)(body: LabelID => A): A = 159 | loop(sigToBlockType(sig))(body) 160 | 161 | def loop[A](resultTypes: List[Type])(body: LabelID => A): A = 162 | loop(FunctionType(Nil, resultTypes))(body) 163 | 164 | def whileLoop()(cond: => Unit)(body: => Unit): Unit = { 165 | loop() { loopLabel => 166 | cond 167 | ifThen() { 168 | body 169 | instrs += Br(loopLabel) 170 | } 171 | } 172 | } 173 | 174 | def tryTable[A](blockType: BlockType)(clauses: List[CatchClause])(body: => A): A = { 175 | instrs += TryTable(blockType, clauses) 176 | val result = body 177 | instrs += End 178 | result 179 | } 180 | 181 | def tryTable[A](resultType: Type)(clauses: List[CatchClause])(body: => A): A = 182 | tryTable(BlockType.ValueType(resultType))(clauses)(body) 183 | 184 | def tryTable[A]()(clauses: List[CatchClause])(body: => A): A = 185 | tryTable(BlockType.ValueType())(clauses)(body) 186 | 187 | def tryTable[A](sig: FunctionType)(clauses: List[CatchClause])(body: => A): A = 188 | tryTable(sigToBlockType(sig))(clauses)(body) 189 | 190 | def tryTable[A](resultTypes: List[Type])(clauses: List[CatchClause])(body: => A): A = 191 | tryTable(FunctionType(Nil, resultTypes))(clauses)(body) 192 | 193 | /** Builds a `switch` over a scrutinee using a `br_table` instruction. 194 | * 195 | * This function produces code that encodes the following control-flow: 196 | * 197 | * {{{ 198 | * switch (scrutinee) { 199 | * case clause0_alt0 | ... | clause0_altN => clause0_body 200 | * ... 201 | * case clauseM_alt0 | ... | clauseM_altN => clauseM_body 202 | * case _ => default 203 | * } 204 | * }}} 205 | * 206 | * All the alternative values must be non-negative and distinct, but they need not be 207 | * consecutive. The highest one must be strictly smaller than 128, as a safety precaution against 208 | * generating unexpectedly large tables. 209 | * 210 | * @param scrutineeSig 211 | * The signature of the `scrutinee` block, *excluding* the i32 result that will be switched 212 | * over. 213 | * @param clauseSig 214 | * The signature of every `clauseI_body` block and of the `default` block. The clauses' params 215 | * must consume at least all the results of the scrutinee. 216 | */ 217 | def switch( 218 | scrutineeSig: FunctionType, 219 | clauseSig: FunctionType 220 | )(scrutinee: () => Unit)(clauses: (List[Int], () => Unit)*)(default: () => Unit): Unit = { 221 | val clauseLabels = clauses.map(_ => genLabel()) 222 | 223 | // Build the dispatch vector, i.e., the array of caseValue -> target clauseLabel 224 | val numCases = clauses.map(_._1.max).max + 1 225 | if (numCases >= 128) 226 | throw new IllegalArgumentException(s"Too many cases for switch: $numCases") 227 | val dispatchVector = new Array[LabelID](numCases) 228 | for { 229 | (clause, clauseLabel) <- clauses.zip(clauseLabels) 230 | caseValue <- clause._1 231 | } { 232 | if (dispatchVector(caseValue) != null) 233 | throw new IllegalArgumentException(s"Duplicate case value for switch: $caseValue") 234 | dispatchVector(caseValue) = clauseLabel 235 | } 236 | 237 | // Compute the BlockType's we will need 238 | require( 239 | clauseSig.params.size >= scrutineeSig.results.size, 240 | "The clauses of a switch must consume all the results of the scrutinee " + 241 | s"(scrutinee results: ${scrutineeSig.results}; clause params: ${clauseSig.params})" 242 | ) 243 | val (doneBlockType, clauseBlockType) = { 244 | val clauseParamsComingFromAbove = clauseSig.params.drop(scrutineeSig.results.size) 245 | val doneBlockSig = FunctionType( 246 | clauseParamsComingFromAbove ::: scrutineeSig.params, 247 | clauseSig.results 248 | ) 249 | val clauseBlockSig = FunctionType( 250 | clauseParamsComingFromAbove ::: scrutineeSig.params, 251 | clauseSig.params 252 | ) 253 | (sigToBlockType(doneBlockSig), sigToBlockType(clauseBlockSig)) 254 | } 255 | 256 | block(doneBlockType) { doneLabel => 257 | block(clauseBlockType) { defaultLabel => 258 | // Fill up empty entries of the dispatch vector with the default label 259 | for (i <- 0 until numCases if dispatchVector(i) == null) 260 | dispatchVector(i) = defaultLabel 261 | 262 | // Enter all the case labels 263 | for (clauseLabel <- clauseLabels.reverse) 264 | instrs += Block(clauseBlockType, Some(clauseLabel)) 265 | 266 | // Load the scrutinee and dispatch 267 | scrutinee() 268 | instrs += BrTable(dispatchVector.toList, defaultLabel) 269 | 270 | // Close all the case labels and emit their respective bodies 271 | for (clause <- clauses) { 272 | instrs += End // close the block whose label is the corresponding label for this clause 273 | clause._2() // emit the body of that clause 274 | instrs += Br(doneLabel) // jump to done 275 | } 276 | } 277 | 278 | default() 279 | } 280 | } 281 | 282 | def switch( 283 | clauseSig: FunctionType 284 | )(scrutinee: () => Unit)(clauses: (List[Int], () => Unit)*)(default: () => Unit): Unit = { 285 | switch(FunctionType.NilToNil, clauseSig)(scrutinee)(clauses: _*)(default) 286 | } 287 | 288 | def switch( 289 | resultType: Type 290 | )(scrutinee: () => Unit)(clauses: (List[Int], () => Unit)*)(default: () => Unit): Unit = { 291 | switch(FunctionType(Nil, List(resultType)))(scrutinee)(clauses: _*)(default) 292 | } 293 | 294 | def switch()( 295 | scrutinee: () => Unit 296 | )(clauses: (List[Int], () => Unit)*)(default: () => Unit): Unit = { 297 | switch(FunctionType.NilToNil)(scrutinee)(clauses: _*)(default) 298 | } 299 | 300 | // Final result 301 | 302 | def buildAndAddToModule(): Function = { 303 | val functionTypeID = specialFunctionType.getOrElse { 304 | val sig = FunctionType(params.toList.map(_.tpe), resultTypes) 305 | moduleBuilder.functionTypeToTypeID(sig) 306 | } 307 | 308 | val dcedInstrs = localDeadCodeEliminationOfInstrs() 309 | 310 | val func = Function( 311 | functionID, 312 | functionOriginalName, 313 | functionTypeID, 314 | params.toList, 315 | resultTypes, 316 | locals.toList, 317 | Expr(dcedInstrs), 318 | functionPos 319 | ) 320 | moduleBuilder.addFunction(func) 321 | func 322 | } 323 | 324 | /** Performs local dead code elimination and produces the final list of instructions. 325 | * 326 | * After a stack-polymorphic instruction, the rest of the block is unreachable. In theory, 327 | * WebAssembly specifies that the rest of the block should be type-checkeable no matter the 328 | * contents of the stack. In practice, however, it seems V8 cannot handle `throw_ref` in such a 329 | * context. It reports a validation error of the form "invalid type for throw_ref: expected 330 | * exnref, found ". 331 | * 332 | * We work around this issue by forcing a pass of local dead-code elimination. This is in fact 333 | * straightforwrd: after every stack-polymorphic instruction, ignore all instructions until the 334 | * next `ELSE` or `END`. The only tricky bit is that if we encounter nested 335 | * `StructuredLabeledInstr`s during that process, must jump over them. That means we need to 336 | * track the level of nesting at which we are. 337 | */ 338 | private def localDeadCodeEliminationOfInstrs(): List[Instr] = { 339 | val resultBuilder = List.newBuilder[Instr] 340 | 341 | val iter = instrs.iterator 342 | while (iter.hasNext) { 343 | // Emit the current instruction 344 | val instr = iter.next() 345 | resultBuilder += instr 346 | 347 | /* If it is a stack-polymorphic instruction, dead-code eliminate until the 348 | * end of the current block. 349 | */ 350 | if (instr.isInstanceOf[StackPolymorphicInstr]) { 351 | var nestingLevel = 0 352 | 353 | while (nestingLevel >= 0 && iter.hasNext) { 354 | val deadCodeInstr = iter.next() 355 | deadCodeInstr match { 356 | case End | Else | _: Catch | CatchAll if nestingLevel == 0 => 357 | /* We have reached the end of the original block of dead code. 358 | * Actually emit this END or ELSE and then drop `nestingLevel` 359 | * below 0 to end the dead code processing loop. 360 | */ 361 | resultBuilder += deadCodeInstr 362 | nestingLevel = -1 // acts as a `break` instruction 363 | 364 | case End => 365 | nestingLevel -= 1 366 | 367 | case _: StructuredLabeledInstr => 368 | nestingLevel += 1 369 | 370 | case _ => 371 | () 372 | } 373 | } 374 | } 375 | } 376 | 377 | resultBuilder.result() 378 | } 379 | } 380 | 381 | object FunctionBuilder { 382 | private final class ParamIDImpl(index: Int, originalName: OriginalName) extends LocalID { 383 | override def toString(): String = 384 | if (originalName.isDefined) originalName.get.toString() 385 | else s"" 386 | } 387 | 388 | private final class LocalIDImpl(index: Int, originalName: OriginalName) extends LocalID { 389 | override def toString(): String = 390 | if (originalName.isDefined) originalName.get.toString() 391 | else s"" 392 | } 393 | 394 | private final class LabelIDImpl(index: Int) extends LabelID { 395 | override def toString(): String = s"