compiler: support panic/recover in Wasm without exceptions, plus explicit unwinding - #5550
compiler: support panic/recover in Wasm without exceptions, plus explicit unwinding#5550jakebailey wants to merge 6 commits into
Conversation
e5f8aee to
a004f53
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds return-based panic/Goexit unwinding support to TinyGo, enabling panic/recover (and Goexit deferred execution) on WebAssembly without relying on Wasm exceptions, and introducing an opt-in “explicit” unwinding mode for targets without native unwinding.
Changes:
- Introduces a return-based unwind signal mechanism in the runtime (asyncify- and explicit-driven) and threads it through panic/recover/Goexit paths.
- Updates the compiler to propagate unwind via inserted post-call checks, new asyncify “catch” wrappers for defer contexts, and adjusted fault/panic lowering.
- Expands coverage with new/updated tests and testdata outputs, and updates standard library package test allowlists accordingly.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| transform/unwind.go | Adds an LLVM llvm.assume insertion pass to help optimize away redundant unwind checks for asyncify-based unwinding. |
| transform/unwind_test.go | Adds a focused transform test that runs an LLVM pipeline to validate unwind assumption effects. |
| transform/transform_test.go | Threads PanicUnwind through the test compiler configuration. |
| transform/testdata/unwind.ll | Adds IR input for testing AddUnwindAssumptions. |
| transform/testdata/unwind.out.ll | Adds expected IR output demonstrating llvm.assume insertion and optimization effects. |
| transform/optimizer.go | Runs AddUnwindAssumptions during optimization when using asyncify-based unwinding. |
| tests/testing/pass/pass_test.go | Adds runtime tests exercising deferred behavior around suspension and concurrency. |
| testdata/testing.go | Enables additional subtests on wasm now that Goexit/fatal/skip semantics are supported. |
| testdata/testing-wasm.txt | Updates wasm expected output to reflect newly enabled subtest behavior. |
| testdata/recover.go | Adds additional recover test cases for interface comparison and map key/runtime-error paths. |
| testdata/recover.txt | Updates expected output for new recover test cases. |
| testdata/recover-explicit.go | Adds a minimal explicit-unwind recover test program. |
| testdata/recover-explicit.txt | Adds expected output for the explicit-unwind recover test. |
| testdata/goexit.go | Adds a Goexit case that verifies deferred code runs (including under -panic=trap). |
| src/runtime/panic.go | Refactors panic/Goexit to a common unwind start path and tracks unwind state in the defer frame. |
| src/runtime/panic_unwind_signal_unicore.go | Adds unwind signal storage/accessors for unicore (non-threads/non-cores) schedulers. |
| src/runtime/panic_unwind_signal_cores.go | Adds per-core unwind signal storage/accessors for scheduler.cores + explicit unwinding. |
| src/runtime/panic_unwind_setjmp.go | Provides startUnwind implementation for setjmp/longjmp targets. |
| src/runtime/panic_unwind_return.go | Implements return-based unwindPending/clearUnwind helpers for explicit/asyncify modes. |
| src/runtime/panic_unwind_none.go | Provides a no-op startUnwind for targets without unwinding. |
| src/runtime/panic_unwind_explicit.go | Implements explicit return-based startUnwind (sets signal + marks frame). |
| src/runtime/panic_unwind_asyncify.go | Implements asyncify-driven startUnwind (sets signal + triggers asyncify unwind). |
| src/internal/task/task_asyncify.go | Adds a synchronous “panic unwind” entrypoint that triggers asyncify unwind without pausing/switching tasks. |
| compiler/map.go | Uses invoke-style runtime calls for panic-capable generic map operations under unwind modes. |
| compiler/interface.go | Routes interface assertion failures through invoke/unwind return handling instead of unconditional unreachable. |
| compiler/defer.go | Makes recover support depend on configured unwind mode; clears unwind state at landing pads and after deferred calls; adds suspend-aware deferred invocation. |
| compiler/compiler.go | Adds PanicUnwind to compiler config and integrates unwind-aware lowering behavior (post-call checks, unwind return blocks). |
| compiler/compiler_test.go | Threads PanicUnwind into compiler test configuration. |
| compiler/calls.go | Adds suspend analysis and asyncify catcher wrappers; introduces unwind propagation checks and unwind return block creation. |
| compiler/asserts.go | Routes runtime assert failures through invoke/unwind return handling instead of unconditional unreachable. |
| compileopts/target.go | Adds panic-unwind to target specs and validates allowed values. |
| compileopts/target_test.go | Adds tests for PanicUnwind() configuration resolution rules. |
| compileopts/options.go | Adds -panic-unwind option validation. |
| compileopts/options_test.go | Adds option verification tests for -panic-unwind. |
| compileopts/config.go | Adds unwind build tags and implements PanicUnwind()/SupportsExplicitUnwind() selection logic. |
| builder/config.go | Enforces invalid scheduler/unwind combinations (e.g. explicit + asyncify/threads) at config creation time. |
| builder/build.go | Threads PanicUnwind into the compiler configuration used for builds. |
| main.go | Adds -panic-unwind CLI flag and plumbs it into build options. |
| main_test.go | Enables recover tests on wasm and adds explicit-unwind wasm test coverage; updates wasm import expectations and adds Goexit/defers coverage. |
| GNUmakefile | Updates standard library test package allowlists and related skip notes based on improved recover/Goexit support. |
| builder/sizes_test.go | Updates expected binary sizes due to changed codegen/runtime behavior. |
| compiler/testdata/basic.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
| compiler/testdata/func.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
| compiler/testdata/generics.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
| compiler/testdata/go1.20.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
| compiler/testdata/large.ll | Updates expected IR for unwind-aware defer lowering, asyncify catcher generation, and unwind propagation behavior. |
| compiler/testdata/slice.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
| compiler/testdata/string.ll | Updates expected IR to return via an unwind path after panic sites (instead of unreachable). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
a004f53 to
5718917
Compare
|
The runner is timing out on |
635ba8f to
1e2d7eb
Compare
Add unwind modes for propagating panic and Goexit without relying on exception handling. Keep setjmp unwinding on existing native targets and use Asyncify automatically when the Asyncify scheduler is selected. Stop Asyncify panic unwinding at the nearest defer frame, then run its deferred calls. Allow ordinary scheduler suspension to pass through the same frame, preserving aggregate results across rewind and nested or concurrent task execution. Add -panic-unwind=auto|explicit. Auto leaves targets without setjmp or Asyncify unchanged, while explicit opts wasm32, riscv64, and Xtensa into return-based unwinding. The trap panic strategy still traps panics immediately, but unwind support remains available for Goexit. Enable recovery, Goexit, and testing package coverage on WebAssembly and WASI targets that use Asyncify.
Move the standard library packages that were blocked by missing panic recovery into the portable test set. Keep image excluded from bare wasm because its tests require filesystem fixtures. Also enable crypto/ecdsa in the fast WASI suite now that Goexit runs deferred calls.
Exercise explicit return-based unwinding through nested and deferred panics, repanics, indirect calls, scalar and aggregate results, and runtime-generated faults. The fixture runs with the scheduler disabled on each WebAssembly target.
1e2d7eb to
abd56c9
Compare
| suspendYes | ||
| ) | ||
|
|
||
| func (b *builder) functionMaySuspend(fn *ssa.Function) bool { |
There was a problem hiding this comment.
Why is this logic needed?
We should have this information already in the createInvoke vs createCall difference:
Lines 98 to 102 in 8b3200c
(So this PR would basically not bail out in hasDeferFrame).
There was a problem hiding this comment.
Oh, right, you're actually traversing the call graph.
Perhaps it's a good idea to merge this logic with the createInvoke, because it would also help other architectures to skip some unwinding steps.
There was a problem hiding this comment.
Oh, yeah, that's a good idea
This is one of my crazier ideas; what if, instead of Wasm exceptions, we instead abuse the unwinding asyncify already slaps onto Wasm in order to unwind and recover panics?
Lo and behold, it works.
Truth be told, this is a lot of cleanup of copilot jank, but I don't hate what's here, especially because the binary overhead on top of asyncify is not all that bad; for the tsgo binary, this adds 5% and makes panic/recover/Goexit work!
On top of this is an "explicit" unwinder setting; it's not much work to basically enable a manual unwinding transform that works for any platform that doesn't have native unwinding. This can be used even on Wasm +
scheduler=none. But since the cost of this is fairly high, it's not on by default.The performance of this seems to not be bad; given one already does asyncify and this just piggy backs on top of that, there's not much more work being done besides in
deferblocks to actually check for panic and then recover.When WASI 0.3 comes along, none of this will matter, since I think Wasm exceptions "just work". But I think this does open the door to current users.
(I did notice that RISCV64 is a platform without native unwinding... not sure why that's the case, but I feel like a separate PR could totally just enable that, in which case all of this would really only be for Wasm and I guess "xtensa", not that I really know what that is.)
I suppose this fixes #2914?