Skip to content

Commit d05e0b7

Browse files
committed
compiler: disambiguate function-local named types
1 parent 292f314 commit d05e0b7

4 files changed

Lines changed: 325 additions & 36 deletions

File tree

compiler/compiler.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,22 @@ type compilerContext struct {
9292
pkg *types.Package
9393
packageDir string // directory for this package
9494
runtimePkg *types.Package
95+
localTypeNames map[*types.TypeName]string
9596
}
9697

9798
// newCompilerContext returns a new compiler context ready for use, most
9899
// importantly with a newly created LLVM context and module.
99100
func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext {
100101
c := &compilerContext{
101-
Config: config,
102-
DumpSSA: dumpSSA,
103-
difiles: make(map[string]llvm.Metadata),
104-
ditypes: make(map[types.Type]llvm.Metadata),
105-
machine: machine,
106-
targetData: machine.CreateTargetData(),
107-
functionInfos: map[*ssa.Function]functionInfo{},
108-
astComments: map[string]*ast.CommentGroup{},
102+
Config: config,
103+
DumpSSA: dumpSSA,
104+
difiles: make(map[string]llvm.Metadata),
105+
ditypes: make(map[types.Type]llvm.Metadata),
106+
machine: machine,
107+
targetData: machine.CreateTargetData(),
108+
functionInfos: map[*ssa.Function]functionInfo{},
109+
astComments: map[string]*ast.CommentGroup{},
110+
localTypeNames: map[*types.TypeName]string{},
109111
}
110112

111113
c.ctx = llvm.NewContext()
@@ -300,6 +302,11 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
300302
// Convert AST to SSA.
301303
ssaPkg.Build()
302304

305+
// Assign names to function-local named types before compiling the
306+
// package, so that types declared in different functions (or in
307+
// different instantiations of a generic function) do not collide.
308+
c.scanLocalTypes(ssaPkg)
309+
303310
// Initialize debug information.
304311
if c.Debug {
305312
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{

compiler/interface.go

Lines changed: 286 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,13 +580,27 @@ var basicTypeNames = [...]string{
580580
// getTypeCodeName returns a name for this type that can be used in the
581581
// interface lowering pass to assign type codes as expected by the reflect
582582
// package. See getTypeCodeNum.
583-
func (c *compilerContext) getTypeCodeName(t types.Type) (string, bool) {
583+
//
584+
// isLocal is true when the type is declared inside a function body.
585+
// Such types need a per-declaration (or per instantiation) suffix
586+
// because their printed names are not unique; scanLocalTypes assigns
587+
// the suffix and stores the result in c.localTypeNames.
588+
func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) {
584589
switch t := types.Unalias(t).(type) {
585590
case *types.Named:
586-
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
587-
return "named:" + t.String() + "$local", true
591+
tn := t.Obj()
592+
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
593+
// Package-scope or builtin: the printed name is unique.
594+
return "named:" + t.String(), false
595+
}
596+
// Function-local type. Both ordinary locals (Parent() != nil)
597+
// and synthetic locals from generic instantiation
598+
// (Parent() == nil) are pre-registered by scanLocalTypes.
599+
n, ok := c.localTypeNames[tn]
600+
if !ok {
601+
panic("compiler: local type " + tn.Name() + " was not registered by scanLocalTypes")
588602
}
589-
return "named:" + t.String(), false
603+
return "named:" + n, true
590604
case *types.Array:
591605
s, isLocal := c.getTypeCodeName(t.Elem())
592606
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
@@ -672,6 +686,274 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (string, bool) {
672686
}
673687
}
674688

689+
// scanLocalTypes assigns names to every function-local named type in
690+
// the package and stores them in c.localTypeNames. Two flavors are
691+
// handled:
692+
//
693+
// 1. Synthetic TypeNames produced by generic instantiation
694+
// (TypeName.Parent() == nil). Two instantiations of the same
695+
// generic function (e.g. F[int] and F[string]) produce TypeNames
696+
// with the same printed name and the same source position, so
697+
// each one is named with the enclosing instance's RelString as
698+
// prefix. RelString encodes the type arguments, matching Go's
699+
// runtime behavior, where F[int].Inner and F[string].Inner are
700+
// distinct types even when Inner does not mention the type
701+
// parameter.
702+
//
703+
// 2. Ordinary function-local TypeNames (TypeName.Parent() != nil).
704+
// Each one is named with its declaring function's RelString plus
705+
// a per-function counter assigned in source order. This mirrors
706+
// the ·N suffix the standard Go compiler uses for such types and
707+
// is robust against //line directives that would otherwise make a
708+
// file:line:column suffix non-unique.
709+
//
710+
// Names depend only on intrinsic SSA properties (RelString and the
711+
// raw token.Pos used as a sort key), so any package compiling the
712+
// same function or instance produces the same identifier.
713+
func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
714+
// Pass 1: locate every generic instance reachable from this
715+
// package (including instances declared in imported packages and
716+
// any function reached through an instance subtree). Synthetic
717+
// TypeNames are produced by instantiation, so we need the call
718+
// graph to find them all.
719+
var instances []*ssa.Function
720+
seenInstWalk := map[*ssa.Function]bool{}
721+
var instWalk func(fn *ssa.Function, inInstance bool)
722+
instWalk = func(fn *ssa.Function, inInstance bool) {
723+
if fn == nil || seenInstWalk[fn] {
724+
return
725+
}
726+
// fn belongs to an instance subtree if it is itself an
727+
// instantiation or if we reached it from one.
728+
//
729+
// len(TypeArgs()) is used instead of fn.Origin() because
730+
// Origin() may call Build() on fn's declaring package, which
731+
// would defeat per-package compilation.
732+
isInstanceRoot := len(fn.TypeArgs()) > 0
733+
if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg {
734+
return
735+
}
736+
if fn.Blocks == nil && fn.AnonFuncs == nil {
737+
return
738+
}
739+
seenInstWalk[fn] = true
740+
isInInstance := inInstance || isInstanceRoot
741+
if isInInstance {
742+
instances = append(instances, fn)
743+
}
744+
for _, anon := range fn.AnonFuncs {
745+
instWalk(anon, isInInstance)
746+
}
747+
var ops [10]*ssa.Value
748+
for _, b := range fn.Blocks {
749+
for _, instr := range b.Instrs {
750+
for _, op := range instr.Operands(ops[:0]) {
751+
if op == nil || *op == nil {
752+
continue
753+
}
754+
if callee, ok := (*op).(*ssa.Function); ok {
755+
instWalk(callee, isInInstance)
756+
}
757+
}
758+
}
759+
}
760+
}
761+
for _, member := range ssaPkg.Members {
762+
switch m := member.(type) {
763+
case *ssa.Function:
764+
instWalk(m, false)
765+
case *ssa.Type:
766+
mset := c.program.MethodSets.MethodSet(m.Type())
767+
for i := 0; i < mset.Len(); i++ {
768+
instWalk(c.program.MethodValue(mset.At(i)), false)
769+
}
770+
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
771+
for i := 0; i < pmset.Len(); i++ {
772+
instWalk(c.program.MethodValue(pmset.At(i)), false)
773+
}
774+
}
775+
}
776+
777+
// Pass 2: collect every non-instance function defined in this
778+
// package together with its closures. Ordinary function-local
779+
// TypeNames are scoped to their declaring function (and visible
780+
// in nested closures only), so the declaring function is always
781+
// somewhere in this lexical tree. Following callees here would
782+
// be wrong: an instantiated function whose substituted signature
783+
// mentions the local type would otherwise race with the actual
784+
// declaring function for ownership.
785+
var packageFuncs []*ssa.Function
786+
var collect func(fn *ssa.Function)
787+
collect = func(fn *ssa.Function) {
788+
if fn == nil || fn.Pkg != ssaPkg {
789+
return
790+
}
791+
if len(fn.TypeArgs()) > 0 {
792+
// Generic instances are handled by pass 1 (their local
793+
// types are synthetic).
794+
return
795+
}
796+
if fn.Blocks == nil && fn.AnonFuncs == nil {
797+
return
798+
}
799+
packageFuncs = append(packageFuncs, fn)
800+
for _, anon := range fn.AnonFuncs {
801+
collect(anon)
802+
}
803+
}
804+
for _, member := range ssaPkg.Members {
805+
switch m := member.(type) {
806+
case *ssa.Function:
807+
collect(m)
808+
case *ssa.Type:
809+
mset := c.program.MethodSets.MethodSet(m.Type())
810+
for i := 0; i < mset.Len(); i++ {
811+
collect(c.program.MethodValue(mset.At(i)))
812+
}
813+
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
814+
for i := 0; i < pmset.Len(); i++ {
815+
collect(c.program.MethodValue(pmset.At(i)))
816+
}
817+
}
818+
}
819+
820+
// Registration is first-writer-wins, so visit each list in a
821+
// deterministic order. Pos() is a defensive tiebreaker.
822+
sortFns := func(fns []*ssa.Function) {
823+
sort.Slice(fns, func(i, j int) bool {
824+
ri, rj := fns[i].RelString(nil), fns[j].RelString(nil)
825+
if ri != rj {
826+
return ri < rj
827+
}
828+
return fns[i].Pos() < fns[j].Pos()
829+
})
830+
}
831+
sortFns(instances)
832+
sortFns(packageFuncs)
833+
for _, fn := range instances {
834+
c.registerLocalTypes(fn, true)
835+
}
836+
for _, fn := range packageFuncs {
837+
c.registerLocalTypes(fn, false)
838+
}
839+
}
840+
841+
// registerLocalTypes walks every type reachable from fn's body and
842+
// records each function-local TypeName whose Parent() matches the
843+
// synthetic flag (Parent() == nil for synthetic, != nil otherwise) in
844+
// c.localTypeNames. Each TypeName is named with fn.RelString as the
845+
// owning function plus a per-function counter assigned in source order.
846+
//
847+
// First-writer-wins: a TypeName already present in c.localTypeNames
848+
// is left alone. The slot is reserved with an empty string during
849+
// collection so later registerLocalTypes calls (within the same
850+
// scanLocalTypes invocation) skip it; the final name is filled in
851+
// after sorting, before scanLocalTypes returns and any
852+
// getTypeCodeName lookups happen.
853+
func (c *compilerContext) registerLocalTypes(fn *ssa.Function, synthetic bool) {
854+
var found []*types.TypeName
855+
seen := map[types.Type]bool{}
856+
var visit func(t types.Type)
857+
visit = func(t types.Type) {
858+
if t == nil || seen[t] {
859+
return
860+
}
861+
seen[t] = true
862+
switch t := t.(type) {
863+
case *types.Alias:
864+
visit(types.Unalias(t))
865+
case *types.Named:
866+
tn := t.Obj()
867+
if tn.Pkg() != nil && (tn.Parent() == nil) == synthetic {
868+
if _, ok := c.localTypeNames[tn]; !ok {
869+
c.localTypeNames[tn] = ""
870+
found = append(found, tn)
871+
}
872+
}
873+
targs := t.TypeArgs()
874+
for i := 0; i < targs.Len(); i++ {
875+
visit(targs.At(i))
876+
}
877+
visit(t.Underlying())
878+
case *types.Pointer:
879+
visit(t.Elem())
880+
case *types.Slice:
881+
visit(t.Elem())
882+
case *types.Array:
883+
visit(t.Elem())
884+
case *types.Chan:
885+
visit(t.Elem())
886+
case *types.Map:
887+
visit(t.Key())
888+
visit(t.Elem())
889+
case *types.Struct:
890+
for i := 0; i < t.NumFields(); i++ {
891+
visit(t.Field(i).Type())
892+
}
893+
case *types.Signature:
894+
if p := t.Params(); p != nil {
895+
for i := 0; i < p.Len(); i++ {
896+
visit(p.At(i).Type())
897+
}
898+
}
899+
if r := t.Results(); r != nil {
900+
for i := 0; i < r.Len(); i++ {
901+
visit(r.At(i).Type())
902+
}
903+
}
904+
case *types.Tuple:
905+
for i := 0; i < t.Len(); i++ {
906+
visit(t.At(i).Type())
907+
}
908+
case *types.Interface:
909+
// A local type can be reachable only through a local
910+
// interface's method signature, so descend into them.
911+
// getTypeCodeName encodes those signatures into the
912+
// interface's identifier, and the seen map breaks
913+
// cycles formed by methods that mention the interface
914+
// itself.
915+
for i := 0; i < t.NumMethods(); i++ {
916+
visit(t.Method(i).Type())
917+
}
918+
}
919+
}
920+
for _, p := range fn.Params {
921+
visit(p.Type())
922+
}
923+
for _, fv := range fn.FreeVars {
924+
visit(fv.Type())
925+
}
926+
for _, l := range fn.Locals {
927+
visit(l.Type())
928+
}
929+
var ops [10]*ssa.Value
930+
for _, b := range fn.Blocks {
931+
for _, instr := range b.Instrs {
932+
if v, ok := instr.(ssa.Value); ok {
933+
visit(v.Type())
934+
}
935+
for _, op := range instr.Operands(ops[:0]) {
936+
if op != nil && *op != nil {
937+
visit((*op).Type())
938+
}
939+
}
940+
}
941+
}
942+
if len(found) == 0 {
943+
return
944+
}
945+
// Sort by raw token.Pos: this gives a total order on declarations
946+
// that is stable across builds and unaffected by //line directives
947+
// (which only adjust the human-facing position from Fset.Position).
948+
sort.Slice(found, func(i, j int) bool {
949+
return found[i].Pos() < found[j].Pos()
950+
})
951+
enclosing := fn.RelString(nil)
952+
for i, tn := range found {
953+
c.localTypeNames[tn] = enclosing + "." + tn.Name() + "$" + strconv.Itoa(i+1)
954+
}
955+
}
956+
675957
// getTypeMethodSet returns a reference (GEP) to a global method set. This
676958
// method set should be unreferenced after the interface lowering pass.
677959
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {

0 commit comments

Comments
 (0)