Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions src/cmd/compile/internal/ssa/llvmdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -18,27 +19,65 @@ 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
}
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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/ssa/ssa2llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
58 changes: 40 additions & 18 deletions src/cmd/compile/internal/ssa/ssa2llvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/cmd/internal/goobj/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"strconv"
)

const BuiltinSymbolSuffixPrefix = "<builtin."
const (
BuiltinSymbolSuffixPrefix = "<builtin."
LinknameSymbolSuffix = "<linkname>"
)

// Builtin (compiler-generated) function references appear
// frequently. We assign special indices for them, so they
Expand Down
34 changes: 34 additions & 0 deletions test/codegen/llvm_linkname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// asmcheck

// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package codegen

import _ "unsafe"

//go:linkname llvmLinknameExternal runtime.llvmLinknameExternal
func llvmLinknameExternal() int

//go:linkname llvmLinknameLocal runtime.llvmLinknameLocal
func llvmLinknameLocal() int {
return 7
}

// LLVM-LABEL: define goabiinternal i64 @codegen.llvmLinknameCalls()
// LLVM: call goabiinternal i64 @"runtime.llvmLinknameExternal<linkname>"()
// LLVM: declare goabiinternal i64 @"runtime.llvmLinknameExternal<linkname>"()
// LLVM-NOT: @"runtime.llvmLinknameLocal<linkname>"
// LLVM-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal<ABI0>"()
// 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<linkname>"()
// LLVM-OPT: declare goabiinternal i64 @"runtime.llvmLinknameExternal<linkname>"()
// LLVM-OPT-NOT: @"runtime.llvmLinknameLocal<linkname>"
// LLVM-OPT-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal<ABI0>"()
// LLVM-OPT-LABEL: define goabiinternal {{.*}}@runtime.llvmLinknameLocal()
func llvmLinknameCalls() int {
return llvmLinknameExternal() + llvmLinknameLocal()
}
1 change: 1 addition & 0 deletions test/llvm_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading