compiler: canonicalize generic instance identities

x/tools SSA names and go/types strings can preserve the source spelling of
type arguments, so aliases can make distinct instances collide. Use the
canonical type encoding for function names, synthetic local type owners,
instantiated named types, and method sets.
This commit is contained in:
Jake Bailey
2026-07-12 14:12:54 -07:00
committed by Ron Evans
parent 73db9776b1
commit c7923d1c64
5 changed files with 51 additions and 46 deletions
+25 -12
View File
@@ -599,8 +599,21 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
case *types.Named: case *types.Named:
tn := t.Obj() tn := t.Obj()
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() { if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
// Package-scope or builtin: the printed name is unique. name := tn.Name()
return "named:" + t.String(), false if tn.Pkg() != nil {
name = tn.Pkg().Path() + "." + name
}
isLocal := false
if targs := t.TypeArgs(); targs.Len() != 0 {
parts := make([]string, targs.Len())
for i := range parts {
var local bool
parts[i], local = c.getTypeCodeName(targs.At(i))
isLocal = isLocal || local
}
name += "[" + strings.Join(parts, ",") + "]"
}
return "named:" + name, isLocal
} }
if tn.Parent() != nil { if tn.Parent() != nil {
// Ordinary function-local type. Use the un-//line-adjusted // Ordinary function-local type. Use the un-//line-adjusted
@@ -707,8 +720,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// Synthetic TypeNames are produced by generic instantiation: two // Synthetic TypeNames are produced by generic instantiation: two
// instantiations of the same generic function (e.g. F[int] and // instantiations of the same generic function (e.g. F[int] and
// F[string]) produce TypeNames with the same printed name and the // F[string]) produce TypeNames with the same printed name and the
// same source position, so each is named with the enclosing // same source position, so each is named with the enclosing instance's
// instance's RelString as prefix. RelString encodes the type // canonical function name as prefix. The function name encodes the type
// arguments, matching Go's runtime behavior, where F[int].Inner and // arguments, matching Go's runtime behavior, where F[int].Inner and
// F[string].Inner are distinct types even when Inner does not mention // F[string].Inner are distinct types even when Inner does not mention
// the type parameter. // the type parameter.
@@ -717,9 +730,9 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// of F[int] is compiled in every package that calls F[int]); its // of F[int] is compiled in every package that calls F[int]); its
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by // reflect/types.type:* global has LinkOnceODRLinkage and is merged by
// name at link time. The chosen name therefore depends only on // name at link time. The chosen name therefore depends only on
// intrinsic SSA properties (RelString and the raw token.Pos used as a // intrinsic SSA properties (the canonical function name and raw token.Pos),
// sort key), so any package compiling the same instance produces the // so any package compiling the same instance produces the same
// same identifier. // identifier.
// //
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are // Ordinary function-local TypeNames (TypeName.Parent() != nil) are
// not handled here: they are nameable only inside their declaring // not handled here: they are nameable only inside their declaring
@@ -807,9 +820,8 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
// registerSyntheticLocalTypes walks every type reachable from fn's // registerSyntheticLocalTypes walks every type reachable from fn's
// body and records each synthetic *types.Named (TypeName.Parent() == // body and records each synthetic *types.Named (TypeName.Parent() ==
// nil) in c.localTypeNames. Each is named with fn.RelString as the // nil) in c.localTypeNames. Each is named with the canonical function
// owning function plus a per-function counter assigned in source // name plus a per-function counter assigned in source order.
// order.
// //
// First-writer-wins: a *types.Named already present in // First-writer-wins: a *types.Named already present in
// c.localTypeNames is left alone, so a synthetic type reachable from // c.localTypeNames is left alone, so a synthetic type reachable from
@@ -920,7 +932,7 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
sort.Slice(found, func(i, j int) bool { sort.Slice(found, func(i, j int) bool {
return found[i].Obj().Pos() < found[j].Obj().Pos() return found[i].Obj().Pos() < found[j].Obj().Pos()
}) })
enclosing := fn.RelString(nil) enclosing := c.canonicalFunctionName(fn)
for i, named := range found { for i, named := range found {
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i)) c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
} }
@@ -929,7 +941,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
// getTypeMethodSet returns a reference (GEP) to a global method set. This // getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass. // method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
globalName := typ.String() + "$methodset" typeName, _ := c.getTypeCodeName(typ)
globalName := typeName + "$methodset"
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if global.IsNil() { if global.IsNil() {
ms := c.program.MethodSets.MethodSet(typ) ms := c.program.MethodSets.MethodSet(typ)
+5 -19
View File
@@ -320,13 +320,7 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
} }
info := functionInfo{ info := functionInfo{
// Pick the default linkName. // Pick the default linkName.
linkName: f.RelString(nil), linkName: c.canonicalFunctionName(f),
}
// RelString is not unique for local type arguments, so add a suffix
// when needed.
if suffix := c.localTypeArgsSuffix(f); suffix != "" {
info.linkName += suffix
} }
// Check for a few runtime functions that are treated specially. // Check for a few runtime functions that are treated specially.
@@ -353,24 +347,16 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
return info return info
} }
func (c *compilerContext) localTypeArgsSuffix(f *ssa.Function) string { func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string {
typeArgs := f.TypeArgs() typeArgs := f.TypeArgs()
if len(typeArgs) == 0 { if len(typeArgs) == 0 {
return "" return f.RelString(nil)
} }
var hasLocal bool
parts := make([]string, len(typeArgs)) parts := make([]string, len(typeArgs))
for i, ta := range typeArgs { for i, ta := range typeArgs {
name, isLocal := c.getTypeCodeName(ta) parts[i], _ = c.getTypeCodeName(ta)
if isLocal {
hasLocal = true
}
parts[i] = name
} }
if !hasLocal { return f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]"
return ""
}
return "$localtype:" + strings.Join(parts, ",")
} }
// parsePragmas is used by getFunctionInfo to parse function pragmas such as // parsePragmas is used by getFunctionInfo to parse function pragmas such as
+15 -9
View File
@@ -17,12 +17,12 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.aliasSize32(ptr %context) unnamed_addr #1 { define hidden i32 @main.aliasSize32(ptr %context) unnamed_addr #1 {
entry: entry:
%0 = call i32 @"main.aliasSize[main.F]"(ptr undef) %0 = call i32 @"main.aliasSize[basic:float32]"(ptr undef)
ret i32 %0 ret i32 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[main.F]"(ptr %context) unnamed_addr #1 { define linkonce_odr hidden i32 @"main.aliasSize[basic:float32]"(ptr %context) unnamed_addr #1 {
entry: entry:
ret i32 4 ret i32 4
} }
@@ -30,15 +30,21 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.aliasSize64(ptr %context) unnamed_addr #1 { define hidden i32 @main.aliasSize64(ptr %context) unnamed_addr #1 {
entry: entry:
%0 = call i32 @"main.aliasSize[main.F]"(ptr undef) %0 = call i32 @"main.aliasSize[basic:float64]"(ptr undef)
ret i32 %0 ret i32 %0
} }
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[basic:float64]"(ptr %context) unnamed_addr #1 {
entry:
ret i32 8
}
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.main(ptr %context) unnamed_addr #1 { define hidden void @main.main(ptr %context) unnamed_addr #1 {
entry: entry:
%0 = call %"main.Point[float32]" @"main.Add[float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef) %0 = call %"main.Point[float32]" @"main.Add[basic:float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
%1 = call %"main.Point[int]" @"main.Add[int]"(i32 0, i32 0, i32 0, i32 0, ptr undef) %1 = call %"main.Point[int]" @"main.Add[basic:int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
%2 = call i32 @main.aliasSize32(ptr undef) %2 = call i32 @main.aliasSize32(ptr undef)
call void @main.checkSize(i32 %2, ptr undef) #4 call void @main.checkSize(i32 %2, ptr undef) #4
%3 = call i32 @main.aliasSize64(ptr undef) %3 = call i32 @main.aliasSize64(ptr undef)
@@ -47,7 +53,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 { define linkonce_odr hidden %"main.Point[float32]" @"main.Add[basic:float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
@@ -110,7 +116,7 @@ declare void @main.checkSize(i32, ptr) #0
declare void @runtime.nilPanic(ptr) #0 declare void @runtime.nilPanic(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[int]" @"main.Add[int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 { define linkonce_odr hidden %"main.Point[int]" @"main.Add[basic:int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
@@ -170,7 +176,7 @@ declare void @main.checkBool(i1, ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.aliasMethod32(ptr %x.typecode, ptr %x.value, ptr %context) unnamed_addr #1 { define hidden void @main.aliasMethod32(ptr %x.typecode, ptr %x.value, ptr %context) unnamed_addr #1 {
entry: entry:
%0 = call i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[main.F]}}.$typeassert"(ptr %x.typecode) #4 %0 = call i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr %x.typecode) #4
br i1 %0, label %typeassert.ok, label %typeassert.next br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry typeassert.next: ; preds = %typeassert.ok, %entry
@@ -181,7 +187,7 @@ typeassert.ok: ; preds = %entry
br label %typeassert.next br label %typeassert.next
} }
declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[main.F]}}.$typeassert"(ptr) #3 declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr) #3
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
+2 -2
View File
@@ -50,12 +50,12 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 { define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 {
entry: entry:
call void @"main.noinlineGenericFunc[int8]"(ptr undef) call void @"main.noinlineGenericFunc[basic:int8]"(ptr undef)
ret void ret void
} }
; Function Attrs: noinline nounwind ; Function Attrs: noinline nounwind
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #4 { define linkonce_odr hidden void @"main.noinlineGenericFunc[basic:int8]"(ptr %context) unnamed_addr #4 {
entry: entry:
ret void ret void
} }
+4 -4
View File
@@ -48,13 +48,13 @@ issue4931PairB labels: 0
issue4931MethodA labels: 0 issue4931MethodA labels: 0
issue4931MethodB labels: 0 issue4931MethodB labels: 0
ok: aliasSize[float32]==32 ok: aliasSize[float32]==32
BUG: aliasSize[float64]==64 ok: aliasSize[float64]==64
ok: aliasLocal[float32] accepts own ok: aliasLocal[float32] accepts own
ok: aliasLocal[float64] accepts own ok: aliasLocal[float64] accepts own
BUG: aliasLocal[float32] rejects [float64] ok: aliasLocal[float32] rejects [float64]
BUG: aliasLocal[float64] rejects [float32] ok: aliasLocal[float64] rejects [float32]
ok: aliasBox[float32] implements Get() float32 ok: aliasBox[float32] implements Get() float32
BUG: aliasBox[float64] implements Get() float64 ok: aliasBox[float64] implements Get() float64
ok: aliasMethod[float32] accepts own ok: aliasMethod[float32] accepts own
ok: aliasMethod[float64] accepts own ok: aliasMethod[float64] accepts own
BUG: aliasMethod[float32] rejects [float64] BUG: aliasMethod[float32] rejects [float64]