Skip to content

compiler: add portable SIMD Midway specialization - #2819

Merged
cpunion merged 6 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/simd-midway-128-20261014
Oct 17, 2026
Merged

cpunion merged 6 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/simd-midway-128-20261014

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Portable simd programs currently reach undefined portable method symbols even though the SIMD128 bridge is implemented. This adds LLGo's first Midway pass: type-check source, generate emulated and 128-bit declarations, rewrite references to the official bridge, then type-check again before constructing Go SSA. Portable reductions run with the default hardware representation or GODEBUG=simd=0.

Based on main 762d7bd73a343f2c8815b45e77c461d850ea45c7, including #2799, #2802, and the GC fixture repair #2821. The GC repair is no longer part of this PR's diff. Related to #2568; wider variants and general FMV remain follow-ups.

Implementation

  • Add a typed rewrite hook to package loading, completing dependency rewrites before checking importers. Preserve original declarations for their initial checks, and expose concrete variants beside them.
  • Handle recursive aggregates, aliases, generic constraints, embedded fields, methods, closures, interfaces, and cross-package calls. Propagate method-body dependencies from generic receiver origins to their instances; give blank dispatcher type parameters usable internal names. Recheck layout constants and named arrays for each representation. Keep function C export/link bindings on the original entry.
  • Evaluate only the selected global initializer, preserving side effects and Go initialization dependencies. Keep independent RHS expressions in separate initialization units, while retaining a single multi-result RHS as one unit. Preserve predeclared type bindings through qualified bridge aliases when source declarations shadow them, and avoid package-wide names when injecting imports. Multiple init declarations execute in order. Retained vector signatures trap if reached without specialization. Carry embed directives onto concrete global storage, including named strings and byte slices. Diagnose go:linkname, llgo:link, and export bindings on SIMD-dependent globals, including known targets using the explicit go: namespace, before generating unrelated alias storage. Blank functions and methods are checked but do not generate variants or seed receiver specialization.
  • Use a type/object dependency worklist, recording declaration references once; index import shadowing per file. Collect initialization edges only for declarations that need them, and resolve binding target packages lazily. Preserve the first unnameable-type diagnostic and stop rewriting at the failed declaration. Failed packages remain IllTyped and are excluded from the loader cache.
  • Preserve direct deferred recovery through Midway dispatchers using the existing transparent-wrapper recovery mechanism, including generic instances. User-written forwarding functions retain ordinary recovery semantics.
  • Reuse the existing bridge and instruction-level FMV. Cap amd64's available representation at 128 while retaining effective AVX eligibility and standard GODEBUG policy. Document the upstream arm64/wasm SIMD128 and 128-bit emulation invariants; reject unsupported sizes at the query/dispatch boundary.
  • Avoid unused gc export compilation when LLGo owns source transformation. Use LLGo's fingerprint for backend output paths, and the prepared package's output when collecting link dependencies. Original source errors remain errors.
  • Add portable execution tests and native/WASI/JavaScript CI coverage. WASI emulation explicitly passes GODEBUG into the guest.

Validation

Latest cleanup and boundary audit:

  • Rechecked the historical review findings against the full PR. Their semantic regressions remain covered: generic receiver propagation, separate initializer units and side effects, recovery dispatch, predeclared bindings, generated names, embedding, binding diagnostics, and loader error/cache behavior. All 14 historical probe/control builds and 28 WASI executions passed again; the initializer-order reproducer prints 1 2 42.
  • Fixed repeated blank functions generating duplicate variant names. Blank functions/methods no longer seed specialization or allocate unused variants. Extended global-binding diagnostics to llgo:link, exported variables, and known targets written with go:; these previously produced detached zero-valued alias storage. New boundary probes passed 28 WASI executions, while the unsupported bindings were rejected at O0/O2.
  • Removed the embed fixture's unnecessary trailing newline and updated the exact-byte expectation. Verified Git's core.autocrlf=true checkout filter preserves the 13-byte fixture; this addresses the Windows CRLF test failures without normalizing or weakening the embedded-content assertions.
  • Consolidated duplicate test imports/fixtures and named regression files by behavior. Removed the unused generic AST-cloning parameter, unreachable free-type-parameter/constraint-union reconstruction and its artificial round-trip test, and the unused comparable bridge alias. Generic declarations still use syntax cloning; valid generic and inferred-aggregate regressions remain covered.
  • Full internal/midway, internal/packages, and cl suites passed, along with four-target TestSIMDMidwayLLVM, metadata and dependency-closure tests, and six build-level binding rejection cases. The shared portable tests also passed with official Go.
  • Darwin/arm64 and WASI/Wasmer portable suites passed at O0, O2, ThinLTO, and FullLTO, each with simd=0/128: 16 suite runs and 192 test passes. Formatting and diff checks passed.

Earlier local coverage includes Linux/amd64 CPU overrides and the GC boundary regression after rebasing onto main. Those execution checks were not repeated for this update. Local JavaScript execution remains unavailable without emcc; wasm LLVM/code generation and WASI execution passed. Hosted CI for the new commit is pending. The separate Midway/FMV bridge-eligibility change and entry-fact/variant-pruning work are not included in this update.

Scope limits

This stage emits emulated and SIMD128 variants only. Reflection-driven portable calls, bodyless portable-dependent user functions, and inferred globals requiring another package's unexported type/field/method names are outside this first stage; the latter two forms report source diagnostics. Global external-symbol bindings involving specialized storage are not supported: Midway diagnoses dependent source variables (go:linkname, llgo:link, or export) and known dependent targets instead of generating independent zero-valued alias globals. LLGo-specific initialization/embedding fixtures are separate from official Go controls because Go 1.27.0's Midway rejects keyed embedded portable fields and lacks the same global-initializer handling. These are compiler correctness checks, not runtime performance results.

@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: portable SIMD Midway specialization

This is a carefully engineered first Midway pass. The two-pass type-check integration in the loader is clean, the tricky cases are handled deliberately (recursive aggregates, aliases, generic constraints, embedded fields, import shadowing, init-order edges), and the comments are genuinely helpful. The runtime representation choice is fail-loud (panic on an unsupported width) rather than a silent miscompile, and the "undefined portable symbol" bug class is correctly closed: bodyless SIMD-dependent functions error cleanly and the re-type-check rejects malformed output. The @-prefixed synthetic identifiers were verified safe against the init/stdcall name-detection sites.

I ran down the one potentially-critical finding and disproved it: a package-level blank var (var _ = simd.Foo()) does not nil-deref in global() — go/types records package-level _ names as non-nil *types.Var (confirmed by compiling a probe), so that path is safe. It is dropped from this review.

Remaining findings are robustness / clarity notes and compile-time performance observations; none block merge. All are well-gated by importsSIMD, so non-SIMD packages pay nothing.

