From b58ce5cca918f6519ef71ae61007f88d3f086ab1 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 11 Aug 2026 21:49:35 +0800 Subject: [PATCH 1/3] cl: stabilize generic local ABI names across cache loads --- cl/compile.go | 5 ++- cl/funcname_nested_closure_test.go | 3 ++ internal/build/build_test.go | 43 +++++++++++++++++++ .../genericlocalcache_test.go | 21 +++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 internal/build/testdata/genericlocalcache/genericlocalcache_test.go diff --git a/cl/compile.go b/cl/compile.go index 67b5d91f7a..8115f58b06 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -2570,7 +2570,10 @@ func (p *context) patchLocalGenericNamed(t *types.Named) (*types.Named, bool) { if isPatchedLocalGenericName(t.Obj().Name()) { return nil, false } - obj := types.NewTypeName(t.Obj().Pos(), t.Obj().Pkg(), p.localNamedName(t, false), nil) + // The generated name already carries the local type's complete identity. + // Keep this detached object positionless so ABI naming does not append a + // loader-relative token.Pos that changes between package-cache processes. + obj := types.NewTypeName(token.NoPos, t.Obj().Pkg(), p.localNamedName(t, false), nil) return types.NewNamed(obj, t.Underlying(), nil), true } diff --git a/cl/funcname_nested_closure_test.go b/cl/funcname_nested_closure_test.go index 32d23885df..f562e5ec2f 100644 --- a/cl/funcname_nested_closure_test.go +++ b/cl/funcname_nested_closure_test.go @@ -102,6 +102,9 @@ func localType[T any]() any { if !ok { t.Fatalf("patchLocalGenericNamed(%v) was not patched", local) } + if pos := patched.Obj().Pos(); pos.IsValid() { + t.Fatalf("patched local generic type position = %v, want token.NoPos", pos) + } name := patched.Obj().Name() if !strings.Contains(name, "[") || strings.Contains(name, "ยท") { t.Fatalf("patched local generic name = %q, want type args without ordinal suffix", name) diff --git a/internal/build/build_test.go b/internal/build/build_test.go index 05c28144ff..764a40abfc 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -321,6 +321,49 @@ func TestDeadcodeBuildColdAndHotPackageCache(t *testing.T) { } } +func TestGenericLocalTypeColdAndHotPackageCache(t *testing.T) { + repoRoot, err := filepath.Abs(filepath.Join("..", "..")) + if err != nil { + t.Fatal(err) + } + fixture := filepath.Join(repoRoot, "internal", "build", "testdata", "genericlocalcache") + const ( + cacheRootEnv = "LLGO_TEST_GENERIC_LOCAL_CACHE_ROOT" + cachePhaseEnv = "LLGO_TEST_GENERIC_LOCAL_CACHE_PHASE" + ) + if cacheRoot := os.Getenv(cacheRootEnv); cacheRoot != "" { + cacheRootFunc = func() string { return cacheRoot } + t.Setenv("LLGO_ROOT", repoRoot) + t.Setenv(llgoBuildCache, "1") + + conf := NewDefaultConf(ModeTest) + conf.OutFile = filepath.Join(t.TempDir(), os.Getenv(cachePhaseEnv)) + conf.RunArgs = []string{"-test.run=^TestLocalRuntimeType$"} + pkgs, err := Build(Invocation{Args: []string{"."}, Config: conf, Dir: fixture}) + if err != nil { + t.Fatal(err) + } + if os.Getenv(cachePhaseEnv) == "hot" { + for _, pkg := range pkgs { + if pkg.CacheHit { + return + } + } + t.Fatal("hot build did not reuse any package archives") + } + return + } + + cacheRoot := t.TempDir() + for _, phase := range []string{"cold", "hot"} { + cmd := exec.Command(os.Args[0], "-test.run=^TestGenericLocalTypeColdAndHotPackageCache$", "-test.count=1") + cmd.Env = append(os.Environ(), cacheRootEnv+"="+cacheRoot, cachePhaseEnv+"="+phase) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("%s cache build: %v\n%s", phase, err, out) + } + } +} + func TestResolveOutputsUsesInvocationDirectory(t *testing.T) { dir := t.TempDir() out := &OutFmtDetails{ diff --git a/internal/build/testdata/genericlocalcache/genericlocalcache_test.go b/internal/build/testdata/genericlocalcache/genericlocalcache_test.go new file mode 100644 index 0000000000..67312b0751 --- /dev/null +++ b/internal/build/testdata/genericlocalcache/genericlocalcache_test.go @@ -0,0 +1,21 @@ +package genericlocalcache + +import ( + "reflect" + "testing" +) + +func localRuntimeType[T any]() reflect.Type { + type local struct { + value T + } + return reflect.TypeOf(local{}) +} + +func TestLocalRuntimeType(t *testing.T) { + intType := localRuntimeType[int]() + stringType := localRuntimeType[string]() + if intType == stringType { + t.Fatalf("generic local runtime types are identical: %v", intType) + } +} From 1bcb70bc0e7ed711939ebf9b832366c63bea77df Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 11 Aug 2026 22:13:11 +0800 Subject: [PATCH 2/3] test: verify generic local cache cold misses --- internal/build/build_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/build/build_test.go b/internal/build/build_test.go index 764a40abfc..57cca90998 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -343,13 +343,22 @@ func TestGenericLocalTypeColdAndHotPackageCache(t *testing.T) { if err != nil { t.Fatal(err) } - if os.Getenv(cachePhaseEnv) == "hot" { + switch phase := os.Getenv(cachePhaseEnv); phase { + case "cold": + for _, pkg := range pkgs { + if pkg.CacheHit { + t.Fatalf("cold build unexpectedly hit package cache for %s", pkg.PkgPath) + } + } + case "hot": for _, pkg := range pkgs { if pkg.CacheHit { return } } t.Fatal("hot build did not reuse any package archives") + default: + t.Fatalf("unknown cache phase %q", phase) } return } From af9eb545153f05af4ff608e97596793f4bb60283 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Wed, 19 Aug 2026 23:28:01 +0800 Subject: [PATCH 3/3] test: align generic local ABI symbol pattern in tplocalclosureiface --- cl/_testgo/tplocalclosureiface/in.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cl/_testgo/tplocalclosureiface/in.go b/cl/_testgo/tplocalclosureiface/in.go index 6c94b86f43..c64008da17 100644 --- a/cl/_testgo/tplocalclosureiface/in.go +++ b/cl/_testgo/tplocalclosureiface/in.go @@ -3,12 +3,12 @@ package main // DARWIN-ARM64-LABEL: define linkonce %"{{.*}}/runtime/internal/runtime.eface" @"main.boxFuncs$1[int]"(ptr swiftself // LINUX-AMD64-LABEL: define linkonce %"{{.*}}/runtime/internal/runtime.eface" @"main.boxFuncs$1[int]"(ptr nest -// CHECK: insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr [[INT_BOX:@"_llgo_main\.box\[int\]\.p[0-9]+"]], ptr undef } +// CHECK: insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr [[INT_BOX:@"_llgo_main\.box\[int\]"]], ptr undef } // CHECK-LABEL: define linkonce i1 @"main.boxFuncs$2[int]"( // CHECK: icmp eq ptr %{{.*}}, [[INT_BOX]] // DARWIN-ARM64-LABEL: define linkonce %"{{.*}}/runtime/internal/runtime.eface" @"main.boxFuncs$1[string]"(ptr swiftself // LINUX-AMD64-LABEL: define linkonce %"{{.*}}/runtime/internal/runtime.eface" @"main.boxFuncs$1[string]"(ptr nest -// CHECK: insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr [[STRING_BOX:@"_llgo_main\.box\[string\]\.p[0-9]+"]], ptr undef } +// CHECK: insertvalue %"{{.*}}/runtime/internal/runtime.eface" { ptr [[STRING_BOX:@"_llgo_main\.box\[string\]"]], ptr undef } // CHECK-LABEL: define linkonce i1 @"main.boxFuncs$2[string]"( // CHECK: icmp eq ptr %{{.*}}, [[STRING_BOX]]