Skip to content

fix(cgo): guard C exports in dependencies and native executables - #2752

Merged
cpunion merged 10 commits into
xgo-dev:mainfrom
cpunion:fix/cgo-export-foreign-threads
Oct 8, 2026
Merged

cpunion merged 10 commits into
xgo-dev:mainfrom
cpunion:fix/cgo-export-foreign-threads

Conversation

@cpunion

@cpunion cpunion commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

A C-created thread calling a //export function in an executable or imported package can crash with Collecting from unknown thread (macOS). Existing thread guards covered only main-package c-shared / c-archive exports.

//export Callback
func Callback(v C.int) C.int {
    runtime.GC()
    return v + 2
}
void *worker(void *unused) {
    Callback(20); // This thread was created by C.
    return NULL;
}
pthread_create(&thread, NULL, worker, NULL);

Extend the existing guards to ordinary Go exports in native Linux, macOS, and Windows builds:

Before: @Callback = Go body, without thread registration
After:  @Callback → EnterForeignThread → @main.Callback → ExitForeignThread

C.Callback function pointers also resolve to the guarded public entry. Retain export metadata through parallel package builds. Preserve package C's direct symbols (Double, Xadd → add) and runtime-owned entries.

Validation:

  • C-created-thread allocation/GC callbacks pass via direct calls and C function pointers; the original crash was reproduced before the fix.
  • Runtime regressions live under test/cgo. Four-level reentry passes on both Go and C-created threads, including GC after inner returns, outer allocation retention, and all defers.
  • The regular test/cgo suite executes the standalone fixture on Linux amd64, macOS arm64, and Windows amd64/arm64/386 (MSVC and MinGW), using the normal build cache.
  • Source ENTRY checks verify public wrappers and their Go targets; package-C ABI, export snapshots, and dead-code retention checks pass.
  • Local Go and LLGo test/cgo runs pass, including repeated runs with caching and the Go 1.20/1.27 version runners. Latest CI is pending.

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@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: fix(cgo): protect native C exports in dependencies and executables

This is a solid, well-scoped fix. Executables and dependency packages that expose //export functions now correctly link the runtime and wrap each export with foreign-thread registration (EnterForeignThread/ExitForeignThread + a one-time EnableForeignThreadRegistration), so callbacks entering Go from C-created threads are GC-safe. The linkedCExports/packageCExports refactor cleanly separates pure declaration extraction from the snapshot fallback, preserving prior validation semantics while surviving isolated-worker disposal.

What I verified (all sound):

  • needsCExportWrappers is applied consistently at both decision sites (linkedCExports, preparePackageModule). cSharedExportArgs intentionally uses a different, consistent symbol-retention mechanism for shared libraries.
  • dceEntryRootCandidates reads pkg.LPkg directly, but that is safe: applyDeadcodeDropOverrides runs only when deadcode drop is enabled, whereas snapshot-then-dispose runs only when it is disabled — the two are mutually exclusive, so no disposed LPkg is ever touched there.
  • Snapshot retention is safe: cExport.sig is a *types.Signature (a go/types value owned by the build-scoped frontend), not an LLVM-backed object, so the snapshot does not keep LLVM graphs alive after disposal — matching the doc comment.
  • The new threads.c fixture is correct: workers[3]/threads[3] are bounded, started gates the join loop to successfully-created threads, every thread is joined/closed, and the result arithmetic ((20+22)×4×3 = 504, 24 total calls) matches main.go's assertions.
  • All rewritten comments accurately describe the new behavior.

Minor, non-blocking observations:

  • dceEntryRootCandidates (build.go:~2610) re-implements per-package export enumeration directly from pkg.LPkg.ExportFuncs()/FuncOf() and, unlike the new packageCExportDeclarations, does not apply the foreign-export filter (strings.Contains(goName, ".") && !strings.HasPrefix(goName, pkg.Path()+".")). It's harmless today (an extra DCE root at worst keeps an already-defined symbol, and this path predates the PR), but the "which exports belong to this package" rule now lives in two places with slightly different logic. Consider routing it through packageCExports/packageCExportDeclarations so the ownership rule is defined once.

No blocking issues found.

Comment thread internal/build/build.go
return exports
}

