Skip to content

windows: implement native fault and SEH execution semantics (R5, depends on #2404) - #2405

Open
cpunion wants to merge 48 commits into
xgo-dev:mainfrom
cpunion:codex/windows-r5-fault-seh-20260825
Open

windows: implement native fault and SEH execution semantics (R5, depends on #2404)#2405
cpunion wants to merge 48 commits into
xgo-dev:mainfrom
cpunion:codex/windows-r5-fault-seh-20260825

Conversation

@cpunion

@cpunion cpunion commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Refs #2325

Depends on #2404.

Summary

  • translates native Windows access violations into Go-compatible panic and debug.SetPanicOnFault behavior while leaving foreign faults to the host
  • emits and consumes Win64 SEH unwind plus LLGo PC/line metadata for accurate panic, recover, and caller stacks
  • supports Windows setjmp lowering and nil-function-call panic behavior across amd64, arm64, and 386-specific paths
  • adds focused runtime, compiler, metadata, and cross-target regression coverage

Platform and size boundary

The fault handlers, unwinders, COFF carriers, wrapper markers, and pointer-store panic-site records are selected only for Windows. A Windows access violation exposes the faulting store PC, so that target needs a PC/line record to reproduce Go's recovered-panic source line without depending on PDB/debugger services.

Emitting one record for every potentially nil store on all native targets is a separate compatibility and representation question tracked in #2406. It is intentionally outside this Windows PR.

Using the same source and output paths for the #2404 parent and this head, all six macOS cprintf, println, and fmtprintf normal/full-LTO executable file sizes are byte-for-byte identical. Data and BSS are also identical; fmtprintf text is 4 B smaller in each mode. Linux is rechecked by the same-run PR benchmark job.

Validation

  • go test ./ssa ./internal/build ./internal/build/funcinfo ./internal/debuginfo
  • focused ./cl Windows metadata and store-site platform-selection tests
  • go test -vet=off ./test/go -run '^TestRuntimeStatementLineInfo$'
  • Windows 11 ARM64 native runtime smoke, including recover from a nil store and exact windows_store_fault.go:167 frame validation
  • same-path macOS normal/full-LTO size comparison against windows: add the Win32 runtime and callback substrate (R4) #2404

The local host's full ./cl LTO suite emits LLVM +zcm/+zcz diagnostics that also occur on the #2404 baseline; the affected focused tests and Windows native tests pass without those diagnostics.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

ad6ae6932c07 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Text size vs base Build vs base Run vs base
Linux cprintf 19144 B -112 B / -0.6% (better) 387 B 0 B / +0.0% 348.145 ms +299 us / +0.1% (worse) 1.466 ms +85.99 us / +6.2% (worse)
Linux cprintf-lto 18976 B -112 B / -0.6% (better) 368 B 0 B / +0.0% 362.416 ms +7.898 ms / +2.2% (worse) 1.348 ms -60.19 us / -4.3% (better)
Linux fmtprintf 1649256 B -3968 B / -0.2% (better) 501028 B -55 B / -0.01098% (better) 2.845 s -23.96 ms / -0.8% (better) 3.663 ms -5.027 us / -0.1% (better)
Linux fmtprintf-lto 1521800 B -4024 B / -0.3% (better) 459570 B -130 B / -0.02828% (better) 8.986 s +162.9 ms / +1.8% (worse) 3.526 ms +29.09 us / +0.8% (worse)
Linux println 61944 B -88 B / -0.1% (better) 15256 B -5 B / -0.03276% (better) 345.665 ms +2.748 ms / +0.8% (worse) 1.748 ms -11.23 us / -0.6% (better)
Linux println-lto 53808 B -32 B / -0.1% (better) 12882 B 0 B / +0.0% 518.198 ms -16.44 ms / -3.1% (better) 1.686 ms -69.14 us / -3.9% (better)
macOS cprintf 84480 B 0 B / +0.0% 16381 B -112 B / -0.7% (better) 441.313 ms -38.03 ms / -7.9% (better) 4.009 ms +1.059 ms / +35.9% (worse)
macOS cprintf-lto 100704 B 0 B / +0.0% 16361 B -112 B / -0.7% (better) 481.377 ms +33.13 ms / +7.4% (worse) 3.781 ms +471.3 us / +14.2% (worse)
macOS fmtprintf 1496384 B -1872 B / -0.1% (better) 881816 B -317 B / -0.03594% (better) 3.336 s +250 ms / +8.1% (worse) 5.944 ms -213.8 us / -3.5% (better)
macOS fmtprintf-lto 1208464 B -512 B / -0.04235% (better) 874840 B -289 B / -0.03302% (better) 8.261 s -1.308 s / -13.7% (better) 9.472 ms +2.209 ms / +30.4% (worse)
macOS println 114784 B -48 B / -0.0418% (better) 34797 B -52 B / -0.1% (better) 428.607 ms +64.1 ms / +17.6% (worse) 4.507 ms +1.116 ms / +32.9% (worse)
macOS println-lto 118656 B 0 B / +0.0% 32449 B -40 B / -0.1% (better) 603.073 ms +34.07 ms / +6.0% (worse) 4.597 ms +908.8 us / +24.6% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 12.150 ns/op -0.11 ns/op / -0.9% (better)
Linux BenchmarkMergeCompilerFlags 145.700 ns/op +1.3 ns/op / +0.9% (worse)
Linux BenchmarkMergeLinkerFlags 95.590 ns/op -0.08 ns/op / -0.1% (better)
Linux BenchmarkChannelBuffered 39.020 ns/op +1.75 ns/op / +4.7% (worse)
Linux BenchmarkChannelHandoff 25019 ns/op +29 ns/op / +0.1% (worse)
Linux BenchmarkDefer 50.230 ns/op +0.1 ns/op / +0.2% (worse)
Linux BenchmarkDirectCall 1.758 ns/op 0 ns/op / +0.0%
Linux BenchmarkGlobalRead 1.758 ns/op -0.001 ns/op / -0.1% (better)
Linux BenchmarkGlobalWrite 2.807 ns/op -0.001 ns/op / -0.03561% (better)
Linux BenchmarkGoroutine 34732 ns/op +4141 ns/op / +13.5% (worse)
Linux BenchmarkInterfaceCall 8.794 ns/op -0.568 ns/op / -6.1% (better)
Linux BenchmarkRuntimeGetG 2.111 ns/op -0.001 ns/op / -0.04735% (better)
macOS BenchmarkLookupPCRandom 12.970 ns/op -1.04 ns/op / -7.4% (better)
macOS BenchmarkMergeCompilerFlags 154.500 ns/op +10.4 ns/op / +7.2% (worse)
macOS BenchmarkMergeLinkerFlags 94.920 ns/op +18.06 ns/op / +23.5% (worse)
macOS BenchmarkChannelBuffered 31.290 ns/op +0.75 ns/op / +2.5% (worse)
macOS BenchmarkChannelHandoff 9420 ns/op +517 ns/op / +5.8% (worse)
macOS BenchmarkDefer 39.490 ns/op -4.83 ns/op / -10.9% (better)
macOS BenchmarkDirectCall 1.374 ns/op +0.163 ns/op / +13.5% (worse)
macOS BenchmarkGlobalRead 1.251 ns/op +0.123 ns/op / +10.9% (worse)
macOS BenchmarkGlobalWrite 1.519 ns/op +0.251 ns/op / +19.8% (worse)
macOS BenchmarkGoroutine 63410 ns/op +7642 ns/op / +13.7% (worse)
macOS BenchmarkInterfaceCall 5.652 ns/op -0.819 ns/op / -12.7% (better)
macOS BenchmarkRuntimeGetG 3.170 ns/op +0.499 ns/op / +18.7% (worse)

Compared with 6fe8babd468c measured in the same runner job.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.52997% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../internal/lib/reflect/makefunc_callback_default.go 0.00% 8 Missing ⚠️
internal/build/build.go 66.66% 1 Missing and 1 partial ⚠️
runtime/internal/lib/reflect/makefunc.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cpunion
cpunion force-pushed the codex/windows-r5-fault-seh-20260825 branch 2 times, most recently from d478989 to cbc9399 Compare August 25, 2026 11:04
cpunion added 24 commits August 25, 2026 21:20
Provide direct Unix and Win32 thread, TLS, synchronization, atomic, stdio, setjmp, libuv, and OS adapters. Keep target-specific libraries and layouts isolated so Windows support does not add code to existing Unix binaries.
Lower PE/COFF dynamic imports, align the UCRT setjmp ABI, terminate through runtime.exit, and keep LLGO_ROOT/module parsing portable across CRLF checkouts. Cover the compiler paths and malformed target flags.
Select the hosted thread, synchronization, and atomic backends; add Windows process, clock, syscall, GC, setjmp, and lifecycle adapters; and retain the existing Unix behavior behind target-specific files. Reuse the Go Windows syscall wrapper surface and isolate Win32 code from other targets.
Implement syscall.NewCallback with cached libffi closures, Go-compatible signature validation, closure-identity keys, and 386 calling conventions. Route both syscall and reflect callbacks through the retained foreign-thread GC lifecycle so repeated callbacks avoid collector registration churn.
Run native PE/COFF runtime, FFI, GC, callback, thread, clock, import, and compiler-path smoke tests under the shared dependency setup. Keep the empty-program import audit alongside the feature tests so unintended runtime dependencies stay visible.
@cpunion
cpunion force-pushed the codex/windows-r5-fault-seh-20260825 branch from 06c8540 to 54685c5 Compare August 25, 2026 13:55
@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai please review the rebased R5 Windows fault/SEH implementation. All CI checks pass (42 successful; the release job is skipped as expected), the Codecov patch check passes, and all 40 R5 commits are patch-identical after rebasing onto #2404s consolidated history.

@fennoai

fennoai Bot commented Aug 25, 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.

Review: Windows native fault & SEH execution semantics (R5)

This is a high-quality, carefully-reasoned change. Strong points worth calling out: the compile-time ABI assertions pinning CONTEXT size/offsets in fault_context_windows_{amd64,arm64}.go (excellent guard against silent ABI drift), copying the OS-owned exception CONTEXT before the SEH walk rather than mutating Windows' record, correct per-arch nil-call recovery (prepareNilCallUnwind), and consistent defensive frame-pointer guards (alignment, memReadable, stride/frame caps). Platform selection is clean: the store-site metadata and COFF machinery are correctly gated to GOOS == "windows" / siteObjectCOFF, consistent with the byte-identical Linux/macOS output claim.

I ran four review passes (quality, performance, security, docs). No critical or blocking issues. Two inline findings below; a few advisory notes follow.

Advisory (no reliable single inline line):

  • Fragile fixed 3-frame unwind in recoverFrameMarksruntime/internal/lib/runtime/unwind_windows_seh_llgo.go: the loop unwinds exactly 3 frames on the assumption recoverFrameMarks -> recoverMark -> Recover -> deferred func. This holds today only because recoverFrameMarks is //go:noinline, recoverMark is reached via an indirect function value, and Recover is a real runtime call. recoverMark itself is not marked //go:noinline; a future refactor that inlines any of these would silently point the mark at the wrong frame with no assertion to catch it. Consider //go:noinline on recoverMark and/or a sanity check on the resulting symbol.
  • 386 fpCallers dereferences the initial FP before validating itruntime/internal/lib/runtime/unwind_windows_386_llgo.go: the first iteration reads *(fp) / *(fp+wordSize) with the alignment check applied only afterward and no memReadable guard, unlike the fault-path windowsFPWalkFrom. Not exploitable here (walks its own live stack from a trusted context), but aligning it with the guarded pattern would harden it.
  • asmQuoteELFSymbol appears unused and its $ escaping is inconsistentinternal/build/funcinfo_table.go: no production caller (only referenced from a test); its \$ escape differs from the $$ doubling used on the COFF push-section path. Confirm whether it should be wired in or removed.
  • Performance (cold path, informational): RtlLookupFunctionEntry is called per frame with a NULL history table (Go threads an UNWIND_HISTORY_TABLE to amortize this), and memReadable issues a VirtualQuery syscall per probe. Both live only on the Windows fault/traceback path with hard frame-count bounds, so impact is bounded — noting only in case stack capture ever becomes hot on Windows.
  • Suggested test: the Go-vs-foreign-fault distinction relies on currentG being zero on any thread that never entered Go. A regression test asserting that a fault on a pure-native thread continues the handler chain (rather than being converted to a Go panic) would lock in this load-bearing invariant.

Comment thread runtime/internal/runtime/_wrap/fault_windows.c
Comment thread runtime/internal/runtime/_wrap/fault_windows.c Outdated
@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the full review in three follow-up commits:

  • 9535e8b15: preserve Windows exception codes, add Go-compatible integer-overflow panic semantics, correct the fault-callback comment, and cover both the overflow counterexample and a fault on a pure native thread.
  • 62b7e632b: pin the recover marker frames with //go:noinline and guard the initial windows/386 FP dereference.
  • ad6ae6932: remove the unused test-only asmQuoteELFSymbol helper.

Local validation passed on Parallels Windows 11 ARM64 (native runtime smoke including both new regressions), Windows/386 package compilation, all three Windows MSVC C targets, and the affected compiler/metadata tests on macOS. I will request re-review after the new CI run is green.

@cpunion
cpunion marked this pull request as ready for review August 25, 2026 16:12
@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai please re-review the three follow-up commits described above. The complete rerun is now green across Windows, Linux, macOS, benchmarks, and Codecov; all review threads are resolved.

@fennoai

fennoai Bot commented Aug 25, 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.

Re-review: follow-up commits (delta 54685c5e..ad6ae693)

I re-reviewed the three follow-up commits stacked linearly on the previously-reviewed head (no rebase; 54685c5e is an ancestor of ad6ae693). Delta is 8 files, +101/-52. All three findings from my prior review are resolved correctly, and the follow-up also added the regression test I suggested. No new issues; nothing outstanding.

9535e8b1 runtime: complete Windows fault classification — resolves the P2 EXCEPTION_INT_OVERFLOW finding and the P3 contradictory-comment finding:

  • The lossy C-side signal mapping is removed; the raw exception code is now threaded end-to-end (llgo_fault_callbackonWindowsFault/panicWindowsException), so classification lives in Go where it belongs.
  • 0xC0000095 (INT_OVERFLOW) is now handled → PanicErrorString("integer overflow"), which yields runtime error: integer overflow (verified against errorString.Error() in z_error.go), matching gc's runtimeError semantics. Divide-by-zero → SIGFPE and access-violation/in-page → SIGSEGV are preserved.
  • The contradictory/stale comment block is replaced by a single accurate comment enumerating the three legitimate return paths (foreign thread, non-Go text, non-nil fault without SetPanicOnFault).

62b7e632 runtime: harden Windows fault stack walking — resolves the 386 fpCallers and the fragile-3-frame-unwind findings:

  • fpCallers now applies the alignment + memReadable(fp) + memReadable(fp+wordSize) guard at the top of every iteration (including the first), matching the fault-path windowsFPWalkFrom pattern.
  • Both recoverMark functions (386 and SEH variants) are now //go:noinline with comments documenting that they must remain distinct frames — exactly the suggested hardening for the fixed 3-frame unwind assumption.

ad6ae693 build: remove unused ELF symbol quoting helper — resolves the dead-code finding: asmQuoteELFSymbol and its test are removed; grep confirms no remaining references.

Added test coverage (windowsruntime): checkIntegerOverflowFault asserts the recovered panic is runtime error: integer overflow, and checkForeignFaultOnNativeThread (new fault.c wrapper raising EXCEPTION_ACCESS_VIOLATION on a native CreateThread) locks in the Go-vs-foreign-fault invariant — the regression test I suggested.

I did not independently verify the CI/Codecov/benchmark status or the resolution of other reviewers' threads referenced in the description; this re-review covers the code delta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant