Skip to content

ssa,runtime: make panic and recover Go-compatible - #2034

Merged
xushiwei merged 21 commits into
xgo-dev:mainfrom
cpunion:codex/stage5-recover-errors
Aug 11, 2026
Merged

ssa,runtime: make panic and recover Go-compatible#2034
xushiwei merged 21 commits into
xgo-dev:mainfrom
cpunion:codex/stage5-recover-errors

Conversation

@cpunion

@cpunion cpunion commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

This consolidates the panic/recover compatibility work previously split across #2033 and #2252 into this PR.

This PR resolves 15 unique upstream GOROOT compatibility cases and removes 35 matching expectation records across the supported native lanes:

  • allow recover only from the direct deferred activation, including transparent compiler-generated method, closure, interface, and reflect wrappers;
  • preserve nested panic ownership and discard only a panic replaced in the same defer frame;
  • let runtime.Goexit supersede an active panic, matching the standard runtime's goexit _panic semantics;
  • construct real runtime.TypeAssertionError values with Go-compatible source, concrete, asserted, and missing-method metadata;
  • route SIGBUS through the recoverable fault path used by SIGSEGV on supported Unix targets;
  • remove 29 xfails, 4 flakes, and 2 obsolete timeout overrides made unnecessary by these fixes.

The review counterexample for fixedbugs/issue8047b.go and the Goexit-during-panic counterexample are included as regression tests. They were not existing xfails and are not counted among the 15 resolved GOROOT cases.

Implementation

Recover ownership

Recover permission is keyed by the deferred activation instead of only by the function. Direct function, method, interface-method, closure, and reflect-created defers carry that identity through compiler-generated transparent wrappers; indirect, nested, or recursively re-entered calls remain unable to recover another activation's panic.

The compiler-side wrapper classification mirrors the standard runtime's abi.FuncIDWrapper rule. Recover-aware interface wrappers retain the GlobalDCE capability check while carrying the raw itab code address only as transient runtime bookkeeping data.

Panic and Goexit state

LLGo now keeps linked panic records, so a nested panic can be recovered without losing an outer panic owned by another deferred activation. A panic replaced while unwinding the same defer frame is discarded and cannot resume later.

The standard runtime represents Goexit with a goexit _panic that aborts linked active panics. LLGo keeps its existing g.goexit representation and performs the equivalent state transition before starting the longjmp-based defer unwind. This keeps recover nil during Goexit while preserving the existing behavior where a panic raised during Goexit may be recovered and Goexit then resumes.

Type assertion panics and faults

Failed non-comma-ok assertions call PanicTypeAssert(source, concrete, asserted, missingMethod) and recover a real *runtime.TypeAssertionError. Source ABI metadata is emitted only for non-empty source interfaces and only in the cold panic block.

SIGBUS uses the same panic/unwind path as SIGSEGV where the native platform defines it, preserving named results and the runtime.Error classification of recovered faults. Wasm and DWARF behavior are unchanged.

Conformance

The 15 resolved GOROOT compatibility cases are:

  • recover.go
  • recover1.go
  • recover2.go
  • recover4.go
  • zerodivide.go
  • fixedbugs/issue18911.go
  • fixedbugs/issue26094.go
  • fixedbugs/issue4066.go
  • fixedbugs/issue52072.go
  • fixedbugs/issue73916.go
  • fixedbugs/issue73916b.go
  • fixedbugs/issue73917.go
  • fixedbugs/issue73920.go
  • interface/embed3.go
  • typeparam/mdempsky/16.go

Validation

Exact expected-removal matrices

Every case whose expectation is removed was rebuilt and executed with both xfail and not_applicable set to empty files. Runner list mode independently confirmed the selected distribution.

Platform Toolchain Selected Directives Result
macOS arm64 Go 1.26.5 15 13 run, 2 rundir 15/15 passed
macOS arm64 Go 1.24.11 11 9 run, 2 rundir 11/11 passed
Linux amd64 (local emulation) Go 1.26.5 15 13 run, 2 rundir 15/15 passed
Linux amd64 (local emulation) Go 1.24.11 11 9 run, 2 rundir 11/11 passed

The two Darwin fixedbugs/issue4066.go timeout removals were also rerun separately with the runner's default 1-minute program timeout on Go 1.26.5 and Go 1.24.11; both passed in about 10 seconds including build setup.

