Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Releases: edadma/wasm

0.4.0 — compact-imports, imported memories/tables, shared mutable globals, CLI flags

17 May 00:37
@edadma edadma

Choose a tag to compare

Third Maven Central release. Substantial drop on the wasm-3.0 conformance side — five spec manifests fully unlocked, the spec sweep passing count climbs by ~650, and the public host-import surface gains memories, tables, and live-cell mutable globals. CLI gets three new flags. Same zero-runtime-dependency profile, same three-platform cross-build (JVM / Scala.js / Scala Native).

Install

libraryDependencies ++= Seq(
 "io.github.edadma" %%% "wasm" % "0.4.0",
 "io.github.edadma" %%% "wasm-wasi" % "0.4.0", // optional — only if you want the WASI shim
)
Coordinate What you get
io.github.edadma:wasm:0.4.0 The interpreter — Runtime.instantiate, ModuleInstance
io.github.edadma:wasm-wasi:0.4.0 The WASI Preview 1 host shim — depends on wasm

The wasm-cli runner is built from this repo but not published; it's a runnable example, not a library.

What's new since 0.3.0

Compact-imports wire format

The wasm-3.0 testsuite emits a compact import-section encoding where a regular-looking import with field_name == "" and a kind byte of 0x7E (shared-kind across the group) or 0x7F (per-import-kind, sub-imports can mix) signals that the just-read mod_name is shared across a group of sub-imports. The 0x7E form is kind sub_count (field_name desc)*; the 0x7F form is sub_count (field_name kind desc)*. The outer count is the TOTAL imports across all groups — so the parser advances i by sub_count per compact group. Before 0.4.0 modules using the compact form failed at parse time with unknown import kind 0x7F.

Imported memories + tables

Parser silently skipped import kinds 0x01 (table) and 0x02 (memory) before 0.4.0; modules importing them then failed downstream when an instruction referenced a memidx or tableidx with "no memory" / "no table". Now:

  • MemoryImport and TableImport surface in the module model alongside GlobalImport.
  • HostModule.memories: Map[String, Memory] and HostModule.tables: Map[String, RuntimeTable] are the new host-side maps. Both forward by reference — guest reads and writes hit the same backing array the host can inspect.
  • Limits checking at instantiation: host's current size ≥ module's declared min, host's max (if any) ≤ module's declared max (if any). Reftype match for tables; shared-vs-unshared match for memories.

Cross-module mutable-global sharing

Module storage for globals is now Array[GlobalCell] instead of Array[Value]. A GlobalCell is a tiny mutable holder; an imported mutable global installs the exporter's cell directly into the importing module's slot, so global.set from either side writes through the same storage — matching the wasm-3.0 spec's "imported mutable globals alias the exporter's storage" rule.

