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..8bff84855f 100644 --- a/cl/_testrt/qsort/in.go +++ b/cl/_testrt/qsort/in.go @@ -26,44 +26,46 @@ 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: br label %_llgo_[[BB1:[0-9]+]] -// CHECK-EMPTY: +// 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: _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/internal/packages/load.go b/internal/packages/load.go index 055be834f5..97c46838dd 100644 --- a/internal/packages/load.go +++ b/internal/packages/load.go @@ -19,6 +19,7 @@ package packages import ( "fmt" "go/ast" + "go/parser" "go/scanner" "go/token" "go/types" @@ -31,7 +32,6 @@ import ( "strconv" "strings" "sync" - "unsafe" "golang.org/x/tools/go/packages" ) @@ -64,8 +64,6 @@ const ( NeedTypesInfo = packages.NeedTypesInfo NeedForTest = packages.NeedForTest - - typecheckCgo = NeedModule - 1 // TODO(xsw): how to check ) const ( @@ -81,38 +79,6 @@ type Error = packages.Error // A Package describes a loaded Go package. type Package = packages.Package -// loaderPackage augments Package with state used during the loading phase -type loaderPackage struct { - *Package - importErrors map[string]error // maps each bad import to its error - loadOnce sync.Once - color uint8 // for cycle detection - needsrc bool // load from source (Mode >= LoadTypes) - needtypes bool // type information is either requested or depended on - initial bool // package was matched by a pattern - goVersion int // minor version number of go command on PATH -} - -// loader holds the working state of a single call to load. -type loader struct { - pkgs map[string]*loaderPackage - Config - sizes types.Sizes // TODO(xsw): ensure offset of sizes - parseCache map[string]unsafe.Pointer - parseCacheMu sync.Mutex - exportMu sync.Mutex // enforces mutual exclusion of exportdata operations - - // Config.Mode contains the implied mode (see impliedLoadMode). - // Implied mode contains all the fields we need the data for. - // In requestedMode there are the actually requested fields. - // We'll zero them out before returning packages to the user. - // This makes it easier for us to get the conditions where - // we need certain modes right. - requestedMode LoadMode -} - -var loadGoVersions sync.Map // map[*loader]string - type Cached struct { *packages.Package Types *types.Package @@ -160,20 +126,15 @@ func (p Deduper) set(id string, cp *Cached) { p.cache.Store(id, cp) } -//go:linkname defaultDriver golang.org/x/tools/go/packages.defaultDriver -func defaultDriver(cfg *Config, patterns ...string) (*packages.DriverResponse, bool, error) - -//go:linkname newLoader golang.org/x/tools/go/packages.newLoader -func newLoader(cfg *Config) *loader - -//go:linkname loadFromExportData golang.org/x/tools/go/packages.(*loader).loadFromExportData -func loadFromExportData(ld *loader, lpkg *loaderPackage) error - -//go:linkname parseFiles golang.org/x/tools/go/packages.(*loader).parseFiles -func parseFiles(ld *loader, filenames []string) ([]*ast.File, []error) - -//go:linkname typesinternalSetUsesCgo golang.org/x/tools/internal/typesinternal.SetUsesCgo -func typesinternalSetUsesCgo(conf *types.Config) bool +// Visit visits all the packages in the import graph whose roots are +// pkgs, calling the optional pre function the first time each package +// is encountered (preorder), and the optional post function after a +// package's dependencies have been visited (postorder). +// The boolean result of pre(pkg) determines whether +// the imports of package pkg are visited. +func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { + packages.Visit(pkgs, pre, post) +} // An importFunc is an implementation of the single-method // types.Importer interface based on a function value. @@ -181,87 +142,220 @@ type importerFunc func(path string) (*types.Package, error) func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } -func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) { - if lpkg.PkgPath == "unsafe" { - // Fill in the blanks to avoid surprises. - lpkg.Types = types.Unsafe - lpkg.Fset = ld.Fset - lpkg.Syntax = []*ast.File{} - lpkg.TypesInfo = new(types.Info) - lpkg.TypesSizes = ld.sizes - return +// LoadEx loads and returns the Go packages named by the given patterns. +// +// Config specifies loading options; +// nil behaves the same as an empty Config. +// +// If any of the patterns was invalid as defined by the +// underlying build system, Load returns an error. +// It may return an empty list of packages without an error, +// for instance for an empty expansion of a valid wildcard. +// Errors associated with a particular package are recorded in the +// corresponding Package's Errors list, and do not cause Load to +// return an error. Clients may need to handle such errors before +// proceeding with further analysis. The PrintErrors function is +// provided for convenient display of all errors. +func LoadEx(dedup Deduper, sizes func(sizes types.Sizes, compiler, arch string) types.Sizes, cfg *Config, patterns ...string) ([]*Package, error) { + return LoadExWithGoVersion(dedup, sizes, cfg, "", patterns...) +} + +// LoadExWithGoVersion is LoadEx with an optional go/types language version +// override. The version uses go/types syntax, such as "go1.22". +func LoadExWithGoVersion(dedup Deduper, sizes func(sizes types.Sizes, compiler, arch string) types.Sizes, cfg *Config, goVersion string, patterns ...string) ([]*Package, error) { + var driverCfg Config + if cfg != nil { + driverCfg = *cfg } + origMode := driverCfg.Mode - if dedup != nil { - if cp := dedup.Check(lpkg.ID); cp != nil { - lpkg.Types = cp.Types - lpkg.Fset = ld.Fset - lpkg.TypesInfo = cp.TypesInfo - lpkg.Syntax = cp.Syntax - lpkg.TypesSizes = ld.sizes - return + // When type information or custom syntax parsing is requested, we do not let + // packages.Load typecheck or parse directly. We request files, imports, embed patterns, + // and module metadata from packages.Load (go list driver), and perform custom parsing + // and typechecking ourselves. + driverCfg.Mode = (origMode &^ (NeedTypes | NeedTypesSizes | NeedTypesInfo | NeedSyntax)) | NeedCompiledGoFiles | NeedImports | NeedName | NeedFiles + if origMode&(NeedEmbedPatterns|NeedEmbedFiles|NeedTypes|NeedTypesInfo|NeedSyntax) != 0 { + driverCfg.Mode |= NeedEmbedPatterns | NeedEmbedFiles | NeedExportFile + } + if origMode&NeedTypesSizes != 0 { + driverCfg.Mode |= NeedTypesSizes + } + if origMode&NeedModule != 0 || origMode&(NeedTypes|NeedTypesInfo) != 0 { + driverCfg.Mode |= NeedModule + } + + initial, err := packages.Load(&driverCfg, patterns...) + if err != nil { + return nil, err + } + + fset := driverCfg.Fset + if fset == nil { + fset = token.NewFileSet() + } + + if origMode&(NeedTypes|NeedTypesInfo|NeedTypesSizes|NeedSyntax) != 0 { + tc := &typecheckContext{ + dedup: dedup, + sizesFn: sizes, + cfg: &driverCfg, + fset: fset, + goVersion: goVersion, + origMode: origMode, } - defer func() { - if !lpkg.IllTyped && lpkg.needtypes && lpkg.needsrc { - dedup.set(lpkg.PkgPath, &Cached{ - Package: lpkg.Package, - Types: lpkg.Types, - TypesInfo: lpkg.TypesInfo, - Syntax: lpkg.Syntax, - }) - } - }() - if dedup.setpath != nil { - lpkg.PkgPath = dedup.setpath(lpkg.PkgPath, lpkg.Name) + + // Perform bottom-up typechecking in dependency post-order + packages.Visit(initial, nil, func(pkg *Package) { + tc.typecheckPackage(pkg) + }) + } + + return initial, nil +} + +type typecheckContext struct { + dedup Deduper + sizesFn func(sizes types.Sizes, compiler, arch string) types.Sizes + cfg *Config + fset *token.FileSet + goVersion string + origMode LoadMode +} + +func (tc *typecheckContext) targetGoVersion(pkg *Package) string { + if tc.goVersion != "" { + return tc.goVersion + } + if pkg.Module != nil && pkg.Module.GoVersion != "" { + return "go" + pkg.Module.GoVersion + } + return "" +} + +func (tc *typecheckContext) parseFile(filename string, fset *token.FileSet) (*ast.File, error) { + fullPath := filename + if !filepath.IsAbs(fullPath) && tc.cfg.Dir != "" { + fullPath = filepath.Join(tc.cfg.Dir, fullPath) + } + var src []byte + hasSrc := false + if tc.cfg.Overlay != nil { + if data, ok := tc.cfg.Overlay[fullPath]; ok { + src, hasSrc = data, true + } else if data, ok := tc.cfg.Overlay[filename]; ok { + src, hasSrc = data, true + } + } + if !hasSrc { + data, err := os.ReadFile(fullPath) + if err != nil { + return nil, err } - if _, ok := dedup.checked.Load(lpkg.PkgPath); !ok { - dedup.checked.Store(lpkg.PkgPath, struct{}{}) - if files, ok := dedup.llgoFiles[lpkg.PkgPath]; ok { - lpkg.CompiledGoFiles = append(lpkg.CompiledGoFiles, files...) + src = data + } + if tc.cfg.ParseFile != nil { + return tc.cfg.ParseFile(fset, fullPath, src) + } + return parser.ParseFile(fset, fullPath, src, parser.AllErrors|parser.ParseComments) +} + +func (tc *typecheckContext) targetCompilerAndArch() (compiler, arch string) { + compiler = "gc" + if tc.cfg != nil { + for _, env := range tc.cfg.Env { + if strings.HasPrefix(env, "GOARCH=") { + arch = env[len("GOARCH="):] } } } + if arch == "" { + arch = os.Getenv("GOARCH") + } + if arch == "" { + arch = runtime.GOARCH + } + return compiler, arch +} - // Call NewPackage directly with explicit name. - // This avoids skew between golist and go/types when the files' - // package declarations are inconsistent. - lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name) - lpkg.Fset = ld.Fset +// computedSizes determines the appropriate types.Sizes for the target package. +// When cross-compiling for WebAssembly (wasm), types.SizesFor("gc", "wasm") may +// return nil on certain Go toolchain configurations; we explicitly fall back to +// 32-bit word size and 4-byte alignment (&types.StdSizes{WordSize: 4, MaxAlign: 4}) +// matching the wasm32 ABI. +func (tc *typecheckContext) computedSizes(pkg *Package) types.Sizes { + compiler, arch := tc.targetCompilerAndArch() + s := pkg.TypesSizes + if s == nil { + s = types.SizesFor(compiler, arch) + if s == nil { + if arch == "wasm" { + s = &types.StdSizes{WordSize: 4, MaxAlign: 4} + } else { + s = types.SizesFor("gc", "amd64") + } + } + } + if tc.sizesFn != nil { + s = tc.sizesFn(s, compiler, arch) + } + return s +} - // Start shutting down if the context is done and do not load - // source or export data files. - // Packages that import this one will have ld.Context.Err() != nil. - // ld.Context.Err() will be returned later by refine. - if ld.Context.Err() != nil { - return +func (tc *typecheckContext) typecheckPackage(pkg *Package) { + fset := pkg.Fset + if fset == nil { + fset = tc.fset + pkg.Fset = fset } - // Subtle: we populate all Types fields with an empty Package - // before loading export data so that export data processing - // never has to create a types.Package for an indirect dependency, - // which would then require that such created packages be explicitly - // inserted back into the Import graph as a final step after export data loading. - // (Hence this return is after the Types assignment.) - // The Diamond test exercises this case. - if !lpkg.needtypes && !lpkg.needsrc { + if pkg.PkgPath == "unsafe" { + pkg.Types = types.Unsafe + pkg.Fset = fset + pkg.Syntax = []*ast.File{} + pkg.TypesInfo = new(types.Info) + pkg.TypesSizes = tc.computedSizes(pkg) return } - if !lpkg.needsrc { - if err := loadFromExportData(ld, lpkg); err != nil { - lpkg.Errors = append(lpkg.Errors, packages.Error{ - Pos: "-", - Msg: err.Error(), - Kind: packages.UnknownError, // e.g. can't find/open/parse export data - }) + + if tc.dedup != nil { + if cp := tc.dedup.Check(pkg.ID); cp != nil { + pkg.Types = cp.Types + pkg.Fset = fset + pkg.TypesInfo = cp.TypesInfo + pkg.Syntax = cp.Syntax + pkg.TypesSizes = tc.computedSizes(pkg) + return + } + defer func() { + if !pkg.IllTyped && pkg.Types != nil && pkg.Types.Complete() { + tc.dedup.set(pkg.PkgPath, &Cached{ + Package: pkg, + Types: pkg.Types, + TypesInfo: pkg.TypesInfo, + Syntax: pkg.Syntax, + }) + } + }() + if tc.dedup.setpath != nil { + pkg.PkgPath = tc.dedup.setpath(pkg.PkgPath, pkg.Name) + } + if _, ok := tc.dedup.checked.Load(pkg.PkgPath); !ok { + tc.dedup.checked.Store(pkg.PkgPath, struct{}{}) + if files, ok := tc.dedup.llgoFiles[pkg.PkgPath]; ok { + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, files...) + } } - return // not a source package, don't get syntax trees } - // go list has already captured cmd/compile's authoritative diagnostics in - // this block. For example, an unexpected else stops gc before recovery AST - // errors are reported, so do not append the local parser/type follow-ons. + pkg.Fset = fset + pkg.TypesSizes = tc.computedSizes(pkg) + hasCompilerSyntaxError := false - for _, err := range lpkg.Errors { + for _, err := range pkg.Errors { + if strings.Contains(err.Msg, "syntax error") { + hasCompilerSyntaxError = true + break + } if err.Kind != packages.ListError || !strings.HasPrefix(err.Msg, "# ") { continue } @@ -272,122 +366,68 @@ func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) { } appendError := func(err error) { - // Convert various error types into the one true Error. var errs []packages.Error switch err := err.(type) { case packages.Error: - // from driver errs = append(errs, err) - case *os.PathError: - // from parser errs = append(errs, packages.Error{ Pos: err.Path + ":1", Msg: err.Err.Error(), Kind: packages.ParseError, }) - case scanner.ErrorList: - // from parser if hasCompilerSyntaxError { return } - for _, err := range err { + for _, e := range err { errs = append(errs, packages.Error{ - Pos: err.Pos.String(), - Msg: err.Msg, + Pos: e.Pos.String(), + Msg: e.Msg, Kind: packages.ParseError, }) } - case types.Error: - // from type checker if hasCompilerSyntaxError { return } - lpkg.TypeErrors = append(lpkg.TypeErrors, err) + pkg.TypeErrors = append(pkg.TypeErrors, err) errs = append(errs, packages.Error{ Pos: err.Fset.Position(err.Pos).String(), Msg: err.Msg, Kind: packages.TypeError, }) - default: - // unexpected impoverished error from parser? errs = append(errs, packages.Error{ Pos: "-", Msg: err.Error(), Kind: packages.UnknownError, }) - - // If you see this error message, please file a bug. - log.Printf("internal error: error %q (%T) without position", err, err) } - - lpkg.Errors = append(lpkg.Errors, errs...) - } - - // If the go command on the PATH is newer than the runtime, - // then the go/{scanner,ast,parser,types} packages from the - // standard library may be unable to process the files - // selected by go list. - // - // There is currently no way to downgrade the effective - // version of the go command (see issue 52078), so we proceed - // with the newer go command but, in case of parse or type - // errors, we emit an additional diagnostic. - // - // See: - // - golang.org/issue/52078 (flag to set release tags) - // - golang.org/issue/50825 (gopls legacy version support) - // - golang.org/issue/55883 (go/packages confusing error) - // - // Should we assert a hard minimum of (currently) go1.16 here? - var runtimeVersion int - if _, err := fmt.Sscanf(runtime.Version(), "go1.%d", &runtimeVersion); err == nil && runtimeVersion < lpkg.goVersion { - defer func() { - if len(lpkg.Errors) > 0 { - appendError(packages.Error{ - Pos: "-", - Msg: fmt.Sprintf("This application uses version go1.%d of the source-processing packages but runs version go1.%d of 'go list'. It may fail to process source files that rely on newer language features. If so, rebuild the application using a newer version of Go.", runtimeVersion, lpkg.goVersion), - Kind: packages.UnknownError, - }) - } - }() + pkg.Errors = append(pkg.Errors, errs...) } - if ld.Config.Mode&NeedTypes != 0 && len(lpkg.CompiledGoFiles) == 0 && lpkg.ExportFile != "" { - // The config requested loading sources and types, but sources are missing. - // Add an error to the package and fall back to loading from export data. - appendError(packages.Error{ - Pos: "-", - Msg: fmt.Sprintf("sources missing for package %s", lpkg.ID), - Kind: packages.ParseError, - }) - _ = loadFromExportData(ld, lpkg) // ignore any secondary errors - - return // can't get syntax trees for this package + if len(pkg.Syntax) == 0 && len(pkg.CompiledGoFiles) > 0 { + for _, file := range pkg.CompiledGoFiles { + f, err := tc.parseFile(file, fset) + if err != nil { + appendError(err) + } else { + pkg.Syntax = append(pkg.Syntax, f) + } + } } - files, errs := parseFiles(ld, lpkg.CompiledGoFiles) - for _, err := range errs { - appendError(err) - } + pkgGoVersion := tc.targetGoVersion(pkg) + normalizeEmbedDriverDiagnostics(pkg.Errors, fset, pkg.Syntax, pkgGoVersion) - lpkg.Syntax = files - normalizeEmbedDriverDiagnostics(lpkg.Errors, ld.Fset, files, packageGoVersion(ld, lpkg)) - if ld.Config.Mode&NeedTypes == 0 { + if tc.origMode&NeedTypes == 0 && tc.origMode&NeedTypesInfo == 0 { return } - // Start shutting down if the context is done and do not type check. - // Packages that import this one will have ld.Context.Err() != nil. - // ld.Context.Err() will be returned later by refine. - if ld.Context.Err() != nil { - return - } + pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name) - lpkg.TypesInfo = &types.Info{ + pkg.TypesInfo = &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), @@ -397,140 +437,66 @@ func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) { Selections: make(map[*ast.SelectorExpr]*types.Selection), FileVersions: make(map[*ast.File]string), } - lpkg.TypesSizes = ld.sizes importer := importerFunc(func(path string) (*types.Package, error) { if path == "unsafe" { return types.Unsafe, nil } - // go/packages does not create import metadata for an absolute path. - // Report the validation error that cmd/compile would have returned - // instead of leaking the loader's "no metadata" implementation detail. if pathpkg.IsAbs(path) { return nil, fmt.Errorf("import path cannot be absolute path") } - // The imports map is keyed by import path. - ipkg := lpkg.Imports[path] + ipkg := pkg.Imports[path] if ipkg == nil { - if err := lpkg.importErrors[path]; err != nil { - return nil, err - } - // There was skew between the metadata and the - // import declarations, likely due to an edit - // race, or because the ParseFile feature was - // used to supply alternative file contents. return nil, fmt.Errorf("no metadata for %s", path) } if ipkg.Types != nil && ipkg.Types.Complete() { return ipkg.Types, nil } - log.Fatalf("internal error: package %q without types was imported from %q", path, lpkg) - panic("unreachable") + return nil, fmt.Errorf("package %q without types was imported from %q", path, pkg.ID) }) - if dedup != nil && dedup.preload != nil { - dedup.preload(lpkg.Types, lpkg.Syntax) + if tc.dedup != nil && tc.dedup.preload != nil { + tc.dedup.preload(pkg.Types, pkg.Syntax) } - // type-check - tc := &types.Config{ - Importer: importer, - - // Type-check bodies of functions only in initial packages. - // Example: for import graph A->B->C and initial packages {A,C}, - // we can ignore function bodies in B. - IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial, - - Error: appendError, - Sizes: ld.sizes, // may be nil - } - tc.GoVersion = packageGoVersion(ld, lpkg) - if (ld.Mode & typecheckCgo) != 0 { - if !typesinternalSetUsesCgo(tc) { - appendError(packages.Error{ - Msg: "typecheckCgo requires Go 1.15+", - Kind: packages.ListError, - }) - return - } + typeConf := &types.Config{ + Importer: importer, + Sizes: pkg.TypesSizes, + Error: appendError, + GoVersion: pkgGoVersion, } - typErr := types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) - lpkg.importErrors = nil // no longer needed - - // In go/types go1.21 and go1.22, Checker.Files failed fast with a - // a "too new" error, without calling tc.Error and without - // proceeding to type-check the package (#66525). - // We rely on the runtimeVersion error to give the suggested remedy. - if typErr != nil && len(lpkg.Errors) == 0 && len(lpkg.Syntax) > 0 { + typErr := types.NewChecker(typeConf, fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax) + if typErr != nil && len(pkg.Errors) == 0 && len(pkg.Syntax) > 0 { if msg := typErr.Error(); strings.HasPrefix(msg, "package requires newer Go version") { appendError(types.Error{ - Fset: ld.Fset, - Pos: lpkg.Syntax[0].Package, + Fset: fset, + Pos: pkg.Syntax[0].Package, Msg: msg, }) } } - // If !Cgo, the type-checker uses FakeImportC mode, so - // it doesn't invoke the importer for import "C", - // nor report an error for the import, - // or for any undefined C.f reference. - // We must detect this explicitly and correctly - // mark the package as IllTyped (by reporting an error). - // TODO(adonovan): if these errors are annoying, - // we could just set IllTyped quietly. - if tc.FakeImportC { - outer: - for _, f := range lpkg.Syntax { - for _, imp := range f.Imports { - if imp.Path.Value == `"C"` { - err := types.Error{Fset: ld.Fset, Pos: imp.Pos(), Msg: `import "C" ignored`} - appendError(err) - break outer - } - } - } - } - - // If types.Checker.Files had an error that was unreported, - // make sure to report the unknown error so the package is illTyped. - if typErr != nil && len(lpkg.Errors) == 0 { + if typErr != nil && len(pkg.Errors) == 0 { appendError(typErr) } - // Record accumulated errors. - illTyped := len(lpkg.Errors) > 0 + illTyped := len(pkg.Errors) > 0 if !illTyped { - for _, imp := range lpkg.Imports { + for _, imp := range pkg.Imports { if imp.IllTyped { illTyped = true break } } } - lpkg.IllTyped = illTyped -} - -func packageGoVersion(ld *loader, lpkg *loaderPackage) string { - if goVersion, ok := loadGoVersions.Load(ld); ok && lpkg.initial { - return goVersion.(string) - } - if lpkg.Module != nil && lpkg.Module.GoVersion != "" { - return "go" + lpkg.Module.GoVersion - } - return "" + pkg.IllTyped = illTyped } const embedPatternDriverDiagnostic = "pattern //: invalid pattern syntax" -// normalizeEmbedDriverDiagnostics handles the two semantic checks that -// cmd/compile performs before parsing embed patterns. go list instead treats a -// trailing // token as a pattern and reports the driver error above. Keep that -// error for every other context; replacing it unconditionally would hide real -// unmatched-pattern diagnostics from compiler clients. func normalizeEmbedDriverDiagnostics(errs []packages.Error, fset *token.FileSet, files []*ast.File, goVersion string) { for i := range errs { if errs[i].Msg != embedPatternDriverDiagnostic { @@ -674,292 +640,3 @@ func commentGroupContains(group *ast.CommentGroup, comment *ast.Comment) bool { } return false } - -func loadRecursiveEx(dedup Deduper, ld *loader, lpkg *loaderPackage) { - lpkg.loadOnce.Do(func() { - // Load the direct dependencies, in parallel. - var wg sync.WaitGroup - for _, ipkg := range lpkg.Imports { - imp := ld.pkgs[ipkg.ID] - wg.Add(1) - go func(imp *loaderPackage) { - loadRecursiveEx(dedup, ld, imp) - wg.Done() - }(imp) - } - wg.Wait() - loadPackageEx(dedup, ld, lpkg) - }) -} - -func refineEx(dedup Deduper, ld *loader, response *packages.DriverResponse) ([]*Package, error) { - roots := response.Roots - rootMap := make(map[string]int, len(roots)) - for i, root := range roots { - rootMap[root] = i - } - ld.pkgs = make(map[string]*loaderPackage) - // first pass, fixup and build the map and roots - var initial = make([]*loaderPackage, len(roots)) - for _, pkg := range response.Packages { - rootIndex := -1 - if i, found := rootMap[pkg.ID]; found { - rootIndex = i - } - - // Overlays can invalidate export data. - // TODO(matloob): make this check fine-grained based on dependencies on overlaid files - exportDataInvalid := len(ld.Overlay) > 0 || pkg.ExportFile == "" && pkg.PkgPath != "unsafe" - // This package needs type information if the caller requested types and the package is - // either a root, or it's a non-root and the user requested dependencies ... - needtypes := (ld.Mode&NeedTypes|NeedTypesInfo != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) - // This package needs source if the call requested source (or types info, which implies source) - // and the package is either a root, or itas a non- root and the user requested dependencies... - needsrc := ((ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) || - // ... or if we need types and the exportData is invalid. We fall back to (incompletely) - // typechecking packages from source if they fail to compile. - (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && exportDataInvalid)) && pkg.PkgPath != "unsafe" - lpkg := &loaderPackage{ - Package: pkg, - needtypes: needtypes, - needsrc: needsrc, - goVersion: response.GoVersion, - } - ld.pkgs[lpkg.ID] = lpkg - if rootIndex >= 0 { - initial[rootIndex] = lpkg - lpkg.initial = true - } - } - for i, root := range roots { - if initial[i] == nil { - return nil, fmt.Errorf("root package %v is missing", root) - } - } - - if ld.Mode&NeedImports != 0 { - // Materialize the import graph. - - const ( - white = 0 // new - grey = 1 // in progress - black = 2 // complete - ) - - // visit traverses the import graph, depth-first, - // and materializes the graph as Packages.Imports. - // - // Valid imports are saved in the Packages.Import map. - // Invalid imports (cycles and missing nodes) are saved in the importErrors map. - // Thus, even in the presence of both kinds of errors, - // the Import graph remains a DAG. - // - // visit returns whether the package needs src or has a transitive - // dependency on a package that does. These are the only packages - // for which we load source code. - var stack []*loaderPackage - var visit func(lpkg *loaderPackage) bool - visit = func(lpkg *loaderPackage) bool { - switch lpkg.color { - case black: - return lpkg.needsrc - case grey: - panic("internal error: grey node") - } - lpkg.color = grey - stack = append(stack, lpkg) // push - stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports - lpkg.Imports = make(map[string]*Package, len(stubs)) - for importPath, ipkg := range stubs { - var importErr error - imp := ld.pkgs[ipkg.ID] - if imp == nil { - // (includes package "C" when DisableCgo) - importErr = fmt.Errorf("missing package: %q", ipkg.ID) - } else if imp.color == grey { - importErr = fmt.Errorf("import cycle: %s", stack) - } - if importErr != nil { - if lpkg.importErrors == nil { - lpkg.importErrors = make(map[string]error) - } - lpkg.importErrors[importPath] = importErr - continue - } - - if visit(imp) { - lpkg.needsrc = true - } - lpkg.Imports[importPath] = imp.Package - } - - // Complete type information is required for the - // immediate dependencies of each source package. - if lpkg.needsrc && ld.Mode&NeedTypes != 0 { - for _, ipkg := range lpkg.Imports { - ld.pkgs[ipkg.ID].needtypes = true - } - } - - // NeedTypeSizes causes TypeSizes to be set even - // on packages for which types aren't needed. - if ld.Mode&NeedTypesSizes != 0 { - lpkg.TypesSizes = ld.sizes - } - stack = stack[:len(stack)-1] // pop - lpkg.color = black - - return lpkg.needsrc - } - - // For each initial package, create its import DAG. - for _, lpkg := range initial { - visit(lpkg) - } - - } else { - // !NeedImports: drop the stub (ID-only) import packages - // that we are not even going to try to resolve. - for _, lpkg := range initial { - lpkg.Imports = nil - } - } - - // Load type data and syntax if needed, starting at - // the initial packages (roots of the import DAG). - if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 { - var wg sync.WaitGroup - for _, lpkg := range initial { - wg.Add(1) - go func(lpkg *loaderPackage) { - loadRecursiveEx(dedup, ld, lpkg) - wg.Done() - }(lpkg) - } - wg.Wait() - } - - // If the context is done, return its error and - // throw out [likely] incomplete packages. - if err := ld.Context.Err(); err != nil { - return nil, err - } - - result := make([]*Package, len(initial)) - for i, lpkg := range initial { - result[i] = lpkg.Package - } - for i := range ld.pkgs { - // Clear all unrequested fields, - // to catch programs that use more than they request. - if ld.requestedMode&NeedName == 0 { - ld.pkgs[i].Name = "" - ld.pkgs[i].PkgPath = "" - } - if ld.requestedMode&NeedFiles == 0 { - ld.pkgs[i].GoFiles = nil - ld.pkgs[i].OtherFiles = nil - ld.pkgs[i].IgnoredFiles = nil - } - if ld.requestedMode&NeedEmbedFiles == 0 { - ld.pkgs[i].EmbedFiles = nil - } - if ld.requestedMode&NeedEmbedPatterns == 0 { - ld.pkgs[i].EmbedPatterns = nil - } - if ld.requestedMode&NeedCompiledGoFiles == 0 { - ld.pkgs[i].CompiledGoFiles = nil - } - if ld.requestedMode&NeedImports == 0 { - ld.pkgs[i].Imports = nil - } - if ld.requestedMode&NeedExportFile == 0 { - ld.pkgs[i].ExportFile = "" - } - if ld.requestedMode&NeedTypes == 0 { - ld.pkgs[i].Types = nil - ld.pkgs[i].Fset = nil - ld.pkgs[i].IllTyped = false - } - if ld.requestedMode&NeedSyntax == 0 { - ld.pkgs[i].Syntax = nil - } - if ld.requestedMode&NeedTypesInfo == 0 { - ld.pkgs[i].TypesInfo = nil - } - if ld.requestedMode&NeedTypesSizes == 0 { - ld.pkgs[i].TypesSizes = nil - } - if ld.requestedMode&NeedModule == 0 { - ld.pkgs[i].Module = nil - } - } - - return result, nil -} - -// LoadEx loads and returns the Go packages named by the given patterns. -// -// Config specifies loading options; -// nil behaves the same as an empty Config. -// -// If any of the patterns was invalid as defined by the -// underlying build system, Load returns an error. -// It may return an empty list of packages without an error, -// for instance for an empty expansion of a valid wildcard. -// Errors associated with a particular package are recorded in the -// corresponding Package's Errors list, and do not cause Load to -// return an error. Clients may need to handle such errors before -// proceeding with further analysis. The PrintErrors function is -// provided for convenient display of all errors. -func LoadEx(dedup Deduper, sizes func(sizes types.Sizes, compiler, arch string) types.Sizes, cfg *Config, patterns ...string) ([]*Package, error) { - return LoadExWithGoVersion(dedup, sizes, cfg, "", patterns...) -} - -// LoadExWithGoVersion is LoadEx with an optional go/types language version -// override. The version uses go/types syntax, such as "go1.22". -func LoadExWithGoVersion(dedup Deduper, sizes func(sizes types.Sizes, compiler, arch string) types.Sizes, cfg *Config, goVersion string, patterns ...string) ([]*Package, error) { - ld := newLoader(cfg) - if goVersion != "" { - loadGoVersions.Store(ld, goVersion) - defer loadGoVersions.Delete(ld) - } - response, external, err := defaultDriver(&ld.Config, patterns...) - if err != nil { - return nil, err - } - - ld.sizes = types.SizesFor(response.Compiler, response.Arch) - if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 { - // Type size information is needed but unavailable. - if external { - // An external driver may fail to populate the Compiler/GOARCH fields, - // especially since they are relatively new (see #63700). - // Provide a sensible fallback in this case. - ld.sizes = types.SizesFor("gc", runtime.GOARCH) - if ld.sizes == nil { // gccgo-only arch - ld.sizes = types.SizesFor("gc", "amd64") - } - } else { - // Go list should never fail to deliver accurate size information. - // Reject the whole Load since the error is the same for every package. - return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q", - response.Compiler, response.Arch) - } - } - - if sizes != nil { - ld.sizes = sizes(ld.sizes, response.Compiler, response.Arch) - } - return refineEx(dedup, ld, response) -} - -// Visit visits all the packages in the import graph whose roots are -// pkgs, calling the optional pre function the first time each package -// is encountered (preorder), and the optional post function after a -// package's dependencies have been visited (postorder). -// The boolean result of pre(pkg) determines whether -// the imports of package pkg are visited. -// -//go:linkname Visit golang.org/x/tools/go/packages.Visit -func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) 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())) }