Not blocking — notes only:

  • Error accumulation is last-write-wins. typeExpr writes f.err at several sites (internal/midway/types.go:24,81,112,136) and keeps building a knowingly-invalid tree; f.err/r.err is only read once per file at rewrite.go:139. For a struct/interface with multiple foreign-unexported members, only the last offending member is reported. Correctness currently holds because the tree isn't used after an error returns, but it couples to that ordering. Consider failing fast or accumulating.

  • In-place AST mutation on the error path. Rewrite mutates file.Decls and decl bodies in place while iterating, so an error after some files were processed leaves pkg.Syntax inconsistent with the still-original pkg.Types/pkg.TypesInfo. This is contained — appendError sets IllTyped, which skips the cache store (load.go:377) and propagates — but the invariant is worth a one-line comment since safety depends entirely on IllTyped short-circuiting every downstream consumer.

  • Comment nit: rewrite.go:60-61 says "Only the public FromArch generic functions" but the guard matches the tofrom_ filename prefix, which also covers ToArch. The code is correct; the comment under-describes it (the README correctly lists both).

See inline comments for the arch-cap and compile-time-complexity observations.

Comment thread runtime/_patch/simd/midway_amd64_llgo.go
Comment thread runtime/_patch/simd/selection_llgo.go
Comment thread internal/midway/analysis.go Outdated
Comment thread internal/midway/rewrite.go Outdated
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.45649% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/midway/rewrite.go 96.62% 9 Missing ⚠️
internal/midway/analysis.go 97.17% 5 Missing ⚠️
internal/midway/globals.go 97.03% 3 Missing and 1 partial ⚠️
internal/midway/types.go 98.88% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the codex/simd-midway-128-20261014 branch from e3c5644 to 3424b61 Compare October 14, 2026 14:57
@zhouguangyuan0718

Copy link
Copy Markdown
Collaborator Author

Rebased onto main 762d7bd73 after #2821; the PR now contains only Midway changes. Review fixes are in 3424b61f3.

The non-inline notes are also addressed:

  • Preserve the first type diagnostic, stop descending into unnameable types, and return after the failed declaration. The new regression fails on the old revision and passes with this fix, covering fields, nested fields, methods, named types, and aliases.
  • Document the in-place mutation/error contract. A loader regression confirms that a failed rewrite marks the package and its importer IllTyped, does not cache their partial state, and allows a clean reload.
  • Correct the conversion comment to include both FromArch and ToArch.

Added inferred-aggregate type reconstruction checks; local typeExpr coverage is 84.9%. The updated PR description lists validation for this revision, including full frontend suites, four-target LLVM verification/code generation, Darwin and WASI execution across O0/O2/ThinLTO/FullLTO, and Linux/amd64 O2 execution under hardware, emulation, and CPU overrides. Hosted CI for the new head is pending.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

