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) { diff --git a/cl/_testgo/equal/in.go b/cl/_testgo/equal/in.go index 7464bc4198..b165d36aa7 100644 --- a/cl/_testgo/equal/in.go +++ b/cl/_testgo/equal/in.go @@ -26,10 +26,8 @@ package main // Arrays: all three elements participate in equality, and inequality negates // the aggregate result rather than changing element semantics. // CHECK-LABEL: define void @"main.init#2"(){{.*}} { -// CHECK: %[[ARRAY_L:[0-9]+]] = load [3 x i64], ptr %{{[0-9]+}} -// CHECK: %[[ARRAY_R:[0-9]+]] = load [3 x i64], ptr %{{[0-9]+}} -// CHECK: extractvalue [3 x i64] %[[ARRAY_L]], 0 -// CHECK: extractvalue [3 x i64] %[[ARRAY_R]], 0 +// CHECK: extractvalue [3 x i64] %[[ARRAY_L:[0-9]+]], 0 +// CHECK: extractvalue [3 x i64] %[[ARRAY_R:[0-9]+]], 0 // CHECK: extractvalue [3 x i64] %[[ARRAY_L]], 1 // CHECK: extractvalue [3 x i64] %[[ARRAY_R]], 1 // CHECK: extractvalue [3 x i64] %[[ARRAY_L]], 2 diff --git a/cl/_testgo/tptypes/in.go b/cl/_testgo/tptypes/in.go index aa489e62e4..8be7423517 100644 --- a/cl/_testgo/tptypes/in.go +++ b/cl/_testgo/tptypes/in.go @@ -108,6 +108,7 @@ func main() { // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintInt"(i64 0) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: %[[TMP16:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// CHECK-NEXT: store %"main.Slice{{\[\[}}]int,int]" zeroinitializer, ptr %[[TMP16]], align 8 // CHECK-NEXT: %[[TMP17:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 8) // CHECK-NEXT: %[[TMP18:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP17]], i64 0 // CHECK-NEXT: store i64 100, ptr %[[TMP18]], align 8 @@ -116,6 +117,7 @@ func main() { // CHECK-NEXT: %[[TMP21:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %[[TMP20]], i64 1, 2 // CHECK-NEXT: %[[TMP22:[0-9]+]] = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[}}]int,int]).Append"(ptr %[[TMP16]], %"{{.*}}/runtime/internal/runtime.Slice" %[[TMP21]]) // CHECK-NEXT: %[[TMP23:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// CHECK-NEXT: store %"main.Slice{{\[\[}}]string,string]" zeroinitializer, ptr %[[TMP23]], align 8 // CHECK-NEXT: %[[TMP24:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 16) // CHECK-NEXT: %[[TMP25:[0-9]+]] = getelementptr inbounds %"{{.*}}/runtime/internal/runtime.String", ptr %[[TMP24]], i64 0 // CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @[[GLOB0]], i64 5 }, ptr %[[TMP25]], align 8 @@ -124,6 +126,7 @@ func main() { // CHECK-NEXT: %[[TMP28:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.Slice" %[[TMP27]], i64 1, 2 // CHECK-NEXT: %[[TMP29:[0-9]+]] = call %"{{.*}}/runtime/internal/runtime.Slice" @"main.(*Slice{{\[\[}}]string,string]).Append"(ptr %[[TMP23]], %"{{.*}}/runtime/internal/runtime.Slice" %[[TMP28]]) // CHECK-NEXT: %[[TMP30:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 24) +// CHECK-NEXT: store %"main.Slice{{\[\[}}]int,int]" zeroinitializer, ptr %[[TMP30]], align 8 // CHECK-NEXT: %[[TMP31:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) // CHECK-NEXT: %[[TMP32:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP31]], i64 0 // CHECK-NEXT: store i64 1, ptr %[[TMP32]], align 8 diff --git a/cl/_testrt/gblarray/in.go b/cl/_testrt/gblarray/in.go index 42b2f044db..481f55de0b 100644 --- a/cl/_testrt/gblarray/in.go +++ b/cl/_testrt/gblarray/in.go @@ -84,8 +84,13 @@ func main() { // CHECK-NEXT: _llgo_[[BB1]]: // CHECK-NEXT: store i1 true, ptr @"main.init$guard", align 1 // CHECK-NEXT: call void @"{{.*}}/runtime/abi.init"() -// CHECK-NEXT: %[[TMP1:[0-9]+]] = call ptr @main.basicType(i64 24) -// CHECK-NEXT: store ptr %[[TMP1]], ptr getelementptr inbounds (ptr, ptr @main.basicTypes, i64 24), align 8 +// CHECK-NEXT: %[[TMP1:[0-9]+]] = alloca [25 x ptr], align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP1]], i8 0, i64 200, i1 false) +// CHECK-NEXT: %[[TMP2:[0-9]+]] = getelementptr inbounds ptr, ptr %[[TMP1]], i64 24 +// CHECK-NEXT: %[[TMP3:[0-9]+]] = call ptr @main.basicType(i64 24) +// CHECK-NEXT: store ptr %[[TMP3]], ptr %[[TMP2]], align 8 +// CHECK-NEXT: %[[TMP4:[0-9]+]] = load [25 x ptr], ptr %[[TMP1]], align 8 +// CHECK-NEXT: store [25 x ptr] %[[TMP4]], ptr @main.basicTypes, align 8 // CHECK-NEXT: br label %_llgo_[[BB2]] // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB2]]: diff --git a/cl/_testrt/index/in.go b/cl/_testrt/index/in.go index 481184aec0..308fce61a6 100644 --- a/cl/_testrt/index/in.go +++ b/cl/_testrt/index/in.go @@ -15,11 +15,7 @@ type S []int // that selected value are consumed. // CHECK: %[[POINT:[0-9]+]] = alloca %main.point // CHECK: %[[POINTS:[0-9]+]] = alloca [3 x %main.point] -// CHECK: %[[POINT2_INIT:[0-9]+]] = getelementptr inbounds %main.point, ptr %[[POINTS]], i64 2 -// CHECK: %[[POINT2_X:[0-9]+]] = getelementptr inbounds %main.point, ptr %[[POINT2_INIT]], i32 0, i32 0 -// CHECK: %[[POINT2_Y:[0-9]+]] = getelementptr inbounds %main.point, ptr %[[POINT2_INIT]], i32 0, i32 1 -// CHECK: store i64 5, ptr %[[POINT2_X]] -// CHECK: store i64 6, ptr %[[POINT2_Y]] +// CHECK: load [3 x %main.point], ptr %[[POINTS]] // CHECK: %[[POINT2:[0-9]+]] = getelementptr inbounds %main.point, ptr %[[POINTS]], i64 2 // CHECK: %[[SELECTED_POINT:[0-9]+]] = load %main.point, ptr %[[POINT2]] // CHECK: store %main.point %[[SELECTED_POINT]], ptr %[[POINT]] @@ -31,13 +27,7 @@ type S []int // Nested arrays select row 1 before indexing its two elements. // CHECK: %[[ROW:[0-9]+]] = alloca [2 x i64] // CHECK: %[[MATRIX:[0-9]+]] = alloca [2 x [2 x i64]] -// CHECK: %[[ROW1_INIT:[0-9]+]] = getelementptr inbounds [2 x i64], ptr %[[MATRIX]], i64 1 -// CHECK: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref" -// CHECK: %[[ROW1_ELEM0:[0-9]+]] = getelementptr inbounds i64, ptr %[[ROW1_INIT]], i64 0 -// CHECK: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref" -// CHECK: %[[ROW1_ELEM1:[0-9]+]] = getelementptr inbounds i64, ptr %[[ROW1_INIT]], i64 1 -// CHECK: store i64 3, ptr %[[ROW1_ELEM0]] -// CHECK: store i64 4, ptr %[[ROW1_ELEM1]] +// CHECK: load [2 x [2 x i64]], ptr %[[MATRIX]] // CHECK: %[[ROW1:[0-9]+]] = getelementptr inbounds [2 x i64], ptr %[[MATRIX]], i64 1 // CHECK: %[[SELECTED_ROW:[0-9]+]] = load [2 x i64], ptr %[[ROW1]] // CHECK: store [2 x i64] %[[SELECTED_ROW]], ptr %[[ROW]] @@ -64,8 +54,7 @@ type S []int // Named pointer-to-array indexing and named-slice indexing use different // lowering. The slice predicate, length and data pointer must stay associated. // CHECK: %[[NAMED_ARRAY:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 16) -// CHECK: %[[NAMED_ELEM_INIT:[0-9]+]] = getelementptr inbounds i64, ptr %[[NAMED_ARRAY]], i64 1 -// CHECK: store i64 2, ptr %[[NAMED_ELEM_INIT]] +// CHECK: store [2 x i64] %{{[0-9]+}}, ptr %[[NAMED_ARRAY]] // CHECK: %[[NAMED_ELEM:[0-9]+]] = getelementptr inbounds i64, ptr %[[NAMED_ARRAY]], i64 1 // CHECK: load i64, ptr %[[NAMED_ELEM]] // CHECK: %[[SLICE_DATA_RAW:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) diff --git a/cl/_testrt/qsort/in.go b/cl/_testrt/qsort/in.go index 27bc53faf2..fdfe9b48e0 100644 --- a/cl/_testrt/qsort/in.go +++ b/cl/_testrt/qsort/in.go @@ -26,44 +26,48 @@ func main() { // CHECK-LABEL: define void @main.main(){{.*}} { // CHECK-NEXT: _llgo_[[BB0:[0-9]+]]: // CHECK-NEXT: %[[TMP0:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 40) -// CHECK-NEXT: %[[TMP1:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 0 -// CHECK-NEXT: %[[TMP2:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 1 -// CHECK-NEXT: %[[TMP3:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 2 -// CHECK-NEXT: %[[TMP4:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 3 -// CHECK-NEXT: %[[TMP5:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 4 -// CHECK-NEXT: store i64 100, ptr %[[TMP1]], align 8 -// CHECK-NEXT: store i64 8, ptr %[[TMP2]], align 8 -// CHECK-NEXT: store i64 23, ptr %[[TMP3]], align 8 -// CHECK-NEXT: store i64 2, ptr %[[TMP4]], align 8 -// CHECK-NEXT: store i64 7, ptr %[[TMP5]], align 8 -// CHECK-NEXT: %[[TMP6:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 0 -// CHECK-NEXT: call void @qsort(ptr %[[TMP6]], i64 5, i64 8, ptr @"main.main$1") -// CHECK-NEXT: %[[TMP7:[0-9]+]] = load [5 x i64], ptr %[[TMP0]], align 8 +// CHECK-NEXT: %[[TMP1:[0-9]+]] = alloca [5 x i64], align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP1]], i8 0, i64 40, i1 false) +// CHECK-NEXT: %[[TMP2:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP1]], i64 0 +// CHECK-NEXT: %[[TMP3:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP1]], i64 1 +// CHECK-NEXT: %[[TMP4:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP1]], i64 2 +// CHECK-NEXT: %[[TMP5:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP1]], i64 3 +// CHECK-NEXT: %[[TMP6:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP1]], i64 4 +// CHECK-NEXT: store i64 100, ptr %[[TMP2]], align 8 +// CHECK-NEXT: store i64 8, ptr %[[TMP3]], align 8 +// CHECK-NEXT: store i64 23, ptr %[[TMP4]], align 8 +// CHECK-NEXT: store i64 2, ptr %[[TMP5]], align 8 +// CHECK-NEXT: store i64 7, ptr %[[TMP6]], align 8 +// CHECK-NEXT: %[[TMP7:[0-9]+]] = load [5 x i64], ptr %[[TMP1]], align 8 +// CHECK-NEXT: store [5 x i64] %[[TMP7]], ptr %[[TMP0]], align 8 +// CHECK-NEXT: %[[TMP8:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 0 +// CHECK-NEXT: call void @qsort(ptr %[[TMP8]], i64 5, i64 8, ptr @"main.main$1") +// CHECK-NEXT: %[[TMP9:[0-9]+]] = load [5 x i64], ptr %[[TMP0]], align 8 // CHECK-NEXT: br label %_llgo_[[BB1:[0-9]+]] // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB1]]: -// CHECK-NEXT: %[[TMP8:[0-9]+]] = phi i64 [ -1, %_llgo_[[BB0]] ], [ %[[TMP9:[0-9]+]], %_llgo_[[BB5:[0-9]+]] ] -// CHECK-NEXT: %[[TMP9]] = add i64 %[[TMP8]], 1 -// CHECK-NEXT: %[[TMP10:[0-9]+]] = icmp slt i64 %[[TMP9]], 5 -// CHECK-NEXT: br i1 %[[TMP10]], label %_llgo_[[BB2:[0-9]+]], label %_llgo_[[BB3:[0-9]+]] +// CHECK-NEXT: %[[TMP10:[0-9]+]] = phi i64 [ -1, %_llgo_[[BB0]] ], [ %[[TMP11:[0-9]+]], %_llgo_[[BB5:[0-9]+]] ] +// CHECK-NEXT: %[[TMP11]] = add i64 %[[TMP10]], 1 +// CHECK-NEXT: %[[TMP12:[0-9]+]] = icmp slt i64 %[[TMP11]], 5 +// CHECK-NEXT: br i1 %[[TMP12]], label %_llgo_[[BB2:[0-9]+]], label %_llgo_[[BB3:[0-9]+]] // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB2]]: -// CHECK-NEXT: %[[TMP11:[0-9]+]] = icmp slt i64 %[[TMP9]], 0 -// CHECK-NEXT: %[[TMP12:[0-9]+]] = icmp uge i64 %[[TMP9]], 5 -// CHECK-NEXT: %[[TMP13:[0-9]+]] = or i1 %[[TMP12]], %[[TMP11]] -// CHECK-NEXT: br i1 %[[TMP13]], label %_llgo_[[BB4:[0-9]+]], label %_llgo_[[BB5]] +// CHECK-NEXT: %[[TMP13:[0-9]+]] = icmp slt i64 %[[TMP11]], 0 +// CHECK-NEXT: %[[TMP14:[0-9]+]] = icmp uge i64 %[[TMP11]], 5 +// CHECK-NEXT: %[[TMP15:[0-9]+]] = or i1 %[[TMP14]], %[[TMP13]] +// CHECK-NEXT: br i1 %[[TMP15]], label %_llgo_[[BB4:[0-9]+]], label %_llgo_[[BB5]] // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB3]]: // CHECK-NEXT: ret void // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB4]]: -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicIndex"(i64 %[[TMP9]], i64 5) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PanicIndex"(i64 %[[TMP11]], i64 5) // CHECK-NEXT: br label %_llgo_[[BB4]] // CHECK-EMPTY: // CHECK-NEXT: _llgo_[[BB5]]: -// CHECK-NEXT: %[[TMP14:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 %[[TMP9]] -// CHECK-NEXT: %[[TMP15:[0-9]+]] = load i64, ptr %[[TMP14]], align 8 -// CHECK-NEXT: %[[TMP16:[0-9]+]] = call i32 (ptr, ...) @printf(ptr @[[GLOB0]], i64 %[[TMP15]]) +// CHECK-NEXT: %[[TMP16:[0-9]+]] = getelementptr inbounds i64, ptr %[[TMP0]], i64 %[[TMP11]] +// CHECK-NEXT: %[[TMP17:[0-9]+]] = load i64, ptr %[[TMP16]], align 8 +// CHECK-NEXT: %[[TMP18:[0-9]+]] = call i32 (ptr, ...) @printf(ptr @[[GLOB0]], i64 %[[TMP17]]) // CHECK-NEXT: br label %_llgo_[[BB1]] // CHECK-NEXT: } diff --git a/cl/_testrt/tpmap/in.go b/cl/_testrt/tpmap/in.go index f0064e393f..9bae857cee 100644 --- a/cl/_testrt/tpmap/in.go +++ b/cl/_testrt/tpmap/in.go @@ -36,55 +36,71 @@ func main() { // CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP1]], i8 0, i64 48, i1 false) // CHECK-NEXT: %[[TMP2:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 0 // CHECK-NEXT: %[[TMP3:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 1 -// CHECK-NEXT: %[[TMP4:[0-9]+]] = getelementptr inbounds %main.T2, ptr %[[TMP3]], i32 0, i32 0 -// CHECK-NEXT: %[[TMP5:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 2 -// CHECK-NEXT: %[[TMP6:[0-9]+]] = getelementptr inbounds %"main.T3[any]", ptr %[[TMP5]], i32 0, i32 0 -// CHECK-NEXT: %[[TMP7:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 3 -// CHECK-NEXT: %[[TMP8:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 4 +// CHECK-NEXT: %[[TMP4:[0-9]+]] = alloca %main.T2, align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP4]], i8 0, i64 8, i1 false) +// CHECK-NEXT: %[[TMP5:[0-9]+]] = getelementptr inbounds %main.T2, ptr %[[TMP4]], i32 0, i32 0 +// CHECK-NEXT: store i64 0, ptr %[[TMP5]], align 8 +// CHECK-NEXT: %[[TMP6:[0-9]+]] = load %main.T2, ptr %[[TMP4]], align 8 +// CHECK-NEXT: %[[TMP7:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 2 +// CHECK-NEXT: %[[TMP8:[0-9]+]] = alloca %"main.T3[any]", align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP8]], i8 0, i64 16, i1 false) +// CHECK-NEXT: %[[TMP9:[0-9]+]] = getelementptr inbounds %"main.T3[any]", ptr %[[TMP8]], i32 0, i32 0 +// CHECK-NEXT: %[[TMP10:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) +// CHECK-NEXT: store i64 0, ptr %[[TMP10]], align 8 +// CHECK-NEXT: %[[TMP11:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %[[TMP10]], 1 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %[[TMP11]], ptr %[[TMP9]], align 8 +// CHECK-NEXT: %[[TMP12:[0-9]+]] = load %"main.T3[any]", ptr %[[TMP8]], align 8 +// CHECK-NEXT: %[[TMP13:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 3 +// CHECK-NEXT: %[[TMP14:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP1]], i32 0, i32 4 // CHECK-NEXT: store i64 0, ptr %[[TMP2]], align 8 -// CHECK-NEXT: store i64 0, ptr %[[TMP4]], align 8 -// CHECK-NEXT: %[[TMP9:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) -// CHECK-NEXT: store i64 0, ptr %[[TMP9]], align 8 -// CHECK-NEXT: %[[TMP10:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %[[TMP9]], 1 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %[[TMP10]], ptr %[[TMP6]], align 8 -// CHECK-NEXT: store ptr null, ptr %[[TMP7]], align 8 -// CHECK-NEXT: store i64 0, ptr %[[TMP8]], align 8 -// CHECK-NEXT: %[[TMP11:[0-9]+]] = load %main.cacheKey, ptr %[[TMP1]], align 8 -// CHECK-NEXT: %[[TMP12:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 48) -// CHECK-NEXT: store %main.cacheKey %[[TMP11]], ptr %[[TMP12]], align 8 -// CHECK-NEXT: %[[TMP13:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_main.cacheKey]_llgo_string", ptr %[[TMP0]], ptr %[[TMP12]]) -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @[[GLOB29]], i64 5 }, ptr %[[TMP13]], align 8 -// CHECK-NEXT: %[[TMP14:[0-9]+]] = alloca %main.cacheKey, align 8 -// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP14]], i8 0, i64 48, i1 false) -// CHECK-NEXT: %[[TMP15:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP14]], i32 0, i32 0 -// CHECK-NEXT: %[[TMP16:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP14]], i32 0, i32 1 -// CHECK-NEXT: %[[TMP17:[0-9]+]] = getelementptr inbounds %main.T2, ptr %[[TMP16]], i32 0, i32 0 -// CHECK-NEXT: %[[TMP18:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP14]], i32 0, i32 2 -// CHECK-NEXT: %[[TMP19:[0-9]+]] = getelementptr inbounds %"main.T3[any]", ptr %[[TMP18]], i32 0, i32 0 -// CHECK-NEXT: %[[TMP20:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP14]], i32 0, i32 3 -// CHECK-NEXT: %[[TMP21:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP14]], i32 0, i32 4 -// CHECK-NEXT: store i64 0, ptr %[[TMP15]], align 8 -// CHECK-NEXT: store i64 0, ptr %[[TMP17]], align 8 -// CHECK-NEXT: %[[TMP22:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) +// CHECK-NEXT: store %main.T2 %[[TMP6]], ptr %[[TMP3]], align 8 +// CHECK-NEXT: store %"main.T3[any]" %[[TMP12]], ptr %[[TMP7]], align 8 +// CHECK-NEXT: store ptr null, ptr %[[TMP13]], align 8 +// CHECK-NEXT: store i64 0, ptr %[[TMP14]], align 8 +// CHECK-NEXT: %[[TMP15:[0-9]+]] = load %main.cacheKey, ptr %[[TMP1]], align 8 +// CHECK-NEXT: %[[TMP16:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 48) +// CHECK-NEXT: store %main.cacheKey %[[TMP15]], ptr %[[TMP16]], align 8 +// CHECK-NEXT: %[[TMP17:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.MapAssign"(ptr @"map[_llgo_main.cacheKey]_llgo_string", ptr %[[TMP0]], ptr %[[TMP16]]) +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.String" { ptr @[[GLOB29]], i64 5 }, ptr %[[TMP17]], align 8 +// CHECK-NEXT: %[[TMP18:[0-9]+]] = alloca %main.cacheKey, align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP18]], i8 0, i64 48, i1 false) +// CHECK-NEXT: %[[TMP19:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP18]], i32 0, i32 0 +// CHECK-NEXT: %[[TMP20:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP18]], i32 0, i32 1 +// CHECK-NEXT: %[[TMP21:[0-9]+]] = alloca %main.T2, align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP21]], i8 0, i64 8, i1 false) +// CHECK-NEXT: %[[TMP22:[0-9]+]] = getelementptr inbounds %main.T2, ptr %[[TMP21]], i32 0, i32 0 // CHECK-NEXT: store i64 0, ptr %[[TMP22]], align 8 -// CHECK-NEXT: %[[TMP23:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %[[TMP22]], 1 -// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %[[TMP23]], ptr %[[TMP19]], align 8 -// CHECK-NEXT: store ptr null, ptr %[[TMP20]], align 8 -// CHECK-NEXT: store i64 0, ptr %[[TMP21]], align 8 -// CHECK-NEXT: %[[TMP24:[0-9]+]] = load %main.cacheKey, ptr %[[TMP14]], align 8 -// CHECK-NEXT: %[[TMP25:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 48) -// CHECK-NEXT: store %main.cacheKey %[[TMP24]], ptr %[[TMP25]], align 8 -// CHECK-NEXT: %[[TMP26:[0-9]+]] = call { ptr, i1 } @"{{.*}}/runtime/internal/runtime.MapAccess2"(ptr @"map[_llgo_main.cacheKey]_llgo_string", ptr %[[TMP0]], ptr %[[TMP25]]) -// CHECK-NEXT: %[[TMP27:[0-9]+]] = extractvalue { ptr, i1 } %[[TMP26]], 0 -// CHECK-NEXT: %[[TMP28:[0-9]+]] = load %"{{.*}}/runtime/internal/runtime.String", ptr %[[TMP27]], align 8 -// CHECK-NEXT: %[[TMP29:[0-9]+]] = extractvalue { ptr, i1 } %[[TMP26]], 1 -// CHECK-NEXT: %[[TMP30:[0-9]+]] = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } undef, %"{{.*}}/runtime/internal/runtime.String" %[[TMP28]], 0 -// CHECK-NEXT: %[[TMP31:[0-9]+]] = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP30]], i1 %[[TMP29]], 1 -// CHECK-NEXT: %[[TMP32:[0-9]+]] = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP31]], 0 -// CHECK-NEXT: %[[TMP33:[0-9]+]] = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP31]], 1 -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %[[TMP32]]) +// CHECK-NEXT: %[[TMP23:[0-9]+]] = load %main.T2, ptr %[[TMP21]], align 8 +// CHECK-NEXT: %[[TMP24:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP18]], i32 0, i32 2 +// CHECK-NEXT: %[[TMP25:[0-9]+]] = alloca %"main.T3[any]", align 8 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr %[[TMP25]], i8 0, i64 16, i1 false) +// CHECK-NEXT: %[[TMP26:[0-9]+]] = getelementptr inbounds %"main.T3[any]", ptr %[[TMP25]], i32 0, i32 0 +// CHECK-NEXT: %[[TMP27:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 8) +// CHECK-NEXT: store i64 0, ptr %[[TMP27]], align 8 +// CHECK-NEXT: %[[TMP28:[0-9]+]] = insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr @_llgo_int, ptr undef }, ptr %[[TMP27]], 1 +// CHECK-NEXT: store %"{{.*}}/runtime/internal/runtime.eface" %[[TMP28]], ptr %[[TMP26]], align 8 +// CHECK-NEXT: %[[TMP29:[0-9]+]] = load %"main.T3[any]", ptr %[[TMP25]], align 8 +// CHECK-NEXT: %[[TMP30:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP18]], i32 0, i32 3 +// CHECK-NEXT: %[[TMP31:[0-9]+]] = getelementptr inbounds %main.cacheKey, ptr %[[TMP18]], i32 0, i32 4 +// CHECK-NEXT: store i64 0, ptr %[[TMP19]], align 8 +// CHECK-NEXT: store %main.T2 %[[TMP23]], ptr %[[TMP20]], align 8 +// CHECK-NEXT: store %"main.T3[any]" %[[TMP29]], ptr %[[TMP24]], align 8 +// CHECK-NEXT: store ptr null, ptr %[[TMP30]], align 8 +// CHECK-NEXT: store i64 0, ptr %[[TMP31]], align 8 +// CHECK-NEXT: %[[TMP32:[0-9]+]] = load %main.cacheKey, ptr %[[TMP18]], align 8 +// CHECK-NEXT: %[[TMP33:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 48) +// CHECK-NEXT: store %main.cacheKey %[[TMP32]], ptr %[[TMP33]], align 8 +// CHECK-NEXT: %[[TMP34:[0-9]+]] = call { ptr, i1 } @"{{.*}}/runtime/internal/runtime.MapAccess2"(ptr @"map[_llgo_main.cacheKey]_llgo_string", ptr %[[TMP0]], ptr %[[TMP33]]) +// CHECK-NEXT: %[[TMP35:[0-9]+]] = extractvalue { ptr, i1 } %[[TMP34]], 0 +// CHECK-NEXT: %[[TMP36:[0-9]+]] = load %"{{.*}}/runtime/internal/runtime.String", ptr %[[TMP35]], align 8 +// CHECK-NEXT: %[[TMP37:[0-9]+]] = extractvalue { ptr, i1 } %[[TMP34]], 1 +// CHECK-NEXT: %[[TMP38:[0-9]+]] = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } undef, %"{{.*}}/runtime/internal/runtime.String" %[[TMP36]], 0 +// CHECK-NEXT: %[[TMP39:[0-9]+]] = insertvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP38]], i1 %[[TMP37]], 1 +// CHECK-NEXT: %[[TMP40:[0-9]+]] = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP39]], 0 +// CHECK-NEXT: %[[TMP41:[0-9]+]] = extractvalue { %"{{.*}}/runtime/internal/runtime.String", i1 } %[[TMP39]], 1 +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" %[[TMP40]]) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 32) -// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %[[TMP33]]) +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintBool"(i1 %[[TMP41]]) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: ret void // CHECK-NEXT: } diff --git a/cl/_testrt/tpunsafe/in.go b/cl/_testrt/tpunsafe/in.go index 5eff46a1b4..6f8ede2622 100644 --- a/cl/_testrt/tpunsafe/in.go +++ b/cl/_testrt/tpunsafe/in.go @@ -49,8 +49,10 @@ func (m *M[T]) check(align, offset1, offset2 uintptr) { // CHECK-LABEL: define void @main.main(){{.*}} { // CHECK-NEXT: _llgo_[[BB0:[0-9]+]]: // CHECK-NEXT: %[[TMP0:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 12) +// CHECK-NEXT: store %"main.M[bool]" zeroinitializer, ptr %[[TMP0]], align 4 // CHECK-NEXT: call void @"main.(*M[bool]).check"(ptr %[[TMP0]], i64 1, i64 8, i64 1) // CHECK-NEXT: %[[TMP1:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 32) +// CHECK-NEXT: store %"main.M[int64]" zeroinitializer, ptr %[[TMP1]], align 8 // CHECK-NEXT: call void @"main.(*M[int64]).check"(ptr %[[TMP1]], i64 8, i64 16, i64 8) // CHECK-NEXT: ret void // CHECK-NEXT: } diff --git a/cl/_testrt/unsafe/in.go b/cl/_testrt/unsafe/in.go index 30fe7514f9..ef1ddd2223 100644 --- a/cl/_testrt/unsafe/in.go +++ b/cl/_testrt/unsafe/in.go @@ -40,10 +40,7 @@ type N struct { // unsafe.Slice validates pointer/length overflow, then constructs a slice whose // data and length are the values consumed by ordinary bounds checks. // CHECK: %[[ARRAY:[0-9]+]] = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 16) -// CHECK: %[[ELEM0:[0-9]+]] = getelementptr inbounds i64, ptr %[[ARRAY]], i64 0 -// CHECK: %[[ELEM1:[0-9]+]] = getelementptr inbounds i64, ptr %[[ARRAY]], i64 1 -// CHECK: store i64 1, ptr %[[ELEM0]] -// CHECK: store i64 2, ptr %[[ELEM1]] +// CHECK: store [2 x i64] %{{[0-9]+}}, ptr %[[ARRAY]] // CHECK: %[[BASE:[0-9]+]] = getelementptr inbounds i64, ptr %[[ARRAY]], i64 0 // CHECK: %[[BASE_INT:[0-9]+]] = ptrtoint ptr %[[BASE]] to i64 // CHECK: %[[SLICE_END:[0-9]+]] = add i64 %[[BASE_INT]], 15 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/go.mod b/go.mod index 375979d3d3..7fe40b537c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/goplus/cobra v1.9.12 //xgo:class github.com/goplus/gogen v1.23.5 github.com/goplus/lib v0.3.1 - github.com/goplus/mod v0.21.2 + github.com/goplus/mod v0.22.0 github.com/mattn/go-tty v0.0.8 github.com/qiniu/x v1.18.3 github.com/xgo-dev/llgo/runtime v0.0.0-00010101000000-000000000000 @@ -14,15 +14,15 @@ require ( github.com/xgo-dev/plan9asm v0.3.5 go.bug.st/serial v1.6.4 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/mod v0.29.0 - golang.org/x/sys v0.37.0 - golang.org/x/tools v0.38.0 + golang.org/x/mod v0.40.0 + golang.org/x/sys v0.47.0 + golang.org/x/tools v0.49.0 ) require ( github.com/creack/goselect v0.1.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/sync v0.17.0 // indirect + golang.org/x/sync v0.22.0 // indirect ) replace github.com/xgo-dev/llgo/runtime => ./runtime diff --git a/go.sum b/go.sum index 712188da2c..f66c3df8c3 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/goplus/gogen v1.23.5 h1:76w3zmAHI+ECI7bPr0enUd0du9+t1IYyXmp43CbIpSs= github.com/goplus/gogen v1.23.5/go.mod h1:Y7ulYW3wonQ3d9er00b0uGFEV/IUZa6okWJZh892ACQ= github.com/goplus/lib v0.3.1 h1:Xws4DBVvgOMu58awqB972wtvTacDbk3nqcbHjdx9KSg= github.com/goplus/lib v0.3.1/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= -github.com/goplus/mod v0.21.2 h1:Oxp5qWv40G5gBu1qH3w/THYvfoYXpLBvrVKfsTgWS9o= -github.com/goplus/mod v0.21.2/go.mod h1:VTyNmzzePgy99A2VQnxIBfoG1x097xilag/t0F0zuTg= +github.com/goplus/mod v0.22.0 h1:knZCdR5m2Nr1/cU1XqG1lND4USG6mXxx/Ca272RYjQk= +github.com/goplus/mod v0.22.0/go.mod h1:APrczG2FtFcQelU4vTq9xw+GrVs4sPPKnRfGidWBlXY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-tty v0.0.8 h1:yxtc0Ye17/1ne/bjy993YUoyP8bJJFa9n5M9XTdwoZQ= @@ -30,14 +30,14 @@ go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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())) }