func packageCExports(pkg *aPackage) []cExport {

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.

Nit (non-blocking): unlike the sibling snapshot accessors linkedModuleGlobals (build.go:~2711) and the loop in cSharedExportArgs (build.go:~2927), packageCExports dereferences pkg without a nil guard. No live bug — both current callers reject/skip nil before reaching here — but a if pkg == nil { return nil } guard would keep this helper consistent with its neighbors and robust for future callers.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

743afec5446e | workflow run | long-term charts

WebAssembly output sizes
Example, profile and compiler Wasm module vs base Generated JS glue vs base
cprintf/j32-emscripten/LLGo 149240 B 0 B / +0.0% 88157 B 0 B / +0.0%
cprintf/j32-goos-js/LLGo 148511 B 0 B / +0.0% 72985 B 0 B / +0.0%
cprintf/j64-emscripten-memory64/LLGo 136707 B 0 B / +0.0% 91328 B 0 B / +0.0%
cprintf/w32-goos-wasip1/LLGo 154296 B 0 B / +0.0% 0 B 0 B / 0.0%
cprintf/w32-wasi/LLGo 154979 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/j32-emscripten/LLGo 3056999 B 0 B / +0.0% 130373 B 0 B / +0.0%
fmtprintf/j32-goos-js/Go 2526852 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/j32-goos-js/LLGo 3152979 B 0 B / +0.0% 100773 B 0 B / +0.0%
fmtprintf/j64-emscripten-memory64/LLGo 2819092 B 0 B / +0.0% 135853 B 0 B / +0.0%
fmtprintf/w32-goos-wasip1/Go 2500019 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/w32-goos-wasip1/LLGo 2355448 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/w32-wasi/LLGo 2352026 B 0 B / +0.0% 0 B 0 B / 0.0%
j32-emscripten/LLGo 148582 B 0 B / +0.0% 88157 B 0 B / +0.0%
j32-goos-js/Go 1895533 B 0 B / +0.0% 0 B 0 B / 0.0%
j32-goos-js/LLGo 148002 B 0 B / +0.0% 72985 B 0 B / +0.0%
j64-emscripten-memory64/LLGo 136047 B 0 B / +0.0% 91328 B 0 B / +0.0%
reflectcall/j32-emscripten/LLGo 1481123 B 0 B / +0.0% 104917 B 0 B / +0.0%
reflectcall/j32-goos-js/Go 2191221 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/j32-goos-js/LLGo 1530979 B 0 B / +0.0% 89743 B 0 B / +0.0%
reflectcall/j64-emscripten-memory64/LLGo 1373248 B 0 B / +0.0% 109492 B 0 B / +0.0%
reflectcall/w32-goos-wasip1/Go 2205707 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/w32-goos-wasip1/LLGo 1283895 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/w32-wasi/LLGo 1281420 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-goos-wasip1/Go 1909947 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-goos-wasip1/LLGo 153943 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-wasi/LLGo 154626 B 0 B / +0.0% 0 B 0 B / 0.0%
LLGo WebAssembly build measurements
Example and profile Build vs base
j32-emscripten 7.369 s -42.44 ms / -0.6% (better)
j32-goos-js 7.043 s -359.6 ms / -4.9% (better)
j64-emscripten-memory64 6.232 s -65.16 ms / -1.0% (better)
reflectcall/w32-wasi 23.233 s -994.7 ms / -4.1% (better)
w32-goos-wasip1 4.387 s +24.65 ms / +0.6% (worse)
w32-wasi 4.068 s -92.01 ms / -2.2% (better)

Compared with 3f01c79b76a1 measured in the same runner job.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

743afec5446e | 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 8568 B 0 B / +0.0% 387 B 0 B / +0.0% 943.378 ms -50.24 ms / -5.1% (better) 1.293 ms -6.291 us / -0.5% (better)
Linux cprintf-lto 8408 B 0 B / +0.0% 368 B 0 B / +0.0% 1.005 s +33.83 ms / +3.5% (worse) 1.304 ms +61.55 us / +5.0% (worse)
Linux fmtprintf 4886280 B 0 B / +0.0% 504761 B 0 B / +0.0% 7.489 s +109.2 ms / +1.5% (worse) 3.153 ms +50.44 us / +1.6% (worse)
Linux fmtprintf-lto 3616568 B 0 B / +0.0% 443075 B 0 B / +0.0% 16.452 s -91.26 ms / -0.6% (better) 2.941 ms +25.4 us / +0.9% (worse)
Linux println 675040 B 0 B / +0.0% 16855 B 0 B / +0.0% 1.013 s -12.61 ms / -1.2% (better) 1.611 ms -19.26 us / -1.2% (better)
Linux println-lto 190040 B 0 B / +0.0% 14273 B 0 B / +0.0% 1.287 s -39.35 ms / -3.0% (better) 1.713 ms +25.28 us / +1.5% (worse)
macOS cprintf 50736 B 0 B / +0.0% 4409 B 0 B / +0.0% 1.686 s +272.7 ms / +19.3% (worse) 4.914 ms +348.3 us / +7.6% (worse)
macOS cprintf-lto 50496 B 0 B / +0.0% 161 B 0 B / +0.0% 1.297 s +168.4 ms / +14.9% (worse) 4.183 ms +718.8 us / +20.8% (worse)
macOS fmtprintf 1774368 B 0 B / +0.0% 880529 B 0 B / +0.0% 8.559 s -349.5 ms / -3.9% (better) 11.538 ms +528.3 us / +4.8% (worse)
macOS fmtprintf-lto 1361232 B 0 B / +0.0% 764957 B 0 B / +0.0% 9.507 s -3.75 s / -28.3% (better) 4.032 ms -3.084 ms / -43.3% (better)
macOS println 99344 B 0 B / +0.0% 24216 B 0 B / +0.0% 1.202 s +300.6 ms / +33.4% (worse) 4.677 ms +690.4 us / +17.3% (worse)
macOS println-lto 83664 B 0 B / +0.0% 21457 B 0 B / +0.0% 1.147 s -114.4 ms / -9.1% (better) 3.868 ms -699.1 us / -15.3% (better)
Windows MinGW cprintf 651264 B 0 B / +0.0% 4550 B 0 B / +0.0% 1.684 s -176.6 ms / -9.5% (better) 3.433 ms -603.1 us / -14.9% (better)
Windows MinGW cprintf-lto 44032 B 0 B / +0.0% 4502 B 0 B / +0.0% 1.713 s -174.2 ms / -9.2% (better) 3.412 ms -734.8 us / -17.7% (better)
Windows MinGW fmtprintf 5440512 B 0 B / +0.0% 604470 B 0 B / +0.0% 7.507 s -135.1 ms / -1.8% (better) 7.920 ms -340.9 us / -4.1% (better)
Windows MinGW fmtprintf-lto 4144128 B 0 B / +0.0% 551590 B 0 B / +0.0% 14.900 s -132.8 ms / -0.9% (better) 7.844 ms +2.3 us / +0.02933% (worse)
Windows MinGW println 706048 B 0 B / +0.0% 25190 B 0 B / +0.0% 1.788 s -74.86 ms / -4.0% (better) 6.576 ms -906.4 us / -12.1% (better)
Windows MinGW println-lto 208896 B 0 B / +0.0% 22070 B 0 B / +0.0% 1.985 s -196.4 ms / -9.0% (better) 6.432 ms -1.521 ms / -19.1% (better)
Windows MinGW 386 cprintf 601600 B 0 B / +0.0% 5352 B 0 B / +0.0% 1.480 s +51.59 ms / +3.6% (worse) 4.366 ms -168.4 us / -3.7% (better)
Windows MinGW 386 cprintf-lto 103424 B 0 B / +0.0% 5112 B 0 B / +0.0% 1.488 s -78.5 ms / -5.0% (better) 4.012 ms -991.5 us / -19.8% (better)
Windows MinGW 386 fmtprintf 4745728 B 0 B / +0.0% 472472 B 0 B / +0.0% 6.564 s -135.8 ms / -2.0% (better) 8.681 ms -1.551 ms / -15.2% (better)
Windows MinGW 386 fmtprintf-lto 4148736 B 0 B / +0.0% 451132 B 0 B / +0.0% 13.095 s +33.44 ms / +0.3% (worse) 8.851 ms -490.7 us / -5.3% (better)
Windows MinGW 386 println 654336 B 0 B / +0.0% 21500 B 0 B / +0.0% 1.607 s +28.54 ms / +1.8% (worse) 8.494 ms +442.2 us / +5.5% (worse)
Windows MinGW 386 println-lto 258560 B 0 B / +0.0% 19328 B 0 B / +0.0% 1.845 s +68.62 ms / +3.9% (worse) 7.426 ms -1.092 ms / -12.8% (better)
Windows MinGW ARM64 cprintf 662016 B 0 B / +0.0% 4436 B 0 B / +0.0% 1.848 s -4.137 ms / -0.2% (better) 6.106 ms +134.9 us / +2.3% (worse)
Windows MinGW ARM64 cprintf-lto 43520 B 0 B / +0.0% 4388 B 0 B / +0.0% 1.881 s +22.8 ms / +1.2% (worse) 6.477 ms +433.9 us / +7.2% (worse)
Windows MinGW ARM64 fmtprintf 5348352 B 0 B / +0.0% 512664 B 0 B / +0.0% 6.959 s +29.56 ms / +0.4% (worse) 12.555 ms +552.4 us / +4.6% (worse)
Windows MinGW ARM64 fmtprintf-lto 4311552 B 0 B / +0.0% 478852 B 0 B / +0.0% 13.531 s +164 ms / +1.2% (worse) 13.102 ms +1.318 ms / +11.2% (worse)
Windows MinGW ARM64 println 713728 B 0 B / +0.0% 23912 B 0 B / +0.0% 1.849 s +22.65 ms / +1.2% (worse) 10.532 ms -763 us / -6.8% (better)
Windows MinGW ARM64 println-lto 215552 B 0 B / +0.0% 21272 B 0 B / +0.0% 2.094 s +3.019 ms / +0.1% (worse) 10.832 ms +267.6 us / +2.5% (worse)
Windows MSVC cprintf 895488 B 0 B / +0.0% 66019 B 0 B / +0.0% 1.655 s -26.61 ms / -1.6% (better) 4.987 ms +1.512 ms / +43.5% (worse)
Windows MSVC cprintf-lto 291328 B 0 B / +0.0% 65971 B 0 B / +0.0% 1.711 s +42.84 ms / +2.6% (worse) 4.086 ms +713.7 us / +21.2% (worse)
Windows MSVC fmtprintf 5742080 B 0 B / +0.0% 700214 B 0 B / +0.0% 7.473 s +57.52 ms / +0.8% (worse) 9.636 ms -936.9 us / -8.9% (better)
Windows MSVC fmtprintf-lto 4469248 B 0 B / +0.0% 651350 B 0 B / +0.0% 14.651 s -87.8 ms / -0.6% (better) 10.242 ms +297.4 us / +3.0% (worse)
Windows MSVC println 1018880 B 0 B / +0.0% 121062 B 0 B / +0.0% 1.636 s -20.73 ms / -1.3% (better) 7.640 ms +400 ns / +0.005236% (worse)
Windows MSVC println-lto 529920 B 0 B / +0.0% 118598 B 0 B / +0.0% 1.930 s +3.374 ms / +0.2% (worse) 7.415 ms -375.1 us / -4.8% (better)
Windows MSVC 386 cprintf 514048 B 0 B / +0.0% 3947 B 0 B / +0.0% 1.702 s +151.9 ms / +9.8% (worse) 7.872 ms +1.635 ms / +26.2% (worse)
Windows MSVC 386 cprintf-lto 44032 B 0 B / +0.0% 3901 B 0 B / +0.0% 1.654 s +66.94 ms / +4.2% (worse) 6.608 ms +361.9 us / +5.8% (worse)
Windows MSVC 386 fmtprintf 4480000 B 0 B / +0.0% 455900 B 0 B / +0.0% 8.074 s +690.3 ms / +9.3% (worse) 14.702 ms -198.2 us / -1.3% (better)
Windows MSVC 386 fmtprintf-lto 3899904 B 0 B / +0.0% 426667 B 0 B / +0.0% 15.150 s +893.9 ms / +6.3% (worse) 13.694 ms +768.1 us / +5.9% (worse)
Windows MSVC 386 println 568320 B 0 B / +0.0% 20356 B 0 B / +0.0% 1.750 s +202.3 ms / +13.1% (worse) 10.802 ms +328.6 us / +3.1% (worse)
Windows MSVC 386 println-lto 199168 B 0 B / +0.0% 18517 B 0 B / +0.0% 2.053 s +253.7 ms / +14.1% (worse) 12.296 ms +1.851 ms / +17.7% (worse)
Windows MSVC ARM64 cprintf 662528 B 0 B / +0.0% 4240 B 0 B / +0.0% 1.743 s +103.6 ms / +6.3% (worse) 7.933 ms +936.4 us / +13.4% (worse)
Windows MSVC ARM64 cprintf-lto 48640 B 0 B / +0.0% 4140 B 0 B / +0.0% 1.761 s +141 ms / +8.7% (worse) 8.001 ms +1.43 ms / +21.8% (worse)
Windows MSVC ARM64 fmtprintf 5345792 B 0 B / +0.0% 512612 B 0 B / +0.0% 7.049 s +303 ms / +4.5% (worse) 15.115 ms +668.4 us / +4.6% (worse)
Windows MSVC ARM64 fmtprintf-lto 4319232 B 0 B / +0.0% 479536 B 0 B / +0.0% 13.695 s +546.2 ms / +4.2% (worse) 15.503 ms +1.506 ms / +10.8% (worse)
Windows MSVC ARM64 println 716288 B 0 B / +0.0% 23956 B 0 B / +0.0% 1.757 s +144.7 ms / +9.0% (worse) 14.175 ms +1.796 ms / +14.5% (worse)
Windows MSVC ARM64 println-lto 220672 B 0 B / +0.0% 21444 B 0 B / +0.0% 1.977 s +143.4 ms / +7.8% (worse) 14.214 ms +2.099 ms / +17.3% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 14.430 ns/op -1.48 ns/op / -9.3% (better)
Linux BenchmarkMergeCompilerFlags 197.300 ns/op +6.2 ns/op / +3.2% (worse)
Linux BenchmarkMergeLinkerFlags 133.500 ns/op +7.4 ns/op / +5.9% (worse)
Linux BenchmarkChannelBuffered 55.060 ns/op +0.41 ns/op / +0.8% (worse)
Linux BenchmarkChannelHandoff 12688 ns/op -108 ns/op / -0.8% (better)
Linux BenchmarkDefer 47.680 ns/op -2.21 ns/op / -4.4% (better)
Linux BenchmarkDirectCall 1.519 ns/op 0 ns/op / +0.0%
Linux BenchmarkGlobalRead 1.560 ns/op -0.005 ns/op / -0.3% (better)
Linux BenchmarkGlobalWrite 7.773 ns/op +0.008 ns/op / +0.1% (worse)
Linux BenchmarkGoroutine 24590 ns/op -6320 ns/op / -20.4% (better)
Linux BenchmarkInterfaceCall 6.405 ns/op +0.187 ns/op / +3.0% (worse)
Linux BenchmarkRuntimeGetG 2.891 ns/op +0.021 ns/op / +0.7% (worse)
macOS BenchmarkLookupPCRandom 11.820 ns/op -6.63 ns/op / -35.9% (better)
macOS BenchmarkMergeCompilerFlags 126 ns/op -61.8 ns/op / -32.9% (better)
macOS BenchmarkMergeLinkerFlags 63.100 ns/op -42.8 ns/op / -40.4% (better)
macOS BenchmarkChannelBuffered 22.840 ns/op -10.84 ns/op / -32.2% (better)
macOS BenchmarkChannelHandoff 6515 ns/op -1250 ns/op / -16.1% (better)
macOS BenchmarkDefer 30.580 ns/op -14.1 ns/op / -31.6% (better)
macOS BenchmarkDirectCall 0.969 ns/op -0.208 ns/op / -17.7% (better)
macOS BenchmarkGlobalRead 1.093 ns/op -0.106 ns/op / -8.8% (better)
macOS BenchmarkGlobalWrite 1.001 ns/op -0.309 ns/op / -23.6% (better)
macOS BenchmarkGoroutine 34783 ns/op -26113 ns/op / -42.9% (better)
macOS BenchmarkInterfaceCall 3.478 ns/op -0.994 ns/op / -22.2% (better)
macOS BenchmarkRuntimeGetG 2.027 ns/op -0.631 ns/op / -23.7% (better)
Windows MinGW BenchmarkLookupPCRandom 12.870 ns/op -0.14 ns/op / -1.1% (better)
Windows MinGW BenchmarkMergeCompilerFlags 628 ns/op -49 ns/op / -7.2% (better)
Windows MinGW BenchmarkMergeLinkerFlags 545.600 ns/op -45.4 ns/op / -7.7% (better)
Windows MinGW BenchmarkChannelBuffered 32.180 ns/op +0.8 ns/op / +2.5% (worse)
Windows MinGW BenchmarkChannelHandoff 950.900 ns/op +24.4 ns/op / +2.6% (worse)
Windows MinGW BenchmarkDefer 57.100 ns/op -0.06 ns/op / -0.1% (better)
Windows MinGW BenchmarkDirectCall 1.548 ns/op +0.002 ns/op / +0.1% (worse)
Windows MinGW BenchmarkGlobalRead 1.552 ns/op +0.002 ns/op / +0.1% (worse)
Windows MinGW BenchmarkGlobalWrite 2.470 ns/op 0 ns/op / +0.0%
Windows MinGW BenchmarkGoroutine 90588 ns/op +160 ns/op / +0.2% (worse)
Windows MinGW BenchmarkInterfaceCall 7.758 ns/op +0.001 ns/op / +0.01289% (worse)
Windows MinGW BenchmarkRuntimeGetG 2.483 ns/op +0.001 ns/op / +0.04029% (worse)
Windows MinGW 386 BenchmarkLookupPCRandom 23.360 ns/op 0 ns/op / +0.0%
Windows MinGW 386 BenchmarkMergeCompilerFlags 606.500 ns/op +8.3 ns/op / +1.4% (worse)
Windows MinGW 386 BenchmarkMergeLinkerFlags 564.500 ns/op -2.8 ns/op / -0.5% (better)
Windows MinGW 386 BenchmarkChannelBuffered 37.090 ns/op +0.13 ns/op / +0.4% (worse)
Windows MinGW 386 BenchmarkChannelHandoff 781 ns/op -40.5 ns/op / -4.9% (better)
Windows MinGW 386 BenchmarkDefer 38.990 ns/op -0.23 ns/op / -0.6% (better)
Windows MinGW 386 BenchmarkDirectCall 1.475 ns/op +0.001 ns/op / +0.1% (worse)
Windows MinGW 386 BenchmarkGlobalRead 1.774 ns/op -0.006 ns/op / -0.3% (better)
Windows MinGW 386 BenchmarkGlobalWrite 7.577 ns/op -0.051 ns/op / -0.7% (better)
Windows MinGW 386 BenchmarkGoroutine 78282 ns/op +581 ns/op / +0.7% (worse)
Windows MinGW 386 BenchmarkInterfaceCall 8.002 ns/op +0.042 ns/op / +0.5% (worse)
Windows MinGW 386 BenchmarkRuntimeGetG 1.786 ns/op +0.013 ns/op / +0.7% (worse)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.070 ns/op -0.01 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 582.900 ns/op +5.8 ns/op / +1.0% (worse)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 538.200 ns/op +9.7 ns/op / +1.8% (worse)
Windows MinGW ARM64 BenchmarkChannelBuffered 38.760 ns/op +1.28 ns/op / +3.4% (worse)
Windows MinGW ARM64 BenchmarkChannelHandoff 2134 ns/op -161 ns/op / -7.0% (better)
Windows MinGW ARM64 BenchmarkDefer 52.900 ns/op -1.97 ns/op / -3.6% (better)
Windows MinGW ARM64 BenchmarkDirectCall 0.663 ns/op -0.0001 ns/op / -0.01508% (better)
Windows MinGW ARM64 BenchmarkGlobalRead 0.590 ns/op -0.0009 ns/op / -0.2% (better)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.737 ns/op -0.0004 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkGoroutine 60621 ns/op -1428 ns/op / -2.3% (better)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.146 ns/op -0.002 ns/op / -0.04822% (better)
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.769 ns/op -0.035 ns/op / -1.9% (better)
Windows MSVC BenchmarkLookupPCRandom 13.050 ns/op -0.2 ns/op / -1.5% (better)
Windows MSVC BenchmarkMergeCompilerFlags 632.600 ns/op -23.4 ns/op / -3.6% (better)
Windows MSVC BenchmarkMergeLinkerFlags 547.700 ns/op -33.5 ns/op / -5.8% (better)
Windows MSVC BenchmarkChannelBuffered 28.900 ns/op +0.22 ns/op / +0.8% (worse)
Windows MSVC BenchmarkChannelHandoff 1102 ns/op +85 ns/op / +8.4% (worse)
Windows MSVC BenchmarkDefer 55.360 ns/op -0.3 ns/op / -0.5% (better)
Windows MSVC BenchmarkDirectCall 1.547 ns/op -0.002 ns/op / -0.1% (better)
Windows MSVC BenchmarkGlobalRead 1.548 ns/op -0.003 ns/op / -0.2% (better)
Windows MSVC BenchmarkGlobalWrite 2.471 ns/op +0.001 ns/op / +0.04049% (worse)
Windows MSVC BenchmarkGoroutine 89072 ns/op -1067 ns/op / -1.2% (better)
Windows MSVC BenchmarkInterfaceCall 8.372 ns/op -0.001 ns/op / -0.01194% (better)
Windows MSVC BenchmarkRuntimeGetG 2.173 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC 386 BenchmarkLookupPCRandom 70.870 ns/op -0.43 ns/op / -0.6% (better)
Windows MSVC 386 BenchmarkMergeCompilerFlags 1046 ns/op +125.2 ns/op / +13.6% (worse)
Windows MSVC 386 BenchmarkMergeLinkerFlags 910.300 ns/op +55.8 ns/op / +6.5% (worse)
Windows MSVC 386 BenchmarkChannelBuffered 52.330 ns/op -1.28 ns/op / -2.4% (better)
Windows MSVC 386 BenchmarkChannelHandoff 15473 ns/op +4720 ns/op / +43.9% (worse)
Windows MSVC 386 BenchmarkDefer 62.160 ns/op -1.81 ns/op / -2.8% (better)
Windows MSVC 386 BenchmarkDirectCall 0.904 ns/op -0.0312 ns/op / -3.3% (better)
Windows MSVC 386 BenchmarkGlobalRead 0.875 ns/op -0.0054 ns/op / -0.6% (better)
Windows MSVC 386 BenchmarkGlobalWrite 17.040 ns/op +0.43 ns/op / +2.6% (worse)
Windows MSVC 386 BenchmarkGoroutine 238594 ns/op +10338 ns/op / +4.5% (worse)
Windows MSVC 386 BenchmarkInterfaceCall 6.614 ns/op -0.188 ns/op / -2.8% (better)
Windows MSVC 386 BenchmarkRuntimeGetG 2.080 ns/op +0.025 ns/op / +1.2% (worse)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.100 ns/op -0.01 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 571.900 ns/op -11.7 ns/op / -2.0% (better)
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 535.400 ns/op -5.7 ns/op / -1.1% (better)
Windows MSVC ARM64 BenchmarkChannelBuffered 38.590 ns/op +1.07 ns/op / +2.9% (worse)
Windows MSVC ARM64 BenchmarkChannelHandoff 2834 ns/op +83 ns/op / +3.0% (worse)
Windows MSVC ARM64 BenchmarkDefer 64.550 ns/op +0.1 ns/op / +0.2% (worse)
Windows MSVC ARM64 BenchmarkDirectCall 0.590 ns/op -0.0002 ns/op / -0.03392% (better)
Windows MSVC ARM64 BenchmarkGlobalRead 0.590 ns/op 0 ns/op / +0.0%
Windows MSVC ARM64 BenchmarkGlobalWrite 3.832 ns/op -0.003 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkGoroutine 62947 ns/op -1733 ns/op / -2.7% (better)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.137 ns/op 0 ns/op / +0.0%
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.805 ns/op +0.003 ns/op / +0.2% (worse)
Timer runtime benchmarks
Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 903 ns/op +2.6 ns/op / +0.3% (worse)
Linux AfterFuncZeroDelivery/LLGo 35977 ns/op -5263 ns/op / -12.8% (better)
Linux CreateStop/Go 289.800 ns/op +0.7 ns/op / +0.2% (worse)
Linux CreateStop/LLGo 2011 ns/op +168 ns/op / +9.1% (worse)
Linux RearmStopped/Go 115.900 ns/op -0.4 ns/op / -0.3% (better)
Linux RearmStopped/LLGo 1344 ns/op -27 ns/op / -2.0% (better)
Linux ResetActive/Go 68.680 ns/op -0.22 ns/op / -0.3% (better)
Linux ResetActive/LLGo 746.300 ns/op -132.2 ns/op / -15.0% (better)
Linux ResetHeap1024/Go 67.100 ns/op +0.01 ns/op / +0.01491% (worse)
Linux ResetHeap1024/LLGo 178.400 ns/op +1 ns/op / +0.6% (worse)
macOS AfterFuncZeroDelivery/Go 470.200 ns/op -72.5 ns/op / -13.4% (better)
macOS AfterFuncZeroDelivery/LLGo 77928 ns/op -12472 ns/op / -13.8% (better)
macOS CreateStop/Go 182.500 ns/op -44.2 ns/op / -19.5% (better)
macOS CreateStop/LLGo 408.600 ns/op -380.8 ns/op / -48.2% (better)
macOS RearmStopped/Go 64.850 ns/op -8.1 ns/op / -11.1% (better)
macOS RearmStopped/LLGo 311.700 ns/op -62.7 ns/op / -16.7% (better)
macOS ResetActive/Go 44.920 ns/op -9.61 ns/op / -17.6% (better)
macOS ResetActive/LLGo 157.600 ns/op -34.2 ns/op / -17.8% (better)
macOS ResetHeap1024/Go 44.880 ns/op -8.94 ns/op / -16.6% (better)
macOS ResetHeap1024/LLGo 82.950 ns/op -10.35 ns/op / -11.1% (better)
Windows MinGW AfterFuncZeroDelivery/Go 553 ns/op +2.5 ns/op / +0.5% (worse)
Windows MinGW AfterFuncZeroDelivery/LLGo 179172 ns/op +418 ns/op / +0.2% (worse)
Windows MinGW CreateStop/Go 115.800 ns/op -2.8 ns/op / -2.4% (better)
Windows MinGW CreateStop/LLGo 449.200 ns/op +11 ns/op / +2.5% (worse)
Windows MinGW RearmStopped/Go 31.270 ns/op +0.1 ns/op / +0.3% (worse)
Windows MinGW RearmStopped/LLGo 281.900 ns/op +4 ns/op / +1.4% (worse)
Windows MinGW ResetActive/Go 20.080 ns/op -0.08 ns/op / -0.4% (better)
Windows MinGW ResetActive/LLGo 152 ns/op -10.1 ns/op / -6.2% (better)
Windows MinGW ResetHeap1024/Go 20.370 ns/op -0.01 ns/op / -0.04907% (better)
Windows MinGW ResetHeap1024/LLGo 125.600 ns/op -3.4 ns/op / -2.6% (better)
Windows MinGW 386 AfterFuncZeroDelivery/Go 839.300 ns/op -21.9 ns/op / -2.5% (better)
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 147000 ns/op -4247 ns/op / -2.8% (better)
Windows MinGW 386 CreateStop/Go 183.200 ns/op -1.8 ns/op / -1.0% (better)
Windows MinGW 386 CreateStop/LLGo 446.200 ns/op +5.9 ns/op / +1.3% (worse)
Windows MinGW 386 RearmStopped/Go 61.480 ns/op -1.72 ns/op / -2.7% (better)
Windows MinGW 386 RearmStopped/LLGo 298.800 ns/op +1.7 ns/op / +0.6% (worse)
Windows MinGW 386 ResetActive/Go 35.310 ns/op -1.68 ns/op / -4.5% (better)
Windows MinGW 386 ResetActive/LLGo 957.200 ns/op +18.2 ns/op / +1.9% (worse)
Windows MinGW 386 ResetHeap1024/Go 35.560 ns/op -0.79 ns/op / -2.2% (better)
Windows MinGW 386 ResetHeap1024/LLGo 163.500 ns/op +2.9 ns/op / +1.8% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 667.300 ns/op -1.8 ns/op / -0.3% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 138048 ns/op -3039 ns/op / -2.2% (better)
Windows MinGW ARM64 CreateStop/Go 200.600 ns/op -1.3 ns/op / -0.6% (better)
Windows MinGW ARM64 CreateStop/LLGo 355.900 ns/op -19.4 ns/op / -5.2% (better)
Windows MinGW ARM64 RearmStopped/Go 70.580 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 RearmStopped/LLGo 251.300 ns/op -1 ns/op / -0.4% (better)
Windows MinGW ARM64 ResetActive/Go 31.010 ns/op -0.14 ns/op / -0.4% (better)
Windows MinGW ARM64 ResetActive/LLGo 122.300 ns/op +5 ns/op / +4.3% (worse)
Windows MinGW ARM64 ResetHeap1024/Go 31.140 ns/op +0.07 ns/op / +0.2% (worse)
Windows MinGW ARM64 ResetHeap1024/LLGo 124.300 ns/op -0.7 ns/op / -0.6% (better)
Windows MSVC AfterFuncZeroDelivery/Go 576.500 ns/op +14.9 ns/op / +2.7% (worse)
Windows MSVC AfterFuncZeroDelivery/LLGo 177184 ns/op +2658 ns/op / +1.5% (worse)
Windows MSVC CreateStop/Go 113.500 ns/op -2.6 ns/op / -2.2% (better)
Windows MSVC CreateStop/LLGo 401.300 ns/op +8.9 ns/op / +2.3% (worse)
Windows MSVC RearmStopped/Go 31.550 ns/op -0.13 ns/op / -0.4% (better)
Windows MSVC RearmStopped/LLGo 248.200 ns/op -56 ns/op / -18.4% (better)
Windows MSVC ResetActive/Go 20.040 ns/op -0.04 ns/op / -0.2% (better)
Windows MSVC ResetActive/LLGo 143.300 ns/op -16.3 ns/op / -10.2% (better)
Windows MSVC ResetHeap1024/Go 20.400 ns/op -0.2 ns/op / -1.0% (better)
Windows MSVC ResetHeap1024/LLGo 125.200 ns/op -3.4 ns/op / -2.6% (better)
Windows MSVC 386 AfterFuncZeroDelivery/Go 1147 ns/op -18 ns/op / -1.5% (better)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 379890 ns/op +15971 ns/op / +4.4% (worse)
Windows MSVC 386 CreateStop/Go 286.800 ns/op +17.5 ns/op / +6.5% (worse)
Windows MSVC 386 CreateStop/LLGo 602.800 ns/op -44.1 ns/op / -6.8% (better)
Windows MSVC 386 RearmStopped/Go 98.640 ns/op +5.46 ns/op / +5.9% (worse)
Windows MSVC 386 RearmStopped/LLGo 409.500 ns/op -4.6 ns/op / -1.1% (better)
Windows MSVC 386 ResetActive/Go 47.200 ns/op +0.35 ns/op / +0.7% (worse)
Windows MSVC 386 ResetActive/LLGo 323 ns/op +24.1 ns/op / +8.1% (worse)
Windows MSVC 386 ResetHeap1024/Go 48.250 ns/op +0.88 ns/op / +1.9% (worse)
Windows MSVC 386 ResetHeap1024/LLGo 182.200 ns/op -2 ns/op / -1.1% (better)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 686.500 ns/op +16.5 ns/op / +2.5% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 158666 ns/op +2401 ns/op / +1.5% (worse)
Windows MSVC ARM64 CreateStop/Go 201.800 ns/op +1.5 ns/op / +0.7% (worse)
Windows MSVC ARM64 CreateStop/LLGo 413.200 ns/op +19.4 ns/op / +4.9% (worse)
Windows MSVC ARM64 RearmStopped/Go 70.560 ns/op -0.08 ns/op / -0.1% (better)
Windows MSVC ARM64 RearmStopped/LLGo 274.300 ns/op +1.5 ns/op / +0.5% (worse)
Windows MSVC ARM64 ResetActive/Go 31.170 ns/op +0.1 ns/op / +0.3% (worse)
Windows MSVC ARM64 ResetActive/LLGo 136.500 ns/op +6.3 ns/op / +4.8% (worse)
Windows MSVC ARM64 ResetHeap1024/Go 31.200 ns/op 0 ns/op / +0.0%
Windows MSVC ARM64 ResetHeap1024/LLGo 137.100 ns/op +1.3 ns/op / +1.0% (worse)

Compared with 3f01c79b76a1 measured in the same runner job.

@cpunion
cpunion marked this pull request as draft October 7, 2026 23:29
@cpunion
cpunion marked this pull request as ready for review October 8, 2026 02:51

@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: fix(cgo): guard C exports in dependencies and native executables

Re-reviewed the current head (8b9982f) across code quality, performance, security/memory-safety, and documentation accuracy. The fix remains solid and well-scoped: executables and dependency packages exposing //export now link the runtime and wrap each export with foreign-thread registration (EnterForeignThread/ExitForeignThread + a one-time EnableForeignThreadRegistration), making C-created-thread callbacks GC-safe, while package C correctly retains its direct unqualified exports.

Verified sound this pass:

  • Memory safety / threading — defineCExportWrappers ordering is correct, and callbackInit (EnableForeignThreadRegistration) runs after rtInit but before any package init/main, so registration is enabled before C can obtain a Go function pointer. The absence of a deferred ExitForeignThread is not a leak: in the TLS-lifecycle path the real unregister happens in the thread-exit destructor, matching the pre-existing library wrapper behavior.
  • cgo address substitution (cgo.go:217-222) — correctly rebinds C's taken address from a same-named/shadowing Go implementation to the public export's final-link wrapper, so the GC-safe registering entry is never bypassed. Well pinned by TestGenExternDeclsUsesPublicExportAddress.
  • threads.c fixture — bounded workers[3]/threads[3], started gates the join loop, no uninitialized handles, no cross-worker races; (20+22)×4×3 = 504 and 36 total calls match main.go.
  • Determinism — ExportFuncs() map iteration order never leaks: linkedCExports and cSharedExportArgs sort before emitting, and the cgo.go loop breaks on an exact match.
  • Comments — all rewritten doc/comments (cl/compile.go, build.go, main_module.go, and the ENTRY/CHECK FileCheck directives in the fixtures) accurately describe the new behavior.

Outstanding (already filed, non-blocking): the earlier nit on packageCExports (build.go:2646) — missing if pkg == nil { return nil } guard, unlike sibling accessors packageCExportDeclarations, linkedModuleGlobals, and cSharedExportArgs — is still unaddressed on this head. It's already tracked in the unresolved inline thread, so I'm not duplicating it here. No live bug (both current callers pre-filter nil); purely a consistency/robustness improvement.

No blocking issues found.

@cpunion
cpunion merged commit b86d349 into xgo-dev:main Oct 8, 2026
98 checks passed
@cpunion
cpunion deleted the fix/cgo-export-foreign-threads branch October 15, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants