Skip to content

cl: preserve C variadic arguments in promoted method wrappers - #2840

Merged
cpunion merged 5 commits into
xgo-dev:mainfrom
cpunion:codex/fix-c-variadic-interface-2838
Oct 18, 2026
Merged

cpunion merged 5 commits into
xgo-dev:mainfrom
cpunion:codex/fix-c-variadic-interface-2838

Conversation

@cpunion

@cpunion cpunion commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Calling a promoted C variadic method through an interface can corrupt its arguments at -O0. The synthetic wrapper adjusts the receiver but drops the incoming ellipsis, so the existing vamethod example prints garbage instead of ifmt (100,200).

Emit LLVM "thunk" wrappers with musttail calls to forward the incoming register and stack arguments unchanged. Check the fixed LLVM prototype, calling convention, and physical/logical result agreement before emitting the call, and omit recover-frame aliases around the tail transfer. This applies to hosted native pointer-receiver wrappers with scalar fixed parameters and at most one scalar result. Wasm, explicit -target profiles (including custom native profiles), value receivers, and aggregate/multiple-result signatures retain their existing lowering. These excluded forms are not fixed by this PR: their wrappers can still drop the incoming ellipsis. Follow-up #2855 tracks correct forwarding and diagnostics, including the reproduced value-receiver/interface failure.

Add O0/O2 execution regressions for the original example and mixed integer/floating-point arguments that exceed the register sets, including nested promotion, value/pointer interface receivers, empty ellipses, and nil receivers. Add LLVM verification and object emission checks after C ABI lowering for 13 Linux, macOS, and Windows target configurations, plus external-declaration, fixed-pointer-parameter, non-variadic, incompatible-receiver, and aggregate/multiple-result checks. Separate SSA tests exercise mismatched fixed prototypes and result-conversion rejection on wasm32 independently of the compiler's target gate. The negative ABI tests establish valid IR and exclusion from the forwarding path; they do not establish correct argument forwarding for excluded forms.

Fixes #2838.

Validation

Passed locally on macOS arm64 with LLVM 22.1.8 and LLD 23.1.3:

  • go test ./cl -run '^Test(CVariadic|IndirectCVariadic)' -count=1
  • go test ./internal/build -run '^TestCVariadicPromotedMethod$' -count=1
  • go test ./cl -run '^TestRunAndTestFromTestrt$/^vamethod$' -count=1
  • go test ./cl -skip '^Test(Run|BuildAndCheckSymbols)' -count=1 -timeout=10m
  • go test ./ssa -count=1
  • go test ./internal/cabi -skip '^Test(Build|ABI)$' -count=1

The new mixed-argument regression fails with the baseline compiler. Also verified the original example at O0 with DWARF enabled after temporarily applying #2836; that independent debug fix is not included here.

The full internal/cabi run hit its five-minute timeout while building the ESP32 toolchain in TestBuild. Cross-target checks above verify and emit objects; execution was tested on macOS arm64.

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

This PR fixes #2838: promoted pointer-method wrappers that forward C variadic calls (e.g. w.Printf(...) through an embedded *CFmt) previously dropped the incoming ellipsis arguments. The fix emits a musttail variadic forwarding call (CallForwardVarargs) instead of a regular call, gated by forwardCVariadicWrapper.

Overall this is a well-crafted, carefully-scoped change. The gating predicate is conservative and its index accesses are safe (the len(call.Args) == 0 guard short-circuits before the call.Args[...] index, and HasNameValist + pointer-receiver guarantees fn.Params is non-empty). The musttail ABI invariant in CallForwardVarargs is validated correctly and fails closed (panic) rather than emitting unsafe IR. The nil-deref/AssertNilDeref receiver check is still emitted ahead of the forwarding call, and bypassing the recover-frame alias is justified because a musttail transfer removes the synthetic wrapper frame and the callee is always a C function.

Test coverage is strong: multi-target IR assertions, O0/O2 end-to-end runtime execution, varargs that overflow both GP and FP register sets after receiver adjustment, empty-varargs, nil-receiver panic, and negative cases (value receiver and external declaration) that lock in the gating boundaries.

The findings below are robustness/maintainability notes, not blocking defects. The main one is a latent panic path that is currently unreachable only because of the target gating.

Minor / non-blocking notes (no reliable inline anchor)

  • cl/instr.go:2829-2846 — the 10-clause || guard in forwardCVariadicWrapper is dense; the safety of the two trailing index accesses depends on invariants established earlier in the same expression. Splitting into labeled early-return guards (with a one-line note that Recv + valist guarantee len(fn.Params) >= 2) would make it more robust to reordering.
  • ssa/expr.go:1497 — "thunk" is a magic string also asserted verbatim by the unit test; a shared constant or a short comment on what the attribute signals to LLVM would reduce producer/test drift.
  • cl/instr_variadic_method_test.go:46-47 — matching the literal ", ...)\n ret i32 " couples the test to LLVM's exact textual whitespace between the call and ret; checking the two properties independently would be less brittle to future LLVM printer changes.

Comment thread ssa/expr.go Outdated
Comment thread cl/instr.go Outdated
@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cpunion

cpunion commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Investigated the wasm-runtime (workers) failure on 9e18154. The full test/go package hit the existing finalizer deadline and then the package's 180-second timeout; the preceding revision also timed out in this GC-heavy suite.