fac37d4e2ef9 | 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% 830.912 ms +72.52 ms / +9.6% (worse) 1.252 ms +2.519 us / +0.2% (worse)
Linux cprintf-lto 8408 B 0 B / +0.0% 368 B 0 B / +0.0% 827.221 ms +79.11 ms / +10.6% (worse) 1.315 ms +74.36 us / +6.0% (worse)
Linux fmtprintf 4915576 B 0 B / +0.0% 506206 B 0 B / +0.0% 5.310 s +625 ms / +13.3% (worse) 4.275 ms +407.9 us / +10.5% (worse)
Linux fmtprintf-lto 3621616 B 0 B / +0.0% 444290 B 0 B / +0.0% 14.060 s +461.1 ms / +3.4% (worse) 3.940 ms +342.2 us / +9.5% (worse)
Linux println 705080 B 0 B / +0.0% 17095 B 0 B / +0.0% 894.061 ms +126.8 ms / +16.5% (worse) 1.616 ms +14.16 us / +0.9% (worse)
Linux println-lto 192928 B 0 B / +0.0% 14484 B 0 B / +0.0% 1.160 s +90.35 ms / +8.4% (worse) 1.638 ms +46.64 us / +2.9% (worse)
macOS cprintf 50736 B 0 B / +0.0% 4409 B 0 B / +0.0% 894.767 ms +37.62 ms / +4.4% (worse) 3.004 ms +150 us / +5.3% (worse)
macOS cprintf-lto 50496 B 0 B / +0.0% 161 B 0 B / +0.0% 852.740 ms +20.3 ms / +2.4% (worse) 3.395 ms +907.1 us / +36.5% (worse)
macOS fmtprintf 1793088 B 0 B / +0.0% 883744 B 0 B / +0.0% 3.712 s +228.7 ms / +6.6% (worse) 37.564 ms +6.096 ms / +19.4% (worse)
macOS fmtprintf-lto 1362176 B 0 B / +0.0% 767588 B 0 B / +0.0% 7.451 s -585.5 ms / -7.3% (better) 45.462 ms -8.716 ms / -16.1% (better)
macOS println 100080 B 0 B / +0.0% 24519 B 0 B / +0.0% 920.053 ms +71.22 ms / +8.4% (worse) 4.516 ms +704.8 us / +18.5% (worse)
macOS println-lto 84016 B 0 B / +0.0% 21760 B 0 B / +0.0% 1.077 s -4.362 ms / -0.4% (better) 4.116 ms +268.8 us / +7.0% (worse)
Windows MinGW cprintf 676352 B 0 B / +0.0% 4550 B 0 B / +0.0% 1.521 s -53.86 ms / -3.4% (better) 3.650 ms -267 us / -6.8% (better)
Windows MinGW cprintf-lto 44544 B 0 B / +0.0% 4502 B 0 B / +0.0% 1.555 s -25.4 ms / -1.6% (better) 3.694 ms +178.8 us / +5.1% (worse)
Windows MinGW fmtprintf 5473792 B 0 B / +0.0% 606134 B 0 B / +0.0% 5.127 s -38.92 ms / -0.8% (better) 11.740 ms +140.4 us / +1.2% (worse)
Windows MinGW fmtprintf-lto 4161024 B 0 B / +0.0% 553254 B 0 B / +0.0% 12.517 s -88.1 ms / -0.7% (better) 11.228 ms -384.4 us / -3.3% (better)
Windows MinGW println 733696 B 0 B / +0.0% 25446 B 0 B / +0.0% 1.532 s -15.94 ms / -1.0% (better) 6.792 ms -7.3 us / -0.1% (better)
Windows MinGW println-lto 210944 B 0 B / +0.0% 22326 B 0 B / +0.0% 1.830 s +15.5 ms / +0.9% (worse) 6.907 ms -56.2 us / -0.8% (better)
Windows MinGW 386 cprintf 627200 B 0 B / +0.0% 5352 B 0 B / +0.0% 1.543 s +144.5 ms / +10.3% (worse) 3.358 ms +6.1 us / +0.2% (worse)
Windows MinGW 386 cprintf-lto 104448 B 0 B / +0.0% 5112 B 0 B / +0.0% 1.409 s +93.64 ms / +7.1% (worse) 3.364 ms -5.9 us / -0.2% (better)
Windows MinGW 386 fmtprintf 4776960 B 0 B / +0.0% 473902 B 0 B / +0.0% 4.495 s -502.6 ms / -10.1% (better) 8.382 ms -193.2 us / -2.3% (better)
Windows MinGW 386 fmtprintf-lto 4163072 B 0 B / +0.0% 452294 B 0 B / +0.0% 9.012 s -160.1 ms / -1.7% (better) 8.597 ms +306.7 us / +3.7% (worse)
Windows MinGW 386 println 681472 B 0 B / +0.0% 21694 B 0 B / +0.0% 1.524 s +93.66 ms / +6.5% (worse) 5.657 ms 0 ns / +0.0%
Windows MinGW 386 println-lto 260608 B 0 B / +0.0% 19514 B 0 B / +0.0% 1.590 s +110.7 ms / +7.5% (worse) 5.528 ms +32.3 us / +0.6% (worse)
Windows MinGW ARM64 cprintf 686080 B 0 B / +0.0% 4436 B 0 B / +0.0% 1.817 s -17.67 ms / -1.0% (better) 6.413 ms +2.1 us / +0.03276% (worse)
Windows MinGW ARM64 cprintf-lto 44032 B 0 B / +0.0% 4388 B 0 B / +0.0% 1.858 s -5.868 ms / -0.3% (better) 6.506 ms -199.1 us / -3.0% (better)
Windows MinGW ARM64 fmtprintf 5382656 B 0 B / +0.0% 514292 B 0 B / +0.0% 5.370 s +73.02 ms / +1.4% (worse) 14.940 ms +544.3 us / +3.8% (worse)
Windows MinGW ARM64 fmtprintf-lto 4327424 B 0 B / +0.0% 480352 B 0 B / +0.0% 12.398 s +477.5 ms / +4.0% (worse) 16.322 ms +1.319 ms / +8.8% (worse)
Windows MinGW ARM64 println 739840 B 0 B / +0.0% 24160 B 0 B / +0.0% 1.850 s +21.72 ms / +1.2% (worse) 10.740 ms +416.6 us / +4.0% (worse)
Windows MinGW ARM64 println-lto 218624 B 0 B / +0.0% 21516 B 0 B / +0.0% 2.094 s -13.82 ms / -0.7% (better) 10.643 ms -333.3 us / -3.0% (better)
Windows MSVC cprintf 921088 B 0 B / +0.0% 66003 B 0 B / +0.0% 1.289 s -24.9 ms / -1.9% (better) 2.847 ms +105.1 us / +3.8% (worse)
Windows MSVC cprintf-lto 292352 B 0 B / +0.0% 65971 B 0 B / +0.0% 1.329 s +103.8 ms / +8.5% (worse) 2.905 ms +166.4 us / +6.1% (worse)
Windows MSVC fmtprintf 5774848 B 0 B / +0.0% 701894 B 0 B / +0.0% 4.118 s -52.26 ms / -1.3% (better) 9.963 ms +58.1 us / +0.6% (worse)
Windows MSVC fmtprintf-lto 4484096 B 0 B / +0.0% 652806 B 0 B / +0.0% 10.017 s +2.598 ms / +0.02594% (worse) 10.495 ms -113.9 us / -1.1% (better)
Windows MSVC println 1044992 B 0 B / +0.0% 121334 B 0 B / +0.0% 1.354 s +152.9 ms / +12.7% (worse) 7.068 ms +1.212 ms / +20.7% (worse)
Windows MSVC println-lto 531456 B 0 B / +0.0% 118870 B 0 B / +0.0% 1.550 s +146.6 ms / +10.4% (worse) 6.744 ms +1.249 ms / +22.7% (worse)
Windows MSVC 386 cprintf 538624 B 0 B / +0.0% 3947 B 0 B / +0.0% 1.419 s -61.28 ms / -4.1% (better) 5.665 ms -141.8 us / -2.4% (better)
Windows MSVC 386 cprintf-lto 44544 B 0 B / +0.0% 3901 B 0 B / +0.0% 1.439 s +34.2 ms / +2.4% (worse) 5.729 ms +556.3 us / +10.8% (worse)
Windows MSVC 386 fmtprintf 4508672 B 0 B / +0.0% 457314 B 0 B / +0.0% 5.012 s +40.56 ms / +0.8% (worse) 12.619 ms -518.9 us / -3.9% (better)
Windows MSVC 386 fmtprintf-lto 3910656 B 0 B / +0.0% 427681 B 0 B / +0.0% 11.275 s +117.1 ms / +1.0% (worse) 13.272 ms +55.1 us / +0.4% (worse)
Windows MSVC 386 println 593920 B 0 B / +0.0% 20566 B 0 B / +0.0% 1.433 s +33.75 ms / +2.4% (worse) 9.669 ms +350.5 us / +3.8% (worse)
Windows MSVC 386 println-lto 200192 B 0 B / +0.0% 18711 B 0 B / +0.0% 1.650 s +227.7 us / +0.0138% (worse) 11.907 ms +2.435 ms / +25.7% (worse)
Windows MSVC ARM64 cprintf 687616 B 0 B / +0.0% 4240 B 0 B / +0.0% 1.562 s +1.128 ms / +0.1% (worse) 6.945 ms +211.8 us / +3.1% (worse)
Windows MSVC ARM64 cprintf-lto 49152 B 0 B / +0.0% 4140 B 0 B / +0.0% 1.590 s +4.272 ms / +0.3% (worse) 7.356 ms +334 us / +4.8% (worse)
Windows MSVC ARM64 fmtprintf 5379072 B 0 B / +0.0% 514164 B 0 B / +0.0% 4.954 s +23.9 ms / +0.5% (worse) 15.750 ms -1.369 ms / -8.0% (better)
Windows MSVC ARM64 fmtprintf-lto 4334080 B 0 B / +0.0% 480960 B 0 B / +0.0% 11.260 s -59.17 ms / -0.5% (better) 16.909 ms -25.9 us / -0.2% (better)
Windows MSVC ARM64 println 741376 B 0 B / +0.0% 24200 B 0 B / +0.0% 1.581 s +25.4 ms / +1.6% (worse) 12.192 ms -251 us / -2.0% (better)
Windows MSVC ARM64 println-lto 223232 B 0 B / +0.0% 21688 B 0 B / +0.0% 1.793 s +3.833 ms / +0.2% (worse) 12.478 ms +212.9 us / +1.7% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 14.660 ns/op +0.27 ns/op / +1.9% (worse)
Linux BenchmarkMergeCompilerFlags 194.300 ns/op +3 ns/op / +1.6% (worse)
Linux BenchmarkMergeLinkerFlags 136.800 ns/op +7.6 ns/op / +5.9% (worse)
Linux BenchmarkChannelBuffered 55.490 ns/op -0.36 ns/op / -0.6% (better)
Linux BenchmarkChannelHandoff 16347 ns/op +3217 ns/op / +24.5% (worse)
Linux BenchmarkDefer 48.850 ns/op -8.1 ns/op / -14.2% (better)
Linux BenchmarkDirectCall 1.164 ns/op -0.013 ns/op / -1.1% (better)
Linux BenchmarkGlobalRead 1.171 ns/op -0.032 ns/op / -2.7% (better)
Linux BenchmarkGlobalWrite 7.765 ns/op -0.016 ns/op / -0.2% (better)
Linux BenchmarkGoroutine 25185 ns/op +80 ns/op / +0.3% (worse)
Linux BenchmarkInterfaceCall 5.896 ns/op -0.114 ns/op / -1.9% (better)
Linux BenchmarkRuntimeGetG 2.810 ns/op -0.042 ns/op / -1.5% (better)
macOS BenchmarkLookupPCRandom 12.670 ns/op -3.96 ns/op / -23.8% (better)
macOS BenchmarkMergeCompilerFlags 99.320 ns/op -71.28 ns/op / -41.8% (better)
macOS BenchmarkMergeLinkerFlags 57.740 ns/op -38.06 ns/op / -39.7% (better)
macOS BenchmarkChannelBuffered 23.060 ns/op -7.66 ns/op / -24.9% (better)
macOS BenchmarkChannelHandoff 7544 ns/op -4439 ns/op / -37.0% (better)
macOS BenchmarkDefer 35.440 ns/op -19.61 ns/op / -35.6% (better)
macOS BenchmarkDirectCall 1.084 ns/op +0.011 ns/op / +1.0% (worse)
macOS BenchmarkGlobalRead 1.256 ns/op +0.204 ns/op / +19.4% (worse)
macOS BenchmarkGlobalWrite 1.643 ns/op +0.584 ns/op / +55.1% (worse)
macOS BenchmarkGoroutine 34465 ns/op -43332 ns/op / -55.7% (better)
macOS BenchmarkInterfaceCall 3.979 ns/op -2.111 ns/op / -34.7% (better)
macOS BenchmarkRuntimeGetG 2.188 ns/op -0.38 ns/op / -14.8% (better)
Windows MinGW BenchmarkLookupPCRandom 11.430 ns/op +0.1 ns/op / +0.9% (worse)
Windows MinGW BenchmarkMergeCompilerFlags 560.700 ns/op +15.5 ns/op / +2.8% (worse)
Windows MinGW BenchmarkMergeLinkerFlags 501.300 ns/op +17.1 ns/op / +3.5% (worse)
Windows MinGW BenchmarkChannelBuffered 43.650 ns/op +0.08 ns/op / +0.2% (worse)
Windows MinGW BenchmarkChannelHandoff 13595 ns/op -3578 ns/op / -20.8% (better)
Windows MinGW BenchmarkDefer 59.140 ns/op +2.31 ns/op / +4.1% (worse)
Windows MinGW BenchmarkDirectCall 1.151 ns/op +0.098 ns/op / +9.3% (worse)
Windows MinGW BenchmarkGlobalRead 1.291 ns/op +0.271 ns/op / +26.6% (worse)
Windows MinGW BenchmarkGlobalWrite 8.428 ns/op +0.027 ns/op / +0.3% (worse)
Windows MinGW BenchmarkGoroutine 79996 ns/op +33 ns/op / +0.04127% (worse)
Windows MinGW BenchmarkInterfaceCall 5.604 ns/op +0.037 ns/op / +0.7% (worse)
Windows MinGW BenchmarkRuntimeGetG 1.865 ns/op +0.008 ns/op / +0.4% (worse)
Windows MinGW 386 BenchmarkLookupPCRandom 15.720 ns/op +0.11 ns/op / +0.7% (worse)
Windows MinGW 386 BenchmarkMergeCompilerFlags 902.300 ns/op -169.7 ns/op / -15.8% (better)
Windows MinGW 386 BenchmarkMergeLinkerFlags 1062 ns/op +81.5 ns/op / +8.3% (worse)
Windows MinGW 386 BenchmarkChannelBuffered 37.470 ns/op +0.72 ns/op / +2.0% (worse)
Windows MinGW 386 BenchmarkChannelHandoff 692.900 ns/op +54.1 ns/op / +8.5% (worse)
Windows MinGW 386 BenchmarkDefer 27.340 ns/op +0.6 ns/op / +2.2% (worse)
Windows MinGW 386 BenchmarkDirectCall 0.955 ns/op +0.0161 ns/op / +1.7% (worse)
Windows MinGW 386 BenchmarkGlobalRead 0.950 ns/op +0.0125 ns/op / +1.3% (worse)
Windows MinGW 386 BenchmarkGlobalWrite 8.491 ns/op +0.103 ns/op / +1.2% (worse)
Windows MinGW 386 BenchmarkGoroutine 51929 ns/op +1016 ns/op / +2.0% (worse)
Windows MinGW 386 BenchmarkInterfaceCall 4.792 ns/op +0.146 ns/op / +3.1% (worse)
Windows MinGW 386 BenchmarkRuntimeGetG 1.187 ns/op +0.012 ns/op / +1.0% (worse)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.150 ns/op -0.04 ns/op / -0.3% (better)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 572 ns/op -11.1 ns/op / -1.9% (better)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 535.700 ns/op -15.1 ns/op / -2.7% (better)
Windows MinGW ARM64 BenchmarkChannelBuffered 38.530 ns/op +1.09 ns/op / +2.9% (worse)
Windows MinGW ARM64 BenchmarkChannelHandoff 5883 ns/op +1842 ns/op / +45.6% (worse)
Windows MinGW ARM64 BenchmarkDefer 55.740 ns/op +0.41 ns/op / +0.7% (worse)
Windows MinGW ARM64 BenchmarkDirectCall 0.590 ns/op -0.0004 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkGlobalRead 0.593 ns/op -0.0002 ns/op / -0.03372% (better)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.663 ns/op +0.0002 ns/op / +0.03015% (worse)
Windows MinGW ARM64 BenchmarkGoroutine 64332 ns/op +836 ns/op / +1.3% (worse)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.144 ns/op +0.007 ns/op / +0.2% (worse)
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.797 ns/op +0.027 ns/op / +1.5% (worse)
Windows MSVC BenchmarkLookupPCRandom 9.625 ns/op +0.079 ns/op / +0.8% (worse)
Windows MSVC BenchmarkMergeCompilerFlags 421.700 ns/op +38.4 ns/op / +10.0% (worse)
Windows MSVC BenchmarkMergeLinkerFlags 367.700 ns/op +18 ns/op / +5.1% (worse)
Windows MSVC BenchmarkChannelBuffered 23.250 ns/op +0.14 ns/op / +0.6% (worse)
Windows MSVC BenchmarkChannelHandoff 1161 ns/op -20 ns/op / -1.7% (better)
Windows MSVC BenchmarkDefer 43.800 ns/op -0.52 ns/op / -1.2% (better)
Windows MSVC BenchmarkDirectCall 1.356 ns/op -0.001 ns/op / -0.1% (better)
Windows MSVC BenchmarkGlobalRead 1.357 ns/op -0.001 ns/op / -0.1% (better)
Windows MSVC BenchmarkGlobalWrite 2.167 ns/op -0.001 ns/op / -0.04613% (better)
Windows MSVC BenchmarkGoroutine 60947 ns/op +2448 ns/op / +4.2% (worse)
Windows MSVC BenchmarkInterfaceCall 7.073 ns/op +0.003 ns/op / +0.04243% (worse)
Windows MSVC BenchmarkRuntimeGetG 1.630 ns/op -0.153 ns/op / -8.6% (better)
Windows MSVC 386 BenchmarkLookupPCRandom 26.490 ns/op 0 ns/op / +0.0%
Windows MSVC 386 BenchmarkMergeCompilerFlags 748.400 ns/op +49.3 ns/op / +7.1% (worse)
Windows MSVC 386 BenchmarkMergeLinkerFlags 705.500 ns/op +15.9 ns/op / +2.3% (worse)
Windows MSVC 386 BenchmarkChannelBuffered 38.750 ns/op -0.01 ns/op / -0.0258% (better)
Windows MSVC 386 BenchmarkChannelHandoff 990.800 ns/op +70.6 ns/op / +7.7% (worse)
Windows MSVC 386 BenchmarkDefer 44.720 ns/op -1 ns/op / -2.2% (better)
Windows MSVC 386 BenchmarkDirectCall 1.546 ns/op -0.002 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkGlobalRead 1.549 ns/op -0.001 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkGlobalWrite 7.778 ns/op -0.017 ns/op / -0.2% (better)
Windows MSVC 386 BenchmarkGoroutine 107297 ns/op -1416 ns/op / -1.3% (better)
Windows MSVC 386 BenchmarkInterfaceCall 8.129 ns/op +0.03 ns/op / +0.4% (worse)
Windows MSVC 386 BenchmarkRuntimeGetG 2.479 ns/op +0.001 ns/op / +0.04036% (worse)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.170 ns/op +0.07 ns/op / +0.6% (worse)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 573.100 ns/op 0 ns/op / +0.0%
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 542.300 ns/op -0.7 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkChannelBuffered 38.790 ns/op +1.24 ns/op / +3.3% (worse)
Windows MSVC ARM64 BenchmarkChannelHandoff 3094 ns/op -72 ns/op / -2.3% (better)
Windows MSVC ARM64 BenchmarkDefer 63.730 ns/op +0.77 ns/op / +1.2% (worse)
Windows MSVC ARM64 BenchmarkDirectCall 0.590 ns/op +0.0003 ns/op / +0.1% (worse)
Windows MSVC ARM64 BenchmarkGlobalRead 0.590 ns/op -0.0001 ns/op / -0.01696% (better)
Windows MSVC ARM64 BenchmarkGlobalWrite 3.753 ns/op +0.001 ns/op / +0.02665% (worse)
Windows MSVC ARM64 BenchmarkGoroutine 58355 ns/op -1382 ns/op / -2.3% (better)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.142 ns/op +0.006 ns/op / +0.1% (worse)
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.800 ns/op +0.029 ns/op / +1.6% (worse)
Timer runtime benchmarks
Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 903.200 ns/op -2.4 ns/op / -0.3% (better)
Linux AfterFuncZeroDelivery/LLGo 41563 ns/op +5159 ns/op / +14.2% (worse)
Linux CreateStop/Go 291 ns/op +1.6 ns/op / +0.6% (worse)
Linux CreateStop/LLGo 1764 ns/op -70 ns/op / -3.8% (better)
Linux RearmStopped/Go 114.900 ns/op -0.8 ns/op / -0.7% (better)
Linux RearmStopped/LLGo 1151 ns/op -296 ns/op / -20.5% (better)
Linux ResetActive/Go 67.340 ns/op -1.12 ns/op / -1.6% (better)
Linux ResetActive/LLGo 712.200 ns/op -40.5 ns/op / -5.4% (better)
Linux ResetHeap1024/Go 67.100 ns/op -0.05 ns/op / -0.1% (better)
Linux ResetHeap1024/LLGo 158.500 ns/op 0 ns/op / +0.0%
macOS AfterFuncZeroDelivery/Go 435.200 ns/op +9.1 ns/op / +2.1% (worse)
macOS AfterFuncZeroDelivery/LLGo 85966 ns/op +24676 ns/op / +40.3% (worse)
macOS CreateStop/Go 134.900 ns/op -18.1 ns/op / -11.8% (better)
macOS CreateStop/LLGo 488.100 ns/op +99.1 ns/op / +25.5% (worse)
macOS RearmStopped/Go 53.100 ns/op -5.15 ns/op / -8.8% (better)
macOS RearmStopped/LLGo 316.900 ns/op +35.8 ns/op / +12.7% (worse)
macOS ResetActive/Go 40.690 ns/op -3.56 ns/op / -8.0% (better)
macOS ResetActive/LLGo 148 ns/op +5.6 ns/op / +3.9% (worse)
macOS ResetHeap1024/Go 39.390 ns/op -6.19 ns/op / -13.6% (better)
macOS ResetHeap1024/LLGo 76.140 ns/op -1.15 ns/op / -1.5% (better)
Windows MinGW AfterFuncZeroDelivery/Go 689.300 ns/op +6.9 ns/op / +1.0% (worse)
Windows MinGW AfterFuncZeroDelivery/LLGo 141875 ns/op -81 ns/op / -0.1% (better)
Windows MinGW CreateStop/Go 184 ns/op -2.3 ns/op / -1.2% (better)
Windows MinGW CreateStop/LLGo 1005 ns/op -15 ns/op / -1.5% (better)
Windows MinGW RearmStopped/Go 69.420 ns/op 0 ns/op / +0.0%
Windows MinGW RearmStopped/LLGo 289.900 ns/op -14.7 ns/op / -4.8% (better)
Windows MinGW ResetActive/Go 30.860 ns/op +0.14 ns/op / +0.5% (worse)
Windows MinGW ResetActive/LLGo 169.700 ns/op -4.6 ns/op / -2.6% (better)
Windows MinGW ResetHeap1024/Go 31.110 ns/op +0.01 ns/op / +0.03215% (worse)
Windows MinGW ResetHeap1024/LLGo 114.400 ns/op -0.4 ns/op / -0.3% (better)
Windows MinGW 386 AfterFuncZeroDelivery/Go 613.100 ns/op -5.2 ns/op / -0.8% (better)
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 106335 ns/op +366 ns/op / +0.3% (worse)
Windows MinGW 386 CreateStop/Go 162.900 ns/op +4.2 ns/op / +2.6% (worse)
Windows MinGW 386 CreateStop/LLGo 413.200 ns/op +12.3 ns/op / +3.1% (worse)
Windows MinGW 386 RearmStopped/Go 62.130 ns/op +1.11 ns/op / +1.8% (worse)
Windows MinGW 386 RearmStopped/LLGo 252.900 ns/op -6.7 ns/op / -2.6% (better)
Windows MinGW 386 ResetActive/Go 29.610 ns/op +0.66 ns/op / +2.3% (worse)
Windows MinGW 386 ResetActive/LLGo 784.100 ns/op +21.6 ns/op / +2.8% (worse)
Windows MinGW 386 ResetHeap1024/Go 29.590 ns/op +0.65 ns/op / +2.2% (worse)
Windows MinGW 386 ResetHeap1024/LLGo 110.500 ns/op +2.4 ns/op / +2.2% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 677.800 ns/op +18.8 ns/op / +2.9% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 156418 ns/op +3432 ns/op / +2.2% (worse)
Windows MinGW ARM64 CreateStop/Go 211.600 ns/op +11.4 ns/op / +5.7% (worse)
Windows MinGW ARM64 CreateStop/LLGo 361.600 ns/op +8 ns/op / +2.3% (worse)
Windows MinGW ARM64 RearmStopped/Go 70.590 ns/op +0.03 ns/op / +0.04252% (worse)
Windows MinGW ARM64 RearmStopped/LLGo 210.500 ns/op -2.4 ns/op / -1.1% (better)
Windows MinGW ARM64 ResetActive/Go 31 ns/op -0.06 ns/op / -0.2% (better)
Windows MinGW ARM64 ResetActive/LLGo 102.600 ns/op +0.3 ns/op / +0.3% (worse)
Windows MinGW ARM64 ResetHeap1024/Go 31.050 ns/op -0.09 ns/op / -0.3% (better)
Windows MinGW ARM64 ResetHeap1024/LLGo 111.900 ns/op -0.4 ns/op / -0.4% (better)
Windows MSVC AfterFuncZeroDelivery/Go 393.800 ns/op +18.9 ns/op / +5.0% (worse)
Windows MSVC AfterFuncZeroDelivery/LLGo 109728 ns/op +2744 ns/op / +2.6% (worse)
Windows MSVC CreateStop/Go 92.230 ns/op +2.03 ns/op / +2.3% (worse)
Windows MSVC CreateStop/LLGo 330.100 ns/op +23.5 ns/op / +7.7% (worse)
Windows MSVC RearmStopped/Go 24.600 ns/op +0.12 ns/op / +0.5% (worse)
Windows MSVC RearmStopped/LLGo 193.600 ns/op +4.7 ns/op / +2.5% (worse)
Windows MSVC ResetActive/Go 14.810 ns/op +0.01 ns/op / +0.1% (worse)
Windows MSVC ResetActive/LLGo 122.800 ns/op +3 ns/op / +2.5% (worse)
Windows MSVC ResetHeap1024/Go 15.080 ns/op +0.26 ns/op / +1.8% (worse)
Windows MSVC ResetHeap1024/LLGo 97.500 ns/op +0.72 ns/op / +0.7% (worse)
Windows MSVC 386 AfterFuncZeroDelivery/Go 952.900 ns/op -2.3 ns/op / -0.2% (better)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 203997 ns/op +3754 ns/op / +1.9% (worse)
Windows MSVC 386 CreateStop/Go 191.500 ns/op -1.4 ns/op / -0.7% (better)
Windows MSVC 386 CreateStop/LLGo 451.100 ns/op +32.7 ns/op / +7.8% (worse)
Windows MSVC 386 RearmStopped/Go 63.330 ns/op -0.14 ns/op / -0.2% (better)
Windows MSVC 386 RearmStopped/LLGo 308.600 ns/op +8 ns/op / +2.7% (worse)
Windows MSVC 386 ResetActive/Go 39.070 ns/op +0.14 ns/op / +0.4% (worse)
Windows MSVC 386 ResetActive/LLGo 925.300 ns/op +521.4 ns/op / +129.1% (worse)
Windows MSVC 386 ResetHeap1024/Go 39.540 ns/op +0.15 ns/op / +0.4% (worse)
Windows MSVC 386 ResetHeap1024/LLGo 160.400 ns/op -0.1 ns/op / -0.1% (better)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 669.200 ns/op -5.5 ns/op / -0.8% (better)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 142954 ns/op -2926 ns/op / -2.0% (better)
Windows MSVC ARM64 CreateStop/Go 202 ns/op +1.8 ns/op / +0.9% (worse)
Windows MSVC ARM64 CreateStop/LLGo 340.400 ns/op +3 ns/op / +0.9% (worse)
Windows MSVC ARM64 RearmStopped/Go 70.580 ns/op +0.03 ns/op / +0.04252% (worse)
Windows MSVC ARM64 RearmStopped/LLGo 222.100 ns/op -2.6 ns/op / -1.2% (better)
Windows MSVC ARM64 ResetActive/Go 30.990 ns/op -0.18 ns/op / -0.6% (better)
Windows MSVC ARM64 ResetActive/LLGo 115.600 ns/op +5.2 ns/op / +4.7% (worse)
Windows MSVC ARM64 ResetHeap1024/Go 31.130 ns/op +0.16 ns/op / +0.5% (worse)
Windows MSVC ARM64 ResetHeap1024/LLGo 115.800 ns/op -1 ns/op / -0.9% (better)

