Skip to content

windows: add the Win32 runtime and callback substrate (R4) - #2404

Merged
xushiwei merged 5 commits into
xgo-dev:mainfrom
cpunion:codex/windows-r4-runtime-20260825
Aug 26, 2026
Merged

windows: add the Win32 runtime and callback substrate (R4)#2404
xushiwei merged 5 commits into
xgo-dev:mainfrom
cpunion:codex/windows-r4-runtime-20260825

Conversation

@cpunion

@cpunion cpunion commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the R4 Win32 runtime and callback substrate from #2325:

  • abstract the existing hosted pthread/TLS/synchronization hooks behind platform-selected aliases, preserving the Unix implementation while adding native Win32 CreateThread, FLS, SRW lock, condition-variable, and INIT_ONCE backends;
  • add Windows stdio, sleep, wall/monotonic clocks, processor count, memory-query, setjmp, libuv sizing, atomic, and process-exit support for 386, amd64, and arm64;
  • initialize QueryPerformanceFrequency once at runtime startup so the monotonic-clock hot path performs only QueryPerformanceCounter, and derive the CPU count from the process affinity mask like Go's Windows runtime;
  • enable BDWGC on Windows, including runtime-created threads and retained registration for callbacks entered from foreign threads;
  • implement variable-width syscall.SyscallN bridges for Windows 386/amd64/arm64 and reuse the Go 1.26 Windows syscall wrappers;
  • lower Go's Windows //go:cgo_import_dynamic pointer declarations to COFF dllimport addresses, so the upstream syscall package initializes without LLGo-specific replacements;
  • implement syscall.NewCallback/NewCallbackCDecl with closure-identity caching, native calling conventions, argument/result validation, GC-root retention, panic/recover, defer, and runtime.Goexit behavior;
  • isolate reflect foreign-thread wrappers, collector startup, and wall-clock adaptation to Windows builds, preserving the original Unix callback, GC, and time paths;
  • terminate Windows executables through runtime.exit/ExitProcess after main returns, matching Go even when another goroutine remains blocked;
  • add native runtime, process-affinity, concurrent GC/finalizer, wall/trace-clock, reflect/libffi, foreign callback, and empty-program smoke coverage;
  • audit produced PE images for accidental direct MSYS/Cygwin/libgcc/libstdc++/winpthread imports.

R5 remains responsible for Windows hardware-fault recovery and SEH-backed traceback unwinding. R6 will add the broader Windows OS/files/process/network standard-library surface; neither concern is hidden or skipped here.

Compatibility and non-Windows impact

  • Windows uses the MSVC ABI and ordinary PE/COFF imports while consuming pinned native MSYS2 import libraries for libffi, libuv, and BDWGC.
  • The Windows/amd64 runtime smoke runs locally on a Windows 11 ARM64 VM through Windows' x64 compatibility layer and natively on the Windows amd64 CI runner. The raw syscall bridge is assembled for all Go-supported Windows architectures (386, amd64, arm64).
  • Non-Windows runtime behavior remains on the original implementation. Windows-only callback registration, GC startup, and CLOCK_REALTIME adaptation are selected by build tags instead of adding helper calls or retained metadata to Unix programs.
  • Latest same-runner macOS arm64 CI keeps the cprintf and println file/text sizes exact with and without LTO. fmtprintf is 16 B smaller as a non-LTO file and unchanged with LTO; removing artificial //line overrides changes the reported text metric by only +16 B/+32 B, without increasing either executable file.
  • Latest same-runner Linux CI keeps all six cprintf, println, and fmtprintf file/text measurements exact with and without LTO.
  • The PE audit confirms that generated executables directly import only expected Windows/native libraries (kernel32, and libgc/libffi when used), not msys-2.0.dll, cygwin1.dll, libwinpthread, libgcc_s, or libstdc++.

