Files
3e00ba5f1d Support WebAssembly exception-handling (setjmp/longjmp) transpilation (#22)
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]>
2026-07-09 11:57:39 +09:00

68 lines
1.5 KiB
Go

package pass
import "github.com/goccy/wasm2go/internal/ssa"
// DCE is dead-code elimination: any value that is never used and has no
// side effect is marked dead (Op = OpInvalid) for Compact to sweep.
//
// Liveness is computed by marking from the roots and following Args:
//
// - roots: side-effecting values (stores, calls, global.set, traps),
// each block's Control value, and the Ret-tail return markers.
// - a value is live iff it is a root or an argument of a live value.
//
// Returns true if anything was newly marked dead.
func DCE(f *ssa.Func) bool {
live := make(map[ssa.ValueID]bool)
var work []*ssa.Value
mark := func(v *ssa.Value) {
if v != nil && v.Op != ssa.OpInvalid && !live[v.ID] {
live[v.ID] = true
work = append(work, v)
}
}
for _, b := range f.Blocks {
mark(b.Control)
nRes := 0
switch b.Kind {
case ssa.BlockRet:
nRes = len(f.Sig.Results)
case ssa.BlockThrow:
// A BlockThrow's tail markers are its thrown operands.
nRes = b.ThrowArgc
}
retStart := len(b.Values) - nRes
for i, v := range b.Values {
if v.Op == ssa.OpInvalid {
continue
}
if v.HasSideEffect() {
mark(v)
}
if i >= retStart { // Ret-tail return marker
mark(v)
}
}
}
for len(work) > 0 {
v := work[len(work)-1]
work = work[:len(work)-1]
for _, a := range v.Args {
mark(a)
}
}
changed := false
for _, b := range f.Blocks {
for _, v := range b.Values {
if v.Op != ssa.OpInvalid && !live[v.ID] {
v.Op = ssa.OpInvalid
v.Args = nil
changed = true
}
}
}
return changed
}