Compared with 762d7bd73a34 measured in the same runner job.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

fac37d4e2ef9 | 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 147057 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 134526 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 3003665 B 0 B / +0.0% 131047 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 3094062 B 0 B / +0.0% 102110 B 0 B / +0.0%
fmtprintf/j64-emscripten-memory64/LLGo 2766011 B 0 B / +0.0% 136527 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 146574 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 145414 B 0 B / +0.0% 72985 B 0 B / +0.0%
j64-emscripten-memory64/LLGo 134083 B 0 B / +0.0% 91712 B 0 B / +0.0%
reflectcall/j32-emscripten/LLGo 1446585 B 0 B / +0.0% 105303 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 1495430 B 0 B / +0.0% 89743 B 0 B / +0.0%
reflectcall/j64-emscripten-memory64/LLGo 1338918 B 0 B / +0.0% 109882 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.458 s -333.6 ms / -4.3% (better)
j32-goos-js 7.313 s +51.14 ms / +0.7% (worse)
j64-emscripten-memory64 6.300 s -144.4 ms / -2.2% (better)
reflectcall/w32-wasi 23.221 s -157.9 ms / -0.7% (better)
w32-goos-wasip1 4.521 s -174.1 ms / -3.7% (better)
w32-wasi 4.344 s -60.79 ms / -1.4% (better)