Validation

  • macOS arm64, Go 1.26.5, LLVM 19.1.7:
    • host tests for runtime/internal/clite/time, runtime/internal/lib/reflect, and runtime/internal/lib/runtime;
    • LLGo TestRecoverDeferredReflectMakeFunc and TestReflectMakeFuncGoroutineStartup;
    • TestReflectTypeMetadataMakeFuncProbe acceptance test;
    • same-output-path binary comparison for cprintf, println, and fmtprintf, with and without LTO.
  • OrbStack Linux arm64, Go 1.26.5, LLVM 19.1.7:
    • the same host-package and LLGo reflect/recover tests;
    • identical-path binary comparison for all six baseline size workloads.
  • Windows 11 ARM64 VM running Windows/amd64 binaries, Go 1.26.7, LLVM 19.1.7, MSVC 14.44:
    • Windows host/compiler and runtime/FFI package tests;
    • native runtime/GC/finalizer/thread/wall-clock/trace-clock/process-affinity smoke;
    • native reflect/libffi and foreign-thread callback smoke;
    • empty-program process-exit smoke;
    • PE direct-import audit;
    • native Go coverage for the internal/build Windows dynamic-import/main-exit pipeline (including alternate patched syntax) and every supported Windows setjmp/sigjmp ABI path in ssa; Codecov reports 92.59% patch coverage for cgo_pragmas.go and 96.49% for ssa/eh.go.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

7c9443330962 | 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% 272.932 ms +4.592 ms / +1.7% (worse) 1.039 ms +113 us / +12.2% (worse)
Linux cprintf-lto 18976 B -112 B / -0.6% (better) 368 B 0 B / +0.0% 274.377 ms -14.49 ms / -5.0% (better) 894.687 us -53.1 us / -5.6% (better)
Linux fmtprintf 1649256 B -3968 B / -0.2% (better) 501032 B -51 B / -0.01018% (better) 2.444 s +10.01 ms / +0.4% (worse) 2.725 ms +226.9 us / +9.1% (worse)
Linux fmtprintf-lto 1521800 B -4024 B / -0.3% (better) 459574 B -126 B / -0.02741% (better) 7.542 s -169.3 ms / -2.2% (better) 2.411 ms -24.4 us / -1.0% (better)
Linux println 61944 B -88 B / -0.1% (better) 15256 B -5 B / -0.03276% (better) 282.834 ms -112.4 ms / -28.4% (better) 1.285 ms +96.15 us / +8.1% (worse)
Linux println-lto 53808 B -32 B / -0.1% (better) 12882 B 0 B / +0.0% 429.814 ms -28.11 ms / -6.1% (better) 1.220 ms +22.59 us / +1.9% (worse)
macOS cprintf 84480 B 0 B / +0.0% 16381 B -112 B / -0.7% (better) 686.779 ms +154.6 ms / +29.0% (worse) 4.628 ms +985 us / +27.0% (worse)
macOS cprintf-lto 100704 B 0 B / +0.0% 16361 B -112 B / -0.7% (better) 597.504 ms +15.21 ms / +2.6% (worse) 3.917 ms +333.5 us / +9.3% (worse)
macOS fmtprintf 1496384 B -1872 B / -0.1% (better) 881820 B -313 B / -0.03548% (better) 3.067 s -657.6 ms / -17.7% (better) 8.551 ms +547.9 us / +6.8% (worse)
macOS fmtprintf-lto 1208464 B -512 B / -0.04235% (better) 874844 B -285 B / -0.03257% (better) 10.871 s +482.4 ms / +4.6% (worse) 10.576 ms +2.4 ms / +29.4% (worse)
macOS println 114784 B -48 B / -0.0418% (better) 34797 B -52 B / -0.1% (better) 459.755 ms -59.74 ms / -11.5% (better) 3.977 ms -357.8 us / -8.3% (better)
macOS println-lto 118656 B 0 B / +0.0% 32449 B -40 B / -0.1% (better) 561.641 ms -211.2 ms / -27.3% (better) 4.500 ms -1.307 ms / -22.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 9.885 ns/op -0.165 ns/op / -1.6% (better)
Linux BenchmarkMergeCompilerFlags 127.700 ns/op 0 ns/op / +0.0%
Linux BenchmarkMergeLinkerFlags 83.170 ns/op -0.79 ns/op / -0.9% (better)
Linux BenchmarkChannelBuffered 52.040 ns/op -0.3 ns/op / -0.6% (better)
Linux BenchmarkChannelHandoff 27839 ns/op +3580 ns/op / +14.8% (worse)
Linux BenchmarkDefer 41 ns/op -0.53 ns/op / -1.3% (better)
Linux BenchmarkDirectCall 1.074 ns/op -0.149 ns/op / -12.2% (better)
Linux BenchmarkGlobalRead 1.046 ns/op -0.255 ns/op / -19.6% (better)
Linux BenchmarkGlobalWrite 7.392 ns/op -0.238 ns/op / -3.1% (better)
Linux BenchmarkGoroutine 36344 ns/op -11316 ns/op / -23.7% (better)
Linux BenchmarkInterfaceCall 5.671 ns/op -0.614 ns/op / -9.8% (better)
Linux BenchmarkRuntimeGetG 1.201 ns/op -0.102 ns/op / -7.8% (better)
macOS BenchmarkLookupPCRandom 14 ns/op -2.79 ns/op / -16.6% (better)
macOS BenchmarkMergeCompilerFlags 198.700 ns/op +26.9 ns/op / +15.7% (worse)
macOS BenchmarkMergeLinkerFlags 109.700 ns/op +9 ns/op / +8.9% (worse)
macOS BenchmarkChannelBuffered 24.580 ns/op -8.23 ns/op / -25.1% (better)
macOS BenchmarkChannelHandoff 8095 ns/op -4645 ns/op / -36.5% (better)
macOS BenchmarkDefer 32.090 ns/op -16.67 ns/op / -34.2% (better)
macOS BenchmarkDirectCall 1.035 ns/op -0.362 ns/op / -25.9% (better)
macOS BenchmarkGlobalRead 1.177 ns/op -0.102 ns/op / -8.0% (better)
macOS BenchmarkGlobalWrite 1.057 ns/op -0.371 ns/op / -26.0% (better)
macOS BenchmarkGoroutine 28357 ns/op -31522 ns/op / -52.6% (better)
macOS BenchmarkInterfaceCall 5.020 ns/op -1.767 ns/op / -26.0% (better)
macOS BenchmarkRuntimeGetG 2.259 ns/op -0.515 ns/op / -18.6% (better)

Compared with 6fe8babd468c measured in the same runner job.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

@cpunion
cpunion marked this pull request as ready for review August 24, 2026 19:03
@cpunion

cpunion commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai please review this Windows runtime/callback substrate after the full CI and patch-coverage gates passed.

@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 Win32 runtime and callback substrate (R4)

Overall this is a high-quality, carefully engineered PR. Four review passes (quality, performance, security, docs) found no critical correctness bugs and no security vulnerabilities. The platform abstractions (filelock, mmap, tls/sync/thread backends) are cleanly split by build tag, the hand-written Windows syscall assembly faithfully adapts Go's own with correct SEH unwind data and stack alignment, argument-count bounds are enforced against the assembly frame sizes, HeapAlloc/HeapFree lifecycles are leak/double-free free, and response-file quoting correctly implements both GNU and CommandLineToArgvW escaping rules. Test coverage is strong.

Only a few low-severity items are worth noting; none are blocking. Two are inline. A few informational notes:

  • FLS destructor immutability (thread_windows.c, llgo_win_fls_set): on the update path (slot != 0, non-nil value) slot->value is refreshed but slot->destructor is not. Benign today (destructor is fixed at key-create time), but a latent inconsistency — consider refreshing it or documenting immutability.
  • NULL-guard consistency (_wrap/runtime_windows.c): llgo_load_library/llgo_get_proc_address/llgo_walltime dereference their error/output pointers without a NULL check, unlike sibling wrappers in thread_windows.c/sync_windows.c. Not exploitable (only reached from trusted runtime shims), but a defensive check would be consistent.
  • FFI_WIN64 value comment (ffi/abi_windows_amd64.go = 1, ffi/abi_windows_arm64.go = 2): both are correct per libffi's per-arch enum, but a side-by-side reader may suspect a copy-paste error. A one-line "value is architecture-specific" note (as done for WindowsStdcallABI) would help.
  • ParseARM float-suffix parsing (internal/goarch/config.go): stripping ,softfloat then ,hardfloat is order-sensitive for malformed inputs containing both suffixes. No real impact; rejecting when both are present would be cleaner.

Comment thread internal/build/build.go Outdated
Comment thread internal/build/cgo_pragmas.go
@fennoai

fennoai Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Review complete

I reviewed the full diff (178 files, ~8.3k additions) across four passes — code quality, performance, security, and documentation accuracy — and submitted findings as a PR review.