To check for an indirect compiler regression, I built compilers from base c41bc55d9 and this PR, disabled LLGO_BUILD_CACHE, and compiled the same test/go source with LLGO_WASM_WORKERS=2 and the Emscripten target. A repeated baseline build and the PR build produced byte-identical 17,351,520-byte Wasm modules (SHA-256 a95099a77c83824931121b63fa5bea082bdd37786271c30e962d96ea6e4c509f); their JavaScript files were also identical. Both full test runs passed locally on macOS ARM64 with Emscripten 6.0.8 and Node 26.8.1 (CI uses Node 24.22.0).

The native forwarding change explicitly excludes Wasm. I will retry the workers job after its workflow completes, without changing the runtime or relaxing its test deadlines. The separate Windows CRLF assertion failure is fixed in 9e18154 and is being verified by the new CI run.

Update: the targeted workers rerun passed without further source changes or relaxed deadlines. All 12 workflows for 9e1815457 have completed successfully: 105 checks passed and 2 were normally skipped. The Windows MSVC and MinGW Go test suites also passed with the CRLF assertion fix.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

fedd13a678b7 | 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% 806.623 ms -50.79 ms / -5.9% (better) 1.301 ms -42.86 us / -3.2% (better)
Linux cprintf-lto 8408 B 0 B / +0.0% 368 B 0 B / +0.0% 800.246 ms -54.32 ms / -6.4% (better) 1.340 ms +97.74 us / +7.9% (worse)
Linux fmtprintf 4916968 B 0 B / +0.0% 506897 B 0 B / +0.0% 4.931 s -39.51 ms / -0.8% (better) 3.790 ms -127.5 us / -3.3% (better)
Linux fmtprintf-lto 3622784 B 0 B / +0.0% 444811 B 0 B / +0.0% 14.020 s -834.5 ms / -5.6% (better) 3.671 ms -268.4 us / -6.8% (better)
Linux println 705048 B 0 B / +0.0% 17095 B 0 B / +0.0% 826.645 ms -41.13 ms / -4.7% (better) 1.684 ms +72.75 us / +4.5% (worse)
Linux println-lto 192928 B 0 B / +0.0% 14484 B 0 B / +0.0% 1.128 s -31.06 ms / -2.7% (better) 1.620 ms -52.14 us / -3.1% (better)
macOS cprintf 50736 B 0 B / +0.0% 4409 B 0 B / +0.0% 1.357 s +517.1 ms / +61.6% (worse) 11.408 ms +7.414 ms / +185.6% (worse)
macOS cprintf-lto 50496 B 0 B / +0.0% 161 B 0 B / +0.0% 1.667 s +717.7 ms / +75.6% (worse) 5.336 ms +2.524 ms / +89.8% (worse)
macOS fmtprintf 1793088 B 0 B / +0.0% 883744 B 0 B / +0.0% 5.786 s +1.739 s / +43.0% (worse) 36.959 ms -6.185 ms / -14.3% (better)
macOS fmtprintf-lto 1362064 B 0 B / +0.0% 766828 B 0 B / +0.0% 9.564 s +2.419 s / +33.9% (worse) 72.806 ms +21.79 ms / +42.7% (worse)
macOS println 100080 B 0 B / +0.0% 24519 B 0 B / +0.0% 1.905 s +1.076 s / +129.9% (worse) 5.914 ms +1.828 ms / +44.7% (worse)
macOS println-lto 84016 B 0 B / +0.0% 21732 B 0 B / +0.0% 1.968 s +986 ms / +100.4% (worse) 8.830 ms +4.345 ms / +96.9% (worse)
Windows MinGW cprintf 676352 B 0 B / +0.0% 4390 B 0 B / +0.0% 1.868 s +164.3 ms / +9.6% (worse) 4.369 ms +787.9 us / +22.0% (worse)
Windows MinGW cprintf-lto 44544 B 0 B / +0.0% 4342 B 0 B / +0.0% 1.785 s +52.29 ms / +3.0% (worse) 3.827 ms +242.4 us / +6.8% (worse)
Windows MinGW fmtprintf 5473280 B 0 B / +0.0% 605974 B 0 B / +0.0% 5.599 s +140.6 ms / +2.6% (worse) 11.810 ms +1.321 ms / +12.6% (worse)
Windows MinGW fmtprintf-lto 4161024 B 0 B / +0.0% 553094 B 0 B / +0.0% 13.574 s +507.2 ms / +3.9% (worse) 11.088 ms +458.6 us / +4.3% (worse)
Windows MinGW println 733696 B 0 B / +0.0% 25286 B 0 B / +0.0% 1.930 s +271.2 ms / +16.3% (worse) 7.808 ms +808.4 us / +11.5% (worse)
Windows MinGW println-lto 210944 B 0 B / +0.0% 22166 B 0 B / +0.0% 2.211 s +221.6 ms / +11.1% (worse) 8.132 ms +588.1 us / +7.8% (worse)
Windows MinGW 386 cprintf 627200 B 0 B / +0.0% 5352 B 0 B / +0.0% 1.584 s -70.45 ms / -4.3% (better) 5.163 ms +28 us / +0.5% (worse)
Windows MinGW 386 cprintf-lto 104448 B 0 B / +0.0% 5112 B 0 B / +0.0% 1.640 s -18.71 ms / -1.1% (better) 5.247 ms +196.5 us / +3.9% (worse)
Windows MinGW 386 fmtprintf 4776960 B 0 B / +0.0% 473902 B 0 B / +0.0% 5.716 s +130.6 ms / +2.3% (worse) 13.219 ms +293 us / +2.3% (worse)
Windows MinGW 386 fmtprintf-lto 4163072 B 0 B / +0.0% 452294 B 0 B / +0.0% 12.735 s -175 ms / -1.4% (better) 12.706 ms -739.2 us / -5.5% (better)
Windows MinGW 386 println 681472 B 0 B / +0.0% 21694 B 0 B / +0.0% 1.589 s -56.07 ms / -3.4% (better) 8.797 ms -14.1 us / -0.2% (better)
Windows MinGW 386 println-lto 260608 B 0 B / +0.0% 19514 B 0 B / +0.0% 1.931 s -9.911 ms / -0.5% (better) 9.311 ms +293.6 us / +3.3% (worse)
Windows MinGW ARM64 cprintf 686592 B 0 B / +0.0% 4204 B 0 B / +0.0% 1.857 s +10.8 ms / +0.6% (worse) 6.033 ms +79.2 us / +1.3% (worse)
Windows MinGW ARM64 cprintf-lto 44032 B 0 B / +0.0% 4156 B 0 B / +0.0% 1.905 s +14.18 ms / +0.7% (worse) 6.531 ms +625.2 us / +10.6% (worse)
Windows MinGW ARM64 fmtprintf 5382144 B 0 B / +0.0% 514060 B 0 B / +0.0% 5.341 s -34.45 ms / -0.6% (better) 14.672 ms +26.9 us / +0.2% (worse)
Windows MinGW ARM64 fmtprintf-lto 4326912 B 0 B / +0.0% 480120 B 0 B / +0.0% 12.010 s +79.11 ms / +0.7% (worse) 15.523 ms +1.28 ms / +9.0% (worse)
Windows MinGW ARM64 println 739840 B 0 B / +0.0% 23928 B 0 B / +0.0% 1.858 s +13.33 ms / +0.7% (worse) 11.094 ms +848.1 us / +8.3% (worse)
Windows MinGW ARM64 println-lto 218112 B 0 B / +0.0% 21284 B 0 B / +0.0% 2.140 s +51.61 ms / +2.5% (worse) 10.383 ms +317.9 us / +3.2% (worse)
Windows MSVC cprintf 921088 B 0 B / +0.0% 66003 B 0 B / +0.0% 1.197 s +96.08 ms / +8.7% (worse) 2.735 ms +6.1 us / +0.2% (worse)
Windows MSVC cprintf-lto 292352 B 0 B / +0.0% 65971 B 0 B / +0.0% 1.118 s +7.907 ms / +0.7% (worse) 2.719 ms -15 us / -0.5% (better)
Windows MSVC fmtprintf 5774848 B 0 B / +0.0% 701894 B 0 B / +0.0% 3.966 s +20.84 ms / +0.5% (worse) 9.220 ms +324.4 us / +3.6% (worse)
Windows MSVC fmtprintf-lto 4484096 B 0 B / +0.0% 652806 B 0 B / +0.0% 9.578 s +249.6 ms / +2.7% (worse) 9.120 ms +41.9 us / +0.5% (worse)
Windows MSVC println 1044992 B 0 B / +0.0% 121334 B 0 B / +0.0% 1.103 s -1.44 ms / -0.1% (better) 5.491 ms +86.4 us / +1.6% (worse)
Windows MSVC println-lto 531456 B 0 B / +0.0% 118870 B 0 B / +0.0% 1.332 s +33.49 ms / +2.6% (worse) 8.690 ms +3.048 ms / +54.0% (worse)
Windows MSVC 386 cprintf 538624 B 0 B / +0.0% 3947 B 0 B / +0.0% 1.638 s +331.3 ms / +25.3% (worse) 3.612 ms -929.3 us / -20.5% (better)
Windows MSVC 386 cprintf-lto 44544 B 0 B / +0.0% 3901 B 0 B / +0.0% 1.225 s -554 ms / -31.1% (better) 4.188 ms -839.7 us / -16.7% (better)
Windows MSVC 386 fmtprintf 4508672 B 0 B / +0.0% 457314 B 0 B / +0.0% 4.228 s +41.14 ms / +1.0% (worse) 9.598 ms +509.6 us / +5.6% (worse)
Windows MSVC 386 fmtprintf-lto 3910656 B 0 B / +0.0% 427681 B 0 B / +0.0% 9.144 s -176.4 ms / -1.9% (better) 9.567 ms -403.3 us / -4.0% (better)
Windows MSVC 386 println 593920 B 0 B / +0.0% 20566 B 0 B / +0.0% 1.398 s -177 ms / -11.2% (better) 6.358 ms -812.3 us / -11.3% (better)
Windows MSVC 386 println-lto 200192 B 0 B / +0.0% 18711 B 0 B / +0.0% 1.892 s +239 ms / +14.5% (worse) 8.145 ms +287.8 us / +3.7% (worse)
Windows MSVC ARM64 cprintf 687616 B 0 B / +0.0% 4240 B 0 B / +0.0% 1.586 s -57.08 ms / -3.5% (better) 6.989 ms -175.6 us / -2.5% (better)
Windows MSVC ARM64 cprintf-lto 49152 B 0 B / +0.0% 4140 B 0 B / +0.0% 1.619 s -19.81 ms / -1.2% (better) 6.702 ms -301.1 us / -4.3% (better)
Windows MSVC ARM64 fmtprintf 5379072 B 0 B / +0.0% 514164 B 0 B / +0.0% 5.078 s -104.7 ms / -2.0% (better) 17.570 ms +191.4 us / +1.1% (worse)
Windows MSVC ARM64 fmtprintf-lto 4334080 B 0 B / +0.0% 480960 B 0 B / +0.0% 11.686 s +171.5 ms / +1.5% (worse) 17.569 ms +54.1 us / +0.3% (worse)
Windows MSVC ARM64 println 741376 B 0 B / +0.0% 24200 B 0 B / +0.0% 1.585 s -35.81 ms / -2.2% (better) 11.753 ms -1.167 ms / -9.0% (better)
Windows MSVC ARM64 println-lto 223232 B 0 B / +0.0% 21688 B 0 B / +0.0% 1.809 s -78.08 ms / -4.1% (better) 11.883 ms -626.1 us / -5.0% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 14.570 ns/op +0.02 ns/op / +0.1% (worse)
Linux BenchmarkMergeCompilerFlags 200.600 ns/op -1.8 ns/op / -0.9% (better)
Linux BenchmarkMergeLinkerFlags 126.700 ns/op -0.2 ns/op / -0.2% (better)
Linux BenchmarkChannelBuffered 56.570 ns/op +1.46 ns/op / +2.6% (worse)
Linux BenchmarkChannelHandoff 13309 ns/op -110 ns/op / -0.8% (better)
Linux BenchmarkDefer 47.320 ns/op +0.11 ns/op / +0.2% (worse)
Linux BenchmarkDirectCall 1.165 ns/op -0.002 ns/op / -0.2% (better)
Linux BenchmarkGlobalRead 1.170 ns/op +0.006 ns/op / +0.5% (worse)
Linux BenchmarkGlobalWrite 7.769 ns/op +0.009 ns/op / +0.1% (worse)
Linux BenchmarkGoroutine 24158 ns/op +29 ns/op / +0.1% (worse)
Linux BenchmarkInterfaceCall 6.235 ns/op +0.024 ns/op / +0.4% (worse)
Linux BenchmarkRuntimeGetG 2.932 ns/op -0.047 ns/op / -1.6% (better)
macOS BenchmarkLookupPCRandom 14.990 ns/op +1.94 ns/op / +14.9% (worse)
macOS BenchmarkMergeCompilerFlags 156.400 ns/op +50.6 ns/op / +47.8% (worse)
macOS BenchmarkMergeLinkerFlags 99.910 ns/op +28.78 ns/op / +40.5% (worse)
macOS BenchmarkChannelBuffered 27.620 ns/op +2.12 ns/op / +8.3% (worse)
macOS BenchmarkChannelHandoff 10385 ns/op +4697 ns/op / +82.6% (worse)
macOS BenchmarkDefer 34.470 ns/op -5.67 ns/op / -14.1% (better)
macOS BenchmarkDirectCall 1.170 ns/op +0.083 ns/op / +7.6% (worse)
macOS BenchmarkGlobalRead 1.118 ns/op +0.041 ns/op / +3.8% (worse)
macOS BenchmarkGlobalWrite 1.177 ns/op +0.09 ns/op / +8.3% (worse)
macOS BenchmarkGoroutine 34863 ns/op -15092 ns/op / -30.2% (better)
macOS BenchmarkInterfaceCall 3.977 ns/op -0.894 ns/op / -18.4% (better)
macOS BenchmarkRuntimeGetG 2.506 ns/op -0.348 ns/op / -12.2% (better)
Windows MinGW BenchmarkLookupPCRandom 13.230 ns/op +0.13 ns/op / +1.0% (worse)
Windows MinGW BenchmarkMergeCompilerFlags 618.500 ns/op -18.4 ns/op / -2.9% (better)
Windows MinGW BenchmarkMergeLinkerFlags 547.500 ns/op -0.8 ns/op / -0.1% (better)
Windows MinGW BenchmarkChannelBuffered 30.130 ns/op +0.06 ns/op / +0.2% (worse)
Windows MinGW BenchmarkChannelHandoff 978.400 ns/op +104.8 ns/op / +12.0% (worse)
Windows MinGW BenchmarkDefer 55.060 ns/op +0.71 ns/op / +1.3% (worse)
Windows MinGW BenchmarkDirectCall 1.551 ns/op +0.003 ns/op / +0.2% (worse)
Windows MinGW BenchmarkGlobalRead 1.549 ns/op 0 ns/op / +0.0%
Windows MinGW BenchmarkGlobalWrite 2.456 ns/op -0.001 ns/op / -0.0407% (better)
Windows MinGW BenchmarkGoroutine 94114 ns/op +2236 ns/op / +2.4% (worse)
Windows MinGW BenchmarkInterfaceCall 8.687 ns/op -0.017 ns/op / -0.2% (better)
Windows MinGW BenchmarkRuntimeGetG 1.865 ns/op 0 ns/op / +0.0%
Windows MinGW 386 BenchmarkLookupPCRandom 26.480 ns/op -0.09 ns/op / -0.3% (better)
Windows MinGW 386 BenchmarkMergeCompilerFlags 793.700 ns/op +29.2 ns/op / +3.8% (worse)
Windows MinGW 386 BenchmarkMergeLinkerFlags 687.100 ns/op +27.8 ns/op / +4.2% (worse)
Windows MinGW 386 BenchmarkChannelBuffered 40.360 ns/op +0.36 ns/op / +0.9% (worse)
Windows MinGW 386 BenchmarkChannelHandoff 821 ns/op +29.2 ns/op / +3.7% (worse)
Windows MinGW 386 BenchmarkDefer 42.660 ns/op -2.03 ns/op / -4.5% (better)
Windows MinGW 386 BenchmarkDirectCall 1.553 ns/op +0.003 ns/op / +0.2% (worse)
Windows MinGW 386 BenchmarkGlobalRead 1.555 ns/op +0.005 ns/op / +0.3% (worse)
Windows MinGW 386 BenchmarkGlobalWrite 7.786 ns/op +0.001 ns/op / +0.01285% (worse)
Windows MinGW 386 BenchmarkGoroutine 103007 ns/op +512 ns/op / +0.5% (worse)
Windows MinGW 386 BenchmarkInterfaceCall 8.349 ns/op -0.022 ns/op / -0.3% (better)
Windows MinGW 386 BenchmarkRuntimeGetG 2.478 ns/op -0.004 ns/op / -0.2% (better)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.080 ns/op -0.01 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 575.200 ns/op +11.3 ns/op / +2.0% (worse)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 529.900 ns/op +5.8 ns/op / +1.1% (worse)
Windows MinGW ARM64 BenchmarkChannelBuffered 38.770 ns/op +0.78 ns/op / +2.1% (worse)
Windows MinGW ARM64 BenchmarkChannelHandoff 2961 ns/op +51 ns/op / +1.8% (worse)
Windows MinGW ARM64 BenchmarkDefer 56.230 ns/op +1.69 ns/op / +3.1% (worse)
Windows MinGW ARM64 BenchmarkDirectCall 0.590 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 BenchmarkGlobalRead 0.664 ns/op +0.0002 ns/op / +0.03012% (worse)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.663 ns/op -0.0002 ns/op / -0.03015% (better)
Windows MinGW ARM64 BenchmarkGoroutine 63287 ns/op +3323 ns/op / +5.5% (worse)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.231 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.775 ns/op +0.001 ns/op / +0.1% (worse)
Windows MSVC BenchmarkLookupPCRandom 9.739 ns/op +0.062 ns/op / +0.6% (worse)
Windows MSVC BenchmarkMergeCompilerFlags 477.300 ns/op +105.7 ns/op / +28.4% (worse)
Windows MSVC BenchmarkMergeLinkerFlags 397.800 ns/op +64 ns/op / +19.2% (worse)
Windows MSVC BenchmarkChannelBuffered 23.210 ns/op +0.14 ns/op / +0.6% (worse)
Windows MSVC BenchmarkChannelHandoff 1112 ns/op +2 ns/op / +0.2% (worse)
Windows MSVC BenchmarkDefer 42.770 ns/op +0.26 ns/op / +0.6% (worse)
Windows MSVC BenchmarkDirectCall 1.358 ns/op +0.001 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalRead 1.358 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalWrite 2.170 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGoroutine 57097 ns/op -2641 ns/op / -4.4% (better)
Windows MSVC BenchmarkInterfaceCall 7.073 ns/op +0.009 ns/op / +0.1% (worse)
Windows MSVC BenchmarkRuntimeGetG 1.631 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC 386 BenchmarkLookupPCRandom 15.920 ns/op -0.15 ns/op / -0.9% (better)
Windows MSVC 386 BenchmarkMergeCompilerFlags 1208 ns/op +230.4 ns/op / +23.6% (worse)
Windows MSVC 386 BenchmarkMergeLinkerFlags 1055 ns/op +373.8 ns/op / +54.9% (worse)
Windows MSVC 386 BenchmarkChannelBuffered 36.810 ns/op -0.27 ns/op / -0.7% (better)
Windows MSVC 386 BenchmarkChannelHandoff 865.600 ns/op +81.1 ns/op / +10.3% (worse)
Windows MSVC 386 BenchmarkDefer 26.600 ns/op -0.72 ns/op / -2.6% (better)
Windows MSVC 386 BenchmarkDirectCall 0.940 ns/op +0.0026 ns/op / +0.3% (worse)
Windows MSVC 386 BenchmarkGlobalRead 0.946 ns/op -0.0185 ns/op / -1.9% (better)
Windows MSVC 386 BenchmarkGlobalWrite 8.504 ns/op +0.033 ns/op / +0.4% (worse)
Windows MSVC 386 BenchmarkGoroutine 54648 ns/op -877 ns/op / -1.6% (better)
Windows MSVC 386 BenchmarkInterfaceCall 4.736 ns/op -0.001 ns/op / -0.02111% (better)
Windows MSVC 386 BenchmarkRuntimeGetG 1.074 ns/op +0.006 ns/op / +0.6% (worse)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.020 ns/op -0.06 ns/op / -0.5% (better)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 574.600 ns/op +9.8 ns/op / +1.7% (worse)
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 537.400 ns/op +7.3 ns/op / +1.4% (worse)
Windows MSVC ARM64 BenchmarkChannelBuffered 39 ns/op +0.76 ns/op / +2.0% (worse)
Windows MSVC ARM64 BenchmarkChannelHandoff 4927 ns/op +1200 ns/op / +32.2% (worse)
Windows MSVC ARM64 BenchmarkDefer 61.410 ns/op -2.43 ns/op / -3.8% (better)
Windows MSVC ARM64 BenchmarkDirectCall 0.590 ns/op -0.0002 ns/op / -0.0339% (better)
Windows MSVC ARM64 BenchmarkGlobalRead 0.591 ns/op +0.0002 ns/op / +0.03388% (worse)
Windows MSVC ARM64 BenchmarkGlobalWrite 3.749 ns/op -0.003 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkGoroutine 59821 ns/op +1963 ns/op / +3.4% (worse)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.144 ns/op -0.002 ns/op / -0.04824% (better)
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.769 ns/op -0.001 ns/op / -0.1% (better)
Timer runtime benchmarks
Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 903.200 ns/op -17.8 ns/op / -1.9% (better)
Linux AfterFuncZeroDelivery/LLGo 38374 ns/op -2312 ns/op / -5.7% (better)
Linux CreateStop/Go 289.800 ns/op -5 ns/op / -1.7% (better)
Linux CreateStop/LLGo 1785 ns/op +43 ns/op / +2.5% (worse)
Linux RearmStopped/Go 116.400 ns/op +0.4 ns/op / +0.3% (worse)
Linux RearmStopped/LLGo 1085 ns/op -127 ns/op / -10.5% (better)
Linux ResetActive/Go 69.650 ns/op +0.91 ns/op / +1.3% (worse)
Linux ResetActive/LLGo 564 ns/op -208.9 ns/op / -27.0% (better)
Linux ResetHeap1024/Go 67.270 ns/op -0.22 ns/op / -0.3% (better)
Linux ResetHeap1024/LLGo 161.600 ns/op -2.8 ns/op / -1.7% (better)
macOS AfterFuncZeroDelivery/Go 516.200 ns/op +47.7 ns/op / +10.2% (worse)
macOS AfterFuncZeroDelivery/LLGo 45442 ns/op +12954 ns/op / +39.9% (worse)
macOS CreateStop/Go 211.300 ns/op +47 ns/op / +28.6% (worse)
macOS CreateStop/LLGo 733.100 ns/op +317.7 ns/op / +76.5% (worse)
macOS RearmStopped/Go 79.190 ns/op +19.27 ns/op / +32.2% (worse)
macOS RearmStopped/LLGo 304.100 ns/op +20.4 ns/op / +7.2% (worse)
macOS ResetActive/Go 51.220 ns/op +8.14 ns/op / +18.9% (worse)
macOS ResetActive/LLGo 145.200 ns/op +0.2 ns/op / +0.1% (worse)
macOS ResetHeap1024/Go 54.860 ns/op +11.43 ns/op / +26.3% (worse)
macOS ResetHeap1024/LLGo 79.390 ns/op +3.13 ns/op / +4.1% (worse)
Windows MinGW AfterFuncZeroDelivery/Go 564.800 ns/op +19 ns/op / +3.5% (worse)
Windows MinGW AfterFuncZeroDelivery/LLGo 182320 ns/op -2474 ns/op / -1.3% (better)
Windows MinGW CreateStop/Go 117.200 ns/op -2.5 ns/op / -2.1% (better)
Windows MinGW CreateStop/LLGo 465.600 ns/op +2.4 ns/op / +0.5% (worse)
Windows MinGW RearmStopped/Go 31.530 ns/op +0.25 ns/op / +0.8% (worse)
Windows MinGW RearmStopped/LLGo 230.600 ns/op -22 ns/op / -8.7% (better)
Windows MinGW ResetActive/Go 20.110 ns/op +0.01 ns/op / +0.04975% (worse)
Windows MinGW ResetActive/LLGo 153.100 ns/op -3.4 ns/op / -2.2% (better)
Windows MinGW ResetHeap1024/Go 20.460 ns/op +0.08 ns/op / +0.4% (worse)
Windows MinGW ResetHeap1024/LLGo 119.200 ns/op +2.5 ns/op / +2.1% (worse)
Windows MinGW 386 AfterFuncZeroDelivery/Go 955.200 ns/op -5 ns/op / -0.5% (better)
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 200590 ns/op +535 ns/op / +0.3% (worse)
Windows MinGW 386 CreateStop/Go 190.800 ns/op +1 ns/op / +0.5% (worse)
Windows MinGW 386 CreateStop/LLGo 465.800 ns/op -16.5 ns/op / -3.4% (better)
Windows MinGW 386 RearmStopped/Go 63.580 ns/op +0.15 ns/op / +0.2% (worse)
Windows MinGW 386 RearmStopped/LLGo 314.400 ns/op -0.3 ns/op / -0.1% (better)
Windows MinGW 386 ResetActive/Go 38.970 ns/op -0.07 ns/op / -0.2% (better)
Windows MinGW 386 ResetActive/LLGo 935.500 ns/op -66.5 ns/op / -6.6% (better)
Windows MinGW 386 ResetHeap1024/Go 39.400 ns/op +0.02 ns/op / +0.1% (worse)
Windows MinGW 386 ResetHeap1024/LLGo 174.200 ns/op -0.6 ns/op / -0.3% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 669.500 ns/op -1.5 ns/op / -0.2% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 149587 ns/op +3682 ns/op / +2.5% (worse)
Windows MinGW ARM64 CreateStop/Go 192.200 ns/op -8.9 ns/op / -4.4% (better)
Windows MinGW ARM64 CreateStop/LLGo 407.500 ns/op +14.3 ns/op / +3.6% (worse)
Windows MinGW ARM64 RearmStopped/Go 70.610 ns/op -0.82 ns/op / -1.1% (better)
Windows MinGW ARM64 RearmStopped/LLGo 219.500 ns/op +2.7 ns/op / +1.2% (worse)
Windows MinGW ARM64 ResetActive/Go 30.990 ns/op -0.48 ns/op / -1.5% (better)
Windows MinGW ARM64 ResetActive/LLGo 111.300 ns/op -15.4 ns/op / -12.2% (better)
Windows MinGW ARM64 ResetHeap1024/Go 31.130 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 ResetHeap1024/LLGo 111.500 ns/op -0.2 ns/op / -0.2% (better)
Windows MSVC AfterFuncZeroDelivery/Go 382.700 ns/op -1.8 ns/op / -0.5% (better)
Windows MSVC AfterFuncZeroDelivery/LLGo 107600 ns/op -1680 ns/op / -1.5% (better)
Windows MSVC CreateStop/Go 99.490 ns/op +10.17 ns/op / +11.4% (worse)
Windows MSVC CreateStop/LLGo 363.700 ns/op +51.5 ns/op / +16.5% (worse)
Windows MSVC RearmStopped/Go 25.560 ns/op +1.06 ns/op / +4.3% (worse)
Windows MSVC RearmStopped/LLGo 197.400 ns/op -1.8 ns/op / -0.9% (better)
Windows MSVC ResetActive/Go 15.570 ns/op +0.79 ns/op / +5.3% (worse)
Windows MSVC ResetActive/LLGo 122 ns/op -3.6 ns/op / -2.9% (better)
Windows MSVC ResetHeap1024/Go 14.960 ns/op +0.19 ns/op / +1.3% (worse)
Windows MSVC ResetHeap1024/LLGo 95.730 ns/op -0.86 ns/op / -0.9% (better)
Windows MSVC 386 AfterFuncZeroDelivery/Go 629.600 ns/op -12.8 ns/op / -2.0% (better)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 112191 ns/op +2710 ns/op / +2.5% (worse)
Windows MSVC 386 CreateStop/Go 163.100 ns/op -0.2 ns/op / -0.1% (better)
Windows MSVC 386 CreateStop/LLGo 401.200 ns/op +31.4 ns/op / +8.5% (worse)
Windows MSVC 386 RearmStopped/Go 62.020 ns/op -0.51 ns/op / -0.8% (better)
Windows MSVC 386 RearmStopped/LLGo 263.400 ns/op -36.4 ns/op / -12.1% (better)
Windows MSVC 386 ResetActive/Go 29.610 ns/op 0 ns/op / +0.0%
Windows MSVC 386 ResetActive/LLGo 783.400 ns/op +234.4 ns/op / +42.7% (worse)
Windows MSVC 386 ResetHeap1024/Go 29.930 ns/op +0.05 ns/op / +0.2% (worse)
Windows MSVC 386 ResetHeap1024/LLGo 108 ns/op +0.4 ns/op / +0.4% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 663.800 ns/op -4.3 ns/op / -0.6% (better)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 144356 ns/op -1769 ns/op / -1.2% (better)
Windows MSVC ARM64 CreateStop/Go 193.300 ns/op -16.9 ns/op / -8.0% (better)
Windows MSVC ARM64 CreateStop/LLGo 345.500 ns/op -2.6 ns/op / -0.7% (better)
Windows MSVC ARM64 RearmStopped/Go 70.670 ns/op +0.05 ns/op / +0.1% (worse)
Windows MSVC ARM64 RearmStopped/LLGo 222.300 ns/op +1.1 ns/op / +0.5% (worse)
Windows MSVC ARM64 ResetActive/Go 31.180 ns/op +0.08 ns/op / +0.3% (worse)
Windows MSVC ARM64 ResetActive/LLGo 111.400 ns/op +4.1 ns/op / +3.8% (worse)
Windows MSVC ARM64 ResetHeap1024/Go 31.180 ns/op +0.07 ns/op / +0.2% (worse)
Windows MSVC ARM64 ResetHeap1024/LLGo 116.900 ns/op -0.9 ns/op / -0.8% (better)

