compiler: support panic/recover in Wasm without exceptions, plus explicit unwinding#5550
Draft
jakebailey wants to merge 3 commits into
Draft
compiler: support panic/recover in Wasm without exceptions, plus explicit unwinding#5550jakebailey wants to merge 3 commits into
jakebailey wants to merge 3 commits into
Conversation
jakebailey
force-pushed
the
wasm-panic-recover
branch
2 times, most recently
from
July 24, 2026 04:25
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.
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.
jakebailey
force-pushed
the
wasm-panic-recover
branch
from
July 24, 2026 13:58
a004f53 to
5718917
Compare
Member
Author
|
The runner is timing out on |
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.
jakebailey
force-pushed
the
wasm-panic-recover
branch
from
July 24, 2026 16:22
635ba8f to
1e2d7eb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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?