Summary: A high-quality, carefully engineered PR. Arch-specific constants (libffi ABI enums, jmp_buf sizes, the 42-arg SyscallN bridge stack layout) are correct across 386/amd64/arm64 and test-backed; the NewCallback closure cache and GC-root retention match upstream Go semantics; COFF COMDAT emission is the right lld-link fix. Security pass found no memory-safety or trust-boundary issues.

5 non-blocking findings (see the review for inline detail):

  • [P2] runtime_windows.cllgo_nanotime re-queries QueryPerformanceFrequency every call; cache it (hot time.Now/scheduler path).
  • [P2] syscall_windows_callback_llgo.go — reconcile the non-defer foreign-thread exit here with the defer form in reflect/makefunc.go; the longjmp rationale in the comment looks questionable.
  • [P3] syscall_windows_llgo.gor2 doc says "floating-point result register" but that only holds on amd64.
  • [P3] sync_windows.goMutexAttr comment attributes validation to Init; it's actually SetType/Mutex.Init.
  • [P3] thread_windows.cllgo_win_fls_set silently drops the destructor on slot update; document the "fixed at first set" invariant.

View job run

@cpunion

cpunion commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the remaining informational review notes in 42624f8 where they improve correctness or clarity: FLS updates now keep the stored destructor synchronized, the architecture-specific libffi ABI values are documented, and malformed GOARM values containing both float suffixes are rejected. The llgo_walltime, llgo_load_library, and llgo_get_proc_address output pointers remain a trusted internal ABI contract; adding per-call null branches there would add overhead without a caller that can legally pass nil.

@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 R4 runtime and callback substrate

Reviewed the full diff (178 files, ~8.3k additions) across four passes: code quality, performance, security, and documentation accuracy.

Overall this is a high-quality, carefully engineered PR. The architecture-specific constants are consistently correct across 386/amd64/arm64 and are backed by tests:

  • libffi ABI enum values (386 STDCALL=2/MS_CDECL=5, amd64 WIN64=1, arm64 WIN64=2)
  • UCRT jmp_buf sizes (64/256/192 bytes)
  • the variable-width SyscallN assembly bridge correctly sizes stack for the 42-argument limit on all three arches, with proper Win64 shadow-space/xmm handling and the x86 dual-cleanup convention
  • SyscallN bounds-checks argument count (n > len(args) and n > 42) before dereferencing
  • the NewCallback closure cache uses check-lock-recheck, frees the loser's closure on a registration race, and keeps GC roots alive — matching upstream Go's never-freed-callback semantics
  • COFF COMDAT emission for ODR globals is the right fix for lld-link and is well-tested

Security pass: no memory-safety or trust-boundary findings. The [65]unsafe.Pointer callback frame buffer is correctly guarded with a heap fallback; setjmp/libuv struct sizes are correctly sized; C shims null-check handles and guard the FLS destructor against process-exit re-entry; the CI workflow uses contents: read on pull_request and interpolates no untrusted PR content. The reflect/value.go change (ret = &vret = v) is itself a genuine fix for a multi-word-return stack overflow.

The findings below are non-blocking: one performance improvement, one consistency question, and minor comment-accuracy nits.

