Skip to content

compiler: add early AVX2 SIMD128 multiversioning through Go APIs - #2748

Merged
xushiwei merged 13 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/simd-fmv-20261007
Oct 12, 2026
Merged

xushiwei merged 13 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/simd-fmv-20261007

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Dependency: #2773 extracts the Windows LTO physical-function identity fix and adds a SIMD-independent regression. The overlapping changes under runtime/internal/lib/runtime remain necessary until that PR merges; rebasing afterward will remove them from this PR's diff. Removing them now reproduces TestSIMDFMVInlineTraceback on Windows ThinLTO even after rebasing onto main 686181ad6.

An archsimd.X86.AVX2() guard currently runs the same baseline SIMD128 implementation even when AVX2 is available. Add early function multiversioning implemented in Go through the LLVM Go API, before aggregate ABI lowering and target optimization, including at O0 and without an LTO plugin.

The public entry loads an atomic cached implementation and tail-forwards to it. A separate noinline resolver selects the baseline or AVX2 body from the immutable post-GODEBUG CPU snapshot. Calls before CPU initialization tail-forward to the baseline without caching. GOAMD64=v3/v4 fold the AVX2 query directly and create no redundant versions, resolver, or slot.

FMV inlining policy follows GoALLC: public dispatchers and implementations remain eligible for normal, feature-compatible LLVM inlining; the resolver and only its pre-initialization fallback edge are opaque. Explicit source noinline directives and disabled inlining remain honored. Compiler-required physical source frames stay on implementations. The synthetic dispatcher has no debug subprogram, so LLVM preserves the real callsite and outer inline chain without adding a duplicate source frame. LLGo's general runtime inline-frame infrastructure remains separate from GoALLC's GoObj inline tree.

Specialized direct calls use matching entries within and across packages. Function addresses retain the original entry; bodyless assembly/linkname declarations do not promise specialized implementations. Both implementations fold the AVX2 query while independent AVX/FMA query results remain observable under GODEBUG. Clones retain Go display names, distinct PC-line records, and their own debug linkage names.

Windows/amd64 SIMD128 parameters use the native indirect ABI explicitly before FMV. Dispatchers and resolvers forward caller-owned parameter storage through tail jumps instead of passing vector temporaries in a released stack frame; vector returns retain their register ABI. Windows function-entry records are grouped by PE unwind boundaries so inline copies cannot overwrite the physical caller identity. Failed native SIMD CI binaries are retained for diagnosis.

All FMV policy, discovery, cloning decisions, call rewriting, dispatcher construction, and Go metadata remapping are in Go. LLVM's generic cloning and CFG utilities preserve SSA/debug references and remove dead branches even at O0. LLGo has no FMV C++ implementation or private C entry point.

Scope: amd64, one AVX2 profile, and scalar/pointer/SIMD128 signatures. Aggregate signatures, general feature combinations, portable simd, and 256/512-bit vector ABIs remain follow-up work.

Dependency: github.com/xgo-dev/llvm v0.10.1 contains the merged generic IR utility APIs from xgo-dev/llvm#56, including checked input handling and the review fixes. This PR uses the official tagged module directly, with no LLVM fork replacement.

Validation on the rebased branch (main afe9a5407):

  • Short test suites for internal/llvmfmv, cl, ssa, internal/abi, and internal/cabi.
  • Linux/macOS/Windows amd64 LLVM verification and O0/O2 assembly checks, including compile baselines, direct cross-package calls, and Windows vector parameter forwarding.
  • FMV unit coverage includes O2/ThinLTO/Full LTO inlining, initialization and cache policy, nested debug inline chains, source attributes, collision rejection, and unsupported signatures.
  • macOS/arm64 O2 complete SIMD execution.
  • Linux/amd64 and Windows/amd64 under Wine: O2 Full LTO complete SIMD execution, plus separate AVX2/AVX/FMA/all-feature override processes. Wine uses GCC MinGW libraries and an environment-only setjmp compatibility alias; native Windows validation remains in this PR's CI.
  • All eight pre-existing PR patches are unchanged by the rebase (git range-diff); the additional dependency update selects the official v0.10.1 tag and removes the fork requirement/checksums.

Earlier implementation validation also covered O0/O2 without LTO, ThinLTO, Full LTO, GOAMD64=v3, and Linux/macOS stage-2 self-host builds. The refreshed CI validates the new PR head.

Part of #2568.

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 22.58065% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cabi/windows_vector.go 0.00% 15 Missing ⚠️
internal/cabi/cabi.go 0.00% 4 Missing ⚠️
ssa/simd_fmv.go 62.50% 3 Missing ⚠️
internal/build/build.go 50.00% 2 Missing ⚠️

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

SIMD128 CPU-guarded function multiversioning