Compared with c41bc55d918b measured in the same runner job.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

fedd13a678b7 | 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 147058 B 0 B / +0.0% 88541 B 0 B / +0.0%
cprintf/j32-goos-js/LLGo 145666 B 0 B / +0.0% 72985 B 0 B / +0.0%
cprintf/j64-emscripten-memory64/LLGo 134527 B 0 B / +0.0% 91712 B 0 B / +0.0%
cprintf/w32-goos-wasip1/LLGo 156385 B 0 B / +0.0% 0 B 0 B / 0.0%
cprintf/w32-wasi/LLGo 157083 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/j32-emscripten/LLGo 2983979 B 0 B / +0.0% 120989 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 3074748 B 0 B / +0.0% 92052 B 0 B / +0.0%
fmtprintf/j64-emscripten-memory64/LLGo 2748837 B 0 B / +0.0% 125530 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 2343602 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/w32-wasi/LLGo 2340298 B 0 B / +0.0% 0 B 0 B / 0.0%
j32-emscripten/LLGo 146575 B 0 B / +0.0% 88541 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 145416 B 0 B / +0.0% 72985 B 0 B / +0.0%
j64-emscripten-memory64/LLGo 134082 B 0 B / +0.0% 91712 B 0 B / +0.0%
reflectcall/j32-emscripten/LLGo 1510134 B 0 B / +0.0% 95329 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 1562301 B 0 B / +0.0% 79769 B 0 B / +0.0%
reflectcall/j64-emscripten-memory64/LLGo 1391259 B 0 B / +0.0% 99026 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 1273789 B 0 B / +0.0% 0 B 0 B / 0.0%
reflectcall/w32-wasi/LLGo 1271430 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 156059 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-wasi/LLGo 156757 B 0 B / +0.0% 0 B 0 B / 0.0%
LLGo WebAssembly build measurements
Example and profile Build vs base
j32-emscripten 7.239 s +57.44 ms / +0.8% (worse)
j32-goos-js 7.179 s +12.71 ms / +0.2% (worse)
j64-emscripten-memory64 6.306 s +43.66 ms / +0.7% (worse)
reflectcall/w32-wasi 24.082 s +450 ms / +1.9% (worse)
w32-goos-wasip1 4.409 s +1.59 ms / +0.03608% (worse)
w32-wasi 4.194 s -129.3 ms / -3.0% (better)