Compared with 762d7bd73a34 measured in the same runner job.

@cpunion

cpunion commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Reviewed commit 97797ec3bc234169875983816d341b2bc92c0aa6. I found two correctness issues:

  1. [P2] Preserve deferred recovery through the generated dispatcher.

    internal/midway/rewrite.go:433–435

    A function with a scalar signature and a SIMD-dependent body receives a dispatcher. When a non-SIMD caller defers that function, the dispatcher invokes the specialized variant as an ordinary call, so recover() in the variant is no longer in the directly deferred function. For example:

    package main
    
    import "simd"
    
    //go:noinline
    func caught() {
        var v simd.Uint32s
        println("lanes", v.Len())
        println("recovered", recover())
    }
    
    func fail() { defer caught(); panic("review panic") }
    func main() { fail(); println("survived") }

    This prints recovered (0x0,0x0) and exits with the original panic. Reproduced on WASI at O0 with default selection and at O2 with both GODEBUG=simd=128 and GODEBUG=simd=0. Removing the SIMD operation allows recovery and prints survived. The dispatcher needs to preserve the deferred callee's recovery identity, for example by integrating with the existing transparent-wrapper/recover-alias handling. Go 1.27's experimental SIMD implementation also exhibits this limitation; it is not specific to LLGo, but it changes normal deferred-recovery semantics and is not among this PR's documented scope limits.

  2. [P2] Preserve separate initialization units for globals with separate RHS expressions.

    internal/midway/globals.go:129–137

    This valid declaration is rejected after rewriting:

    var A, B = simd.BroadcastUint32s(1), A

    The actual compiler reports initialization cycle: A refers to itself. Go initializes A first, then B; the rewrite combines both initializers into one tuple-returning closure and inserts a dependency read of A, making that combined initializer depend on itself. Split declarations with independent RHS expressions into their original initialization units before wrapping them; retain grouped initialization for a single multi-result RHS.