This is a carefully engineered, well-tested change. The dispatch guard is sound: the x86.avx2 query is constant-folded to true only inside the cloned .__llgo_fmv_avx2 variant, while the baseline entry keeps a genuine runtime AVX2() call before the musttail forward — so AVX2 code never runs on an unsupported CPU, and the independent AVX/FMA queries correctly stay dynamic (+avx2 does not imply +fma in LLVM's x86 model). The subtle hazards are handled correctly: deferred erasure of query calls avoids iterator invalidation, UsedIDs is seeded with every existing PC-site ID before hash-remapping so string substitution can't alias, getNumOperands() is cached before addOperand(), and idempotency / symbol-collision / non-amd64 no-op are all explicitly tested. The README additions and code comments accurately describe the behavior.

I found no correctness or security defects. The inline comments below are a compile-time performance concern plus two maintainability/robustness notes — none are blocking.

Reviewed: internal/llvmfmv/*, cl/simd_fmv.go, ssa/simd_fmv.go, cl/{compile,instr}.go, internal/build/build.go, dev/test_native_simd.sh, test/simd/*.

Comment thread internal/llvmfmv/fmv.cpp Outdated
for (auto &F : M) {
if (F.hasFnAttribute(Done) || !supported(F))
continue;
Function *Query = F.isDeclaration() ? nullptr : guardQuery(F);

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.

[P2] FMV runs a full-module instruction scan on every amd64 build

llvmfmv.Run is invoked for every package module during every build (internal/build/build.go:3288). On amd64, this discovery loop calls guardQuery(F) for every supported() non-declaration function, and guardQuery walks all of F's instructions (fmv.cpp:60) before the cheap hasFnAttribute(Entry) short-circuit on line 207 is consulted. Since supported() is true for essentially any ordinary function (scalar / pointer / ≤128-bit-vector signature), this turns the pass into an O(all instructions in the module) traversal even for packages that contain no SIMD code at all — the overwhelmingly common case.

Consider gating the per-function scan: a function can only be a root if the module declares at least one llgo.cpu.query function, so compute that once up front and skip guardQuery entirely otherwise. Relatedly, UsedIDs is populated by scanning all llgo.pcline metadata (lines 215-220) on every amd64 module even when Originals ends up empty; returning early when there are no candidates avoids that cost too.

Comment thread internal/llvmfmv/fmv.cpp Outdated
MDNode *Row = Info->getOperand(I);
if (Row->getNumOperands() < 6)
continue;
auto *Name = dyn_cast<MDString>(Row->getOperand(1));

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.

[P2] Metadata operand indices duplicated from ssa/funcinfo.go

cloneSourceInfo hardcodes the operand layout of the llgo.funcinfo (operand 1 = linker symbol, min 6 fields) and llgo.pcline (operand 1 = id, operand 2 = symbol, exactly 6 fields, line 102-103) metadata. That schema is defined independently in ssa/funcinfo.go (EmitFuncInfo/EmitPCLineInfo), with no shared constant or cross-reference. If the field order or count in funcinfo.go ever changes, this C++ silently reads the wrong fields or skips rows via the < 6 / != 6 guards and corrupts clone symbol / PC-site mappings — with no compile-time error. A comment in each file pointing at the other (and documenting the exact layout next to these index accesses) would make the coupling explicit for future maintainers.

Comment thread internal/llvmfmv/fmv.cpp Outdated
return nullptr;
char *Result = static_cast<char *>(std::malloc(Error.size() + 1));
if (!Result)
std::abort();

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.

[P3] std::abort() on malloc failure turns a compile error into a crash

llgoRunSIMDFMV hard-aborts the whole compiler process if the small diagnostic-string allocation fails. This path only runs when there is already a recoverable error to report (e.g. a symbol collision), so an OOM here escalates a reportable build error into a process crash that skips cleanup. Returning a static, non-malloc sentinel the Go side can detect would keep the error recoverable. Minor robustness nit, not a correctness or security issue.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

124bf32095c6 | 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 145322 B 0 B / +0.0% 88157 B 0 B / +0.0%
cprintf/j32-goos-js/LLGo 144550 B 0 B / +0.0% 72985 B 0 B / +0.0%
cprintf/j64-emscripten-memory64/LLGo 133033 B 0 B / +0.0% 91328 B 0 B / +0.0%
cprintf/w32-goos-wasip1/LLGo 155024 B 0 B / +0.0% 0 B 0 B / 0.0%
cprintf/w32-wasi/LLGo 155702 B 0 B / +0.0% 0 B 0 B / 0.0%
fmtprintf/j32-emscripten/LLGo 2996265 B +39 B / +0.001302% (worse) 130661 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 3091058 B +39 B / +0.001262% (worse) 102110 B 0 B / +0.0%
fmtprintf/j64-emscripten-memory64/LLGo 2758893 B +39 B / +0.001414% (worse) 136141 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 2340397 B +51 B / +0.002179% (worse) 0 B 0 B / 0.0%
fmtprintf/w32-wasi/LLGo 2337073 B +51 B / +0.002182% (worse) 0 B 0 B / 0.0%
j32-emscripten/LLGo 144835 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 144298 B 0 B / +0.0% 72985 B 0 B / +0.0%
j64-emscripten-memory64/LLGo 132586 B 0 B / +0.0% 91328 B 0 B / +0.0%
reflectcall/j32-emscripten/LLGo 1443041 B +39 B / +0.002703% (worse) 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 1492784 B +39 B / +0.002613% (worse) 89743 B 0 B / +0.0%
reflectcall/j64-emscripten-memory64/LLGo 1335603 B +39 B / +0.00292% (worse) 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 1271106 B +51 B / +0.004012% (worse) 0 B 0 B / 0.0%
reflectcall/w32-wasi/LLGo 1268727 B +51 B / +0.00402% (worse) 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 154698 B 0 B / +0.0% 0 B 0 B / 0.0%
w32-wasi/LLGo 155376 B 0 B / +0.0% 0 B 0 B / 0.0%
LLGo WebAssembly build measurements
Example and profile Build vs base
j32-emscripten 7.299 s +136.6 ms / +1.9% (worse)
j32-goos-js 7.333 s +337 ms / +4.8% (worse)
j64-emscripten-memory64 6.319 s +53.89 ms / +0.9% (worse)
reflectcall/w32-wasi 23.404 s -153.5 ms / -0.7% (better)
w32-goos-wasip1 4.522 s +156.5 ms / +3.6% (worse)
w32-wasi 4.300 s +27.88 ms / +0.7% (worse)

Compared with cb8de09d84b6 measured in the same runner job.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

0b68785fcc89 | 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% 721.858 ms +28.29 ms / +4.1% (worse) 1.313 ms +41.36 us / +3.3% (worse)
Linux cprintf-lto 8408 B 0 B / +0.0% 368 B 0 B / +0.0% 726.011 ms +36.59 ms / +5.3% (worse) 1.360 ms +118.4 us / +9.5% (worse)
Linux fmtprintf 4887640 B +160 B / +0.003274% (worse) 504643 B +29 B / +0.005747% (worse) 4.962 s +430.5 ms / +9.5% (worse) 3.112 ms +92.57 us / +3.1% (worse)
Linux fmtprintf-lto 3607712 B -72 B / -0.001996% (better) 442924 B 0 B / +0.0% 14.360 s +514.8 ms / +3.7% (worse) 2.951 ms -12.88 us / -0.4% (better)
Linux println 681536 B -120 B / -0.0176% (better) 16855 B 0 B / +0.0% 729.920 ms +45.33 ms / +6.6% (worse) 1.597 ms +158 ns / +0.009894% (worse)
Linux println-lto 190040 B -120 B / -0.1% (better) 14273 B 0 B / +0.0% 1.017 s +27.65 ms / +2.8% (worse) 1.657 ms +85.12 us / +5.4% (worse)
macOS cprintf 50736 B 0 B / +0.0% 4409 B 0 B / +0.0% 618.930 ms -119.3 ms / -16.2% (better) 2.347 ms -794.5 us / -25.3% (better)
macOS cprintf-lto 50496 B 0 B / +0.0% 161 B 0 B / +0.0% 656.612 ms -166 ms / -20.2% (better) 2.390 ms -61 us / -2.5% (better)
macOS fmtprintf 1774544 B +176 B / +0.009919% (worse) 880697 B +32 B / +0.003634% (worse) 3.224 s +10.57 ms / +0.3% (worse) 8.108 ms -167.8 us / -2.0% (better)
macOS fmtprintf-lto 1361232 B 0 B / +0.0% 765037 B 0 B / +0.0% 6.858 s -1.864 s / -21.4% (better) 3.751 ms -1.638 ms / -30.4% (better)
macOS println 99344 B 0 B / +0.0% 24216 B 0 B / +0.0% 661.657 ms -113.9 ms / -14.7% (better) 3.159 ms -318.1 us / -9.1% (better)
macOS println-lto 83664 B 0 B / +0.0% 21457 B 0 B / +0.0% 766.881 ms -145.5 ms / -15.9% (better) 3.101 ms +268.5 us / +9.5% (worse)
Windows MinGW cprintf 655360 B -512 B / -0.1% (better) 4550 B 0 B / +0.0% 1.141 s +11.6 ms / +1.0% (worse) 2.752 ms +40 us / +1.5% (worse)
Windows MinGW cprintf-lto 44032 B 0 B / +0.0% 4502 B 0 B / +0.0% 1.170 s -5.546 ms / -0.5% (better) 2.728 ms -34.9 us / -1.3% (better)
Windows MinGW fmtprintf 5446144 B 0 B / +0.0% 604790 B +32 B / +0.005291% (worse) 3.968 s +5.353 ms / +0.1% (worse) 6.192 ms -45.4 us / -0.7% (better)
Windows MinGW fmtprintf-lto 4146176 B -512 B / -0.01235% (better) 551910 B 0 B / +0.0% 9.951 s -86.05 ms / -0.9% (better) 6.736 ms +546.6 us / +8.8% (worse)
Windows MinGW println 710144 B -512 B / -0.1% (better) 25190 B 0 B / +0.0% 1.151 s +6.409 ms / +0.6% (worse) 5.027 ms -13.4 us / -0.3% (better)
Windows MinGW println-lto 208896 B 0 B / +0.0% 22070 B 0 B / +0.0% 1.368 s -7.172 ms / -0.5% (better) 4.945 ms -33 us / -0.7% (better)
Windows MinGW 386 cprintf 604672 B -512 B / -0.1% (better) 5352 B 0 B / +0.0% 1.624 s +43.7 ms / +2.8% (worse) 6.569 ms +973.9 us / +17.4% (worse)
Windows MinGW 386 cprintf-lto 103424 B 0 B / +0.0% 5112 B 0 B / +0.0% 1.670 s +43.31 ms / +2.7% (worse) 6.377 ms +533.6 us / +9.1% (worse)
Windows MinGW 386 fmtprintf 4750336 B 0 B / +0.0% 472696 B 0 B / +0.0% 5.640 s +98.52 ms / +1.8% (worse) 13.040 ms +1.11 ms / +9.3% (worse)
Windows MinGW 386 fmtprintf-lto 4149760 B 0 B / +0.0% 451064 B 0 B / +0.0% 13.196 s +68.67 ms / +0.5% (worse) 11.985 ms -1.359 ms / -10.2% (better)
Windows MinGW 386 println 656896 B -512 B / -0.1% (better) 21500 B 0 B / +0.0% 1.626 s +30.9 ms / +1.9% (worse) 11.374 ms +1.526 ms / +15.5% (worse)
Windows MinGW 386 println-lto 258560 B -512 B / -0.2% (better) 19328 B 0 B / +0.0% 1.913 s +24.63 ms / +1.3% (worse) 11.178 ms +1.163 ms / +11.6% (worse)
Windows MinGW ARM64 cprintf 665600 B -512 B / -0.1% (better) 4436 B 0 B / +0.0% 1.727 s -31.65 ms / -1.8% (better) 6.477 ms +227.7 us / +3.6% (worse)
Windows MinGW ARM64 cprintf-lto 43520 B 0 B / +0.0% 4388 B 0 B / +0.0% 1.795 s -20.34 ms / -1.1% (better) 6.090 ms -329.1 us / -5.1% (better)
Windows MinGW ARM64 fmtprintf 5357568 B +3072 B / +0.1% (worse) 512924 B +32 B / +0.006239% (worse) 5.255 s +82.11 ms / +1.6% (worse) 12.713 ms +207.3 us / +1.7% (worse)
Windows MinGW ARM64 fmtprintf-lto 4315648 B +1024 B / +0.02373% (worse) 479104 B 0 B / +0.0% 11.740 s -208.5 ms / -1.7% (better) 11.966 ms -715.5 us / -5.6% (better)
Windows MinGW ARM64 println 717312 B -512 B / -0.1% (better) 23912 B 0 B / +0.0% 1.739 s -26.34 ms / -1.5% (better) 10.505 ms -216.1 us / -2.0% (better)
Windows MinGW ARM64 println-lto 215552 B 0 B / +0.0% 21272 B 0 B / +0.0% 2.005 s +3.497 ms / +0.2% (worse) 11.074 ms +112.4 us / +1.0% (worse)
Windows MSVC cprintf 900096 B 0 B / +0.0% 66019 B 0 B / +0.0% 1.105 s +19.26 ms / +1.8% (worse) 2.854 ms +73.5 us / +2.6% (worse)
Windows MSVC cprintf-lto 291328 B 0 B / +0.0% 65971 B 0 B / +0.0% 1.171 s +58.24 ms / +5.2% (worse) 2.720 ms -102.7 us / -3.6% (better)
Windows MSVC fmtprintf 5748736 B 0 B / +0.0% 700518 B +16 B / +0.002284% (worse) 3.927 s +62.08 ms / +1.6% (worse) 7.686 ms +638.2 us / +9.1% (worse)
Windows MSVC fmtprintf-lto 4470784 B 0 B / +0.0% 651590 B 0 B / +0.0% 9.898 s +387.8 ms / +4.1% (worse) 7.562 ms +254.8 us / +3.5% (worse)
Windows MSVC println 1022976 B -512 B / -0.1% (better) 121062 B 0 B / +0.0% 1.166 s +73.24 ms / +6.7% (worse) 5.920 ms +331.6 us / +5.9% (worse)
Windows MSVC println-lto 529920 B 0 B / +0.0% 118598 B 0 B / +0.0% 1.384 s +76.86 ms / +5.9% (worse) 5.448 ms -285.6 us / -5.0% (better)
Windows MSVC 386 cprintf 516608 B 0 B / +0.0% 3947 B 0 B / +0.0% 1.363 s +1.859 ms / +0.1% (worse) 8.410 ms +2.622 ms / +45.3% (worse)
Windows MSVC 386 cprintf-lto 44032 B 0 B / +0.0% 3901 B 0 B / +0.0% 1.393 s +28.37 ms / +2.1% (worse) 6.051 ms +628.6 us / +11.6% (worse)
Windows MSVC 386 fmtprintf 4483072 B -512 B / -0.01142% (better) 456108 B 0 B / +0.0% 5.241 s +216.3 ms / +4.3% (worse) 12.028 ms +506.5 us / +4.4% (worse)
Windows MSVC 386 fmtprintf-lto 3900928 B 0 B / +0.0% 426635 B 0 B / +0.0% 11.336 s +2.466 ms / +0.02176% (worse) 11.417 ms +170.1 us / +1.5% (worse)
Windows MSVC 386 println 570880 B -512 B / -0.1% (better) 20356 B 0 B / +0.0% 1.351 s +12.16 ms / +0.9% (worse) 10.041 ms +761.6 us / +8.2% (worse)
Windows MSVC 386 println-lto 199168 B 0 B / +0.0% 18517 B 0 B / +0.0% 1.580 s +7.064 ms / +0.4% (worse) 9.837 ms +385.5 us / +4.1% (worse)
Windows MSVC ARM64 cprintf 666112 B 0 B / +0.0% 4240 B 0 B / +0.0% 1.526 s +82.4 ms / +5.7% (worse) 7.263 ms +321.9 us / +4.6% (worse)
Windows MSVC ARM64 cprintf-lto 48640 B 0 B / +0.0% 4140 B 0 B / +0.0% 1.544 s +77.69 ms / +5.3% (worse) 7.108 ms +379.7 us / +5.6% (worse)
Windows MSVC ARM64 fmtprintf 5353984 B +3072 B / +0.1% (worse) 512868 B +32 B / +0.00624% (worse) 4.907 s -74.85 ms / -1.5% (better) 14.859 ms +197.2 us / +1.3% (worse)
Windows MSVC ARM64 fmtprintf-lto 4322816 B +512 B / +0.01185% (worse) 479776 B 0 B / +0.0% 11.131 s -274.7 ms / -2.4% (better) 13.767 ms -1.079 ms / -7.3% (better)
Windows MSVC ARM64 println 719360 B -512 B / -0.1% (better) 23956 B 0 B / +0.0% 1.547 s +36.58 ms / +2.4% (worse) 12.450 ms +112.3 us / +0.9% (worse)
Windows MSVC ARM64 println-lto 220672 B 0 B / +0.0% 21444 B 0 B / +0.0% 1.729 s +12.02 ms / +0.7% (worse) 11.972 ms -132.6 us / -1.1% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 14.720 ns/op +0.1 ns/op / +0.7% (worse)
Linux BenchmarkMergeCompilerFlags 204 ns/op +9.7 ns/op / +5.0% (worse)
Linux BenchmarkMergeLinkerFlags 129.600 ns/op +2.9 ns/op / +2.3% (worse)
Linux BenchmarkChannelBuffered 55.200 ns/op +0.54 ns/op / +1.0% (worse)
Linux BenchmarkChannelHandoff 13684 ns/op +409 ns/op / +3.1% (worse)
Linux BenchmarkDefer 46.270 ns/op +0.3 ns/op / +0.7% (worse)
Linux BenchmarkDirectCall 1.165 ns/op -0.007 ns/op / -0.6% (better)
Linux BenchmarkGlobalRead 1.168 ns/op +0.004 ns/op / +0.3% (worse)
Linux BenchmarkGlobalWrite 7.782 ns/op +0.02 ns/op / +0.3% (worse)
Linux BenchmarkGoroutine 24738 ns/op +663 ns/op / +2.8% (worse)
Linux BenchmarkInterfaceCall 5.849 ns/op +0.023 ns/op / +0.4% (worse)
Linux BenchmarkRuntimeGetG 2.901 ns/op -0.08 ns/op / -2.7% (better)
macOS BenchmarkLookupPCRandom 12.730 ns/op +0.35 ns/op / +2.8% (worse)
macOS BenchmarkMergeCompilerFlags 120.100 ns/op +11.9 ns/op / +11.0% (worse)
macOS BenchmarkMergeLinkerFlags 89 ns/op +21.07 ns/op / +31.0% (worse)
macOS BenchmarkChannelBuffered 22.390 ns/op -3.11 ns/op / -12.2% (better)
macOS BenchmarkChannelHandoff 6725 ns/op -2815 ns/op / -29.5% (better)
macOS BenchmarkDefer 28.480 ns/op -4.78 ns/op / -14.4% (better)
macOS BenchmarkDirectCall 1.002 ns/op -0.063 ns/op / -5.9% (better)
macOS BenchmarkGlobalRead 1.573 ns/op -0.202 ns/op / -11.4% (better)
macOS BenchmarkGlobalWrite 0.943 ns/op -0.1311 ns/op / -12.2% (better)
macOS BenchmarkGoroutine 37913 ns/op +5955 ns/op / +18.6% (worse)
macOS BenchmarkInterfaceCall 3.468 ns/op -0.451 ns/op / -11.5% (better)
macOS BenchmarkRuntimeGetG 1.930 ns/op -0.214 ns/op / -10.0% (better)
Windows MinGW BenchmarkLookupPCRandom 9.731 ns/op +0.046 ns/op / +0.5% (worse)
Windows MinGW BenchmarkMergeCompilerFlags 370.700 ns/op -13.4 ns/op / -3.5% (better)
Windows MinGW BenchmarkMergeLinkerFlags 331.300 ns/op -13.3 ns/op / -3.9% (better)
Windows MinGW BenchmarkChannelBuffered 23.910 ns/op -0.32 ns/op / -1.3% (better)
Windows MinGW BenchmarkChannelHandoff 1086 ns/op +42 ns/op / +4.0% (worse)
Windows MinGW BenchmarkDefer 40.960 ns/op +1.81 ns/op / +4.6% (worse)
Windows MinGW BenchmarkDirectCall 1.356 ns/op -0.002 ns/op / -0.1% (better)
Windows MinGW BenchmarkGlobalRead 1.358 ns/op +0.001 ns/op / +0.1% (worse)
Windows MinGW BenchmarkGlobalWrite 2.165 ns/op 0 ns/op / +0.0%
Windows MinGW BenchmarkGoroutine 57521 ns/op +371 ns/op / +0.6% (worse)
Windows MinGW BenchmarkInterfaceCall 6.791 ns/op -0.448 ns/op / -6.2% (better)
Windows MinGW BenchmarkRuntimeGetG 1.901 ns/op +0.271 ns/op / +16.6% (worse)
Windows MinGW 386 BenchmarkLookupPCRandom 68.990 ns/op 0 ns/op / +0.0%
Windows MinGW 386 BenchmarkMergeCompilerFlags 918.900 ns/op -7.8 ns/op / -0.8% (better)
Windows MinGW 386 BenchmarkMergeLinkerFlags 880 ns/op +13.6 ns/op / +1.6% (worse)
Windows MinGW 386 BenchmarkChannelBuffered 52.240 ns/op +0.44 ns/op / +0.8% (worse)
Windows MinGW 386 BenchmarkChannelHandoff 3662 ns/op -5848 ns/op / -61.5% (better)
Windows MinGW 386 BenchmarkDefer 57.760 ns/op +2.15 ns/op / +3.9% (worse)
Windows MinGW 386 BenchmarkDirectCall 0.872 ns/op +0.0002 ns/op / +0.02295% (worse)
Windows MinGW 386 BenchmarkGlobalRead 1.166 ns/op +0.009 ns/op / +0.8% (worse)
Windows MinGW 386 BenchmarkGlobalWrite 16.170 ns/op -0.02 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkGoroutine 90897 ns/op -3386 ns/op / -3.6% (better)
Windows MinGW 386 BenchmarkInterfaceCall 6.658 ns/op -0.024 ns/op / -0.4% (better)
Windows MinGW 386 BenchmarkRuntimeGetG 2.032 ns/op -0.016 ns/op / -0.8% (better)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.160 ns/op +0.09 ns/op / +0.7% (worse)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 575.500 ns/op -3.5 ns/op / -0.6% (better)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 543.100 ns/op -7.7 ns/op / -1.4% (better)
Windows MinGW ARM64 BenchmarkChannelBuffered 37.600 ns/op -0.03 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkChannelHandoff 2763 ns/op +310 ns/op / +12.6% (worse)
Windows MinGW ARM64 BenchmarkDefer 50.540 ns/op -2.13 ns/op / -4.0% (better)
Windows MinGW ARM64 BenchmarkDirectCall 0.737 ns/op +0.0001 ns/op / +0.01356% (worse)
Windows MinGW ARM64 BenchmarkGlobalRead 0.593 ns/op +0.0034 ns/op / +0.6% (worse)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.737 ns/op -0.0001 ns/op / -0.01357% (better)
Windows MinGW ARM64 BenchmarkGoroutine 59536 ns/op -2844 ns/op / -4.6% (better)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.232 ns/op +0.001 ns/op / +0.02364% (worse)
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.799 ns/op -0.001 ns/op / -0.1% (better)
Windows MSVC BenchmarkLookupPCRandom 9.588 ns/op +0.011 ns/op / +0.1% (worse)
Windows MSVC BenchmarkMergeCompilerFlags 374.200 ns/op +1.4 ns/op / +0.4% (worse)
Windows MSVC BenchmarkMergeLinkerFlags 343.400 ns/op +9.5 ns/op / +2.8% (worse)
Windows MSVC BenchmarkChannelBuffered 23.070 ns/op -1.39 ns/op / -5.7% (better)
Windows MSVC BenchmarkChannelHandoff 989.500 ns/op -90.5 ns/op / -8.4% (better)
Windows MSVC BenchmarkDefer 42.170 ns/op -3.01 ns/op / -6.7% (better)
Windows MSVC BenchmarkDirectCall 1.358 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalRead 1.629 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalWrite 2.165 ns/op 0 ns/op / +0.0%
Windows MSVC BenchmarkGoroutine 57358 ns/op +197 ns/op / +0.3% (worse)
Windows MSVC BenchmarkInterfaceCall 7.343 ns/op +0.28 ns/op / +4.0% (worse)
Windows MSVC BenchmarkRuntimeGetG 1.406 ns/op -0.494 ns/op / -26.0% (better)
Windows MSVC 386 BenchmarkLookupPCRandom 26.570 ns/op -0.05 ns/op / -0.2% (better)
Windows MSVC 386 BenchmarkMergeCompilerFlags 735.800 ns/op +23.9 ns/op / +3.4% (worse)
Windows MSVC 386 BenchmarkMergeLinkerFlags 650.500 ns/op -50.5 ns/op / -7.2% (better)
Windows MSVC 386 BenchmarkChannelBuffered 39.160 ns/op -0.05 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkChannelHandoff 893.900 ns/op +12.7 ns/op / +1.4% (worse)
Windows MSVC 386 BenchmarkDefer 51.190 ns/op +2.98 ns/op / +6.2% (worse)
Windows MSVC 386 BenchmarkDirectCall 1.549 ns/op -0.006 ns/op / -0.4% (better)
Windows MSVC 386 BenchmarkGlobalRead 1.864 ns/op +0.004 ns/op / +0.2% (worse)
Windows MSVC 386 BenchmarkGlobalWrite 7.769 ns/op -0.01 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkGoroutine 106718 ns/op -3666 ns/op / -3.3% (better)
Windows MSVC 386 BenchmarkInterfaceCall 8.410 ns/op -0.004 ns/op / -0.04754% (better)
Windows MSVC 386 BenchmarkRuntimeGetG 2.486 ns/op +0.005 ns/op / +0.2% (worse)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.200 ns/op +0.11 ns/op / +0.9% (worse)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 582.800 ns/op +6.1 ns/op / +1.1% (worse)
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 578.300 ns/op +38.1 ns/op / +7.1% (worse)
Windows MSVC ARM64 BenchmarkChannelBuffered 38.050 ns/op -0.97 ns/op / -2.5% (better)
Windows MSVC ARM64 BenchmarkChannelHandoff 1855 ns/op -17 ns/op / -0.9% (better)
Windows MSVC ARM64 BenchmarkDefer 61.810 ns/op +1.4 ns/op / +2.3% (worse)
Windows MSVC ARM64 BenchmarkDirectCall 0.664 ns/op -0.0019 ns/op / -0.3% (better)
Windows MSVC ARM64 BenchmarkGlobalRead 0.593 ns/op +0.0019 ns/op / +0.3% (worse)
Windows MSVC ARM64 BenchmarkGlobalWrite 3.797 ns/op -0.005 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkGoroutine 57302 ns/op -1568 ns/op / -2.7% (better)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.145 ns/op -0.008 ns/op / -0.2% (better)
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.804 ns/op -0.003 ns/op / -0.2% (better)
Timer runtime benchmarks
Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 910.800 ns/op +7.1 ns/op / +0.8% (worse)
Linux AfterFuncZeroDelivery/LLGo 36081 ns/op -220 ns/op / -0.6% (better)
Linux CreateStop/Go 290.100 ns/op +0.7 ns/op / +0.2% (worse)
Linux CreateStop/LLGo 1648 ns/op +153 ns/op / +10.2% (worse)
Linux RearmStopped/Go 116.500 ns/op +0.6 ns/op / +0.5% (worse)
Linux RearmStopped/LLGo 1161 ns/op +98 ns/op / +9.2% (worse)
Linux ResetActive/Go 69.230 ns/op +0.61 ns/op / +0.9% (worse)
Linux ResetActive/LLGo 656.500 ns/op -188.9 ns/op / -22.3% (better)
Linux ResetHeap1024/Go 67.770 ns/op +0.38 ns/op / +0.6% (worse)
Linux ResetHeap1024/LLGo 161.900 ns/op +1.1 ns/op / +0.7% (worse)
macOS AfterFuncZeroDelivery/Go 429.300 ns/op -4.7 ns/op / -1.1% (better)
macOS AfterFuncZeroDelivery/LLGo 72310 ns/op +2953 ns/op / +4.3% (worse)
macOS CreateStop/Go 155.900 ns/op +20.7 ns/op / +15.3% (worse)
macOS CreateStop/LLGo 381.500 ns/op +5.6 ns/op / +1.5% (worse)
macOS RearmStopped/Go 67.220 ns/op +9.94 ns/op / +17.4% (worse)
macOS RearmStopped/LLGo 278.100 ns/op +17.4 ns/op / +6.7% (worse)
macOS ResetActive/Go 47.230 ns/op +4.83 ns/op / +11.4% (worse)
macOS ResetActive/LLGo 121 ns/op -0.1 ns/op / -0.1% (better)
macOS ResetHeap1024/Go 45.230 ns/op +2.14 ns/op / +5.0% (worse)
macOS ResetHeap1024/LLGo 81.740 ns/op +3.4 ns/op / +4.3% (worse)
Windows MinGW AfterFuncZeroDelivery/Go 381.700 ns/op +13.2 ns/op / +3.6% (worse)
Windows MinGW AfterFuncZeroDelivery/LLGo 109142 ns/op -262 ns/op / -0.2% (better)
Windows MinGW CreateStop/Go 90.940 ns/op +0.93 ns/op / +1.0% (worse)
Windows MinGW CreateStop/LLGo 345.300 ns/op +25.9 ns/op / +8.1% (worse)
Windows MinGW RearmStopped/Go 24.480 ns/op +0.03 ns/op / +0.1% (worse)
Windows MinGW RearmStopped/LLGo 186.300 ns/op -1.3 ns/op / -0.7% (better)
Windows MinGW ResetActive/Go 14.830 ns/op -0.03 ns/op / -0.2% (better)
Windows MinGW ResetActive/LLGo 116.500 ns/op +1.9 ns/op / +1.7% (worse)
Windows MinGW ResetHeap1024/Go 14.920 ns/op +0.09 ns/op / +0.6% (worse)
Windows MinGW ResetHeap1024/LLGo 97.730 ns/op -1.19 ns/op / -1.2% (better)
Windows MinGW 386 AfterFuncZeroDelivery/Go 1130 ns/op 0 ns/op / +0.0%
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 201054 ns/op +5086 ns/op / +2.6% (worse)
Windows MinGW 386 CreateStop/Go 271.900 ns/op -2.9 ns/op / -1.1% (better)
Windows MinGW 386 CreateStop/LLGo 649.100 ns/op +14.1 ns/op / +2.2% (worse)
Windows MinGW 386 RearmStopped/Go 92.440 ns/op -0.07 ns/op / -0.1% (better)
Windows MinGW 386 RearmStopped/LLGo 390 ns/op +5 ns/op / +1.3% (worse)
Windows MinGW 386 ResetActive/Go 46.300 ns/op +0.05 ns/op / +0.1% (worse)
Windows MinGW 386 ResetActive/LLGo 338.900 ns/op -87.7 ns/op / -20.6% (better)
Windows MinGW 386 ResetHeap1024/Go 46.640 ns/op +0.24 ns/op / +0.5% (worse)
Windows MinGW 386 ResetHeap1024/LLGo 167.600 ns/op +0.2 ns/op / +0.1% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 676.100 ns/op -1.5 ns/op / -0.2% (better)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 141026 ns/op -2279 ns/op / -1.6% (better)
Windows MinGW ARM64 CreateStop/Go 204.900 ns/op +3 ns/op / +1.5% (worse)
Windows MinGW ARM64 CreateStop/LLGo 315 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 RearmStopped/Go 70.620 ns/op +0.08 ns/op / +0.1% (worse)
Windows MinGW ARM64 RearmStopped/LLGo 208.700 ns/op +1.1 ns/op / +0.5% (worse)
Windows MinGW ARM64 ResetActive/Go 30.870 ns/op -0.07 ns/op / -0.2% (better)
Windows MinGW ARM64 ResetActive/LLGo 108.200 ns/op +1.8 ns/op / +1.7% (worse)
Windows MinGW ARM64 ResetHeap1024/Go 31.010 ns/op -0.14 ns/op / -0.4% (better)
Windows MinGW ARM64 ResetHeap1024/LLGo 109.200 ns/op -0.3 ns/op / -0.3% (better)
Windows MSVC AfterFuncZeroDelivery/Go 375.300 ns/op -1.9 ns/op / -0.5% (better)
Windows MSVC AfterFuncZeroDelivery/LLGo 106582 ns/op -87 ns/op / -0.1% (better)
Windows MSVC CreateStop/Go 89.950 ns/op -0.49 ns/op / -0.5% (better)
Windows MSVC CreateStop/LLGo 319.100 ns/op +0.7 ns/op / +0.2% (worse)
Windows MSVC RearmStopped/Go 24.510 ns/op +0.03 ns/op / +0.1% (worse)
Windows MSVC RearmStopped/LLGo 184.900 ns/op +4.5 ns/op / +2.5% (worse)
Windows MSVC ResetActive/Go 14.800 ns/op 0 ns/op / +0.0%
Windows MSVC ResetActive/LLGo 113.900 ns/op -0.4 ns/op / -0.3% (better)
Windows MSVC ResetHeap1024/Go 14.800 ns/op -0.03 ns/op / -0.2% (better)
Windows MSVC ResetHeap1024/LLGo 99.050 ns/op -0.47 ns/op / -0.5% (better)
Windows MSVC 386 AfterFuncZeroDelivery/Go 966.900 ns/op +3.2 ns/op / +0.3% (worse)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 200740 ns/op -1320 ns/op / -0.7% (better)
Windows MSVC 386 CreateStop/Go 195.700 ns/op +1.4 ns/op / +0.7% (worse)
Windows MSVC 386 CreateStop/LLGo 429.600 ns/op -10.8 ns/op / -2.5% (better)
Windows MSVC 386 RearmStopped/Go 63.720 ns/op +0.1 ns/op / +0.2% (worse)
Windows MSVC 386 RearmStopped/LLGo 312.100 ns/op +8.9 ns/op / +2.9% (worse)
Windows MSVC 386 ResetActive/Go 39.200 ns/op -0.13 ns/op / -0.3% (better)
Windows MSVC 386 ResetActive/LLGo 922.900 ns/op -17 ns/op / -1.8% (better)
Windows MSVC 386 ResetHeap1024/Go 40.020 ns/op +0.56 ns/op / +1.4% (worse)
Windows MSVC 386 ResetHeap1024/LLGo 169.400 ns/op +6.9 ns/op / +4.2% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 663.400 ns/op +5.3 ns/op / +0.8% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 147425 ns/op -2478 ns/op / -1.7% (better)
Windows MSVC ARM64 CreateStop/Go 192.700 ns/op -6.4 ns/op / -3.2% (better)
Windows MSVC ARM64 CreateStop/LLGo 392.600 ns/op +2 ns/op / +0.5% (worse)
Windows MSVC ARM64 RearmStopped/Go 70.760 ns/op +0.05 ns/op / +0.1% (worse)
Windows MSVC ARM64 RearmStopped/LLGo 232.200 ns/op -10.4 ns/op / -4.3% (better)
Windows MSVC ARM64 ResetActive/Go 31.150 ns/op +0.06 ns/op / +0.2% (worse)
Windows MSVC ARM64 ResetActive/LLGo 120.900 ns/op -5 ns/op / -4.0% (better)
Windows MSVC ARM64 ResetHeap1024/Go 31.150 ns/op -0.03 ns/op / -0.1% (better)
Windows MSVC ARM64 ResetHeap1024/LLGo 116.900 ns/op -1.8 ns/op / -1.5% (better)

Compared with cb8de09d84b6 measured in the same runner job.

@zhouguangyuan0718 zhouguangyuan0718 changed the title compiler: add early AVX2 multiversioning for SIMD128 compiler: add early AVX2 SIMD128 multiversioning through Go APIs Oct 7, 2026
@zhouguangyuan0718

Copy link
Copy Markdown
Collaborator Author

@fennoai review

Please review the current PR head 5cbe200e139730781df5de69f5227d34b1ad7b17 for actionable correctness defects and missing regression coverage. The implementation has changed substantially since the earlier C++ review: FMV now uses the Go LLVM bindings, with lazy dispatch, Windows vector ABI lowering, and source/debug metadata propagation.

Please focus on CPU initialization and GODEBUG, musttail ABI/lifetime safety, O0/O2 and Thin/Full LTO, and source-location identity. Also assess whether the four changes under runtime/internal/lib/runtime are still necessary after main merged #2764 (frontend source-location records). The PR has not yet incorporated that main change; distinguish findings on the current head from recommendations for rebasing onto current main.

Report concrete failing scenarios and distinguish correctness fixes from optional simplifications. Please do not modify the branch.

@fennoai

fennoai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review: PR #2748 — SIMD128 CPU-guarded FMV (rewritten)

Re-reviewed at head 5cbe200e. The implementation moved from the earlier eager-dispatch C++ pass to a lazy indirect dispatcher in Go (internal/llvmfmv): each guarded root becomes a thunk that atomically loads a per-function slot and musttail-forwards; the slot starts at a noinline resolver that, on first call, reads the runtime feature mask (runtime/internal/runtime.CPUFeatures, published by internal/cpu after GODEBUG is applied), selects baseline vs. .__llgo_fmv_avx2, caches it, and tail-forwards.

I ran correctness, safety, performance, and documentation passes over the whole diff. No blocking correctness defects; the core safety property holds. Findings below are grouped by the focus areas you called out, and separated into correctness vs. optional.

Focus areas

1. CPU init / GODEBUG — correct. The x86.avx2 query folds to true only inside the +avx,+avx2 variant; the baseline keeps an AVX2-free body. The uninitialized path (bit 0 clear) tail-forwards to the baseline and does not cache (fmv.go:309-314), so a guarded call during internal/cpu bootstrap safely runs the baseline. GOAMD64=v3/v4 unconditionally folds the query with no runtime guard (fmv.go:124-132) — this is sound: the official policy only registers AVX2 as a GODEBUG-toggleable option when level < 3, so GODEBUG=cpu.avx2=off cannot disable it at v3+.

  • Minor (optional): the mask load at fmv.go:305 is a plain, non-atomic load (only SetAlignment(8)), and init writes it non-atomically (init_llgo.go:20), while the slot itself uses monotonic atomics. This is benign on amd64 — CPUFeatures is write-once in package init, which happens-before any dispatch — but it's inconsistent with the "immutable after init" contract in cpu_features.go. Consider a monotonic-atomic load (or a one-line comment at fmv.go:305 explaining why a plain aligned load is deliberately sufficient).

2. musttail ABI / lifetime — correct. LowerWindowsVectorParams runs before llvmfmv.Run (build.go:3328-3329), so Win64 128-bit vectors are already indirect (caller-owned pointers) before the dispatcher/resolver/baseline/variant are cloned from the original — the dangling-stack-pointer hazard the pass guards against (windows_vector.go:5-8) cannot occur across the tail jump. tailReturn forwards return+param ABI attributes over index 0..ParamsCount and the matching calling convention (fmv.go:326-342). supported() excludes aggregates/wide vectors, so no byval/sret temporary is inserted between the musttail and its ret. The disable-tail-calls=true + musttail + inline-entry combination is valid and is exercised end-to-end by fmvInlineTrace in test/simd/fmv_amd64_test.go.

3. O0/O2 and Thin/Full LTO — correct. The legality cleanup in specialize (fmv.go:237-253) runs unconditionally, so feature-disabled branches are removed even at O0 rather than relying on the optimizer. Inline policy (resolver-only noinline, inline-entry separation of the synthetic thunk from the required physical frame) is covered across all three pipelines in policy_test.go (default<O2>, thinlto, lto).

4. Source-location identity — correct. pcsite ID remapping is collision-safe: usedIDs is seeded with every existing id and each fnv-derived id is reserved before the next is computed (source.go:37,82-85), and fixed-width %016x keys avoid substring-overlap in the strings.NewReplacer. Dropping the thunk subprogram (fmv.go:286) while keeping subprograms on the physical implementations, plus propagating the real callsite via a nodebug callee, is directly verified by TestDispatcherInlineDebugIdentity.

Optional correctness hardening (not blocking)

  • fmv.go:213-220 — invoke of a query is not folded. The scan collects both call and invoke, but only IsACallInst() queries are constant-folded; an invoke @avx2() would survive unfolded into the variant/baseline (and leave an un-redirected edge). This can't happen today — the CPU queries are leaf predicates that never unwind — but the asymmetry is a latent trap if query emission ever changes. Suggest either handling the invoke form or asserting it cannot occur.
  • fmv.go:74 — supportedType admits <N x i1> vectors. For i1 lanes width==1, so <= 128/width lets vectors up to <128 x i1> through. Go SIMD doesn't emit these as ABI boundary types today, so it's low risk, but a lower bound (e.g. width >= 8) would make the gate strict.
  • Feature-bit duplication. featureInitialized/featureAVX2 (fmv.go:24-25) and the init publisher (init_llgo.go:16-19) carry matched "keep in sync" comments but no shared definition. They live in different modules (compiler vs. runtime), so a shared constant may not be trivial — flagging only as a drift risk.

Missing regression coverage

  • No unit-level test drives a disable-tail-calls=true dispatcher through the LLVM optimizer (fmv_test.go's @root carries the attribute but only runs VerifyModule; inlineFixture lacks it). The behavior is covered at the integration/runtime level, so this is optional defense-in-depth at the internal/llvmfmv layer.
  • TestCompileBaselineElidesVersions (policy_test.go:82) asserts no versioning artifacts remain at v3/v4 but doesn't positively assert the in-place query was folded to the enabled constant and the fallback branch removed. A positive check would lock in the specialize(..., nil, true) path.
  • No coverage for the invoke-form query above (ties to the first hardening item).

runtime/internal/lib/runtime changes vs. main's #2764

The four changes (runtimeFuncPCSiteEntry in the three funcinfo_entry_slack_*.go files + the nativeEntries dedup in symtab.go:943,960-967) are independent of #2764 and remain necessary. #2764 ("frontend source-location records", merged to main as 3a1604cad) is a compiler-frontend/SSA change — it touches cl/, ssa/, internal/callerlocation, etc., and does not modify runtime/internal/lib/runtime/ at all. These #2748 changes solve a different-layer problem (Windows COFF/SEH LTO inline-copy PC-site dedup so a caller's identity survives when an inlinee's entry-site asm is carried into it). On rebase onto current main the file sets are disjoint, so no conflict from #2764 is expected. Recommendation for the rebase, not a defect on this head: after rebasing, re-run the Windows amd64/arm64 traceback tests to confirm the dedup still composes with whatever #2764 changed in frontend location emission.

Performance & docs

No material compile-time regression: for ordinary amd64 packages with no SIMD, Run does two linear function-level passes (a few string-attribute lookups each) and returns at the len(originals)==0 early-out (fmv.go:157) before any map build, cloning, or specialization. LowerWindowsVectorParams adds one extra module pass but only on Windows amd64. Optional micro-opt: the two function iterations at fmv.go:113 and fmv.go:134 could be fused. The README's new "CPU-guarded SIMD128 specialization" section and the in-code comments accurately describe the new lazy-dispatch mechanism, the feature-bit layout, and the test matrix.

Overall: solid, carefully-tested work. Nothing here needs to block merge; the invoke-query fold and the non-atomic mask-load consistency are the two I'd most want addressed, and the rest are optional. I did not modify the branch, per your request.

Comment thread internal/llvmfmv/fmv.go
Comment on lines +175 to +176
variant := fn.CloneFunction()
variant.SetName(fn.Name() + variantSuffix)

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.

[P1] Give mergeable AVX2 clones their own COMDAT on Windows

CloneFunction() preserves linkonce linkage but does not copy COMDAT. When two packages instantiate the same generic SIMD function, both objects emit its AVX2 clone without the COFF grouping needed to merge duplicate definitions.

Reproduced on 0b68785fc with this function in a shared package, called as shared.Echo(x, 1) from both a.A and b.B:

//go:noinline
func Echo[T ~int](x archsimd.Float32x4, n T) archsimd.Float32x4 {
    if archsimd.X86.AVX2() {
        return x.Add(x)
    }
    return x
}

Both generated package modules retain COMDAT on Echo[int] but omit it on Echo[int].__llgo_fmv_avx2. Compiling their actual LLGo-generated IR to Windows COFF with Clang 22 at O0 and O2 reproduces:

lld-link: error: duplicate symbol: fmvcomdat/shared.Echo[int].__llgo_fmv_avx2

Please give the externally mergeable clone a stable COMDAT keyed by its own symbol name, preserving the original selection kind, and add a Windows link regression with the same generic instantiation emitted in two packages. Adding that COMDAT to the minimal IR reproducer makes the same link succeed. The internal baseline/resolver clones do not need it.

Comment thread internal/build/build.go
ret := aPkg.LPkg

ctx.cTransformer.SetSkipFuncs(cabiSkipFuncsForPlan9Asm(ctx, pkgPath, ret.Module()))
ctx.cTransformer.LowerWindowsVectorParams(ret.Module())

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.

[P3] Apply Win64 vector lowering to C export wrappers too

Package implementations now lower SIMD128 parameters to ptr, but lowerMainCExportModule leaves their declarations/calls in the generated export wrappers as vectors:

; implementation
 define <4 x float> @main.Exported(ptr)
; wrapper call
 call <4 x float> @main.Exported(<4 x float>)

This prevents LTO from inlining even a simple exported Float32x4 addition. Verified with actual Clang bitcode + lld-link under both Full and Thin LTO: the current code retains an extra call and parameter stack temporary; applying the same lowering to the wrapper module inlines addps and reduces the example's stack allocation from 72 to 56 bytes.

Please also run LowerWindowsVectorParams in lowerMainCExportModule and cover this path. This is a nonblocking optimization issue; I have not found a runtime correctness failure from the mismatch.

@xushiwei
xushiwei merged commit e9a9164 into xgo-dev:main Oct 12, 2026
84 of 93 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.

3 participants