Compared with c41bc55d918b measured in the same runner job.

@zhouguangyuan0718 zhouguangyuan0718 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed commit 9e18154. The thunk + musttail approach is appropriate for the supported pointer-receiver wrappers: forwarding the incoming ellipsis is required for correctness, including at O0. The prototype, calling-convention and result checks are useful safeguards. The focused cl/ssa tests and the O0/O2 execution regression passed locally on macOS arm64 with LLVM 22 and LLD 23.

One remaining correctness limitation should be tracked explicitly: promoted C variadic methods with a value receiver still lose their arguments when called through an interface. I reproduced this on this PR's head at O0: the example below should print 42 but prints -1.

main.go:

package main

import _ "unsafe"

const LLGoFiles = "_wrap/check.c"

type Receiver int32

//llgo:link Receiver.Check C.review_check
func (Receiver) Check(__llgo_va_list ...any) int32 { return 0 }

type Embedded struct{ Receiver }

type Checker interface {
    Check(__llgo_va_list ...any) int32
}

//go:noinline
func check(v Checker) int32 {
    return v.Check(int32(100), int32(200))
}

func main() {
    e := Embedded{7}
    println(check(&e))
}

_wrap/check.c:

#include <stdarg.h>
#include <stdint.h>

int32_t review_check(int32_t receiver, ...) {
    va_list args;
    va_start(args, receiver);
    int a = va_arg(args, int);
    int b = va_arg(args, int);
    va_end(args);
    return receiver == 7 && a == 100 && b == 200 ? 42 : -1;
}

Run the fixture with llgo run -O0 -ldflags=-w=true ./path/to/fixture.

This is an existing limitation deliberately excluded by forwardCVariadicWrapper, not a regression introduced by this PR, and it does not invalidate the fix for #2838. Please track the remaining unsupported forwarding forms in a follow-up issue and retain the narrow scope in the documentation. For calls whose incoming ellipsis cannot be forwarded correctly, a clear diagnostic would be preferable to silently generating an ordinary call that drops arguments. The negative ABI tests currently establish valid IR, not correct argument forwarding for these excluded cases.

I would keep this PR focused on the qualified ABI subset rather than expanding it to solve every remaining signature and target in the same change.

@cpunion

cpunion commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator Author

@zhouguangyuan0718 Addressed in fedd13a.

Validation passed:

go test ./cl ./ssa -run '^Test(CVariadic|IndirectCVariadic)' -count=1
go test ./internal/build -run '^TestCVariadicPromotedMethod$' -count=1

The execution regression covers the original and mixed-argument fixtures at O0/O2. Correct forwarding or diagnostics for the excluded forms remains tracked by #2855.

@cpunion
cpunion force-pushed the codex/fix-c-variadic-interface-2838 branch from fedd13a to a7e2843 Compare October 18, 2026 21:11
@cpunion
cpunion merged commit 65e2f2f into xgo-dev:main Oct 18, 2026
103 checks passed
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.

darwin/arm64: C variadic interface method calls corrupt arguments at O0

2 participants