Skip to content

cl: support static initializer folding for struct and composite slices - #2374

Closed
cpunion wants to merge 5 commits into
xgo-dev:mainfrom
cpunion:codex/fold-struct-slice-static-inits
Closed

cl: support static initializer folding for struct and composite slices#2374
cpunion wants to merge 5 commits into
xgo-dev:mainfrom
cpunion:codex/fold-struct-slice-static-inits

Conversation

@cpunion

@cpunion cpunion commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Note

Depends on #2371: This PR builds on top of #2371 (all: bump golang.org/x/tools to v0.49.0, fold composite literals, and add LTO baseline benchmarks).

Summary

Extends cl/static_init.go's static initializer folding engine from scalar-only slices to arbitrary struct and composite literal slices (such as []Struct{...}).


Key Changes

  1. Struct & Composite Slice Static Folding (cl/static_init.go):

    • Upgraded staticSliceInitOf to collect and resolve element stores recursively through collectAllocStores.
    • Updated buildStaticSliceInit to reconstruct per-element AST/SSA trees via staticInitNode.add() and evaluate them into LLVM struct constants (p.pkg.ConstSlice(...)), eliminating runtime element assignment loops in package init() functions.
    • Automatically marks all intermediate temporary stack allocs, GEPs, and element stores for suppression from package init().
  2. Test Coverage (cl/rewrite_internal_test.go):

    • Added TestStaticGlobalStructSliceLiteralInit verifying that package-level struct slice variables (e.g. var All = []Info{...}) compile into static global constants (@"staticinit.All$data") without emitting runtime.AllocZ or runtime initializer stores.

Measured Impact

  • internal/godebugs.init: Initializer function size reduced from 3,780 bytes to 24 bytes (99.4% reduction, -3.75 KB text).
  • DCE Reachability: Unreferenced global struct slices no longer force their initialization instructions to be retained in executable __text, enabling linker -dead_strip and -deadcodedrop to discard unreferenced slices cleanly.

@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: fold struct/slice static inits

Solid, well-structured change with unusually thorough test coverage (cl/rewrite_internal_test.go exercises cycle detection, out-of-range children, blank fields, executable referrers, and zero-sized fallbacks). The recursive tracing is correctly bounded by the visited set and single-referrer checks (linear in traversed instructions), and large-array folding is capped by maxStaticInitArrayElements. Dependency bumps in go.mod/go.sum are clean version updates with no substitutions. Doc comments and the benchmark README changes accurately match the code/config.

Two soundness concerns in the new SSA tracing in cl/static_init.go are worth a look before merge (both flagged inline). They are non-blocking observations — the invariants happen to hold for the current entry points, but the helper functions don't enforce them internally.

Minor / nits:

  • staticInitStorePathToAlloc (~line 337-355) and staticInitStorePath (~line 463-481) build paths via append on the recursively-returned slice. This is the same slice-aliasing hazard that appendStaticInitPath was introduced to avoid. It's safe today because these call sites are always single-level, but a short comment (or reusing the non-aliasing helper) would prevent a future multi-level caller from hitting aliasing.
  • ssa/decl.go Global.Init/InitNil: the moduleZeroName guard silently no-ops. Correct, but a one-line comment on why (mirroring the good comment in doNewVarEx) would help future readers.

Comment thread cl/static_init.go
if !ok || elemStore.Val != ref {
return false
}
case *ssa.UnOp:

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.

Load consumer not validated — potential unsound folding.

The *ssa.UnOp (load) branch appends the load to instrs (suppressing it) but only checks that the load has exactly one referrer — it never inspects what that referrer is. The callers (collectStaticGlobalInits and handleStoreVal) validate that the specific load they originate from feeds the expected store, but an alloc can have additional load referrers.

If the traced alloc is loaded a second time and that value flows into a call or another store, this branch accepts it (len(unopRefs) == 1 passes), the load and the alloc's stores get suppressed, yet the extra consumer is left referencing a value whose producing load is no longer emitted (and reads an alloc that is no longer initialized at runtime). That is a potential miscompilation.

Consider validating that unopRefs[0] is itself part of the folded/suppressed set (mirroring the elemStore.Addr == ref checks in the FieldAddr/IndexAddr cases), or returning false for unrecognized consumers. TestStaticSliceInitRejectsExecutableReferrers only covers the top-level alloc, not a nested/aliased load.

Comment thread cl/static_init.go
}
for _, ref := range refs {
switch ref := ref.(type) {
case *ssa.Slice:

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.

*ssa.Slice branch validates but neither records nor suppresses its instructions.

This *ssa.Slice branch validates the shape (single referrer that is a store consuming the slice) but — unlike the FieldAddr/IndexAddr/Store cases — it neither appends ref/elemStore to *instrs nor checks elemStore.Addr, and it collects no values.

When collectAllocStores is entered from staticSliceInitOf, the slice+store are pre-seeded into instrs separately, so this is fine there. But the function is also reachable for nested allocs via handleStoreVal -> collectAllocStores(innerAlloc, ...). If such a nested alloc has a *ssa.Slice referrer, this branch returns success while leaving the slice and its store un-suppressed, risking inconsistent/duplicate initialization.

The correctness of this branch is coupled to the specific caller rather than self-contained. Recommend either recording/suppressing the slice's store here (and validating its target) or explicitly rejecting the Slice shape outside the dedicated slice entry point, plus a clarifying comment.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.75309% with 49 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/static_init.go 68.98% 33 Missing and 16 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

ea1eb414875d | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Build vs base Run vs base
Linux cprintf 19288 B 0 B / +0.0% 324.535 ms +1.543 ms / +0.5% (worse) 1.247 ms -16.16 us / -1.3% (better)
Linux cprintf\_lto 19120 B 0 B / +0.0% 337.626 ms +5.876 ms / +1.8% (worse) 1.265 ms -33.89 us / -2.6% (better)
Linux fmtprintf 1866192 B +6040 B / +0.3% (worse) 2.879 s +105.3 ms / +3.8% (worse) 3.207 ms +35.82 us / +1.1% (worse)
Linux fmtprintf\_lto 1779128 B +2024 B / +0.1% (worse) 14.326 s -737.8 ms / -4.9% (better) 3.177 ms -19.24 us / -0.6% (better)
Linux println 68776 B 0 B / +0.0% 341.804 ms +2.921 ms / +0.9% (worse) 1.578 ms -26.5 us / -1.7% (better)
Linux println\_lto 62464 B 0 B / +0.0% 550.366 ms -8.713 ms / -1.6% (better) 1.585 ms -11.55 us / -0.7% (better)
macOS cprintf 84672 B 0 B / +0.0% 416.525 ms -22.69 ms / -5.2% (better) 3.306 ms -87.42 us / -2.6% (better)
macOS cprintf\_lto 100912 B 0 B / +0.0% 488.086 ms +26.24 ms / +5.7% (worse) 3.246 ms +272.7 us / +9.2% (worse)
macOS fmtprintf 1909280 B +16560 B / +0.9% (worse) 3.040 s +366.3 ms / +13.7% (worse) 11.054 ms -1.09 ms / -9.0% (better)
macOS fmtprintf\_lto 1626224 B -176 B / -0.01082% (better) 18.703 s +1.203 s / +6.9% (worse) 4.716 ms -1.838 ms / -28.0% (better)
macOS println 121360 B 0 B / +0.0% 451.321 ms +13.24 ms / +3.0% (worse) 4.676 ms -328 us / -6.6% (better)
macOS println\_lto 128528 B 0 B / +0.0% 689.316 ms +101.2 ms / +17.2% (worse) 4.622 ms +738.2 us / +19.0% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 13.290 ns/op +0.05 ns/op / +0.4% (worse)
Linux BenchmarkMergeCompilerFlags 151.200 ns/op +0.3 ns/op / +0.2% (worse)
Linux BenchmarkMergeLinkerFlags 94.530 ns/op +0.05 ns/op / +0.1% (worse)
Linux BenchmarkChannelBuffered 35.230 ns/op +1.21 ns/op / +3.6% (worse)
Linux BenchmarkChannelHandoff 26284 ns/op -1201 ns/op / -4.4% (better)
Linux BenchmarkDefer 44.530 ns/op -1.28 ns/op / -2.8% (better)
Linux BenchmarkDirectCall 1.558 ns/op +0.001 ns/op / +0.1% (worse)
Linux BenchmarkGlobalRead 1.870 ns/op +0.314 ns/op / +20.2% (worse)
Linux BenchmarkGlobalWrite 2.487 ns/op +0.008 ns/op / +0.3% (worse)
Linux BenchmarkGoroutine 41338 ns/op -2287 ns/op / -5.2% (better)
Linux BenchmarkInterfaceCall 8.098 ns/op -0.001 ns/op / -0.01235% (better)
Linux BenchmarkRuntimeGetG 1.870 ns/op -0.311 ns/op / -14.3% (better)
macOS BenchmarkLookupPCRandom 12.020 ns/op -4.39 ns/op / -26.8% (better)
macOS BenchmarkMergeCompilerFlags 103.900 ns/op -54.4 ns/op / -34.4% (better)
macOS BenchmarkMergeLinkerFlags 73.750 ns/op -26.55 ns/op / -26.5% (better)
macOS BenchmarkChannelBuffered 23.020 ns/op -1.26 ns/op / -5.2% (better)
macOS BenchmarkChannelHandoff 6726 ns/op -1234 ns/op / -15.5% (better)
macOS BenchmarkDefer 31.680 ns/op -1.66 ns/op / -5.0% (better)
macOS BenchmarkDirectCall 1.042 ns/op -0.012 ns/op / -1.1% (better)
macOS BenchmarkGlobalRead 1.026 ns/op +0.0294 ns/op / +3.0% (worse)
macOS BenchmarkGlobalWrite 1.060 ns/op +0.05 ns/op / +5.0% (worse)
macOS BenchmarkGoroutine 44578 ns/op +14665 ns/op / +49.0% (worse)
macOS BenchmarkInterfaceCall 5.821 ns/op +1.089 ns/op / +23.0% (worse)
macOS BenchmarkRuntimeGetG 2.003 ns/op -0.002 ns/op / -0.1% (better)

Compared with 0314faeba69d measured in the same runner job.

@cpunion

cpunion commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of restructured PRs.

@cpunion cpunion closed this Aug 20, 2026
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.

1 participant