Validation: the existing internal/midway and internal/packages suites, TestSIMDMidwayLLVM across its four targets, TestRewritePortableSIMDMetadata, and the existing WASI portable tests at O0 passed. Native execution checks were blocked by a local macOS SDK/LLD incompatibility; the recovery failure above was verified using Wasmer.

@cpunion

cpunion commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Follow-up review of the unchanged head 97797ec3bc234169875983816d341b2bc92c0aa6: I reproduced four additional issues, plus a silent initialization-order change caused by the initializer grouping reported earlier.

  1. [P1] Propagate generic receiver dependencies to instantiated types.

    internal/midway/analysis.go:268–278

    The dependency graph marks the generic receiver when its method body uses SIMD, but does not propagate that dependency to its concrete instances in the same package. This program compiles successfully, then panics:

    package main
    import "simd"
    
    type Box[T any] struct{}
    func (Box[T]) Width() int {
        var v simd.Uint32s
        return v.Len()
    }
    var Global Box[int]
    func main() { println(Global.Width()) }

    Actual result: panic: unexpected use of unspecialized portable SIMD, rather than printing 4. Box[int] is not marked dependent, so Global and its caller remain unspecialized while the original method body is replaced with the trap. No reflection or assembly is involved. Reproduced on WASI at O0 and at O2 with both GODEBUG=simd=128 and GODEBUG=simd=0; replacing the SIMD method body with return 4 succeeds.

    The same missing propagation can instead reject a valid program: adding func Identity(x Box[int]) Box[int] { return x } and calling Identity(Box[int]{}) from a rewritten function produces cannot use Box@simd0[int]{} ... as Box[int]. Dependency propagation needs to connect generic origins/receivers to their instances and instantiated method signatures.

  2. [P2] Preserve predeclared type bindings when reconstructing inferred globals.

    internal/midway/types.go:55–59

    typeExpr reconstructs basic types using their bare names. Those names may be shadowed in the user's package:

    type int = string
    var Width = (simd.Uint32s{}).Len()

    The original declaration is valid: Width infers the built-in integer type returned by Len, regardless of the local alias. The generated temporary/result declarations instead resolve int to the user's string alias. The actual compiler rejects both branches with cannot use ...Len() (value of type int) as int value in assignment. Generated type references must retain the original binding or avoid introducing an ambiguous type name.

  3. [P2] Give blank type parameters usable names before forwarding them.

    internal/midway/rewrite.go:413–420

    A legal unused generic parameter becomes an illegal type argument in the generated dispatcher:

    func Width[_ any]() int {
        var v simd.Uint32s
        return v.Len()
    }
    // A normal caller can use Width[int]().

    The dispatcher forwards the parameter by its literal name, emitting the equivalent of Width@simd0[_](). The second type check fails with cannot use _ as value or type. As with blank value parameters, assign a fresh internal name to each blank type parameter in the retained dispatcher before constructing its explicit type arguments.

  4. [P3] Include package-wide declarations when choosing generated import aliases.

    internal/midway/rewrite.go:223–227

    f.used contains identifiers from only the current file. A legal package-level declaration in another file can therefore collide with an injected import:

    // main.go
    package main
    import "simd"
    func Width() int { var v simd.Uint32s; return v.Len() }
    func main() { println(Width()) }
    // other.go
    package main
    var llgoMidway1 = 1

    This is rejected after the pass injects llgoMidway1 "simd/internal/bridge": llgoMidway1 already declared through import of package bridge. Check the package scope as well as the current file's names when allocating an alias.

There is also stronger evidence for the previously reported global-initializer grouping issue: it can silently reorder side effects, rather than only reporting a false cycle. For this program:

package main
import "simd"
var events []int
func vector() simd.Uint32s {
    events = append(events, 1)
    return simd.BroadcastUint32s(1)
}
func scalar() int { events = append(events, 2); return 42 }
var A, B = vector(), C
var C = scalar()
func main() { println(events[0], events[1], B) }

WASI O0 prints 2 1 42. Replacing the grouped declaration with the semantically equivalent var A = vector(); var B = C prints the expected 1 2 42. Grouping the independent initializers incorrectly makes A wait for C too.

Validation: all four additional failures were reproduced through the actual PR compiler. Original source acceptance was also checked with the frontend type checker. Additional rewrite/recheck controls for grouped constants, named arrays/aliases, comma-ok map reads and assertions, type switches, ordinary generic functions, inferred anonymous results, and local types passed. Go 1.27's experimental SIMD compiler also fails the generic/inferred-global examples, so those runs were not treated as passing semantic controls. Native runtime execution remains blocked by the local SDK/LLD incompatibility; execution evidence above is from Wasmer.

@zhouguangyuan0718

Copy link
Copy Markdown
Collaborator Author

Addressed the six correctness issues from the initial review and the follow-up in ca6a62d.

  • Generic method-body dependencies now propagate through receiver origins to concrete instances, including pointer receivers and instance signatures.
  • Independent initializer RHS expressions remain separate initialization units. A single multi-result RHS stays grouped. Both the false cycle and the silent 2 1 42 ordering failure are fixed.
  • Midway dispatchers participate in the existing transparent recovery-wrapper handling, including instantiated generic dispatchers. Ordinary user-written indirect calls still cannot recover the caller's panic.
  • Generated references to shadowed predeclared types use qualified bridge aliases, preserving their type identity; this also covers file-level import shadowing and built-in error.
  • Blank type parameters receive fresh compiler-only names before dispatcher forwarding.
  • Injected import aliases avoid names in both the current file and package scope.

Validation: all reported failure classes were independently reproduced on 97797ec. After the fix, 14 probe/control builds and 28 WASI/Wasmer executions passed. The portable suite passed on Darwin/arm64 and WASI/Wasmer under O0, O2, ThinLTO, and FullLTO, each with simd=0 and simd=128 (16 runs, 176 test passes). Full internal/midway, internal/packages, and cl suites passed, as did the new recovery classification test, four-target Midway LLVM/code-generation tests, and metadata tests.

Hosted CI for the new commit is pending. The separately prepared FMV bridge-eligibility fix and entry-fact/variant-pruning work remain outside this update.

@cpunion cpunion 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 ca6a62d2c8709a012757bc98e32724293618628a. Two additional silent data-correctness issues remain in global specialization. Both were reproduced through the PR compiler at O0/O2 on WASI with hardware and emulated selection; corresponding controls behave correctly. The existing Midway/package-loader suites, four-target Midway LLVM checks, metadata checks, and recovery-classification regression passed.

Comment thread internal/midway/globals.go Outdated
Comment on lines +81 to +83
extra = append(extra, &ast.GenDecl{TokPos: pos, Tok: token.VAR, Specs: []ast.Spec{&ast.ValueSpec{
Names: []*ast.Ident{ident(id.Name+suffix(width), pos)}, Type: f.typeExpr(obj.Type(), width, pos),
}}})

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.

[P2] Initialize embedded data in the concrete global variants

The new declarations have no GenDecl.Doc or ValueSpec.Doc, so go:embed initializes only the retained original variable. Executable code reads data@simd0 / data@simd128, which stay zero-valued. This also affects named scalar types whose only SIMD dependency is a method body:

package main
import (_ "embed"; "simd")
type Data string
func (Data) Width() int { var v simd.Uint32s; return v.Len() }
//go:embed text.txt
var data Data
func main() { println("data", string(data), "width", data.Width()) }

With text.txt containing embedded data, the result is data width 4, instead of data embedded data width 4. Replacing Width's body with return 4 restores the embedded contents. Reproduced with the PR compiler on WASI at O0 and O2, under both GODEBUG=simd=128 and simd=0; also reproduced on Darwin/arm64 at O0. Please preserve embed initialization for the concrete storage and add an execution regression.

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.

Verified on 851907a0ac00846e5f73fa95208673344279d6ad: the original named-string reproducer now prints data embedded data width 4 in both hardware and emulated modes. The named-string/byte-slice execution regressions also pass on Darwin/arm64 and WASI, including initialization-time reads. I separately reported the new fixture's Windows CRLF checkout failure in #2819 (comment); that failure is a test expectation issue, not lost embed data.

Comment on lines +372 to +373
if packageObject(obj) && f.a.dependent(obj) {
n.Name += suffix(width)

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.

[P2] Preserve global linkname aliases when rewriting their references

For a SIMD-dependent variable, this rewrites an alias reference to Alias@simd0 / Alias@simd128. However, the go:linkname relationship remains on the retained original declaration, while global() allocates the alias variants as independent zero-valued globals. The alias consequently stops observing its target's contents:

package main
import ("simd"; _ "unsafe")
var V = simd.BroadcastUint32s(7)
//go:linkname Alias main.V
var Alias simd.Uint32s
func main() {
    out := make([]uint32, 4)
    Alias.Store(out)
    println(out[0])
}

This builds successfully and prints 0 instead of 7. Reproduced on WASI at O0 and O2 with both GODEBUG=simd=128 and simd=0. A second probe using a named integer with a SIMD-dependent method prints Alias=0, V=7; its non-SIMD control prints Alias=7, V=7, confirming this is an alias-storage regression rather than a vector-store issue. Please preserve the alias relationship between the corresponding concrete variants, or diagnose an unsupported binding before generating code.

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.

Verified on 851907a0ac00846e5f73fa95208673344279d6ad: both original SIMD alias reproducers now fail during compilation with cannot specialize go:linkname binding for SIMD-dependent global Alias, while the non-SIMD alias control still prints alias 7 V 7 4. The new same-package/imported-target diagnostic tests pass too. This resolves the silent wrong-storage behavior by explicitly rejecting the unsupported binding; it does not add SIMD global-linkname support.

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

Follow-up review of 851907a: the new embed regression has a reproducible Windows checkout failure. Broader execution qualification is still in progress.

Comment thread test/simd/portable/embed_llgo_test.go Outdated
var embeddedAtInit = string(midwayEmbeddedText) + string(midwayEmbeddedBytes)

func TestPortableEmbeddedGlobals(t *testing.T) {
const want = "embedded data\n"

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.

[P2] Preserve the fixture's line endings or avoid a fixed LF expectation

The new text fixture has no eol=lf/binary attribute, so Windows checkouts convert it to CRLF. Embedding correctly preserves those bytes, but this fixed expectation makes the portable SIMD suite fail. The latest Windows MinGW/amd64 job already fails here: job log reports text="embedded data\r\n", bytes="embedded data\r\n", and the corresponding doubled initializer string.

I also reproduced the same failure on Darwin/arm64 with this exact compiler revision by copying the portable test package, changing only testdata/embedded.txt to embedded data\r\n, and running llgo test -O0 -run '^TestPortableEmbeddedGlobals$'. The LF checkout passes. Please pin the fixture's bytes in .gitattributes (as the existing golden files do), or derive the expected content without assuming checkout line endings, so the new regression does not break the Windows SIMD matrix.

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.

Verified fixed in fac37d4e2ef9449a04256daeb132555dabc53757. A fresh git -c core.autocrlf=true checkout-index of the updated fixture produces exactly b'embedded data', with no line ending to convert. I copied the current portable test package into that checkout and ran llgo test -O0 -v -count=1 -run '^TestPortableEmbeddedGlobals$' using a compiler built from this exact commit; it passes. The latest Midway/package-loader suites and four-target code-generation/global-binding regressions also pass. This verifies the fixture fix locally; the new Windows CI jobs are still pending.

@cpunion
cpunion merged commit c41bc55 into xgo-dev:main Oct 17, 2026
112 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.

2 participants