Files
wasm2go/testdata/eh_multi_catch.wat
ae7e9bd0e5 Support C++-shaped exception handling: multi-clause catch, delegate targets, rethrow depths (#39)
The legacy-EH support added for clang's SjLj lowering only covered the
single-tag patterns SjLj emits. Clang's -fwasm-exceptions C++ output
(e.g. llama.cpp) additionally needs:

- try with multiple catch clauses (catch $a / catch $b / catch_all),
  dispatched by tag in clause order
- catch_all-only trys and rethrow inside catch_all (the cleanup-during-
  unwind shape), including `rethrow l` with l > 0 naming an OUTER
  catch's exception
- `delegate l` forwarding to a specific outer try, skipping every try
  nested in between

Lowering now resolves delegate and rethrow label depths to their target
try regions (ctrl frames track handler state), records each region's
protected-body block set — the CFG alone cannot delimit it, because a
br that exits a try early makes its continuation reachable from inside
— and handles `delegate` closing a try inside a dead region.

The recover-trampoline emitter drops its single-non-catch_all-handler
restriction: every region gets an active flag and its own caught-
exception variable, the dispatch is an innermost-first if-chain with
per-clause tag tests, a delegate arm clears the flags of the regions it
skips, and any edge leaving a protected body clears its flag (a br out
of a try previously left the flag set, so a later unrelated throw could
misroute into the exited try's handler).

The structured emitter registers per-region exception variables so
outer-targeting rethrows resolve correctly, bails to the trampoline
when a delegate's target is not what a plain re-panic would reach or
when a body block escapes the region (its continuation would otherwise
be duplicated inside the protective closure), and closes a try no path
falls out of with trap+spin so Go's termination analysis accepts the
function.

SSA verification computes dominators over the CFG augmented with
virtual entry-to-handler edges, so handler landing pads (which have no
CFG predecessors) may use values defined before their try.

gcasm routes functions that call their own EH closures to the pure
fallback: the outer function of a structured try has no panic/recover
runtime call of its own — the defer/recover lives inside the closure it
calls.

Verified this session: `go test -race -timeout 20m ./...` exit 0 (all
packages ok). New fixtures cover multi-clause dispatch on both emit
paths, catch_all+rethrow, a delegate skipping an intermediate handler,
an outer-targeting rethrow, and the try-exit flag drop. A full
llama.cpp module (wasm32-wasip1, -fwasm-exceptions, SIMD disabled)
transpiles and generates correct text end-to-end. The
googlesql/wasmify five-step e2e chain was NOT run this session.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-08 10:59:07 +09:00

46 lines
894 B
WebAssembly Text Format

;; Multi-clause catch dispatch: one try with catch $a / catch $b / catch_all.
;; $f(0) throws $a(10) -> 10+1 = 11; $f(1) throws $b(20) -> 20+2 = 22;
;; $f(2) throws $c (no payload) -> catch_all = 99. run = 11+22+99 = 132.
(module
(tag $a (param i32))
(tag $b (param i32))
(tag $c)
(func $throw_sel (param i32)
local.get 0
i32.eqz
if
i32.const 10
throw $a
end
local.get 0
i32.const 1
i32.eq
if
i32.const 20
throw $b
end
throw $c)
(func $f (param i32) (result i32)
try (result i32)
local.get 0
call $throw_sel
i32.const 0
catch $a
i32.const 1
i32.add
catch $b
i32.const 2
i32.add
catch_all
i32.const 99
end)
(func (export "run") (result i32)
i32.const 0
call $f
i32.const 1
call $f
i32.add
i32.const 2
call $f
i32.add))