Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Commit

Permalink
Actually use our linker as the linker for sample/fastLinkJS.
Browse files Browse the repository at this point in the history
Now, `sample/fastLinkJS` uses our WebAssembly linker, and
`sample/run` actually executes the `main` method of the sample
project, linked to WebAssembly.
  • Loading branch information
sjrd committed Mar 29, 2024
1 parent c42c6bd commit 5a392db
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 37 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ jobs:
run: npm install
- name: Tests
run: sbt tests/test
- name: Run the Sample
run: sbt sample/run
- name: Compile wasmJVM
run: sbt wasmJVM/compile
- name: Run the Sample
run: sbt sample/run
- name: Format
run: sbt scalafmtCheckAll
63 changes: 42 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,61 @@

[![CI](https://github.com/tanishiking/scala-wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/tanishiking/scala-wasm/actions/workflows/ci.yml)

### Usage
### Prerequisites

- first, `npm install`
- then `sample/run` to compile `sample/src/main/scala/Sample.scala`.
- prints the WebAssembly Text Format (WAT) (Stack IR form) to the console, for debugging
- writes the binary format (WASM) to `target/output.wasm`
This project requires Node.js >= 22, which is available as nightly builds as of this writing.
This is necessary to get enough support of WasmGC.

Run the binary using through `run.js` using a JavaScript engine that supports WasmGC, such as Deno
If you are using NVM, you can instal Node.js 22 as follows:

```sh
$ deno run --allow-read run.mjs
# Enable resolution of nightly builds
$ NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly nvm install v22
# Switch to Node.js 22
$ nvm use 22
```

### Debugging tools
Otherwise, you can [manually download nightly builds of Node.js](https://nodejs.org/download/nightly/).

- The WasmGC reference interpreter can be used to validate and convert between the binary and text form:
- https://github.com/WebAssembly/gc/tree/main/interpreter
- Use docker image for it https://github.com/tanishiking/wasmgc-docker
### Setup

### Testing
Before doing anything else, run `npm install`.

Requires NodeJS >= 22 (for enough support of WasmGC).
### Run the sample

```sh
# if you are using NVM, you can install Node 22 with
$ NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly nvm install v22
# switch to use Node 22
$ nvm use 22
```
In `sample/src/main/scala/Sample.scala` you can find a sandbox to play around.

You can build and run it like any other Scala.js project from sbt:

- `sample/fastLinkJS` compiles and links the project with the WebAssembly backend.
- `sample/run` runs the sample linked to WebAssembly with `node`.

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.

In that directory, you will also find a `main.wat` file, which is not used for execution.
It contains the WebAsembly Text Format representation of `main.wasm`, for debugging purposes.

:warning: If you modify the linker code, you need to `reload` and `sample/clean` for your changes to take effect on the sample.

You can also use the `run.mjs` script to play with `@JSExportTopLevel` exports.

- Run from the command line with `node run.mjs`.
- Run from the browser by starting an HTTP server (e.g., `python -m http.server`) and navigate to `testrun.html`.

### Test suite

Run the test suite with `tests/test`.

- `tests/test` will
- Run `testSuite/run` to compile the Scala code under `test-suite` to WebAssembly
- Run the WebAssembly binary using NodeJS
- Each Scala program in `test-suite` should have a function that has no arguments and return a Boolean value. The test passes if the function returns `true`.
- When you add a test,
- Each Scala program in `test-suite` should have a `def main(): Unit` function. The test passes if the function successfully executes without throwing.
- When you add a test,
- Add a file under `test-suite`
- Add a test case to `cli/src/main/scala/TestSuites.scala` (`methodName` should be a exported function name).

### Debugging tools

- The WasmGC reference interpreter can be used to validate and convert between the binary and text form:
- https://github.com/WebAssembly/gc/tree/main/interpreter
- Use docker image for it https://github.com/tanishiking/wasmgc-docker
18 changes: 5 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import org.scalajs.linker.interface.OutputPatterns

val scalaV = "2.12.19"

// Include wasm.jvm on the classpath used to dynamically load Scala.js linkers
Global / scalaJSLinkerImpl / fullClasspath :=
(wasm.jvm / Compile / fullClasspath).value

inThisBuild(Def.settings(
scalacOptions ++= Seq(
"-encoding",
Expand Down Expand Up @@ -49,22 +53,10 @@ lazy val wasm = crossProject(JVMPlatform, JSPlatform)

lazy val sample = project
.in(file("sample"))
.enablePlugins(ScalaJSPlugin)
.enablePlugins(WasmLinkerPlugin)
.settings(
scalaVersion := scalaV,
scalaJSUseMainModuleInitializer := true,
Compile / jsEnv := {
import org.scalajs.jsenv.nodejs.NodeJSEnv
val cp = Attributed
.data((Compile / fullClasspath).value)
.mkString(";")
val env = Map(
"SCALAJS_CLASSPATH" -> cp,
"SCALAJS_MODE" -> "sample",
)
new NodeJSEnv(NodeJSEnv.Config().withEnv(env).withArgs(List("--enable-source-maps")))
},
Compile / jsEnvInput := (`cli` / Compile / jsEnvInput).value
)

lazy val testSuite = project
Expand Down
51 changes: 51 additions & 0 deletions project/WasmLinkerPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package build

import sbt._
import sbt.Keys._

import org.scalajs.linker._
import org.scalajs.linker.interface.{ModuleKind, _}

import org.scalajs.sbtplugin._
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._

object WasmLinkerPlugin extends AutoPlugin {
override lazy val requires = ScalaJSPlugin

/** A `LinkerImpl` that reflectively loads our `WebAssemblyLinkerImpl`. */
private class WasmLinkerImpl(base: LinkerImpl.Reflect)
extends LinkerImpl.Forwarding(base) {

private val loader = base.loader

private val clearableLinkerMethod = {
Class.forName("wasm.WebAssemblyLinkerImpl", true, loader)
.getMethod("clearableLinker", classOf[StandardConfig])
}

override def clearableLinker(config: StandardConfig): ClearableLinker =
clearableLinkerMethod.invoke(null, config).asInstanceOf[ClearableLinker]
}

override def projectSettings: Seq[Setting[_]] = Def.settings(
// Use a separate cache box for the LinkerImpl in this project (don't use the Global one)
scalaJSLinkerImplBox := new CacheBox,

// Use our custom WasmLinkerImpl as the linker implementation used by fast/fullLinkJS
scalaJSLinkerImpl := {
val cp = (scalaJSLinkerImpl / fullClasspath).value
scalaJSLinkerImplBox.value.ensure {
new WasmLinkerImpl(LinkerImpl.reflect(Attributed.data(cp)))
}
},

// Automatically install all the configs required by the Wasm backend
scalaJSLinkerConfig ~= { prev =>
prev
.withModuleKind(ModuleKind.ESModule)
.withSemantics(_.optimized)
.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs"))
.withOptimizer(false)
},
)
}
2 changes: 1 addition & 1 deletion run.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, field } from "./target/sample/main.mjs";
import { test, field } from "./sample/target/scala-2.12/sample-fastopt/main.mjs";

console.log(field);
const o = test(7);
Expand Down
4 changes: 4 additions & 0 deletions sample/src/main/scala/Sample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ object Main {
true
}

def main(args: Array[String]): Unit = {
println("hello world")
}

private def println(x: Any): Unit =
js.Dynamic.global.console.log("" + x)
}

0 comments on commit 5a392db

Please sign in to comment.