The host-import surface gains HostGlobal.live(vt, mut, cell) for sharing externally-owned cells; the existing HostGlobal(vt, mut, value) factory still works for the snapshot case (immutable globals + mutable globals the host doesn't need to observe).

New ModuleInstance accessors

Hosts forwarding one module's exports as another module's imports need richer introspection than globalValue / exportedMemory alone. Added:

  • exportedTable(name): Either[WasmError, RuntimeTable]
  • exportedFunctionType(name): Either[WasmError, FuncType]
  • exportedGlobalCell(name): Either[WasmError, GlobalCell]
  • exportedGlobalMutability(name): Either[WasmError, Boolean]
  • exportedMemoryNames / exportedTableNames / exportedGlobalNames (sorted enumeration helpers)

CLI flags

Three new flags on wasm-cli:

  • --trace — installs Tracer.Counting and prints [trace] ops=N calls=N hostCalls=N throws=N traps=N maxDepth=N to stderr after _start / main / --invoke returns.
  • --validate-only — parses + validates the module and exits without instantiating. 0 on ok, 1 with diagnostic. Useful as a CI lint step on generated wasm.
  • --stdin <path> — redirects the guest's fd 0 to a host file. The file is read once and streamed byte-for-byte through fd_read until exhausted.

WASI stdin reader

WasiContext gained a stdin: (Array[Byte], Int, Int) => Int field (POSIX-read shape). Default returns 0 bytes (EOF) so the historical "fd 0 returns EBADF" behaviour is gone — fd 0 now reads cleanly as empty input. WasiContext.stdinFromBytes(payload) builds a cursor-tracking reader from a byte array (used by the CLI's --stdin <path>).

Documentation

  • README trimmed from ~285 lines to ~55 lines; detail moved into the juicer-rendered docs site.
  • New Development/ section with architecture.md (sub-projects + source-tree layout) and testing.md (unit suites, W3C testsuite runner, fixture regeneration).

Numbers

  • 881 tests on the JVM (653 interpreter + 209 WASI + 19 CLI), all green.
  • 653 + 209 also green on Scala.js (Node 20+) and Scala Native (0.5.11).
  • 142 W3C manifests in the spec runner / ~53,000 assertions / 51,714 passing / 224 failing / 1,273 skipped.
  • 133 of 142 manifests fully green (up from 129 in 0.3.0). 5 manifests fully unlocked: names, exports, memory_grow, table_copy, table_grow.
  • 9 manifests pinned in KnownFailures (down from 13 in 0.3.0). Residual causes are wasm-3.0 typed-function-references / GC reftype short forms (0x63 / 0x64) — a separate proposal we don't implement.

Cross-build matrix

Target Version Runtime requirement
Scala 3.8.3
JVM Java 17 or newer
Scala.js 1.21.0 Node.js 20+
Scala Native 0.5.11 Clang

Try it

git clone https://github.com/edadma/wasm
cd wasm
sbt 'cliJVM/run examples/hello.wasm'
# Hello, world!

Or against a real WASI binary with a host preopen and the new --trace flag:

mkdir -p /tmp/sandbox
echo "Hello from the host filesystem" > /tmp/sandbox/hello.txt
sbt 'cliJVM/run --trace --preopen /tmp/sandbox:/sandbox \
 wasi/shared/src/test/resources/fixtures/real_rust_fileread.wasm'
# Hello from the host filesystem
# [trace] ops=... calls=... hostCalls=... throws=... traps=... maxDepth=...

Documentation

Full docs at https://edadma.github.io/wasm/:

  • Getting Started — install + run your first module
  • Concepts — the validator, host imports, traps and errors, the tracer
  • WASI — the 29 syscalls implemented and the three preopen flavours
  • CLI — every flag, dispatch rules, recipes
  • Reference — supported opcodes, binary sections, error variants
  • Spec compliance — W3C testsuite slice, what the runner caught, what's pinned
  • Development — sub-project layout, test invocation, W3C testsuite + fixture regeneration

License

ISC.

Assets 2
Loading

0.3.0 — five new proposals, W3C testsuite runner, 13 bug fixes

16 May 20:32
@edadma edadma

Choose a tag to compare

Second Maven Central release. Adds five new WebAssembly proposals on top of 0.1.1's MVP + first-generation post-MVP set, ships the W3C testsuite runner, fixes a dozen real interpreter bugs surfaced along the way, and brings full strict-validation conformance across the wasm-3.0 binary format. Same zero-runtime-dependency surface, same three-platform cross-build (JVM / Scala.js / Scala Native).

Install

libraryDependencies ++= Seq(
 "io.github.edadma" %%% "wasm" % "0.3.0",
 "io.github.edadma" %%% "wasm-wasi" % "0.3.0", // optional — only if you want the WASI shim
)
Coordinate What you get
io.github.edadma:wasm:0.3.0 The interpreter — Runtime.instantiate, ModuleInstance
io.github.edadma:wasm-wasi:0.3.0 The WASI Preview 1 host shim — depends on wasm

The wasm-cli runner is built from this repo but not published; it's a runnable example, not a library.

What's new since 0.1.1

Five new proposals

  • Exception handling — legacy form (Phase 7.E): try / catch tagidx / catch_all / throw / rethrow / delegate plus a new Section 13 (Tag) and import/export kind 0x04. Uncaught throws surface as WasmError.UncaughtException(tagIdx, args).
  • Exception handling — modern try_table form: opcode 0x1F with all four catch shapes, plus the exnref value type (wire byte 0x69) and throw_ref. Both EH forms coexist; the validator and runtime share one tag-dispatch path.
  • Tail-call proposal: return_call and return_call_indirect, with proper stack-frame replacement (not just a "do a regular call and return" shim) so deeply tail-recursive programs run in constant stack.
  • Relaxed SIMD (Phase 8.F): all 20 sub-opcodes (0x100..0x113) — i32x4.relaxed_dot_i8x16_i7x16_add_s and friends. Implementation matches the deterministic-where-possible side of the spec.
  • Threads / atomics proposal: 66 atomic ops covering load / store / rmw (add, sub, and, or, xor, xchg, cmpxchg) at every width (8 / 16 / 32 / 64) for both i32 and i64, plus memory.atomic.wait32 / wait64 / notify / fence and shared memory (limits flag bit 0x02). Single-thread semantics: wait on a matching value traps as "would block".

Spec proposal landings on the imports/globals side

  • Imported globals (kind 0x03): GlobalImport in the module model, resolved at instantiation from a new HostGlobal value on HostModule.globals: Map[String, HostGlobal].
  • Extended-const proposal: i32.add / i32.sub / i32.mul / i64.add / i64.sub / i64.mul are now legal inside a constant expression. The parser flattens the wire-format stack sequence into an expression tree; validator and runtime walk it recursively.
  • Relaxed const-expr (wasm-3.0 GC-proposal relaxation): global.get N in a const-expr can reference any earlier-defined immutable global, not just imports. Active data and element segment offsets accept the same forms.

W3C testsuite runner

A new JVM-only integrated runner consumes wast2json JSON manifests + .wasm blobs and dispatches assert_return / assert_trap / assert_invalid / assert_malformed against Runtime.instantiate + inst.invoke. The curated slice covers 142 manifests / ~53,000 assertions — the full SIMD proposal, bulk memory + tables + element segments, EH and tail-call proposals, binary-format and UTF-8 edge cases. 129 manifests fully green; 13 pinned in KnownFailures with documented feature gaps. See Spec compliance for the full table.

Thirteen interpreter bugs fixed (each surfaced by the runner)

  1. i32.trunc_f64_s over-rejected values strictly between -2^31 and -2^31 - 1 (off-by-one range check).
  2. MemArg.offset was Int, so a wasm u32 offset of 0xFFFFFFFF was stored as Java -1 and the OOB trap silently wrapped to low memory. Widened to Long.
  3. align immediate validation was missing for plain load / store (only the atomic path enforced it).
  4. if-without-else accepted mismatched params/results (the implicit empty else-branch needs startTypes == endTypes).
  5. v128.const wasn't accepted in const expressions ((global v128 (v128.const ...)) failed to parse).
  6. Untyped select (0x1B) rejected v128 operands — SIMD treats v128 as a numtype for select.
  7. i8x16.popcnt was completely unimplemented.
  8. i16x8.q15mulr_sat_s was completely unimplemented (only the relaxed-SIMD variant was present).
  9. try_table catch labels counted with the try_table on the label stack — off-by-one in both the validator and the runtime's throw-dispatch.
  10. Export-section validation was missing entirely — duplicate names silently shadowed each other, and out-of-range idx for any kind instantiated.
  11. Name-field UTF-8 validation was missing — new String(bytes, "UTF-8") silently replaced bad bytes with U+FFFD. Now strict RFC 3629.
  12. Seven small binary-format strictness rules — LEB128 minimal-encoding range checks (u32 / s32 / s64), section ID range, section order + uniqueness (Tag and DataCount have non-monotonic numeric IDs), section size mismatch, custom-section name overruns size, too-many-locals u32 overflow.
  13. Imported globals + extended-const + relaxed const-expr were not surfaced — three spec features that travel together, now landing as one drop with full validator + runtime support.

Other additions

  • Tracer hooks for opcode dispatch, function-frame transitions, throws, and traps. No-op default keeps zero overhead for the common case.

Numbers

  • 870 tests on the JVM (648 interpreter + 208 WASI + 14 CLI), all green.
  • 648 + 208 also green on Scala.js (Node 20+) and Scala Native (0.5.11).
  • 142 W3C manifests in the spec runner / ~53,000 assertions / 51,084 passing.
  • Deterministic across all three platforms: every i32 / i64 / f32 / f64 opcode produces bit-identical results, including IEEE-754 edge cases.

Cross-build matrix

Target Version Runtime requirement
Scala 3.8.3
JVM Java 17 or newer
Scala.js 1.21.0 Node.js 20+
Scala Native 0.5.11 Clang

Try it

git clone https://github.com/edadma/wasm
cd wasm
sbt 'cliJVM/run examples/hello.wasm'
# Hello, world!

Or against a real WASI binary with a host preopen:

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

Documentation

Full docs at https://edadma.github.io/wasm/:

  • Getting Started — install + run your first module
  • Concepts — the validator, host imports, traps and errors
  • WASI — the 29 syscalls implemented and the three preopen flavours
  • CLI--preopen, --invoke, --args, dispatch rules
  • Reference — supported opcodes, binary sections, error variants
  • Spec compliance — W3C testsuite slice, what the runner caught, what's pinned

License

ISC.

Loading

0.1.1 — first Maven Central release

15 May 11:04
@edadma edadma

Choose a tag to compare

First Maven Central release. Cross-built on JVM, Scala.js, and Scala Native; zero external runtime dependencies on the interpreter, one (the interpreter) on the WASI shim.

Install

libraryDependencies ++= Seq(
 "io.github.edadma" %%% "wasm" % "0.1.1",
 "io.github.edadma" %%% "wasm-wasi" % "0.1.1", // optional — only if you want the WASI shim
)
Coordinate What you get
io.github.edadma:wasm:0.1.1 The interpreter — Runtime.instantiate, ModuleInstance
io.github.edadma:wasm-wasi:0.1.1 The WASI Preview 1 host shim — depends on wasm

The wasm-cli runner is built from this repo but not published; it's a runnable example, not a library.

What's in the box

WebAssembly support: the Core MVP plus five post-MVP proposals — ~250 opcodes total.

  • Core MVP — every numeric opcode (i32 / i64 / f32 / f64 arithmetic, comparisons, conversions), the full control-flow surface (block / loop / if / br / br_if / br_table / call / call_indirect / return), memory load / store at every width, linear-memory grow, globals (mutable + immutable), tables, element + data segments, function imports + exports, the start function, and multi-value blocks / functions.
  • Sign-extensioni32.extend8_s, i32.extend16_s, i64.extend8_s, i64.extend16_s, i64.extend32_s.
  • Non-trapping float-to-int (Phase 8.A) — *.trunc_sat_f*_* for every shape.
  • Bulk-memory (Phase 8.B) — memory.init, memory.copy, memory.fill, data.drop, table.init, table.copy, elem.drop.
  • Reference types (Phase 8.C) — funcref + externref value types, ref.null / ref.is_null / ref.func, table.get / table.set / table.grow / table.size / table.fill, typed select t* (0x1C).
  • Multi-memory (Phase 8.D) — the memarg memidx flag, a vector of memories per module, multi-memory imports/exports, plus a HostFuncMulti host surface for hosts that need to look at the called memidx.
  • Fixed-width SIMD (Phase 8.E) — the complete SIMD proposal: V128 value type, v128.const, every load + store (including v128.load{8,16,32,64}_splat, v128.load*_zero, v128.load*x*_s/u, v128.load*_lane, v128.store*_lane), full lane access (splat / extract_lane / replace_lane / shuffle / swizzle), integer + float arithmetic, shifts, min/max, bitwise + reductions, comparisons, narrow / extend / extadd_pairwise / extmul, float ↔ int conversions, demote / promote, and i32x4.dot_i16x8_s.

WASI Preview 1: 24 syscalls implemented end-to-end against either an in-memory FS, a name-only stub preopen, or a real on-disk directory via HostPreopen.fromDir. Real rustc-built wasm32-wasip1 binaries run unchanged — println!, std::fs::read_to_string, and std::fs::write are committed as test fixtures and pass in CI.

Validation up front. Every imported module runs through a separate validator before any code executes. Bad binaries fail at Runtime.instantiate with a function <N>: byte offset 0x<hex>: <details> error, not at run time.

Numbers

  • 687 tests on the JVM (521 interpreter + 157 WASI + 9 CLI), all green.
  • 521 + 157 also green on Scala.js (Node 20+) and Scala Native (0.5.11).
  • Deterministic across all three platforms: every i32 / i64 / f32 / f64 opcode produces bit-identical results, including IEEE-754 edge cases.

External validation: the sysl compiler ships seven backends; one of them (wasm32-WASI) targets this interpreter through the WASI shim. sysl's full standard-library test suite — 973 cases across a large mixed workload — runs end-to-end on wasm 0.1.1 with zero divergence from the reference run on wasmtime.

Cross-build matrix

Target Version Runtime requirement
Scala 3.8.3
JVM Java 17 or newer
Scala.js 1.21.0 Node.js 20+
Scala Native 0.5.11 Clang

Try it

git clone https://github.com/edadma/wasm
cd wasm
sbt 'cliJVM/run examples/hello.wasm'
# Hello, world!

Or against a real WASI binary with a host preopen:

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

Documentation

Full docs at https://edadma.github.io/wasm/:

  • Getting Started — install + run your first module
  • Concepts — the validator, host imports, traps and errors
  • WASI — the 24 syscalls implemented and the three preopen flavours
  • CLI--preopen, --invoke, --args, dispatch rules
  • Reference — supported opcodes, binary sections, error variants

License

ISC.

Loading

AltStyle によって変換されたページ (->オリジナル) /