mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-10-30 15:06:48 +00:00
Transpile wasm modules that use the exception-handling proposal — the
try / catch / catch_all / throw / delegate / rethrow ops plus the tag
section. Clang lowers C/C++ setjmp/longjmp onto these ops under
`-mllvm -wasm-enable-sjlj`, so this is what lets wasm2go handle any
module built that way.
EH constructs are emitted as Go panic/recover: a throw becomes a
panic(*wasmExc); a try body runs inside a closure guarded by a deferred
recover; catch dispatches on the tag and narrows the operand slots.
setjmp-style resume — a longjmp re-entering the protected region — is
handled by a general recover-trampoline that wraps the whole function in
a `for { ... }` loop with a __pc dispatch, so re-entry is a loop
iteration rather than a goto across the closure boundary.
Pieces:
- Parser: decode the EH ops and the tag section.
- SSA / lower: throw -> BlockThrow terminator; try / catch / catch_all
lowering with mutable locals; delegate + rethrow; no local phis at
loop headers in mutable-locals mode.
- Codegen: emit try/catch/throw as panic/recover; real setjmp/longjmp
exception-resume loops; EH in multi-package output; structured
br_table + shared forward joins so more EH functions take the
structured path; the general recover-trampoline for goto-EH /
loop-spanning-try / resume; and bail a try-body function return to the
trampoline (a plain return cannot escape the *wasmExc closure).
- gcasm: fall back to pure Go for non-replayable jump tables so the
pure-Go asm backend never hard-fails a bundle.
New E2E coverage: EH try/catch, setjmp/longjmp resume loops, structured
br_table, multi-package EH, and the recover-trampoline, plus a
checked-in testdata wasm. go build ./..., go test ./... and golangci-lint
are green.
Claude-Session: https://claude.ai/code/session_01QWhxMdYGXtDRtDdKZXqnHG
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
23 lines
706 B
WebAssembly Text Format
23 lines
706 B
WebAssembly Text Format
;; br_table exercised through the STRUCTURED emitter: three cases (plus default)
|
|
;; each set $r then br to the common join $end, which returns $r. This is the
|
|
;; N-way analogue of an if/else with a shared join. run(sel) yields:
|
|
;; 0 -> 11, 1 -> 22, 2 -> 33, anything else -> 33 (default maps to case 2).
|
|
(module
|
|
(func (export "run") (param $sel i32) (result i32)
|
|
(local $r i32)
|
|
(block $end
|
|
(block $c2
|
|
(block $c1
|
|
(block $c0
|
|
local.get $sel
|
|
br_table $c0 $c1 $c2 $c2)
|
|
i32.const 11
|
|
local.set $r
|
|
br $end)
|
|
i32.const 22
|
|
local.set $r
|
|
br $end)
|
|
i32.const 33
|
|
local.set $r)
|
|
local.get $r))
|