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]>
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package ssa
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// FuncString renders a Func as a multiline IR dump, modeled on
|
|
// cmd/compile/internal/ssa's `compile -d=ssa/all/dump` output but
|
|
// simpler. Format:
|
|
//
|
|
// func Name(p0 i32, p1 i32) i32 {
|
|
// b0:
|
|
// v1 = OpParam [0] : i32
|
|
// v2 = OpParam [1] : i32
|
|
// v3 = OpAdd32 v1 v2 : i32
|
|
// Plain → b1
|
|
// b1:
|
|
// v4 = OpCopy v3 : i32
|
|
// Ret
|
|
// }
|
|
//
|
|
// The dump is the source of truth for golden-file tests on passes.
|
|
func FuncString(f *Func) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "func %s(", f.Name)
|
|
for i, p := range f.Sig.Params {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(&b, "p%d %s", i, p)
|
|
}
|
|
b.WriteString(") ")
|
|
if len(f.Sig.Results) == 1 {
|
|
b.WriteString(f.Sig.Results[0].String())
|
|
} else if len(f.Sig.Results) > 1 {
|
|
b.WriteString("(")
|
|
for i, r := range f.Sig.Results {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(r.String())
|
|
}
|
|
b.WriteString(")")
|
|
}
|
|
b.WriteString(" {\n")
|
|
for _, blk := range f.Blocks {
|
|
fmt.Fprintf(&b, " b%d:\n", blk.ID)
|
|
if len(blk.Preds) > 0 {
|
|
b.WriteString(" ; preds=")
|
|
for i, e := range blk.Preds {
|
|
if i > 0 {
|
|
b.WriteString(",")
|
|
}
|
|
fmt.Fprintf(&b, " b%d", e.Block.ID)
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
for _, v := range blk.Values {
|
|
fmt.Fprintf(&b, " %s : %s\n", v.String(), v.Type)
|
|
}
|
|
switch blk.Kind {
|
|
case BlockPlain:
|
|
if len(blk.Succs) > 0 {
|
|
fmt.Fprintf(&b, " Plain → b%d\n", blk.Succs[0].Block.ID)
|
|
} else {
|
|
b.WriteString(" Plain → <none>\n")
|
|
}
|
|
case BlockIf:
|
|
thenID, elseID := BlockID(-1), BlockID(-1)
|
|
if len(blk.Succs) > 0 {
|
|
thenID = blk.Succs[0].Block.ID
|
|
}
|
|
if len(blk.Succs) > 1 {
|
|
elseID = blk.Succs[1].Block.ID
|
|
}
|
|
ctrlID := ValueID(0)
|
|
if blk.Control != nil {
|
|
ctrlID = blk.Control.ID
|
|
}
|
|
fmt.Fprintf(&b, " If v%d → b%d, b%d\n", ctrlID, thenID, elseID)
|
|
case BlockRet:
|
|
b.WriteString(" Ret\n")
|
|
case BlockUnreachable:
|
|
b.WriteString(" Unreachable\n")
|
|
case BlockThrow:
|
|
fmt.Fprintf(&b, " Throw tag=%d\n", blk.TagIndex)
|
|
case BlockBrTable:
|
|
ctrlID := ValueID(0)
|
|
if blk.Control != nil {
|
|
ctrlID = blk.Control.ID
|
|
}
|
|
fmt.Fprintf(&b, " BrTable v%d →", ctrlID)
|
|
for si, e := range blk.Succs {
|
|
if si == blk.TableDefault {
|
|
fmt.Fprintf(&b, " b%d(default", e.Block.ID)
|
|
if len(blk.TableCases[si]) > 0 {
|
|
fmt.Fprintf(&b, ",%v", blk.TableCases[si])
|
|
}
|
|
b.WriteString(")")
|
|
} else {
|
|
fmt.Fprintf(&b, " b%d%v", e.Block.ID, blk.TableCases[si])
|
|
}
|
|
}
|
|
b.WriteString("\n")
|
|
default:
|
|
fmt.Fprintf(&b, " %s\n", "<invalid block kind>")
|
|
}
|
|
}
|
|
b.WriteString("}\n")
|
|
return b.String()
|
|
}
|