Skip to content

Feat/exnref as objects - #204

Open
andreaTP wants to merge 4 commits into
bytecodealliance:mainfrom
andreaTP:feat/exnref-as-objects
Open

andreaTP wants to merge 4 commits into
bytecodealliance:mainfrom
andreaTP:feat/exnref-as-objects

Conversation

@andreaTP

@andreaTP andreaTP commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This makes exnref a real Object subject to the host GC lifecycle etc.
More spec tests and real world examples shows that this is the correct way forward.

This is rolled out as breaking change for people using ExnRef explicitly at the boundaries, but I'd expect those use cases to be very limited(non at best of my understanding).

Exception references had no identity. Instance.registerException keyed exceptions by tag index, so two live exceptions sharing a tag collapsed onto one another — capture A (payload 1), capture B (payload 2), then throw_ref A, and you got B. The spec requires a fresh exception instance per throw.

exnref is now the WasmException object itself, carried on the object-ref machinery the GcRefStore elimination already built. Verified against wasmtime 50.0.0-dev: the reproducer goes from 22/22/22, select→2 to the correct 12/12/12, select→1,2, on both the interpreter and the AOT compiler.

Breaking changes

  1. apply() now throws for exnref signatures — use applyWithRefs() / CallResult, exactly as for WasmGC types and externref since 1.0.
  2. Instance.exn(int) and registerException(...) are removed. Modules compiled ahead of time by an earlier version must be regenerated. AOT output carries no version stamp, so a stale artifact fails with NoSuchMethodError at the first exception thrown — not at build or class-load time.
  3. InterpreterMachine.THROW_REF changed from (Instance, int, …) to (Instance, Object, …). It is protected static on a public class, so external subclasses break at compile time. It is not referenced from generated bytecode.

A GitHub-wide search found no application code exposing exnref at a host boundary — export exnref returns zero hits, and every (result exnref) / (param exnref) hit is a spec suite, a toolchain internal (Binaryen, wasm-bindgen, Kotlin, Scala.js), a browser-engine test, or documentation.

Also fixed

  • ref.test / ref.cast against exn returned false for valid exception references.
  • throw_ref on a non-exception reference now traps instead of escaping as ClassCastException.

Coverage

New corpus tests, all over both the interpreter and the AOT compiler, with every expected value cross-checked against wasmtime:

  • identity preserved in locals, globals and tables
  • select (result exnref) — the un-normalised VEC_VALUE_TYPE operand path
  • ref.test against exn and noexn
  • br_table carrying an exnref through two label depths
  • an exnref crossing to a host function and back via applyWithRefs
  • apply() rejection on an exnref signature

Plus ApprovalTest.verifyExceptionRefs, pinning the AOT exception-ref bytecode, which had no coverage at all before: exceptions.wat contains no throw_ref, catch_ref or catch_all_ref.

Spec suites are unchanged at 62240 (runtime-tests) and 34110 (compiler-tests), 0 failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant