From b13567e7d5add29239d5b753420b868767c346ff Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 20 Aug 2026 16:54:09 +0800 Subject: [PATCH 1/4] cl, ssa: fix zero-sized global init and fold composite literal alloc stores --- cl/rewrite_internal_test.go | 242 ++++++++++++++++++++++++++++++++++-- cl/static_init.go | 171 +++++++++++++++++++++++-- ssa/decl.go | 6 + 3 files changed, 400 insertions(+), 19 deletions(-) diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 0eeb380771..8277369b3d 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -205,6 +205,12 @@ func Use() string { if strings.Contains(ir, "@staticinit.MethodNames = global %staticinit.Names zeroinitializer") { t.Fatalf("MethodNames still uses a zero initializer:\n%s", ir) } + if !strings.Contains(ir, `[%"github.com/xgo-dev/llgo/runtime/internal/runtime.String" { ptr @0, i64 9 }, %"github.com/xgo-dev/llgo/runtime/internal/runtime.String" { ptr @1, i64 12 }]`) { + t.Fatalf("unexpected MethodNames.Value initializer:\n%s", ir) + } + if !strings.Contains(ir, `%staticinit.Nested { [2 x %"github.com/xgo-dev/llgo/runtime/internal/runtime.String"] [%"github.com/xgo-dev/llgo/runtime/internal/runtime.String" { ptr @2, i64 8 }, %"github.com/xgo-dev/llgo/runtime/internal/runtime.String" { ptr @3, i64 11 }] }`) { + t.Fatalf("unexpected MethodNames.Nested initializer:\n%s", ir) + } for _, want := range []string{`c"KeepValue"`, `c"KeepValueAlt"`, `c"KeepType"`, `c"KeepTypeAlt"`} { if !strings.Contains(ir, want) { t.Fatalf("missing %s in IR:\n%s", want, ir) @@ -320,9 +326,8 @@ var Value = Outer{ } var ( - blankSliceField *ssa.FieldAddr - sawDirectBlank, sawNestedBlank bool - sawBlankArray, sawNonBlankSibling bool + blankSliceField *ssa.FieldAddr + sawDirectBlank, sawNonBlankSibling bool ) initFn := pkg.Func("init") for _, block := range initFn.Blocks { @@ -353,21 +358,15 @@ var Value = Outer{ switch { case len(fields) == 1 && fields[0] == "_": sawDirectBlank = true - case len(fields) > 1 && want && !indexed: - sawNestedBlank = true - case want && indexed: - sawBlankArray = true - case !want: + case !want && !indexed: sawNonBlankSibling = true } } } - if !sawDirectBlank || !sawNestedBlank || !sawBlankArray || !sawNonBlankSibling { + if !sawDirectBlank || !sawNonBlankSibling { t.Fatalf( - "missing SSA classification coverage: direct=%v nested=%v array=%v sibling=%v", + "missing SSA classification coverage: direct=%v sibling=%v", sawDirectBlank, - sawNestedBlank, - sawBlankArray, sawNonBlankSibling, ) } @@ -1382,3 +1381,222 @@ func f() {} t.Fatal("compiled owner should be cached") } } + +func TestCollectAllocStoresFromSSA(t *testing.T) { + const src = `package allocstore + +type Point struct { + X, Y int +} + +type Nested struct { + P Point + Arr [2]int + Tag string +} + +func testConstNested() Nested { + return Nested{ + P: Point{10, 20}, + Arr: [2]int{30, 40}, + Tag: "hello", + } +} + +func testConstPoint() Point { + return Point{100, 200} +} + +func testDynamic() Point { + return Point{next(), 200} +} + +func testCall(p Point) {} + +func testEscape() { + var p = Point{1, 2} + testCall(p) + var n = Nested{P: Point{3, 4}} + pRef := &n.P + pRef.X = 99 +} + +func testArrayInit() [2]int { + var a [2]int + a[0] = 10 + a[1] = 20 + return a +} + +func testDirectStore() int { + var x int + x = 42 + return x +} + +func testArrayDynamic() [2]int { + var a [2]int + a[0] = next() + return a +} + +func next() int { return 1 } +` + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "allocstore.go", src, 0) + if err != nil { + t.Fatal(err) + } + importer := gpackages.NewImporter(fset) + pkg, _, err := ssautil.BuildPackage( + &types.Config{Importer: importer}, + fset, + types.NewPackage("allocstore", "allocstore"), + []*ast.File{file}, + ssa.SanityCheckFunctions, + ) + if err != nil { + t.Fatal(err) + } + + var foundAllocs []*ssa.Alloc + var foundStores []*ssa.Store + var foundFields []*ssa.FieldAddr + var foundIndices []*ssa.IndexAddr + + for _, member := range pkg.Members { + fn, ok := member.(*ssa.Function) + if !ok { + continue + } + for _, block := range fn.Blocks { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.Alloc: + if !instr.Heap { + foundAllocs = append(foundAllocs, instr) + } + case *ssa.Store: + foundStores = append(foundStores, instr) + case *ssa.FieldAddr: + foundFields = append(foundFields, instr) + case *ssa.IndexAddr: + foundIndices = append(foundIndices, instr) + } + } + } + } + + if len(foundAllocs) == 0 { + t.Fatal("expected to find local allocs in SSA") + } + + // Test collectAllocStores on all found allocs + for _, alloc := range foundAllocs { + var stores []staticInitStore + var instrs []ssa.Instruction + collectAllocStores(alloc, nil, &stores, &instrs, make(map[*ssa.Alloc]bool)) + collectAllocStores(alloc, []staticInitPathElem{{index: 1}}, &stores, &instrs, make(map[*ssa.Alloc]bool)) + } + + targetAlloc := foundAllocs[0] + + // 1. Cycle detection + var stores []staticInitStore + var instrs []ssa.Instruction + visited := map[*ssa.Alloc]bool{targetAlloc: true} + if collectAllocStores(targetAlloc, nil, &stores, &instrs, visited) { + t.Fatal("expected cycle protection to return false") + } + + // 2. appendStaticInitPath + p1 := []staticInitPathElem{{index: 1}, {index: 2}} + p2 := []staticInitPathElem{{index: 3}} + merged := appendStaticInitPath(p1, p2) + if len(merged) != 3 || merged[0].index != 1 || merged[1].index != 2 || merged[2].index != 3 { + t.Fatalf("unexpected appendStaticInitPath result: %+v", merged) + } + + // 3. handleStoreVal branches + if len(foundStores) > 0 { + store := foundStores[0] + // Test Const store + cStore := &ssa.Store{Addr: store.Addr, Val: ssa.NewConst(constant.MakeInt64(1), types.Typ[types.Int])} + stores = nil + if !handleStoreVal(cStore, p1, &stores, &instrs, make(map[*ssa.Alloc]bool)) { + t.Fatal("handleStoreVal failed on const") + } + if len(stores) != 1 || len(stores[0].path) != 2 { + t.Fatalf("unexpected handleStoreVal result: %+v", stores) + } + + // Test non-const non-unop store + badStore := &ssa.Store{Addr: store.Addr, Val: store.Addr} + if handleStoreVal(badStore, p1, &stores, &instrs, make(map[*ssa.Alloc]bool)) { + t.Fatal("expected handleStoreVal to fail on non-const, non-alloc Val") + } + + // Test Heap alloc + heapAlloc := &ssa.Alloc{Heap: true, Comment: "heap"} + heapUnOp := &ssa.UnOp{Op: token.MUL, X: heapAlloc} + heapStore := &ssa.Store{Addr: store.Addr, Val: heapUnOp} + if handleStoreVal(heapStore, p1, &stores, &instrs, make(map[*ssa.Alloc]bool)) { + t.Fatal("expected handleStoreVal to fail on heap alloc") + } + } + + // 4. staticInitStorePathToAlloc edge cases + if _, ok := staticInitStorePathToAlloc(nil, targetAlloc); ok { + t.Fatal("expected nil addr to fail") + } + if path, ok := staticInitStorePathToAlloc(targetAlloc, targetAlloc); !ok || len(path) != 0 { + t.Fatalf("expected exact alloc to return empty path, got %+v, %v", path, ok) + } + if len(foundAllocs) > 1 { + if _, ok := staticInitStorePathToAlloc(foundAllocs[1], targetAlloc); ok { + t.Fatal("expected different alloc to fail") + } + } + if len(foundFields) > 0 { + field := foundFields[0] + _, _ = staticInitStorePathToAlloc(field, targetAlloc) + } + if len(foundIndices) > 0 { + index := foundIndices[0] + _, _ = staticInitStorePathToAlloc(index, targetAlloc) + } +} + +func TestStaticGlobalPointerIndirectionLiteralInit(t *testing.T) { + const src = `package staticinit + +type Inner struct { + A [2]int + B string +} + +type Outer struct { + I Inner + Val int +} + +var G = Outer{ + I: Inner{ + A: [2]int{10, 20}, + B: "hello", + }, + Val: 99, +} + +func Use() int { + return G.I.A[0] + G.I.A[1] + len(G.I.B) + G.Val +} +` + ir := compileWithRewrites(t, src, nil) + if strings.Contains(ir, "@staticinit.G = global %staticinit.Outer zeroinitializer") { + t.Fatalf("G still uses a zero initializer:\n%s", ir) + } + if !strings.Contains(ir, `c"hello"`) { + t.Fatalf("missing hello in IR:\n%s", ir) + } +} diff --git a/cl/static_init.go b/cl/static_init.go index 7edd7fa942..7ac5197a18 100644 --- a/cl/static_init.go +++ b/cl/static_init.go @@ -18,6 +18,7 @@ package cl import ( "go/constant" + "go/token" "go/types" "sort" "strings" @@ -43,6 +44,7 @@ type staticInitStore struct { type staticInitCandidate struct { stores []staticInitStore slice *staticSliceInit + instrs []ssa.Instruction invalid bool } @@ -133,16 +135,32 @@ func (p *context) collectStaticGlobalInits(pkg *ssa.Package) { continue } } - value, isConst := store.Val.(*ssa.Const) - if !ok || !isConst { + if value, isConst := store.Val.(*ssa.Const); isConst { + candidate.stores = append(candidate.stores, staticInitStore{ + store: store, + path: path, + value: value, + }) + } else if unop, ok := store.Val.(*ssa.UnOp); ok && unop.Op == token.MUL { + if alloc, ok := unop.X.(*ssa.Alloc); ok && !alloc.Heap { + unopRefs, ok := nonDebugReferrers(unop) + if !ok || len(unopRefs) != 1 || unopRefs[0] != store { + candidate.invalid = true + continue + } + if !collectAllocStores(alloc, path, &candidate.stores, &candidate.instrs, make(map[*ssa.Alloc]bool)) { + candidate.invalid = true + continue + } + candidate.instrs = append(candidate.instrs, unop, store) + } else { + candidate.invalid = true + continue + } + } else { candidate.invalid = true continue } - candidate.stores = append(candidate.stores, staticInitStore{ - store: store, - path: path, - value: value, - }) } } @@ -180,12 +198,151 @@ func (p *context) collectStaticGlobalInits(pkg *ssa.Package) { p.staticInitInstrs[instr] = none{} } } + for _, instr := range candidate.instrs { + p.staticInitInstrs[instr] = none{} + } for _, store := range candidate.stores { p.staticInitStores[store.store] = none{} } } } +// collectAllocStores recursively traces store instructions made to a local stack alloc, +// recording constant stores into out and tracking intermediate instructions for suppression. +// The visited map guards against cyclic pointer graphs. +func collectAllocStores(alloc *ssa.Alloc, basePath []staticInitPathElem, out *[]staticInitStore, instrs *[]ssa.Instruction, visited map[*ssa.Alloc]bool) bool { + if visited[alloc] { + return false + } + visited[alloc] = true + *instrs = append(*instrs, alloc) + + refs, ok := nonDebugReferrers(alloc) + if !ok { + return false + } + for _, ref := range refs { + switch ref := ref.(type) { + case *ssa.UnOp: + if ref.Op != token.MUL { + return false + } + unopRefs, ok := nonDebugReferrers(ref) + if !ok || len(unopRefs) != 1 { + return false + } + *instrs = append(*instrs, ref) + case *ssa.FieldAddr: + subPath, ok := staticInitStorePathToAlloc(ref, alloc) + if !ok { + return false + } + fieldRefs, ok := nonDebugReferrers(ref) + if !ok || len(fieldRefs) != 1 { + return false + } + elemStore, ok := fieldRefs[0].(*ssa.Store) + if !ok || elemStore.Addr != ref { + return false + } + if !handleStoreVal(elemStore, appendStaticInitPath(basePath, subPath), out, instrs, visited) { + return false + } + *instrs = append(*instrs, ref, elemStore) + case *ssa.IndexAddr: + subPath, ok := staticInitStorePathToAlloc(ref, alloc) + if !ok { + return false + } + indexRefs, ok := nonDebugReferrers(ref) + if !ok || len(indexRefs) != 1 { + return false + } + elemStore, ok := indexRefs[0].(*ssa.Store) + if !ok || elemStore.Addr != ref { + return false + } + if !handleStoreVal(elemStore, appendStaticInitPath(basePath, subPath), out, instrs, visited) { + return false + } + *instrs = append(*instrs, ref, elemStore) + case *ssa.Store: + if ref.Addr != alloc { + return false + } + if !handleStoreVal(ref, appendStaticInitPath(basePath, nil), out, instrs, visited) { + return false + } + *instrs = append(*instrs, ref) + default: + return false + } + } + return true +} + +// appendStaticInitPath concatenates base and sub paths into a newly allocated slice +// to avoid slice-aliasing hazards when branching across multiple struct fields or array elements. +func appendStaticInitPath(base, sub []staticInitPathElem) []staticInitPathElem { + res := make([]staticInitPathElem, len(base)+len(sub)) + copy(res, base) + copy(res[len(base):], sub) + return res +} + +// handleStoreVal inspects a store value, appending constant stores directly or recursing +// into inner nested local allocs reached through pointer indirection (*ssa.UnOp). +func handleStoreVal(store *ssa.Store, fullPath []staticInitPathElem, out *[]staticInitStore, instrs *[]ssa.Instruction, visited map[*ssa.Alloc]bool) bool { + if val, ok := store.Val.(*ssa.Const); ok { + *out = append(*out, staticInitStore{ + store: store, + path: fullPath, + value: val, + }) + return true + } + if unop, ok := store.Val.(*ssa.UnOp); ok && unop.Op == token.MUL { + if innerAlloc, ok := unop.X.(*ssa.Alloc); ok && !innerAlloc.Heap { + unopRefs, ok := nonDebugReferrers(unop) + if !ok || len(unopRefs) != 1 || unopRefs[0] != store { + return false + } + return collectAllocStores(innerAlloc, fullPath, out, instrs, visited) + } + } + return false +} + +// staticInitStorePathToAlloc resolves the nested path elements from an address expression +// back to the root target alloc. +func staticInitStorePathToAlloc(addr ssa.Value, target *ssa.Alloc) ([]staticInitPathElem, bool) { + switch addr := addr.(type) { + case *ssa.Alloc: + if addr == target { + return nil, true + } + return nil, false + case *ssa.FieldAddr: + path, ok := staticInitStorePathToAlloc(addr.X, target) + if !ok { + return nil, false + } + return append(path, staticInitPathElem{index: addr.Field}), true + case *ssa.IndexAddr: + path, ok := staticInitStorePathToAlloc(addr.X, target) + if !ok { + return nil, false + } + index, ok := staticInitConstIndex(addr.Index) + if !ok { + return nil, false + } + return append(path, staticInitPathElem{index: index}), true + default: + return nil, false + } +} + func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) { slice, ok := store.Val.(*ssa.Slice) if !ok || slice.Low != nil || slice.High != nil || slice.Max != nil { diff --git a/ssa/decl.go b/ssa/decl.go index 43d1a87afc..7a02a912b5 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -172,10 +172,16 @@ func (p Package) VarOf(name string) Global { // Init initializes the global variable with the given value. func (g Global) Init(v Expr) { + if g.impl.Name() == moduleZeroName { + return + } g.impl.SetInitializer(v.impl) } func (g Global) InitNil() { + if g.impl.Name() == moduleZeroName { + return + } g.impl.SetInitializer(llvm.ConstNull(g.impl.GlobalValueType())) } From 448dae3810cdb298ff6daaa7fb723843098c9b42 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 20 Aug 2026 16:54:13 +0800 Subject: [PATCH 2/4] benchmark: use PR merge-base for baseline and add LTO workload measurements --- .github/workflows/benchmark.yml | 13 ++++++++++++- benchmark/baseline/README.md | 6 +++--- benchmark/baseline/main.go | 10 ++++++++-- benchmark/baseline/main_test.go | 1 + 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 68821b81fa..72b4573a1e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -31,13 +31,24 @@ jobs: LLGO_ROOT: ${{ github.workspace }} steps: - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Determine pull request merge-base + if: github.event_name == 'pull_request' + id: merge-base + run: | + git fetch https://github.com/${{ github.event.pull_request.base.repo.full_name }}.git ${{ github.event.pull_request.base.ref }} + base_sha=$(git merge-base FETCH_HEAD ${{ github.event.pull_request.head.sha }}) + echo "sha=$base_sha" >> "$GITHUB_OUTPUT" + echo "Computed pull request merge-base: $base_sha (head: ${{ github.event.pull_request.head.sha }})" - name: Check out pull request base benchmark source if: github.event_name == 'pull_request' uses: actions/checkout@v7 with: repository: ${{ github.event.pull_request.base.repo.full_name }} - ref: ${{ github.event.pull_request.base.sha }} + ref: ${{ steps.merge-base.outputs.sha }} path: .benchmark/source persist-credentials: false diff --git a/benchmark/baseline/README.md b/benchmark/baseline/README.md index 32073268cf..77c5163aab 100644 --- a/benchmark/baseline/README.md +++ b/benchmark/baseline/README.md @@ -10,9 +10,9 @@ commit, branch, or pull-request series. The program workloads reuse: -- `benchmark/binary_size/cprintf`: only `lib/c.Printf`; -- `benchmark/binary_size/println`: only the built-in `println`; -- `benchmark/binary_size/fmtprintf`: `fmt.Printf`. +- `benchmark/binary_size/cprintf`: only `lib/c.Printf` (default and `-lto=full`); +- `benchmark/binary_size/println`: only the built-in `println` (default and `-lto=full`); +- `benchmark/binary_size/fmtprintf`: `fmt.Printf` (default and `-lto=full`). For each workload, the collector performs an unmeasured warm build, then records median build time, median process time, file size, executable-code bytes, diff --git a/benchmark/baseline/main.go b/benchmark/baseline/main.go index f9e4cbe19b..805f0314f1 100644 --- a/benchmark/baseline/main.go +++ b/benchmark/baseline/main.go @@ -51,12 +51,16 @@ type workload struct { name string source string output string + flags []string } var workloads = []workload{ {name: "cprintf", source: "benchmark/binary_size/cprintf/main.go", output: "Hello, world\n"}, + {name: "cprintf_lto", source: "benchmark/binary_size/cprintf/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, {name: "println", source: "benchmark/binary_size/println/main.go", output: "Hello, world\n"}, + {name: "println_lto", source: "benchmark/binary_size/println/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, {name: "fmtprintf", source: "benchmark/binary_size/fmtprintf/main.go", output: "Hello, world\n"}, + {name: "fmtprintf_lto", source: "benchmark/binary_size/fmtprintf/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, } var expectedGoBenchmarks = []string{ @@ -223,15 +227,17 @@ func collect(ctx context.Context, root, llgo, out string, buildRuns, runRuns int var sizes, timings []metric for _, item := range workloads { binary := filepath.Join(binDir, item.name) + buildArgs := append([]string{"build"}, item.flags...) + buildArgs = append(buildArgs, "-o", binary, filepath.Join(root, item.source)) // Keep first-use toolchain and filesystem caches out of the measured // median so the first revision is not systematically disadvantaged. - if err := run(ctx, env, io.Discard, llgo, "build", "-o", binary, filepath.Join(root, item.source)); err != nil { + if err := run(ctx, env, io.Discard, llgo, buildArgs...); err != nil { return fmt.Errorf("warm build %s: %w", item.name, err) } buildDurations := make([]time.Duration, 0, buildRuns) for range buildRuns { start := time.Now() - if err := run(ctx, env, io.Discard, llgo, "build", "-o", binary, filepath.Join(root, item.source)); err != nil { + if err := run(ctx, env, io.Discard, llgo, buildArgs...); err != nil { return fmt.Errorf("build %s: %w", item.name, err) } buildDurations = append(buildDurations, time.Since(start)) diff --git a/benchmark/baseline/main_test.go b/benchmark/baseline/main_test.go index 2fe56a56e5..88d06d9f13 100644 --- a/benchmark/baseline/main_test.go +++ b/benchmark/baseline/main_test.go @@ -97,6 +97,7 @@ func TestExportBenchmarks(t *testing.T) { "Unit file-bytes better=lower assume=exact", "Unit build-ns better=lower", "BenchmarkProgram/cprintf 1 1 file-bytes 1 text-bytes 1 data-bytes 1 bss-bytes 1 build-ns 1 run-ns", + "BenchmarkProgram/cprintf_lto 1 1 file-bytes 1 text-bytes 1 data-bytes 1 bss-bytes 1 build-ns 1 run-ns", "BenchmarkRuntimeGetG-1 100 12.5 ns/op", } { if !strings.Contains(text, want) { From 8b5d5cbf6f08222f86cbac2bc0ad8855e96b450f Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 20 Aug 2026 16:54:16 +0800 Subject: [PATCH 3/4] cl: support static initializer folding for struct and composite slices --- cl/rewrite_internal_test.go | 35 ++++++++++++++ cl/static_init.go | 95 ++++++++++++++++++------------------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 8277369b3d..9f7d79bb4f 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -434,6 +434,41 @@ func Use() string { return CallbackTypes[1] } } } +func TestStaticGlobalStructSliceLiteralInit(t *testing.T) { + const src = `package staticinit + +type Info struct { + Name string + Package string + Changed int +} + +var All = []Info{ + {"godebug1", "runtime", 1}, + {"godebug2", "internal/poll", 2}, +} + +func Use() Info { return All[0] } +` + ir := compileWithRewrites(t, src, nil) + for _, want := range []string{ + `@"staticinit.All$data" = global [2 x %staticinit.Info]`, + `@staticinit.All = global %"github.com/xgo-dev/llgo/runtime/internal/runtime.Slice" { ptr @"staticinit.All$data", i64 2, i64 2 }`, + `c"godebug1"`, + `c"runtime"`, + `c"godebug2"`, + `c"internal/poll"`, + } { + if !strings.Contains(ir, want) { + t.Fatalf("missing static struct slice initializer %q in IR:\n%s", want, ir) + } + } + assertNoStoreToGlobal(t, ir, "@staticinit.All") + if strings.Contains(ir, "runtime.AllocZ") { + t.Fatalf("static struct slice initializer still allocates at runtime:\n%s", ir) + } +} + func TestStaticSliceInitRejectsExecutableReferrers(t *testing.T) { const src = `package foo diff --git a/cl/static_init.go b/cl/static_init.go index 7ac5197a18..51f94f07a9 100644 --- a/cl/static_init.go +++ b/cl/static_init.go @@ -53,7 +53,7 @@ type staticSliceInit struct { slice *ssa.Slice alloc *ssa.Alloc array *types.Array - values map[int]*ssa.Const + stores []staticInitStore instrs []ssa.Instruction } @@ -223,6 +223,18 @@ func collectAllocStores(alloc *ssa.Alloc, basePath []staticInitPathElem, out *[] } for _, ref := range refs { switch ref := ref.(type) { + case *ssa.Slice: + if ref.Low != nil || ref.High != nil || ref.Max != nil { + return false + } + sliceRefs, ok := nonDebugReferrers(ref) + if !ok || len(sliceRefs) != 1 { + return false + } + elemStore, ok := sliceRefs[0].(*ssa.Store) + if !ok || elemStore.Val != ref { + return false + } case *ssa.UnOp: if ref.Op != token.MUL { return false @@ -361,57 +373,21 @@ func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) { return nil, false } - ret := &staticSliceInit{ - store: store, slice: slice, alloc: alloc, array: array, - values: make(map[int]*ssa.Const), - instrs: []ssa.Instruction{alloc, slice, store}, - } sliceRefs, ok := nonDebugReferrers(slice) if !ok || len(sliceRefs) != 1 || sliceRefs[0] != store { return nil, false } - refs, ok := nonDebugReferrers(alloc) - if !ok { - return nil, false + + ret := &staticSliceInit{ + store: store, slice: slice, alloc: alloc, array: array, + instrs: []ssa.Instruction{slice, store}, } - seenSlice := false - for _, ref := range refs { - switch ref := ref.(type) { - case *ssa.Slice: - if ref != slice || seenSlice { - return nil, false - } - seenSlice = true - case *ssa.IndexAddr: - if ref.X != alloc { - return nil, false - } - index, ok := staticInitConstIndex(ref.Index) - if !ok || index >= int(array.Len()) { - return nil, false - } - indexRefs, ok := nonDebugReferrers(ref) - if !ok || len(indexRefs) != 1 { - return nil, false - } - elemStore, ok := indexRefs[0].(*ssa.Store) - if !ok || elemStore.Addr != ref { - return nil, false - } - value, ok := elemStore.Val.(*ssa.Const) - if !ok { - return nil, false - } - if _, exists := ret.values[index]; exists { - return nil, false - } - ret.values[index] = value - ret.instrs = append(ret.instrs, ref, elemStore) - default: - return nil, false - } + + visited := make(map[*ssa.Alloc]bool) + if !collectAllocStores(alloc, nil, &ret.stores, &ret.instrs, visited) { + return nil, false } - return ret, seenSlice + return ret, true } func staticInitZeroSized(typ types.Type) bool { @@ -432,14 +408,33 @@ func staticInitZeroSized(typ types.Type) bool { func (p *context) buildStaticSliceInit(global *ssa.Global, init *staticSliceInit) (llssa.Expr, bool) { n := int(init.array.Len()) + elemType := init.array.Elem() + elemStores := make(map[int][]staticInitStore, n) + for _, s := range init.stores { + if len(s.path) == 0 { + return llssa.Expr{}, false + } + idx := s.path[0].index + if idx < 0 || idx >= n { + return llssa.Expr{}, false + } + elemStores[idx] = append(elemStores[idx], staticInitStore{ + store: s.store, + path: s.path[1:], + value: s.value, + }) + } values := make([]llssa.Expr, n) for i := range values { - var node *staticInitNode - if value := init.values[i]; value != nil { - node = &staticInitNode{value: value} + stores := elemStores[i] + root := new(staticInitNode) + for _, store := range stores { + if !root.add(store.path, store.value) { + return llssa.Expr{}, false + } } var ok bool - values[i], ok = p.buildStaticInitExpr(init.array.Elem(), node) + values[i], ok = p.buildStaticInitExpr(elemType, root) if !ok { return llssa.Expr{}, false } From d565df753ba992fa9e22156652ab1605015fc939 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 20 Aug 2026 16:54:19 +0800 Subject: [PATCH 4/4] cl: support recursive address projections in collectAllocStores --- cl/static_init.go | 58 +++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/cl/static_init.go b/cl/static_init.go index 51f94f07a9..8cdcbde362 100644 --- a/cl/static_init.go +++ b/cl/static_init.go @@ -245,44 +245,58 @@ func collectAllocStores(alloc *ssa.Alloc, basePath []staticInitPathElem, out *[] } *instrs = append(*instrs, ref) case *ssa.FieldAddr: - subPath, ok := staticInitStorePathToAlloc(ref, alloc) - if !ok { - return false - } - fieldRefs, ok := nonDebugReferrers(ref) - if !ok || len(fieldRefs) != 1 { + if !collectAddrStores(ref, alloc, basePath, out, instrs, visited) { return false } - elemStore, ok := fieldRefs[0].(*ssa.Store) - if !ok || elemStore.Addr != ref { + *instrs = append(*instrs, ref) + case *ssa.IndexAddr: + if !collectAddrStores(ref, alloc, basePath, out, instrs, visited) { return false } - if !handleStoreVal(elemStore, appendStaticInitPath(basePath, subPath), out, instrs, visited) { + *instrs = append(*instrs, ref) + case *ssa.Store: + if ref.Addr != alloc { return false } - *instrs = append(*instrs, ref, elemStore) - case *ssa.IndexAddr: - subPath, ok := staticInitStorePathToAlloc(ref, alloc) - if !ok { + if !handleStoreVal(ref, appendStaticInitPath(basePath, nil), out, instrs, visited) { return false } - indexRefs, ok := nonDebugReferrers(ref) - if !ok || len(indexRefs) != 1 { + *instrs = append(*instrs, ref) + default: + return false + } + } + return true +} + +// collectAddrStores recursively visits field/index address projections derived from rootAlloc, +// recording constant stores and intermediate instructions. +func collectAddrStores(addr ssa.Value, rootAlloc *ssa.Alloc, basePath []staticInitPathElem, out *[]staticInitStore, instrs *[]ssa.Instruction, visited map[*ssa.Alloc]bool) bool { + refs, ok := nonDebugReferrers(addr) + if !ok { + return false + } + for _, ref := range refs { + switch ref := ref.(type) { + case *ssa.FieldAddr: + if !collectAddrStores(ref, rootAlloc, basePath, out, instrs, visited) { return false } - elemStore, ok := indexRefs[0].(*ssa.Store) - if !ok || elemStore.Addr != ref { + *instrs = append(*instrs, ref) + case *ssa.IndexAddr: + if !collectAddrStores(ref, rootAlloc, basePath, out, instrs, visited) { return false } - if !handleStoreVal(elemStore, appendStaticInitPath(basePath, subPath), out, instrs, visited) { + *instrs = append(*instrs, ref) + case *ssa.Store: + if ref.Addr != addr { return false } - *instrs = append(*instrs, ref, elemStore) - case *ssa.Store: - if ref.Addr != alloc { + subPath, ok := staticInitStorePathToAlloc(addr, rootAlloc) + if !ok { return false } - if !handleStoreVal(ref, appendStaticInitPath(basePath, nil), out, instrs, visited) { + if !handleStoreVal(ref, appendStaticInitPath(basePath, subPath), out, instrs, visited) { return false } *instrs = append(*instrs, ref)