From 8ee583d3f308543ed18b4573089ba301efc43d6a Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 02:22:28 +0800 Subject: [PATCH] cmd/compile: encode linkname pulls in LLVM names --- src/cmd/compile/internal/ssa/llvmdata.go | 69 +++++++++++++++---- src/cmd/compile/internal/ssa/ssa2llvm.go | 1 + src/cmd/compile/internal/ssa/ssa2llvm_test.go | 58 +++++++++++----- src/cmd/internal/goobj/builtin.go | 5 +- test/codegen/llvm_linkname.go | 34 +++++++++ test/llvm_tests.json | 1 + 6 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 test/codegen/llvm_linkname.go diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index d73f9909cf7eba..3cdb622def25a9 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -4,6 +4,7 @@ package ssa import ( "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/internal/goobj" "cmd/internal/obj" @@ -18,16 +19,55 @@ import ( "github.com/goallc/go-llvm" ) +type llvmGoObjSymbolKey struct { + name string + abi obj.ABI +} + +var llvmGoObjLocalDefinitions map[llvmGoObjSymbolKey]bool + +func llvmGoObjSymbolKeyFor(s *obj.LSym) llvmGoObjSymbolKey { + return llvmGoObjSymbolKey{name: s.Name, abi: s.ABI()} +} + +// initLLVMGoObjLocalDefinitions records the distinction needed only by +// linkname: a declaration without a local definition is a pull, while a local +// function or data definition is a push. Builtin references do not consult +// this set. +func initLLVMGoObjLocalDefinitions() { + llvmGoObjLocalDefinitions = make(map[llvmGoObjSymbolKey]bool) + for _, fn := range typecheck.Target.Funcs { + if fn == nil || fn.Nname == nil || len(fn.Body) == 0 { + continue + } + s := fn.LinksymABI(fn.ABI) + llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true + } + for _, name := range typecheck.Target.Externs { + if name == nil || name.Op() != ir.ONAME || name.Class != ir.PEXTERN { + continue + } + s := name.Linksym() + llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true + } +} + +func llvmGoObjLinknameReference(s *obj.LSym) bool { + return s != nil && (s.IsLinkname() || s.IsLinknameStd()) && + !llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] +} + // llvmGoObjReferenceName is the single naming boundary for undefined Go -// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only -// suffix tells the GoObj writer to serialize a surviving relocation through -// the predefined builtin index table. +// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only name +// records whether the GoObj writer must serialize a surviving relocation as a +// builtin-index or linkname pull. func llvmGoObjReferenceName(s *obj.LSym) string { if s == nil { base.Fatalf("nil GoObj symbol reference") } - if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) { - base.Fatalf("Go symbol name %q uses reserved LLVM builtin suffix", s.Name) + if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) || + strings.Contains(s.Name, goobj.LinknameSymbolSuffix) { + base.Fatalf("Go symbol name %q uses reserved LLVM reference suffix", s.Name) } if base.Ctxt.Flag_linkshared { return s.Name @@ -35,10 +75,9 @@ func llvmGoObjReferenceName(s *obj.LSym) string { if name, ok := goobj.BuiltinSymbolName(s.Name, int(s.ABI())); ok { return name } - // Linkname references currently retain their ordinary linker name. A - // runtime implementation may itself be linknamed while compiler-generated - // references to the same logical symbol still use the builtin table, so the - // builtin lookup above deliberately takes precedence over this attribute. + if llvmGoObjLinknameReference(s) { + return s.Name + goobj.LinknameSymbolSuffix + } return s.Name } @@ -69,21 +108,22 @@ func emitGoObjCgoModuleAsm() { } // attachGoObjSymbolRef attaches the part of an undefined imported Go symbol's -// identity that cannot be recovered from an LLVM relocation. Builtin identity -// is carried by the declaration name instead. +// identity that cannot be recovered from an LLVM relocation. Builtin and +// linkname identity is carried by the declaration name instead. func attachGoObjSymbolRef(value llvm.Value, s *obj.LSym) { if value.IsNil() || s == nil { base.Fatalf("invalid LLVM value in GoObj symbol reference") } - if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) { + if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) || + strings.Contains(value.Name(), goobj.LinknameSymbolSuffix) { return } // Linknamed symbols live in GoObj's non-package namespace even when the // compiler learned about them through an imported package. Their export // symbol index addresses that package's ordinary symbol block and must not // be attached to the LLVM declaration as an imported reference. - if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() { + if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() || s.IsLinknameStd() { return } localPkg := objabi.PathToPrefix(base.Ctxt.Pkgpath) @@ -656,6 +696,9 @@ func setGoObjDataFlags(g llvm.Value, s *obj.LSym) { if s.IsLinkname() { flag2 |= 1 << 4 // goobj.SymFlagLinkname } + if s.IsLinknameStd() { + flag2 |= goobj.SymFlagLinknameStd + } if s.ABIWrapper() { flag2 |= 1 << 5 // goobj.SymFlagABIWrapper } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index c2eb1be100793b..e431ea4a3a8d01 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -3576,6 +3576,7 @@ func InitModule(pkg *types.Pkg) { currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) goObjCompilerUsed = nil goObjCompilerUsedNames = make(map[string]bool) + initLLVMGoObjLocalDefinitions() emitLateGoObjBuiltinDeclarations() initLLVMDebugInfo(pkg) } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_test.go b/src/cmd/compile/internal/ssa/ssa2llvm_test.go index 6dd54e3519e954..5a254df0b77e5f 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_test.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_test.go @@ -193,38 +193,60 @@ func TestLLVMFunctionStorageName(t *testing.T) { } } -func TestLLVMGoObjBuiltinReferenceName(t *testing.T) { +func TestLLVMGoObjReferenceNames(t *testing.T) { oldLinkshared := base.Ctxt.Flag_linkshared + oldLocalDefinitions := llvmGoObjLocalDefinitions base.Ctxt.Flag_linkshared = false - t.Cleanup(func() { base.Ctxt.Flag_linkshared = oldLinkshared }) + llvmGoObjLocalDefinitions = make(map[llvmGoObjSymbolKey]bool) + t.Cleanup(func() { + base.Ctxt.Flag_linkshared = oldLinkshared + llvmGoObjLocalDefinitions = oldLocalDefinitions + }) - s := base.Ctxt.LookupABI("runtime.panicdivide", obj.ABIInternal) - want, ok := goobj.BuiltinSymbolName(s.Name, int(s.ABI())) + builtin := base.Ctxt.LookupABI("runtime.panicdivide", obj.ABIInternal) + wantBuiltin, ok := goobj.BuiltinSymbolName(builtin.Name, int(builtin.ABI())) if !ok { t.Fatal("runtime.panicdivide is absent from GoObj builtin table") } - if got := llvmGoObjReferenceName(s); got != want { - t.Fatalf("builtin reference name = %q, want %q", got, want) + if got := llvmGoObjReferenceName(builtin); got != wantBuiltin { + t.Fatalf("builtin reference name = %q, want %q", got, wantBuiltin) } - oldLinkname := s.IsLinkname() - t.Cleanup(func() { s.Set(obj.AttrLinkname, oldLinkname) }) - s.Set(obj.AttrLinkname, true) - if got := llvmGoObjReferenceName(s); got != want { - t.Fatalf("linknamed builtin reference name = %q, want builtin %q", got, want) + // Builtin and linkname are mutually exclusive reference encodings. The + // builtin table wins when an implementation also carries a linkname bit. + oldBuiltinLinkname := builtin.IsLinkname() + builtin.Set(obj.AttrLinkname, true) + t.Cleanup(func() { builtin.Set(obj.AttrLinkname, oldBuiltinLinkname) }) + if got := llvmGoObjReferenceName(builtin); got != wantBuiltin { + t.Fatalf("linknamed builtin reference name = %q, want %q", got, wantBuiltin) } - s.Set(obj.AttrLinkname, oldLinkname) - linkname := base.Ctxt.LookupABI("runtime.llvmLinknameOnly", obj.ABIInternal) - oldLinknameOnly := linkname.IsLinkname() - t.Cleanup(func() { linkname.Set(obj.AttrLinkname, oldLinknameOnly) }) + + linkname := base.Ctxt.LookupABI("runtime.llvmLinknamePull", obj.ABIInternal) + oldLinkname := linkname.IsLinkname() linkname.Set(obj.AttrLinkname, true) + t.Cleanup(func() { linkname.Set(obj.AttrLinkname, oldLinkname) }) + if got, want := llvmGoObjReferenceName(linkname), linkname.Name+goobj.LinknameSymbolSuffix; got != want { + t.Fatalf("linkname pull name = %q, want %q", got, want) + } + llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(linkname)] = true if got := llvmGoObjReferenceName(linkname); got != linkname.Name { - t.Fatalf("non-builtin linkname reference name = %q, want %q", got, linkname.Name) + t.Fatalf("local linkname definition name = %q, want %q", got, linkname.Name) + } + + linknameStd := base.Ctxt.LookupABI("runtime.llvmLinknameStdPull", obj.ABIInternal) + oldLinknameStd := linknameStd.IsLinknameStd() + linknameStd.Set(obj.AttrLinknameStd, true) + t.Cleanup(func() { linknameStd.Set(obj.AttrLinknameStd, oldLinknameStd) }) + if got, want := llvmGoObjReferenceName(linknameStd), linknameStd.Name+goobj.LinknameSymbolSuffix; got != want { + t.Fatalf("linknamestd pull name = %q, want %q", got, want) } + builtin.Set(obj.AttrLinkname, oldBuiltinLinkname) base.Ctxt.Flag_linkshared = true - if got := llvmGoObjReferenceName(s); got != s.Name { - t.Fatalf("linkshared builtin reference name = %q, want %q", got, s.Name) + for _, s := range []*obj.LSym{builtin, linkname, linknameStd} { + if got := llvmGoObjReferenceName(s); got != s.Name { + t.Fatalf("linkshared reference name = %q, want %q", got, s.Name) + } } } diff --git a/src/cmd/internal/goobj/builtin.go b/src/cmd/internal/goobj/builtin.go index 131ec8d602ef27..635f3ff401a7d4 100644 --- a/src/cmd/internal/goobj/builtin.go +++ b/src/cmd/internal/goobj/builtin.go @@ -9,7 +9,10 @@ import ( "strconv" ) -const BuiltinSymbolSuffixPrefix = ""() +// LLVM: declare goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-NOT: @"runtime.llvmLinknameLocal" +// LLVM-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal"() +// LLVM: call goabiinternal i64 @runtime.llvmLinknameLocal() +// LLVM-LABEL: define goabiinternal i64 @runtime.llvmLinknameLocal() +// LLVM-OPT-LABEL: define goabiinternal i64 @codegen.llvmLinknameCalls() +// LLVM-OPT: call goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-OPT: declare goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-OPT-NOT: @"runtime.llvmLinknameLocal" +// LLVM-OPT-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal"() +// LLVM-OPT-LABEL: define goabiinternal {{.*}}@runtime.llvmLinknameLocal() +func llvmLinknameCalls() int { + return llvmLinknameExternal() + llvmLinknameLocal() +} diff --git a/test/llvm_tests.json b/test/llvm_tests.json index f57350aa5db3cb..30bae97d1e85ca 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -55,6 +55,7 @@ "codegen/llvm_trunc.go": "SSA-verified float64 truncation toward zero through the LLVM trunc intrinsic", "codegen/llvm_memops.go": "pointer-free zero and overlap-safe move intrinsics, runtime memequal, and native-int slice masks", "codegen/llvm_memory_order.go": "preserve Go SSA Memory-token order when LLVM emission precedes native scheduling", + "codegen/llvm_linkname.go": "external linkname pulls use a name suffix while local definitions and ABI wrappers remain canonical", "codegen/llvm_newproc.go": "pointer-typed funcval argument for the runtime.newproc raw ABI call", "codegen/llvm_nilcheck.go": "explicit branch to recoverable runtime.panicmem with stable continuation and optimized IR checks", "codegen/llvm_reflect_method.go": "reflect method calls preserve linker reachability flags in LLVM GoObj functions",