Comment thread runtime/internal/lib/runtime/_wrap/runtime_windows.c Outdated
Comment thread runtime/internal/lib/runtime/syscall_windows_callback_llgo.go
Comment thread runtime/internal/lib/runtime/syscall_windows_llgo.go Outdated
Comment thread runtime/internal/clite/sync/sync_windows.go Outdated
Comment thread runtime/internal/thread/_wrap/thread_windows.c
@cpunion
cpunion force-pushed the codex/windows-r4-runtime-20260825 branch from 42624f8 to 3392c92 Compare August 25, 2026 00:51
@cpunion cpunion changed the title windows: add the Win32 runtime and callback substrate (R4, depends on #2403) windows: add the Win32 runtime and callback substrate (R4) Aug 25, 2026
@cpunion
cpunion force-pushed the codex/windows-r4-runtime-20260825 branch from 3392c92 to b605148 Compare August 25, 2026 03:08
@cpunion
cpunion marked this pull request as draft August 25, 2026 03:48
@cpunion
cpunion marked this pull request as ready for review August 25, 2026 06:43
@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai please re-review the final Windows R4 runtime/callback substrate after the target-isolation cleanup. All CI checks now pass, patch coverage is 90.00%, the Windows native runtime/FFI/GC/wall-clock validation passes, and the same-runner Linux/macOS benchmarks show no binary-size growth.

@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 summary

Large, well-structured port adding Windows runtime support (~4.4k lines across the compiler build path, the C-lite runtime, and the low-level runtime). Reviewed for code quality, performance, security, and documentation accuracy via four focused passes.

Overall: no correctness-blocking or security defects found. The work is unusually disciplined:

  • Build-tag splits across the many _windows.go / _default.go / _unix.go files are consistent and mutually exclusive; symbols are defined exactly once per target (verified the makefunc_callback_windows.go vs makefunc_impl_default.go De Morgan complements and the nativeKeyDestructor func-vs-alias split).
  • ABI-sensitive pieces are handled carefully and backed by tests: syscall bridge frame sizes bound the arg count on the Go side before entering the .S stub (no overflow), the FFI callback path bounds frame size and falls back to a heap slice for >65 args, and setjmp buffer sizes/alignment are per-arch.
  • CI is not exposed to fork-PR secret theft: windows.yml uses pull_request (not pull_request_target) with permissions: contents: read, and the added PowerShell steps interpolate no attacker-controlled github.event.* fields.
  • The prior panic("TODO: ticksPerSecond for windows") is genuinely resolved (trace clock normalized to ns), with no stale TODOs left behind.

The findings below are all minor / non-blocking (nits and one informational perf note).

Also noted (no inline): runtime/internal/clite/os/dir_unix.go now mixes a modern //go:build line with legacy // +build lines; stat.go in the same PR updates both forms consistently. Consider dropping the redundant // +build lines in dir_unix.go for consistency with the new files, since a drift between the two would trip gofmt/vet.

Comment thread runtime/internal/lib/runtime/runtime_gc.go Outdated
Comment thread runtime/internal/clite/debug/_wrap/debug.c Outdated
Comment thread runtime/internal/lib/runtime/_wrap/runtime_windows.c
@fennoai

fennoai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Re-review complete

Re-reviewed the updated diff (120 files, ~4.4k additions; range 1f8cd64...656a9c9) across code-quality, performance, security, and documentation passes. Findings submitted as a PR review.

All 5 findings from the prior review are resolved

  • llgo_nanotime caches the QPC frequency via llgo_nanotime_init (was per-call).
  • Callback foreign-thread exit comment rewritten to the real rationale; reflect/makefunc.go refactored so the paths no longer diverge.
  • syscall_syscalln r2 doc now per-arch accurate (FP amd64 / high word 386 / zero arm64).
  • MutexAttr comment corrected to SetType/Mutex.Init.
  • llgo_win_fls_set now updates slot->destructor on slot reuse.

This revision is clean. No new issues from the target-isolation cleanup:

  • Security — no new memory-safety, trust-boundary, injection, or ABI/frame-sizing issues. SyscallN bounds enforced; .S frames and setjmp jmp_buf sizes correct; CI changes pin versions and pass no untrusted PR content into shell.
  • Performance — none. Per-callback GC registration short-circuits on the TLS flag before the collector; closure cache uses double-checked locking; pre-Go1.26 wrappers are //go:nosplit.
  • Build tags_unix/_windows/_default/pre_go126 partitioning is complete and non-overlapping.

2 low-priority (P3) nits:

  • runtime/internal/runtime/setjmp_windows.go (inline) — the "hidden frame operand" comment doesn't describe the 386 path, where _setjmp3 gets a zero record count rather than a frame pointer. Suggest clarifying per-arch.
  • internal/build/cgo_pragmas.go (line ~102, lowerWindowsCgoImportPointers) — when reusing an existing NamedFunction declaration, arity isn't checked against the alias's %argc; two //go:cgo_import_dynamic entries mapping the same symbol with different arg counts would silently reuse the first signature. Trusted input today, but validating arity on reuse would fail fast with a clear diagnostic. (This finding was omitted from the inline review due to a title-length limit in the submission tooling; recorded here so it isn't lost.)

Design note (not a defect): windowsCallbackMaxFrame charges one full word per non-zero arg — a deliberately conservative approximation of Go's packed callback frame. It can reject some dense sub-word signatures on windows/386 that upstream syscall.NewCallback accepts; documented as intentional.

Nothing blocking. Note: this was a review-only task; I did not run the build/test suite — you confirmed CI, 90% patch coverage, and the native/benchmark validation already pass.

View job run

@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up: the two inline documentation nits are fixed in 513804e and all three threads are resolved. I kept the legacy // +build constraints in dir_unix.go: that file already carried legacy constraints, and this PR updates them to remain exactly equivalent to the changed //go:build expression. Removing legacy constraints across the package is an unrelated cleanup.

@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: Windows R4 runtime and callback substrate (post target-isolation cleanup)

Re-reviewed the updated diff (120 files, ~4.4k additions; authoritative range 1f8cd64...656a9c9) across four passes — code quality, performance, security, documentation — with attention to what changed since the prior review.

All 5 findings from the previous review are resolved:

  • llgo_nanotime now caches the QPC frequency via llgo_nanotime_init instead of querying it per call.
  • The callback foreign-thread exit comment was rewritten to state the real rationale (normal returns call ExitForeignThread to keep the non-lifecycle fallback correct; Goexit leaves via the G lifecycle), and reflect/makefunc.go was refactored so the two paths no longer diverge.
  • syscall_syscalln's r2 doc now describes per-arch behavior precisely (FP on amd64, high word on 386, zero on arm64).
  • The MutexAttr comment now correctly attributes rejection to SetType/Mutex.Init.
  • llgo_win_fls_set now updates slot->destructor on slot reuse, with a clarifying comment.

This revision is clean. The target-isolation cleanup (ssa/eh.go, abitype.go, the native_backend_*/sync_backend_* alias split, and the foreign_thread_gc_windows/foreign_thread_stub_windows partition) is well-structured:

  • Security pass: no new memory-safety, trust-boundary, injection, or ABI/frame-sizing issues. SyscallN bounds are enforced (n > len(args), n > 42); the .S bridge stack frames match; setjmp jmp_buf sizes (64/256/192) and the callback frame buffer remain correct; the CI workflow changes pin dependency versions and pass no untrusted PR content into shell/PowerShell.
  • Performance pass: no meaningful concerns. Per-callback GC registration short-circuits on the foreignThreadGCRegistrationOwned TLS flag before touching the collector; the closure cache uses double-checked locking with entry construction outside the lock; the pre-Go1.26 syscall wrappers are //go:nosplit so their variadic backing array stays on the stack.
  • Build-tag partitioning across _unix/_windows/_default/pre_go126 variants is complete and non-overlapping (no missing platform, no duplicate symbols).

Two low-priority documentation/robustness nits remain, noted inline. Neither is blocking.

One design note (not a defect): windowsCallbackMaxFrame charges one full word per non-zero argument, a deliberately conservative approximation of Go's more tightly-packed callback frame. This can reject some dense sub-word signatures on windows/386 that upstream syscall.NewCallback would accept. The code documents this as intentional; worth keeping in mind as a compatibility gap rather than an exact port.

Comment thread runtime/internal/runtime/setjmp_windows.go Outdated
@cpunion

cpunion commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the remaining non-inline dynamic-import review note in e87c370. %n is a count of consumed uintptr slots rather than LLVM parameter arity, so the lowering now validates the normalized %n shape for every directive sharing a remote symbol and rejects conflicting mappings before reusing the declaration. The review counterexample (Value%1 and Value%2 through different locals) is included as a regression test.

@cpunion
cpunion force-pushed the codex/windows-r4-runtime-20260825 branch from dea79a0 to e87c370 Compare August 25, 2026 08:37
@cpunion
cpunion requested a review from xushiwei August 25, 2026 10:53
@cpunion
cpunion marked this pull request as draft August 25, 2026 11:47
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-r4-runtime-20260825 branch from 8e4f3c1 to 7c94433 Compare August 25, 2026 13:43
@cpunion
cpunion marked this pull request as ready for review August 25, 2026 14:18

@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 R4 runtime support

Large, well-structured PR bringing up the Windows runtime (threading, TLS/FLS, SRW-lock sync, syscall/callback bridges, setjmp ABI, foreign-thread GC registration). Build-tag partitioning (default/unix/windows + arch splits) is consistent, C/asm shims clean up handles and heap allocations on both success and failure paths, and test coverage (FFI, GC, threads, setjmp sizes) is thorough. No blocking correctness defect found in the concurrency, atomics, or syscall-lowering paths.

The notes below are non-blocking. Inline comments cover the specific locations; a few cross-cutting observations follow.

Cross-cutting / no reliable single line:

  • Performance — sync.Once/Mutex/TLS route through non-inlinable C shims. On Windows, (*Once).Do calls into llgo_win_onceInitOnceExecuteOnce on every invocation (not just first), and Mutex.Lock/Unlock wrap AcquireSRWLockExclusive/Release via out-of-line C calls. These primitives sit under getSemaState (sema_llgo.go), which backs all Go channels/mutex/WaitGroup blocking, so the per-op Go→C crossing is on a genuinely hot path. Consider a Go-side atomic fast-path in Once.Do and //go:linkname directly to the SRW ops (as the pthread path does) to drop one indirection per lock/unlock. llgo_win_fls_set also does a HeapAlloc per new TLS slot with an extra pointer chase on every Get; worth confirming that indirection is off the getg fast path.

  • Unverified struct sizes. The hard-coded libuv handle sizes in sizes_windows.go/sizes_windows_386.go (uvHandleSize=96, uvAsyncSize=224, uvTimerSize=160, uvSignalSize=264, and 386 variants) are not covered by any test in this PR, unlike the setjmp/atomic sizes which typesize_test.go validates on the native host. If a value is wrong the runtime silently corrupts memory. Consider a compile-time size assertion or a note pinning them to the packaged libuv version (1.52.1).

  • Supply chain (informational). .github/actions/setup-deps/action.yml installs MSYS2 packages via pacman -U <url> from repo.msys2.org with pinned versions but no repo-side sha256 pin (trust rests on the MSYS2 keyring). Consistent with the existing pattern; flagged for awareness.

  • arm64 syscall bridge unwind (low-confidence). In _wrap/syscall_windows.S, the arm64 .seh_startepilogue/.seh_endepilogue block appears to repeat prologue-style save directives (.seh_save_regp, .seh_save_fplr). CI only checks the symbol name, not unwind-table validity, so a malformed epilogue record could pass CI yet break stack unwinding through this frame during a Windows exception. Worth diffing the generated .xdata/.pdata against a known-good MSVC/Go epilogue.

Comment thread internal/goarch/config.go
value = strings.TrimSuffix(value, softFloat)
}
if strings.HasSuffix(value, hardFloat) {
} else if strings.HasSuffix(value, hardFloat) {

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.

Behavior change to non-Windows code path, buried in a Windows PR. Switching from if to else if changes ParseARM semantics: "7,hardfloat,softfloat" previously parsed successfully (both suffixes stripped) and now returns an error — config_test.go was updated to expect wantErr: true for exactly that case. This is arguably a correctness improvement (rejecting contradictory float suffixes), but it changes an existing ARM-parsing path unrelated to Windows and is easy to miss in a 6600-line diff. Please confirm it's intentional and consider calling it out in the PR description.


import _ "unsafe"

const windowsSyscallMaxArgs = 42

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.

windowsSyscallMaxArgs = 42 is coupled by prose only to three separate hand-written assembly frame sizes in _wrap/syscall_windows.S (amd64 subq $360, arm64 sub sp, #304 / 34 stack args, 386 dynamic). If this constant is bumped without adjusting the .S frames, the bridge would silently overflow the outgoing-argument area (stack corruption) instead of panicking. Consider a comment on each .S frame stating the derived max-arg count, or a CI/compile-time assertion tying the constant to the frame sizes.

@@ -1,4 +1,5 @@
//go:build !(darwin && amd64)
//go:build !windows && !(darwin && amd64)

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.

Mixed build-tag syntax: the //go:build !windows && !(darwin && amd64) line is correct, but the two legacy // +build lines below are ANDed to !windows && (!darwin || !amd64). They happen to be equivalent here, but the dual-syntax is easy to get wrong (stat.go in this same PR was updated cleanly). Consider dropping the legacy // +build lines or aligning them, since they only matter for pre-1.17 toolchains.

@xushiwei
xushiwei merged commit e179169 into xgo-dev:main Aug 26, 2026
43 checks passed
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.

2 participants