Focused and regression checks

  • Standard Go passes the new Goexit-during-active-panic counterexample; the pre-fix LLGo binary reproduced the bug by recovering "outer", and the final Darwin and Linux LLGo binaries pass it with recover() == nil.
  • The final LLGo binary passes all TestReflect* and TestRecover* tests in test/go, plus the existing Goexit lifecycle/state tests.
  • go test ./ssa ./cl -count=1 -timeout=20m passed; the affected Goexit compiler/IR subtest also passed independently.
  • go test ./test/goroot -count=1 passed after the expectation cleanup.
  • Snapshot audit found no added concrete private hashes; pre-change snapshots fail on the new recover ABI/state transitions, confirming that the retained snapshot updates are semantically required.

CI

At head 72e9f900d, pull-request CI completed with 41 successful checks, 1 expected conditional release skip, and 0 failures. Codecov reports 99.15% patch coverage against an 89.59% target.

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.23729% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/recover.go 86.60% 8 Missing and 7 partials ⚠️
ssa/eh.go 97.61% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch 3 times, most recently from 9662dbc to 3076e79 Compare July 8, 2026 07:04
@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch 3 times, most recently from f4d93ca to 207675c Compare July 9, 2026 05:35
@cpunion cpunion added go-test-compat Go standard-library and GOROOT test compatibility bug Something isn't working bugfix Fixes an existing bug or regression go1.26 Go 1.26 compatibility and support and removed bug Something isn't working labels Jul 16, 2026
@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch from 207675c to bd5f8b5 Compare July 16, 2026 04:37
@cpunion cpunion changed the title ssa,runtime: real runtime.Error values for type asserts and fault signals [Based on #2033] ssa,runtime: return runtime.Error values for assertions and faults Jul 16, 2026
@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch 7 times, most recently from 5ee4bd8 to a7b4d8c Compare July 24, 2026 03:29
@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch 3 times, most recently from a1dbac0 to 934dc07 Compare July 29, 2026 01:50
@cpunion cpunion changed the title [Based on #2033] ssa,runtime: return runtime.Error values for assertions and faults ssa,runtime: return runtime.Error values for assertions and faults Jul 29, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

c952ad8a1b1d | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Build vs base Run vs base
Linux cprintf 18656 B +0.6% (worse) 299.057 ms +1.6% (worse) 1.270 ms -7.6% (better)
Linux fmtprintf 1881664 B +0.2% (worse) 3.119 s +0.7% (worse) 3.353 ms +0.1% (worse)
Linux println 68512 B +0.6% (worse) 297.639 ms +2.3% (worse) 1.620 ms +1.1% (worse)
macOS cprintf 84672 B +0.0% 332.211 ms +11.6% (worse) 2.544 ms +3.9% (worse)
macOS fmtprintf 1889248 B +0.1% (worse) 2.423 s -1.5% (better) 13.734 ms +20.8% (worse)
macOS println 121216 B +0.0% (worse) 314.801 ms +1.7% (worse) 3.359 ms -3.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 13.360 ns/op -0.3% (better)
Linux BenchmarkMergeCompilerFlags 157.100 ns/op +4.5% (worse)
Linux BenchmarkMergeLinkerFlags 101 ns/op +6.2% (worse)
Linux BenchmarkChannelBuffered 34.880 ns/op +3.6% (worse)
Linux BenchmarkChannelHandoff 27952 ns/op +1.2% (worse)
Linux BenchmarkDefer 47.820 ns/op -1.6% (better)
Linux BenchmarkDirectCall 1.557 ns/op +0.1% (worse)
Linux BenchmarkGlobalRead 1.555 ns/op -0.1% (better)
Linux BenchmarkGlobalWrite 2.486 ns/op +0.0%
Linux BenchmarkGoroutine 32239 ns/op +2.0% (worse)
Linux BenchmarkInterfaceCall 7.783 ns/op +0.0%
Linux BenchmarkRuntimeGetG 2.183 ns/op +16.8% (worse)
macOS BenchmarkLookupPCRandom 12.150 ns/op +9.9% (worse)
macOS BenchmarkMergeCompilerFlags 119.300 ns/op +17.3% (worse)
macOS BenchmarkMergeLinkerFlags 72.300 ns/op +11.1% (worse)
macOS BenchmarkChannelBuffered 24.030 ns/op +4.0% (worse)
macOS BenchmarkChannelHandoff 8754 ns/op +16.1% (worse)
macOS BenchmarkDefer 29 ns/op +6.1% (worse)
macOS BenchmarkDirectCall 1.065 ns/op +8.9% (worse)
macOS BenchmarkGlobalRead 1.086 ns/op -4.8% (better)
macOS BenchmarkGlobalWrite 1.063 ns/op +6.1% (worse)
macOS BenchmarkGoroutine 25645 ns/op +10.9% (worse)
macOS BenchmarkInterfaceCall 4.642 ns/op +10.1% (worse)
macOS BenchmarkRuntimeGetG 2.079 ns/op +4.5% (worse)

Compared with c9515d8cb9cf measured in the same runner job.

@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch from 9077388 to e0f1b54 Compare August 10, 2026 06:28
Re-expresses xgo-dev#1918 on the xgo-dev#2023 base (its remaining ~11k diff lines were
the pre-xgo-dev#2012 funcinfo draft, superseded by the stage-5 chain):

- recover() only succeeds when called directly by a deferred function
  (gc semantics): the panic node records the owning Defer frame at
  rethrow (panicKey/panicNode + GoDeferData), and Recover checks the
  caller is that frame's direct deferred call. Closure wraps carry
  StartRecoverFrameAlias/EndRecoverFrame so method-value and closure
  adapters stay transparent to the ownership check.
- Rethrow keeps the xgo-dev#2023 PanicTraceback hook on the unrecovered path.
- xfail: retire fixedbugs/issue4066 (2m-timeout entries; now runs in
  ~2.7s), fixedbugs/issue73916 and issue73916b (go1.26 recover
  semantics), validated on darwin/arm64 go1.26.

Supersedes xgo-dev#1918.
@cpunion
cpunion force-pushed the codex/stage5-recover-errors branch from e0f1b54 to 16aab70 Compare August 10, 2026 09:03
@cpunion

cpunion commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai review

@fennoai

fennoai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

FennoAI is processing your request, please wait a moment...

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — panic/recover Go-compatibility

Reviewed the diff (c9515d8...72e9f90) across correctness, performance, security, and doc accuracy. Overall this is a high-quality, well-tested change: the activation-token recover model, linked panic records, and Goexit/type-assert/SIGBUS handling are carefully documented against the upstream Go runtime semantics (abi.FuncIDWrapper, goexit _panic, TypeAssertionError), and the new tests (recover_activation_test.go, recover_metadata_test.go, recover_defer_fixedbugs_test.go, recover_fault_unix_test.go) are strong regression guards — including the IR-ordering assertions for the raw-load-vs-checked-load interaction.

Two non-blocking observations below (both inline). Nothing here blocks merge.

Strengths

  • moveToDefer correctly frees superseded panic nodes and clears gp.recoverPanic when freeing the suspended node, avoiding a dangling pointer.
  • abortPanics cleanly drains linked panics and resets recover state for the Goexit transition.
  • functionMayRecoverSeen guards recursion via a seen set; the GlobalDCE capability check is retained even on the recover-token path (ssa/interface.go), which the comment explains well.

Notes on pre-existing behavior (not introduced here, so not flagged inline)

  • The panic value is stored in a c.Malloc node (z_rt.go) — the same non-GC-heap pattern the base already used for panic_. If bdwgc does not scan the libc-malloc heap, a panic value with no other live reference could in principle be reclaimed mid-unwind. Worth a separate look, but it predates this PR.
  • g already embedded the ~512-byte panicPCStore by value before this PR; the per-goroutine footprint is unchanged by these changes.

View job run

Comment thread runtime/internal/runtime/z_baremetal.go
Comment thread cl/compile.go Outdated
Keep may-recover facts in a compilation-scoped, concurrency-safe Go SSA cache so closure values and dependency archive cache hits are classified without LLVM attributes. Make the lower-level Builder conservative, report the actual missing interface method at runtime, document bare-metal Goexit behavior, and cover the cross-package cache path.
iwasm does not provide the setjmp/longjmp imports used by LLGo defer. Keep the WASM fixture free of that defer path, and execute native artifacts after every cache scenario so dependency archive hits still validate cross-package recover.
Return the first missing interface method separately from the implementation boolean. This removes the empty-string sentinel from Implements and only resolves the method name on type-assertion error paths.
@xushiwei
xushiwei merged commit 6bc4226 into xgo-dev:main Aug 11, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fixes an existing bug or regression go1.26 Go 1.26 compatibility and support go-test-compat Go standard-library and GOROOT test compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants