What it is
io.github.edadma.wasm is a Scala 3 WebAssembly interpreter, a WASI Preview 1 host shim, and a small CLI runner — three libraries sharing one codebase, all cross-built on JVM, Scala.js, and Scala Native.
import io.github.edadma.wasm.*
import io.github.edadma.wasm.wasi.{Wasi, WasiContext, HostPreopen}
val bytes: Array[Byte] = /* a .wasm binary */
val ctx = WasiContext.default.copy(
preopens = Seq(HostPreopen.fromDir("/var/data", "/data")),
)
Runtime.instantiate(bytes, Seq(EnvModule.default, Wasi.preview1(ctx))) match
case Right(inst) => Wasi.run(inst, "_start") // → Right(exitCode) or Left(WasmError)
case Left(err) => System.err.println(err)
Three rustc-built wasm32-wasip1 fixtures are committed and pass in CI — a println!, a std::fs::read_to_string, and a std::fs::write — so “runs real rust binaries” isn’t an aspiration, it’s covered by the test suite. The full sysl standard-library test suite (973 cases) also runs end-to-end on this interpreter as sysl’s wasm32-WASI backend — large mixed workload, zero divergence from the reference run on wasmtime.
Why this one?
- Zero runtime dependencies.
interpuses only the Scala stdlib.wasidepends only oninterp. Both ship to Maven Central (io.github.edadma:wasm:0.4.0/wasm-wasi:0.4.0) without dragging in a third-party transitive surface. - One codebase, three platforms. JVM, Scala.js, and Scala Native all share the same
shared/interpreter and WASI shim. Platform-specific code is limited to theHostPreopen.fromDirimplementation (java.nio.fileon JVM/Native,fs.*Syncon JS). - Deterministic numerics. Every
i32/i64/f32/f64opcode produces bit-identical results across all three platforms, including IEEE-754 edge cases. - Validation up front. Every imported module runs through a separate validator before any code executes. Bad binaries fail at
Runtime.instantiatewith afunction <N>: byte offset 0x<hex>: <details>error, not at run time. - WASI sandboxed by default.
HostPreopen.fromDirrejects absolute paths, NUL bytes, and..-escape attempts; programs see preopens through the sameWasiContext.Preopentrait whether they’re backed by a real directory, an in-memory map, or a name-only stub.
Cross-platform
| Target | Status |
|---|---|
| JVM (Scala 3.8.3, Java 17+) | ✓ |
| Scala.js 1.21.0 (Node 20+) | ✓ |
| Scala Native 0.5.11 | ✓ |
881 tests on the JVM (653 interpreter + 209 WASI + 19 CLI), all green; the interpreter and WASI test suites also pass on Scala.js and Scala Native. The official W3C testsuite runs through an integrated runner — 142 manifests / ~53,000 assertions in the current slice (the full SIMD proposal, bulk memory + tables + element segments, EH and tail-call proposals, plus binary-format and UTF-8 edge cases), 133 manifests fully green and 9 pinned (mostly wasm-3.0 GC-proposal reftype short forms and a handful of niche residuals). See Spec compliance for the table.
Try it
git clone https://github.com/edadma/wasm
cd wasm
sbt 'cliJVM/run examples/hello.wasm'
# Hello, world!
Or with a real rustc-built WASI binary against a host directory:
mkdir -p /tmp/sandbox
echo "Hello from the host filesystem" > /tmp/sandbox/hello.txt
sbt 'cliJVM/run --preopen /tmp/sandbox:/sandbox \
wasi/shared/src/test/resources/fixtures/real_rust_fileread.wasm'
# Hello from the host filesystem
Where to go next
- Getting Started — install the artifacts, run your first module.
- Concepts — the validator, host imports, traps and errors.
- WASI — the 29 syscalls implemented, the three preopen flavours, and the BSD-inetd socket model.
- CLI —
--preopen,--invoke,--args,--stdin,--trace,--validate-only, and the dispatch rules. - Reference — supported opcodes, binary sections, error variants.