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]] 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..57cca90998 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -321,6 +321,58 @@ 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) + } + 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 + } + + 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) + } +}