From 4d75ca032554e8efd829c6092c9c804dbd8eac58 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 14 Aug 2026 02:32:58 +0800 Subject: [PATCH 1/5] cmd/compile: support runtime-specific LLVM lowering --- src/cmd/compile/internal/gc/compile.go | 14 +- src/cmd/compile/internal/liveness/plive.go | 9 +- src/cmd/compile/internal/ssa/llvmdata.go | 75 ++++++++- src/cmd/compile/internal/ssa/llvmtypeddata.go | 2 + src/cmd/compile/internal/ssa/ssa2llvm.go | 122 ++++++++++++-- src/cmd/compile/internal/ssa/ssa2llvm_stub.go | 8 +- src/cmd/compile/internal/ssa/ssa2llvm_test.go | 155 ++++++++++++++++++ src/cmd/compile/internal/ssagen/pgen.go | 3 + .../vendor/github.com/goallc/go-llvm/ir.go | 3 + test/codegen/_cgo_llvm_unsafe_args.go | 14 +- test/codegen/llvm_memops.go | 4 +- 11 files changed, 379 insertions(+), 30 deletions(-) diff --git a/src/cmd/compile/internal/gc/compile.go b/src/cmd/compile/internal/gc/compile.go index ff340f35a01228..8a188e0648ff05 100644 --- a/src/cmd/compile/internal/gc/compile.go +++ b/src/cmd/compile/internal/gc/compile.go @@ -16,6 +16,7 @@ import ( "cmd/compile/internal/liveness" "cmd/compile/internal/objw" "cmd/compile/internal/pgoir" + "cmd/compile/internal/ssa" "cmd/compile/internal/ssagen" "cmd/compile/internal/staticinit" "cmd/compile/internal/types" @@ -66,10 +67,15 @@ func enqueueFunc(fn *ir.Func, symABIs *ssagen.SymABIs) { // is ABI0, and only ABI0 assembly function can have a FUNCDATA // reference to args_stackmap (see cmd/internal/obj/plist.go:Flushplist). // So avoid introducing an args_stackmap if the func is not ABI0. - liveness.WriteFuncMap(fn, abiInfo) - - x := ssagen.EmitArgInfo(fn, abiInfo) - objw.Global(x, int32(len(x.P)), obj.RODATA|obj.LOCAL) + argMap := liveness.WriteFuncMap(fn, abiInfo) + + argInfo := ssagen.EmitArgInfo(fn, abiInfo) + objw.Global(argInfo, int32(len(argInfo.P)), obj.RODATA|obj.LOCAL) + if base.Flag.EnableLLVM && symABIs.HasDef(fn.Sym()) { + // Assembly FUNCDATA directives refer to these definitions from + // another archive member, so LLVM cannot discover their uses. + ssa.MarkGoObjDataReferencedOutsideLLVM(argMap, argInfo) + } } return } diff --git a/src/cmd/compile/internal/liveness/plive.go b/src/cmd/compile/internal/liveness/plive.go index 198f6418d6363a..eb034bc7d04200 100644 --- a/src/cmd/compile/internal/liveness/plive.go +++ b/src/cmd/compile/internal/liveness/plive.go @@ -1555,11 +1555,11 @@ func isfat(t *types.Type) bool { } // WriteFuncMap writes the pointer bitmaps for bodyless function fn's -// inputs and outputs as the value of symbol .args_stackmap. -// If fn has outputs, two bitmaps are written, otherwise just one. -func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) { +// inputs and outputs as the value of symbol .args_stackmap and returns +// that symbol. If fn has outputs, two bitmaps are written, otherwise just one. +func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) *obj.LSym { if ir.FuncName(fn) == "_" { - return + return nil } nptr := int(abiInfo.ArgWidth() / int64(types.PtrSize)) bv := bitvec.New(int32(nptr)) @@ -1592,6 +1592,7 @@ func WriteFuncMap(fn *ir.Func, abiInfo *abi.ABIParamResultInfo) { } objw.Global(lsym, int32(off), obj.RODATA|obj.LOCAL) + return lsym } // checkStackmapOverflow checks for potential overflow in runtime stackmap reading. diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 955e741ff88eba..0498627a56e730 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -193,10 +193,38 @@ func LowerGoObjData() { setGoObjKeepMetadata(g, s) setGoObjGotypeMetadata(g, s) setGoObjMarkerRelocMetadata(g, s) + if lowerer.externalRoots[s] { + // This definition is referenced from a different archive member, so + // its use is invisible to LLVM. Keep it present and distinct through + // GlobalDCE and ConstantMerge; Go linker reachability still decides + // whether the GoObj symbol survives in the final binary. + preserveGoObjMetadataValues(g) + } + } + for s := range lowerer.externalRoots { + if !lowerer.lowered[s] { + base.Fatalf("GoObj data referenced outside LLVM was not lowered: %s", s.Name) + } } emitGoObjCompilerUsed() } +// MarkGoObjDataReferencedOutsideLLVM marks compiler data definitions whose +// references live in another archive member and therefore cannot participate +// in LLVM IR reachability. The definitions are kept only through object +// emission; the Go linker remains responsible for final reachability. +func MarkGoObjDataReferencedOutsideLLVM(syms ...*obj.LSym) { + if currentLLVMDataLowerer == nil { + base.Fatalf("marking external GoObj data before LLVM module initialization") + } + for _, s := range syms { + if s == nil { + base.Fatalf("marking nil GoObj data referenced outside LLVM") + } + currentLLVMDataLowerer.externalRoots[s] = true + } +} + // FinalizeGoObjSymbolMetadata carries native GoObj definition classes and // package-local indices after NumberSyms has assigned them. LowerGoObjData runs // first so imported-reference metadata retains the same pre-numbering @@ -243,6 +271,14 @@ func setGoObjPackageSymbolIndexMetadata(value llvm.Value, s *obj.LSym) { if value.IsNil() || s == nil || s.PkgIdx != goobj.PkgIdxSelf || !s.Indexed() || s.SymIdx < 0 { base.Fatalf("invalid LLVM GoObj package symbol index") } + // Calls emitted before obj.NumberSyms may initially classify a runtime + // builtin as an undefined PkgIdxBuiltin reference. When compiling runtime, + // that same LLVM GlobalValue later becomes this package's definition. + // Definitions are addressed by their package symbol index, so remove the + // now-stale undefined-reference attachment before handing the module to the + // GoObj AsmPrinter. + value.EraseGlobalMetadata(GlobalCtxt.MDKindID("goobj.builtin")) + value.EraseGlobalMetadata(GlobalCtxt.MDKindID("goobj.import")) value.SetGlobalMetadata(GlobalCtxt.MDKindID(goObjSymbolIndexMD), GlobalCtxt.MDNode([]llvm.Metadata{ llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(s.SymIdx), false).ConstantAsMetadata(), })) @@ -275,7 +311,11 @@ func llvmGoDataRef(s *obj.LSym) llvm.Value { if s == nil { base.Fatalf("nil Go data symbol in LLVM lowering") } - if s.Type == objabi.STEXT || s.Type == objabi.STEXTFIPS || s.ABI() == obj.ABIInternal { + // FuncPCABI0 carries an ABI0 LSym through OpAddr, but bodyless assembly + // functions still have the unresolved Sxxx kind here. Recover the semantic + // function identity from the front end before choosing an LLVM GlobalValue; + // ABI alone is insufficient because ordinary data symbols also use ABI0. + if llvmGoFunctionSymbol(s) { data := map[*obj.LSym]bool(nil) if currentLLVMDataLowerer != nil { data = currentLLVMDataLowerer.data @@ -325,6 +365,28 @@ func llvmGoDataRef(s *obj.LSym) llvm.Value { return g } +func llvmGoFunctionSymbol(s *obj.LSym) bool { + if s.Type == objabi.STEXT || s.Type == objabi.STEXTFIPS || s.ABI() == obj.ABIInternal { + return true + } + // Bodyless assembly declarations are initialized without setupTextLSym, so + // their LSym remains Sxxx. typecheck.Target.Funcs is the authoritative list + // of current-package function declarations and includes generated ABI + // wrappers before LLVM module initialization. + if typecheck.Target == nil { + return false + } + for _, fn := range typecheck.Target.Funcs { + if fn == nil || fn.Nname == nil || fn.Sym() == nil || fn.Sym().Name == "_" { + continue + } + if fn.LinksymABI(fn.ABI) == s { + return true + } + } + return false +} + func (l *llvmDataLowerer) globalName(s *obj.LSym) string { if s.Name != "" { return s.Name @@ -459,7 +521,7 @@ func llvmExternalDataRef(s *obj.LSym, data map[*obj.LSym]bool) llvm.Value { // at this point. Their ABI nevertheless identifies them as functions (for // example runtime.memequal64 in an equality closure), so do not rely on // STEXT alone here. - if s.Type == objabi.STEXT || s.Type == objabi.STEXTFIPS || s.ABI() == obj.ABIInternal { + if llvmGoFunctionSymbol(s) { storageName := llvmFunctionStorageName(s.Name, llvmCallConv(s.ABI())) if f := CurrentModule.NamedFunction(storageName); !f.IsNil() { attachGoObjSymbolRef(f, s) @@ -564,9 +626,18 @@ func setGoObjFunctionFlags(fn llvm.Value, s *obj.LSym) { if s.ReflectMethod() { flag |= goobj.SymFlagReflectMethod } + if s.NoSplit() { + flag |= goobj.SymFlagNoSplit + } + if s.IsPkgInit() { + flag2 |= goobj.SymFlagPkgInit + } if s.IsLinkname() || s.Name == "main.main" { flag2 |= goobj.SymFlagLinkname } + if s.IsLinknameStd() { + flag2 |= goobj.SymFlagLinknameStd + } if s.ABIWrapper() { flag2 |= goobj.SymFlagABIWrapper } diff --git a/src/cmd/compile/internal/ssa/llvmtypeddata.go b/src/cmd/compile/internal/ssa/llvmtypeddata.go index 1bae746864e857..165c46de1677d7 100644 --- a/src/cmd/compile/internal/ssa/llvmtypeddata.go +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -22,6 +22,7 @@ import ( type llvmDataLowerer struct { data map[*obj.LSym]bool roots map[*obj.LSym]bool + externalRoots map[*obj.LSym]bool lowered map[*obj.LSym]bool values map[*obj.LSym]llvm.Value anonymousCount int @@ -34,6 +35,7 @@ func newLLVMDataLowerer(data map[*obj.LSym]bool) *llvmDataLowerer { return &llvmDataLowerer{ data: data, roots: make(map[*obj.LSym]bool), + externalRoots: make(map[*obj.LSym]bool), lowered: make(map[*obj.LSym]bool), values: make(map[*obj.LSym]llvm.Value), runtimeTypes: make(map[*types.Type]llvm.Type), diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index ff6c67c2990f46..95cb51b6ffbff9 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -580,7 +580,12 @@ func (lfc *LLVMFuncContext) currentG(v *Value) llvm.Value { } i64 := GlobalCtxt.Int64Type() - registerName := GlobalCtxt.MetadataAsValue(GlobalCtxt.MDString(register)) + // llvm.read_register requires a metadata node whose first operand is the + // register-name string. A bare MDString passes IR verification but crashes + // SelectionDAG's intrinsic lowering when it casts the operand to MDNode. + registerName := GlobalCtxt.MetadataAsValue(GlobalCtxt.MDNode([]llvm.Metadata{ + GlobalCtxt.MDString(register), + })) sig := llvm.FunctionType(i64, []llvm.Type{registerName.Type()}, false) fn := getOrInsertLLVMIntrinsic("llvm.read_register.i64", sig) raw := lfc.b.CreateCall(sig, fn, []llvm.Value{registerName}, v.String()+".register") @@ -943,9 +948,8 @@ func (lfc *LLVMFuncContext) cgoUnsafeArgAddress(name *ir.Name, llvmName string) return lfc.b.CreateGEP(GlobalCtxt.Int8Type(), lfc.ABI0FrameBase, []llvm.Value{index}, llvmName+".frame") } -func markLLVMGCLeaf(fn, call llvm.Value) { +func markLLVMGCLeafCall(call llvm.Value) { attr := GlobalCtxt.CreateStringAttribute(goGCLeafFunctionAttr, "") - fn.AddFunctionAttr(attr) call.AddCallSiteAttribute(llvmAttributeFunctionIndex, attr) } @@ -1069,7 +1073,7 @@ func (lfc *LLVMFuncContext) llvmRuntimeMemmove(dst, src, length llvm.Value) llvm attachGoObjABISymbolRef(fn, "runtime.memmove", obj.ABIInternal) call := lfc.b.CreateCall(sig.Type, fn, []llvm.Value{dst, src, length}, "") call.SetInstructionCallConv(goABIInternalCallConv) - markLLVMGCLeaf(fn, call) + markLLVMGCLeafCall(call) return call } @@ -1129,7 +1133,7 @@ func (lfc *LLVMFuncContext) llvmMemEq(v *Value) llvm.Value { attachGoObjABISymbolRef(fn, "runtime.memequal", obj.ABIInternal) call := lfc.b.CreateCall(sig.Type, fn, []llvm.Value{left, right, size}, v.String()) call.SetInstructionCallConv(goABIInternalCallConv) - markLLVMGCLeaf(fn, call) + markLLVMGCLeafCall(call) return call } @@ -1931,7 +1935,7 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { configureLLVMCall(call, sig) lfc.materializeAddressedResults(v, call, aux) if llvmGCLeaf { - markLLVMGCLeaf(fn, call) + markLLVMGCLeafCall(call) } return call } @@ -2040,6 +2044,46 @@ func llvmFunctionUsesClosureContext(f *Func) bool { return hasContext } +// llvmRuntimeConstructedClosure reports whether call uses the runtime's +// trusted hand-built funcval shape. The runtime receives some callback entry +// points as unsafe.Pointer and stores that code word into a local funcval. +// Scalar replacement can forward the pointer-to-uintptr Convert directly to +// the closure call instead of reloading the funcval's first word. +// +// Follow the call's memory chain and require the exact forwarded code value to +// have been stored at offset zero of the context. This keeps arbitrary integer +// indirect calls fail-closed and preserves the code/context identity that the +// ordinary Load form establishes structurally. +func llvmRuntimeConstructedClosure(call, code, context *Value) bool { + if code.Op != OpConvert || !code.Type.IsUintptr() || len(code.Args) != 2 || + !code.Args[0].Type.IsUnsafePtr() || !code.Args[1].Type.IsMemory() || code.Uses != 2 || + context == nil || !context.Type.IsPtr() || len(call.Args) == 0 { + return false + } + for mem, steps := call.Args[len(call.Args)-1], 0; mem != nil && steps < 32; steps++ { + switch mem.Op { + case OpStore: + if len(mem.Args) != 3 { + return false + } + addr := mem.Args[0] + if mem.Args[1] == code && (addr == context || + (addr.Op == OpOffPtr && auxIntToInt64(addr.AuxInt) == 0 && len(addr.Args) == 1 && addr.Args[0] == context)) { + return true + } + mem = mem.Args[2] + case OpVarDef, OpVarLive: + if len(mem.Args) != 1 { + return false + } + mem = mem.Args[0] + default: + return false + } + } + return false +} + func llvmLocalName(v *Value) (*ir.Name, llvmLocalKey) { sym := auxToSym(v.Aux) name, ok := sym.(*ir.Name) @@ -2822,10 +2866,17 @@ func (lfc *LLVMFuncContext) CompileBlock(BB *Block, values []*Value) { lfc.b.CreateUnreachable() case BlockJumpTable: index := lfc.GenLV(BB.Controls[0]) - table := lfc.b.CreateSwitch(index, lfc.BBs[BB.Succs[0].Block().ID], len(BB.Succs)) + // BlockJumpTable's control is already proven to be in range, and every + // Go SSA edge is represented by one indexed successor. A real successor + // as LLVM's default would add an extra CFG edge and make PHIs on a + // repeated target have one too few incoming values. + defaultBlock := GlobalCtxt.AddBasicBlock(lfc.LF, BB.String()+".jump.default") + table := lfc.b.CreateSwitch(index, defaultBlock, len(BB.Succs)) for i, succ := range BB.Succs { table.AddCase(llvm.ConstInt(index.Type(), uint64(i), false), lfc.BBs[succ.Block().ID]) } + lfc.b.SetInsertPointAtEnd(defaultBlock) + lfc.b.CreateUnreachable() default: BB.Func.fe.Fatalf(BB.Pos, "unsupported SSA block kind in LLVM lowering: %s", BB.Kind) } @@ -3121,6 +3172,10 @@ func LLVMCompile(f *Func) { if !code.Type.IsUintptr() { v.Fatalf("direct closure call code address has type %v", code.Type) } + case OpConvert: + if !base.Flag.CompilingRuntime || !llvmRuntimeConstructedClosure(v, code, v.Args[1]) { + v.Fatalf("closure call has unsupported converted code pointer") + } default: v.Fatalf("closure call code pointer has unsupported form %s", code.Op) } @@ -3247,6 +3302,27 @@ func LLVMCompile(f *Func) { } var parameterHomes []*Value var parameterLifetimeSlots []llvmStackSlot + type cgoUnsafeParameterHome struct { + index int + name *ir.Name + typ *types.Type + slot llvmStackSlot + } + var cgoUnsafeParameterHomes []cgoUnsafeParameterHome + if cgoUnsafeArgs { + for i, param := range inParams { + if param.Name == nil || param.Type.Size() == 0 { + continue + } + slot, _ := preallocateLocal(param.Name, param.Name.Sym().Name+".cgo") + cgoUnsafeParameterHomes = append(cgoUnsafeParameterHomes, cgoUnsafeParameterHome{ + index: i, + name: param.Name, + typ: param.Type, + slot: slot, + }) + } + } for _, BB := range f.Blocks { for _, v := range BB.Values { if v.Op != OpLocalAddr || v.Uses == 0 { @@ -3359,16 +3435,38 @@ func LLVMCompile(f *Func) { // incoming register piece separately and addresses stack-assigned parameters // in their incoming slots. The full aggregate store makes any existing piece // loads and stores redundant and lets normal LLVM memory optimization remove - // them. A future optimization may bind wholly stack-assigned parameters - // directly to their incoming fixed stack slots. + // them. Store the physical ABI carrier directly through the opaque pointer: + // reconstructing its semantic named aggregate first obscures the formal + // argument store from SelectionDAG and prevents a wholly stack-assigned + // parameter home from being folded back to its incoming fixed stack slot. FCtxt.b.SetInsertPointAtEnd(FCtxt.BBs[f.Entry.ID]) + if len(cgoUnsafeParameterHomes) != 0 { + var owner *Value + for _, block := range f.Blocks { + if len(block.Values) != 0 { + owner = block.Values[0] + break + } + } + if owner == nil { + f.fe.Fatalf(f.Entry.Pos, "cgo unsafe argument function has no SSA value for diagnostics") + } + for _, home := range cgoUnsafeParameterHomes { + param := FCtxt.LF.Param(home.index) + param = FCtxt.llvmValueFromABI(owner, param, home.typ, home.slot.Type, home.name.Sym().Name+".cgo.home") + if param.Type() != getLLVMType(home.slot.Type) { + f.fe.Fatalf(home.name.Pos(), "cgo unsafe argument home changes LLVM representation") + } + init := FCtxt.b.CreateStore(param, home.slot.Value) + init.SetAlignment(int(home.slot.Type.Alignment())) + } + } for _, v := range parameterHomes { name, key := llvmLocalName(v) slot := FCtxt.Locals[key] param, paramType := FCtxt.paramForArgNameAndType(v, name) - param = FCtxt.llvmValueFromABI(v, param, paramType, slot.Type, v.String()+".home") - if param.Type() != getLLVMType(slot.Type) { - v.Fatalf("parameter home changes LLVM representation") + if paramType.Size() != slot.Type.Size() || param.Type() != getLLVMABIType(slot.Type) { + v.Fatalf("parameter home has incompatible physical ABI carrier") } init := FCtxt.b.CreateStore(param, slot.Value) init.SetAlignment(int(slot.Type.Alignment())) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_stub.go b/src/cmd/compile/internal/ssa/ssa2llvm_stub.go index 31cdaa1abd7c43..db5796061f6988 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_stub.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_stub.go @@ -2,7 +2,10 @@ package ssa -import "cmd/compile/internal/types" +import ( + "cmd/compile/internal/types" + "cmd/internal/obj" +) func LLVMCompile(f *Func) {} @@ -12,6 +15,9 @@ func InitModule(pkg *types.Pkg) { func LowerGoObjData() { } +func MarkGoObjDataReferencedOutsideLLVM(syms ...*obj.LSym) { +} + func FinalizeGoObjSymbolMetadata() { } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_test.go b/src/cmd/compile/internal/ssa/ssa2llvm_test.go index a25aeab2f93848..424396ee28edd1 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_test.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_test.go @@ -10,12 +10,167 @@ import ( "strings" "testing" + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" "github.com/goallc/go-llvm" ) +func TestLLVMGoObjCompilerUsedOnlyKeepsExternalDataRoots(t *testing.T) { + oldModule := CurrentModule + oldLowerer := currentLLVMDataLowerer + oldCompilerUsed := goObjCompilerUsed + oldCompilerUsedNames := goObjCompilerUsedNames + oldData := base.Ctxt.Data + module := GlobalCtxt.NewModule("goobj_external_data_roots") + CurrentModule = module + currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) + goObjCompilerUsed = nil + goObjCompilerUsedNames = make(map[string]bool) + t.Cleanup(func() { + base.Ctxt.Data = oldData + goObjCompilerUsedNames = oldCompilerUsedNames + goObjCompilerUsed = oldCompilerUsed + currentLLVMDataLowerer = oldLowerer + CurrentModule = oldModule + module.Dispose() + }) + + newLocalData := func(name string, value byte) *obj.LSym { + s := &obj.LSym{Name: name, Type: objabi.SRODATA, Size: 1, P: []byte{value}} + s.Set(obj.AttrLocal, true) + return s + } + externalRoot := newLocalData("test.external.root", 1) + ordinaryLocal := newLocalData("test.ordinary.local", 2) + base.Ctxt.Data = []*obj.LSym{externalRoot, ordinaryLocal} + MarkGoObjDataReferencedOutsideLLVM(externalRoot) + LowerGoObjData() + + var used string + for _, line := range strings.Split(module.String(), "\n") { + if strings.HasPrefix(line, "@llvm.compiler.used =") { + used = line + break + } + } + if used == "" { + t.Fatalf("module has no llvm.compiler.used:\n%s", module.String()) + } + if !strings.Contains(used, "@test.external.root") { + t.Fatalf("external GoObj root is not compiler-used: %s", used) + } + if strings.Contains(used, "@test.ordinary.local") { + t.Fatalf("ordinary local GoObj data is unnecessarily compiler-used: %s", used) + } +} + +func TestLLVMUntypedABI0FunctionAddressCreatesFunctionDeclaration(t *testing.T) { + oldModule := CurrentModule + oldLowerer := currentLLVMDataLowerer + oldTarget := typecheck.Target + module := GlobalCtxt.NewModule("abi0_function_address") + CurrentModule = module + currentLLVMDataLowerer = nil + typecheck.Target = new(ir.Package) + t.Cleanup(func() { + typecheck.Target = oldTarget + currentLLVMDataLowerer = oldLowerer + CurrentModule = oldModule + module.Dispose() + }) + + typ := llvm.FunctionType(GlobalCtxt.VoidType(), nil, false) + internal := llvm.AddFunction(module, "runtime.asyncPreempt", typ) + internal.SetFunctionCallConv(goABIInternalCallConv) + + pkg := types.NewPkg("runtime", "runtime") + fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup("asyncPreempt"), nil) + fn.ABI = obj.ABI0 + typecheck.Target.Funcs = append(typecheck.Target.Funcs, fn) + sym := fn.LinksymABI(fn.ABI) + if sym.Type != objabi.Sxxx { + t.Fatalf("test requires an unresolved bodyless LSym, got %v", sym.Type) + } + got := llvmGoDataRef(sym) + if got.IsAFunction().IsNil() || got.Name() != "runtime.asyncPreempt" { + t.Fatalf("ABI0 function address resolved to %q, want ABI0 function declaration", got.Name()) + } +} + +func TestLLVMRuntimeConstructedClosure(t *testing.T) { + mem := &Value{ID: 1, Op: OpInitMem, Type: types.TypeMem} + rawCode := &Value{ID: 2, Op: OpArg, Type: types.Types[types.TUNSAFEPTR]} + code := &Value{ID: 3, Op: OpConvert, Type: types.Types[types.TUINTPTR], Args: []*Value{rawCode, mem}, Uses: 2} + context := &Value{ID: 4, Op: OpLocalAddr, Type: types.NewPtr(types.Types[types.TUINTPTR])} + codeAddress := &Value{ID: 5, Op: OpOffPtr, Type: types.NewPtr(types.Types[types.TUINTPTR]), Args: []*Value{context}} + codeStore := &Value{ID: 6, Op: OpStore, Type: types.TypeMem, Args: []*Value{codeAddress, code, mem}} + otherStore := &Value{ID: 7, Op: OpStore, Type: types.TypeMem, Args: []*Value{context, context, codeStore}} + argument := &Value{ID: 8, Op: OpArg, Type: types.Types[types.TUNSAFEPTR]} + call := &Value{ID: 9, Op: OpClosureLECall, Type: types.TypeMem, Args: []*Value{code, context, argument, otherStore}} + + if !llvmRuntimeConstructedClosure(call, code, context) { + t.Fatal("runtime-constructed funcval was not recognized") + } + + wrongCode := &Value{ID: 10, Op: OpConvert, Type: types.Types[types.TUINTPTR], Args: []*Value{rawCode, mem}, Uses: 2} + call.Args[0] = wrongCode + if llvmRuntimeConstructedClosure(call, wrongCode, context) { + t.Fatal("code value not stored in the funcval context was accepted") + } +} + +func TestLLVMJumpTableDefaultIsUnreachable(t *testing.T) { + module := GlobalCtxt.NewModule("jump_table_default") + builder := GlobalCtxt.NewBuilder() + t.Cleanup(module.Dispose) + t.Cleanup(builder.Dispose) + + i64 := GlobalCtxt.Int64Type() + function := llvm.AddFunction(module, "jump_table_default", llvm.FunctionType(i64, []llvm.Type{i64}, false)) + jumpLLVM := llvm.AddBasicBlock(function, "jump") + mergeLLVM := llvm.AddBasicBlock(function, "merge") + otherLLVM := llvm.AddBasicBlock(function, "other") + + jump := &Block{ID: 1, Kind: BlockJumpTable} + merge := &Block{ID: 2} + other := &Block{ID: 3} + control := &Value{ID: 1, Type: types.Types[types.TINT]} + jump.Controls[0] = control + jump.Succs = []Edge{{b: merge}, {b: merge}, {b: other}} + context := &LLVMFuncContext{ + BBs: map[ID]llvm.BasicBlock{ + jump.ID: jumpLLVM, + merge.ID: mergeLLVM, + other.ID: otherLLVM, + }, + Vs: map[ID]llvm.Value{control.ID: function.Param(0)}, + LF: function, + b: builder, + } + context.CompileBlock(jump, nil) + + builder.SetInsertPointAtEnd(mergeLLVM) + phi := builder.CreatePHI(i64, "carried") + seven := llvm.ConstInt(i64, 7, false) + phi.AddIncoming([]llvm.Value{seven, seven}, []llvm.BasicBlock{jumpLLVM, jumpLLVM}) + builder.CreateRet(phi) + builder.SetInsertPointAtEnd(otherLLVM) + builder.CreateRet(llvm.ConstInt(i64, 9, false)) + + if err := llvm.VerifyModule(module, llvm.ReturnStatusAction); err != nil { + t.Fatalf("jump table added a non-SSA default edge: %v\n%s", err, module.String()) + } + if ir := module.String(); !strings.Contains(ir, "b1.jump.default") || !strings.Contains(ir, "unreachable") { + t.Fatalf("jump table has no unreachable default block\n%s", ir) + } +} + func TestLLVMCurrentGRegister(t *testing.T) { for _, test := range []struct { name string diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go index b7dbda4a350170..f81fbefa80207a 100644 --- a/src/cmd/compile/internal/ssagen/pgen.go +++ b/src/cmd/compile/internal/ssagen/pgen.go @@ -307,6 +307,9 @@ func Compile(fn *ir.Func, worker int, profile *pgoir.Profile) { // enter genssa: ssa.Compile has already emitted LLVM IR, while genssa // consumes native register-allocation state and emits the _go_.o // member that llc is replacing. + // TODO(goallc): Enforce runtime //go:nowritebarrier(rec) against the + // finalized LLVM IR call graph in an LLVM pass. Do not approximate it + // by scanning SSA after LLVM IR emission. return } // Note: check arg size to fix issue 25507. diff --git a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go index c24fe620fb7b89..34ee4fc69cd320 100644 --- a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go +++ b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go @@ -742,6 +742,9 @@ func (v Value) SetMetadata(kind int, node Metadata) { func (v Value) SetGlobalMetadata(kind int, node Metadata) { C.LLVMGlobalSetMetadata(v.C, C.unsigned(kind), node.C) } +func (v Value) EraseGlobalMetadata(kind int) { + C.LLVMGlobalEraseMetadata(v.C, C.unsigned(kind)) +} // Obtain the string value of the instruction. Same as would be printed with // Value.Dump() (with two spaces at the start but no newline at the end). diff --git a/test/codegen/_cgo_llvm_unsafe_args.go b/test/codegen/_cgo_llvm_unsafe_args.go index b3889b0bdf39b1..4cec88cf2d522e 100644 --- a/test/codegen/_cgo_llvm_unsafe_args.go +++ b/test/codegen/_cgo_llvm_unsafe_args.go @@ -10,30 +10,34 @@ package codegen func llvmCgoUnsafeSink(*uintptr) // LLVM-LABEL: define goabi0 i64 @"codegen.llvmCgoUnsafeFrame"( -// LLVM-SAME: i64 %p) #[[NOINLINE:[0-9]+]] gc "goallc" +// LLVM-SAME: i64 %p, i64 %q) #[[NOINLINE:[0-9]+]] gc "goallc" // LLVM-NOT: alloca // LLVM: [[FRAME:%.*]] = {{.*}}call ptr @llvm.go.abi0.frame() // LLVM-NOT: llvm.addressofreturnaddress // LLVM-NOT: llvm.sponentry -// LLVM: [[RESULT:%.*]] = getelementptr i8, ptr [[FRAME]], i64 8 +// LLVM: [[Q:%.*]] = getelementptr i8, ptr [[FRAME]], i64 8 +// LLVM: [[RESULT:%.*]] = getelementptr i8, ptr [[FRAME]], i64 16 // LLVM: store i64 %p, ptr [[FRAME]] +// LLVM: store i64 %q, ptr [[Q]] // LLVM: {{.*}}call goabiinternal void @codegen.llvmCgoUnsafeSink(ptr{{.*}} [[FRAME]]) // LLVM: {{%.*}} = load i64, ptr [[RESULT]] // LLVM: attributes #[[NOINLINE]] = { {{.*}}noinline // LLVM-OPT-LABEL: define goabi0 i64 @"codegen.llvmCgoUnsafeFrame"( -// LLVM-OPT-SAME: i64 %p) {{.*}}#[[OPT_NOINLINE:[0-9]+]] gc "goallc" +// LLVM-OPT-SAME: i64 %p, i64 %q) {{.*}}#[[OPT_NOINLINE:[0-9]+]] gc "goallc" // LLVM-OPT-NOT: alloca // LLVM-OPT: [[OPT_FRAME:%.*]] = {{.*}}call ptr @llvm.go.abi0.frame() // LLVM-OPT-NOT: llvm.addressofreturnaddress // LLVM-OPT-NOT: llvm.sponentry -// LLVM-OPT: [[OPT_RESULT:%.*]] = getelementptr i8, ptr [[OPT_FRAME]], i64 8 +// LLVM-OPT: [[OPT_Q:%.*]] = getelementptr i8, ptr [[OPT_FRAME]], i64 8 +// LLVM-OPT: [[OPT_RESULT:%.*]] = getelementptr i8, ptr [[OPT_FRAME]], i64 16 // LLVM-OPT: store i64 %p, ptr [[OPT_FRAME]] +// LLVM-OPT: store i64 %q, ptr [[OPT_Q]] // LLVM-OPT: {{.*}}call goabiinternal void @codegen.llvmCgoUnsafeSink(ptr{{.*}} [[OPT_FRAME]]) // LLVM-OPT: {{%.*}} = load i64, ptr [[OPT_RESULT]] // LLVM-OPT: attributes #[[OPT_NOINLINE]] = { {{.*}}noinline // //go:cgo_unsafe_args -func llvmCgoUnsafeFrame(p uintptr) (r uintptr) { +func llvmCgoUnsafeFrame(p, q uintptr) (r uintptr) { llvmCgoUnsafeSink(&p) return } diff --git a/test/codegen/llvm_memops.go b/test/codegen/llvm_memops.go index 801d1221b9425d..97666d49f8a947 100644 --- a/test/codegen/llvm_memops.go +++ b/test/codegen/llvm_memops.go @@ -90,14 +90,14 @@ func llvmMoveAligned(dst, src *[3]uint64) { // LLVM-DAG: define goabiinternal void @codegen.llvmMoveLarge( // LLVM-DAG: call goabiinternal void @runtime.memmove(ptr %dst, ptr {{%.*}}, i64 128) #{{[0-9]+}} -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.memmove(ptr, ptr, i64) #{{[0-9]+}} +// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.memmove(ptr, ptr, i64) func llvmMoveLarge(dst *[128]byte, src [128]byte) { *dst = src } // LLVM-DAG: define goabiinternal i8 @codegen.llvmMemEq( // LLVM-DAG: call goabiinternal i8 @runtime.memequal(ptr {{%.*}}, ptr {{%.*}}, i64 {{%.*}}) #{{[0-9]+}} -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal i8 @runtime.memequal(ptr, ptr, i64) #{{[0-9]+}} +// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal i8 @runtime.memequal(ptr, ptr, i64) func llvmMemEq(a, b string) bool { return a == b } From a3eb69058902fe044a1936be426d2eb48b197f25 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 14 Aug 2026 21:50:55 +0800 Subject: [PATCH 2/5] cmd/compile: encode GoObj builtins in LLVM symbol names --- src/cmd/compile/internal/ssa/llvmdata.go | 104 +++++++++++++----- src/cmd/compile/internal/ssa/ssa2llvm.go | 33 +++--- src/cmd/compile/internal/ssa/ssa2llvm_test.go | 88 +++++++++++++++ src/cmd/internal/goobj/builtin.go | 25 ++++- src/cmd/internal/goobj/builtin_test.go | 57 ++++++++++ src/cmd/internal/goobj/builtinlist.go | 14 +++ src/cmd/internal/goobj/mkbuiltin.go | 89 ++++++++------- test/codegen/arithmetic.go | 2 +- test/codegen/interface_assertion.go | 9 +- test/codegen/interface_conversion.go | 3 +- test/codegen/interface_equality.go | 6 +- test/codegen/interface_switch.go | 5 +- test/codegen/issue52635.go | 4 +- test/codegen/issue63332.go | 2 +- test/codegen/llvm_defer.go | 30 ++--- test/codegen/llvm_defer_noreturn.go | 8 +- test/codegen/llvm_memops.go | 10 +- test/codegen/llvm_newproc.go | 5 +- test/codegen/llvm_opendefer.go | 4 +- test/codegen/llvm_writebarrier.go | 2 +- test/codegen/mathbits.go | 4 +- test/codegen/memops.go | 2 +- 22 files changed, 377 insertions(+), 129 deletions(-) create mode 100644 src/cmd/internal/goobj/builtin_test.go diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 0498627a56e730..d73f9909cf7eba 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -13,10 +13,35 @@ import ( "fmt" "sort" "strconv" + "strings" "github.com/goallc/go-llvm" ) +// 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. +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 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. + return s.Name +} + // emitGoObjImportMetadata carries the exact package path and linker // fingerprint already decoded by the Go importer. LLVM must not rediscover // either value from an importcfg file or a symbol-name prefix. @@ -43,23 +68,16 @@ func emitGoObjCgoModuleAsm() { CurrentModule.SetInlineAsm(".goobj.cgo " + strconv.Quote(string(data)) + "\n") } -// attachGoObjSymbolRef attaches the part of an undefined Go symbol's identity -// that cannot be recovered from an LLVM relocation. The optimized relocation -// stream still decides which declarations become GoObj references; the -// attachment only supplies the imported package's symbol index or the builtin -// index when such a relocation survives. +// 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. func attachGoObjSymbolRef(value llvm.Value, s *obj.LSym) { if value.IsNil() || s == nil { base.Fatalf("invalid LLVM value in GoObj symbol reference") } - if !base.Ctxt.Flag_linkshared && !s.IsLinkname() { - if idx := goobj.BuiltinIdx(s.Name, int(s.ABI())); idx >= 0 { - value.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.builtin"), GlobalCtxt.MDNode([]llvm.Metadata{ - llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(idx), false).ConstantAsMetadata(), - })) - return - } + if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) { + return } // Linknamed symbols live in GoObj's non-package namespace even when the // compiler learned about them through an imported package. Their export @@ -86,15 +104,51 @@ func attachGoObjSymbolRef(value llvm.Value, s *obj.LSym) { })) } -// attachGoObjABISymbolRef is for LLVM-generated runtime calls that do not +func getOrInsertLLVMFunctionRef(s *obj.LSym, sig llvmFuncSignature, cc llvm.CallConv) llvm.Value { + if s == nil || llvmCallConv(s.ABI()) != cc { + base.Fatalf("invalid LLVM GoObj function reference") + } + value := getOrInsertLLVMFunction(llvmGoObjReferenceName(s), sig, cc) + attachGoObjSymbolRef(value, s) + return value +} + +// getOrInsertLLVMABISymbolRef is for LLVM-generated runtime calls that do not // carry an SSA AuxCall. Use the compiler's ABI-aware symbol table so their // builtin/non-package classification stays identical to the native writer. -func attachGoObjABISymbolRef(value llvm.Value, name string, abi obj.ABI) { +func getOrInsertLLVMABISymbolRef(name string, abi obj.ABI, sig llvmFuncSignature, cc llvm.CallConv) llvm.Value { s := base.Ctxt.LookupABI(name, abi) if s == nil || s.Name != name || s.ABI() != abi { base.Fatalf("invalid LLVM GoObj symbol model for %s", name) } - attachGoObjSymbolRef(value, s) + return getOrInsertLLVMFunctionRef(s, sig, cc) +} + +// emitLateGoObjBuiltinDeclarations emits only the declarations consumed by +// LLVM machine passes. Ordinary builtin declarations are created lazily from +// their exact SSA AuxCall signatures. +func emitLateGoObjBuiltinDeclarations() { + if base.Ctxt.Flag_linkshared { + return + } + voidSig := llvmFuncSignature{ + Type: llvm.FunctionType(GlobalCtxt.VoidType(), nil, false), + ReturnType: GlobalCtxt.VoidType(), + ClosureContextIndex: -1, + } + for i := 0; i < goobj.NBuiltin(); i++ { + if !goobj.BuiltinIsLate(i) { + continue + } + name, abiValue := goobj.BuiltinName(i) + storageName, ok := goobj.BuiltinSymbolName(name, abiValue) + if !ok { + base.Fatalf("late LLVM runtime helper %s is absent from GoObj builtin table", name) + } + abi := obj.ABI(abiValue) + fn := getOrInsertLLVMFunction(storageName, voidSig, llvmCallConv(abi)) + preserveGoObjMetadataValues(fn) + } } // LowerGoObjData lowers compiler-emitted linker data into LLVM globals. The @@ -271,13 +325,9 @@ func setGoObjPackageSymbolIndexMetadata(value llvm.Value, s *obj.LSym) { if value.IsNil() || s == nil || s.PkgIdx != goobj.PkgIdxSelf || !s.Indexed() || s.SymIdx < 0 { base.Fatalf("invalid LLVM GoObj package symbol index") } - // Calls emitted before obj.NumberSyms may initially classify a runtime - // builtin as an undefined PkgIdxBuiltin reference. When compiling runtime, - // that same LLVM GlobalValue later becomes this package's definition. - // Definitions are addressed by their package symbol index, so remove the - // now-stale undefined-reference attachment before handing the module to the - // GoObj AsmPrinter. - value.EraseGlobalMetadata(GlobalCtxt.MDKindID("goobj.builtin")) + // An early imported declaration can later become a local definition through + // compiler-generated data. Definitions use their package symbol index, so + // discard the stale imported-reference attachment. value.EraseGlobalMetadata(GlobalCtxt.MDKindID("goobj.import")) value.SetGlobalMetadata(GlobalCtxt.MDKindID(goObjSymbolIndexMD), GlobalCtxt.MDNode([]llvm.Metadata{ llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(s.SymIdx), false).ConstantAsMetadata(), @@ -311,6 +361,9 @@ func llvmGoDataRef(s *obj.LSym) llvm.Value { if s == nil { base.Fatalf("nil Go data symbol in LLVM lowering") } + if llvmGoObjReferenceName(s) != s.Name { + return llvmExternalDataRef(s, nil) + } // FuncPCABI0 carries an ABI0 LSym through OpAddr, but bodyless assembly // functions still have the unresolved Sxxx kind here. Recover the semantic // function identity from the front end before choosing an LLVM GlobalValue; @@ -522,7 +575,7 @@ func llvmExternalDataRef(s *obj.LSym, data map[*obj.LSym]bool) llvm.Value { // example runtime.memequal64 in an equality closure), so do not rely on // STEXT alone here. if llvmGoFunctionSymbol(s) { - storageName := llvmFunctionStorageName(s.Name, llvmCallConv(s.ABI())) + storageName := llvmFunctionStorageName(llvmGoObjReferenceName(s), llvmCallConv(s.ABI())) if f := CurrentModule.NamedFunction(storageName); !f.IsNil() { attachGoObjSymbolRef(f, s) return f @@ -532,11 +585,12 @@ func llvmExternalDataRef(s *obj.LSym, data map[*obj.LSym]bool) llvm.Value { attachGoObjSymbolRef(f, s) return f } - if g := CurrentModule.NamedGlobal(s.Name); !g.IsNil() { + storageName := llvmGoObjReferenceName(s) + if g := CurrentModule.NamedGlobal(storageName); !g.IsNil() { attachGoObjSymbolRef(g, s) return g } - g := llvm.AddGlobal(CurrentModule, GlobalCtxt.Int8Type(), s.Name) + g := llvm.AddGlobal(CurrentModule, GlobalCtxt.Int8Type(), storageName) attachGoObjSymbolRef(g, s) return g } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 95cb51b6ffbff9..adb71d68642f11 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -1069,8 +1069,7 @@ func (lfc *LLVMFuncContext) llvmRuntimeMemmove(dst, src, length llvm.Value) llvm ReturnType: GlobalCtxt.VoidType(), ClosureContextIndex: -1, } - fn := getOrInsertLLVMFunction("runtime.memmove", sig, goABIInternalCallConv) - attachGoObjABISymbolRef(fn, "runtime.memmove", obj.ABIInternal) + fn := getOrInsertLLVMABISymbolRef("runtime.memmove", obj.ABIInternal, sig, goABIInternalCallConv) call := lfc.b.CreateCall(sig.Type, fn, []llvm.Value{dst, src, length}, "") call.SetInstructionCallConv(goABIInternalCallConv) markLLVMGCLeafCall(call) @@ -1129,8 +1128,7 @@ func (lfc *LLVMFuncContext) llvmMemEq(v *Value) llvm.Value { ResultCount: 1, ClosureContextIndex: -1, } - fn := getOrInsertLLVMFunction("runtime.memequal", sig, goABIInternalCallConv) - attachGoObjABISymbolRef(fn, "runtime.memequal", obj.ABIInternal) + fn := getOrInsertLLVMABISymbolRef("runtime.memequal", obj.ABIInternal, sig, goABIInternalCallConv) call := lfc.b.CreateCall(sig.Type, fn, []llvm.Value{left, right, size}, v.String()) call.SetInstructionCallConv(goABIInternalCallConv) markLLVMGCLeafCall(call) @@ -1869,6 +1867,15 @@ func llvmStaticCallSignature(v *Value, aux *AuxCall, sig llvmFuncSignature) llvm if aux.NArgs() != wantArgs || aux.NResults() != 0 { v.Fatalf("%s has unexpected raw call signature: %d arguments, %d results", aux.Fn.Name, aux.NArgs(), aux.NResults()) } + // Runtime implementations may call a function that also has a compiler + // builtin entry (notably newproc) through its ordinary typed Go signature. + // Only the compiler-created form uses raw uintptr carriers and needs pointer + // restoration. + for i := int64(0); i < pointerArgs; i++ { + if typ := aux.TypeOfArg(i); typ == nil || !typ.IsUintptr() { + return sig + } + } for i := int64(0); i < pointerArgs; i++ { if int(i) >= len(v.Args)-1 || v.Args[i].Type == nil { v.Fatalf("argument %d to %s is not pointer-shaped", i, aux.Fn.Name) @@ -1884,9 +1891,6 @@ func llvmStaticCallSignature(v *Value, aux *AuxCall, sig llvmFuncSignature) llvm if !pointerShaped && !writeBarrierTypeAddr { v.Fatalf("argument %d to %s is not pointer-shaped", i, aux.Fn.Name) } - if typ := aux.TypeOfArg(i); typ == nil || !typ.IsUintptr() { - v.Fatalf("argument %d to %s is not raw uintptr", i, aux.Fn.Name) - } } params := append([]llvm.Type(nil), sig.Type.ParamTypes()...) for i := int64(0); i < pointerArgs; i++ { @@ -1907,8 +1911,7 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { sig := llvmStaticCallSignature(v, aux, llvmSignature(aux)) cc := llvmCallConv(aux.ABI().Which()) - fn := getOrInsertLLVMFunction(aux.Fn.Name, sig, cc) - attachGoObjSymbolRef(fn, aux.Fn) + fn := getOrInsertLLVMFunctionRef(aux.Fn, sig, cc) // AMD64 rewrites some Move and Eq operations to static runtime calls before // LLVM emission. Keep the same leaf contract as the dedicated LLVM lowering // paths so RewriteStatepointsForGC does not turn these raw helpers into @@ -2128,8 +2131,7 @@ func (lfc *LLVMFuncContext) panicBounds(v *Value) llvm.Value { Type: llvm.FunctionType(GlobalCtxt.VoidType(), []llvm.Type{x.Type(), y.Type()}, false), ReturnType: GlobalCtxt.VoidType(), } - fn := getOrInsertLLVMFunction(llvmBoundsPanicNames[kind], sig, goABIInternalCallConv) - attachGoObjABISymbolRef(fn, llvmBoundsPanicNames[kind], obj.ABIInternal) + fn := getOrInsertLLVMABISymbolRef(llvmBoundsPanicNames[kind], obj.ABIInternal, sig, goABIInternalCallConv) call := lfc.b.CreateCall(sig.Type, fn, []llvm.Value{x, y}, "") call.SetInstructionCallConv(goABIInternalCallConv) return call @@ -2892,8 +2894,7 @@ func (lfc *LLVMFuncContext) emitOpenDeferRecovery() { ReturnType: GlobalCtxt.VoidType(), ClosureContextIndex: -1, } - deferReturn := getOrInsertLLVMFunction("runtime.deferreturn", deferReturnSig, goABIInternalCallConv) - attachGoObjABISymbolRef(deferReturn, "runtime.deferreturn", obj.ABIInternal) + deferReturn := getOrInsertLLVMABISymbolRef("runtime.deferreturn", obj.ABIInternal, deferReturnSig, goABIInternalCallConv) lfc.b.SetInsertPointAtEnd(lfc.OpenDeferRecovery) call := lfc.b.CreateCall(deferReturnSig.Type, deferReturn, nil, "") @@ -3656,6 +3657,7 @@ func InitModule(pkg *types.Pkg) { currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) goObjCompilerUsed = nil goObjCompilerUsedNames = make(map[string]bool) + emitLateGoObjBuiltinDeclarations() initLLVMDebugInfo(pkg) } @@ -3694,6 +3696,10 @@ func addGoObjConfigMetadata(pkg *types.Pkg) { if pkg.Name == "main" { main = "1" } + std := "0" + if base.Ctxt.Std { + std = "1" + } config := GlobalCtxt.MDNode([]llvm.Metadata{ GlobalCtxt.MDString("goallc.goobj"), GlobalCtxt.MDString(buildcfg.GOOS), @@ -3705,6 +3711,7 @@ func addGoObjConfigMetadata(pkg *types.Pkg) { GlobalCtxt.MDString(pkg.Path), GlobalCtxt.MDString(main), GlobalCtxt.MDString(shared), + GlobalCtxt.MDString(std), GlobalCtxt.MDNode(experimentMetadata), }) CurrentModule.AddNamedMetadataOperand("goobj.config", config) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_test.go b/src/cmd/compile/internal/ssa/ssa2llvm_test.go index 424396ee28edd1..c04a391207ed35 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_test.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_test.go @@ -14,6 +14,7 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" + "cmd/internal/goobj" "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/src" @@ -195,6 +196,10 @@ func TestLLVMCurrentGRegister(t *testing.T) { } func TestLLVMFunctionStorageName(t *testing.T) { + morestack, ok := goobj.BuiltinSymbolName("runtime.morestack", int(obj.ABI0)) + if !ok { + t.Fatal("runtime.morestack ABI0 is absent from GoObj builtin table") + } for _, test := range []struct { name string cc llvm.CallConv @@ -202,6 +207,7 @@ func TestLLVMFunctionStorageName(t *testing.T) { }{ {"runtime.morestack", goABI0CallConv, "runtime.morestack"}, {"runtime.morestack", goABIInternalCallConv, "runtime.morestack"}, + {morestack, goABI0CallConv, morestack + ""}, } { if got := llvmFunctionStorageName(test.name, test.cc); got != test.want { t.Errorf("llvmFunctionStorageName(%q, %d) = %q, want %q", test.name, test.cc, got, test.want) @@ -209,6 +215,88 @@ func TestLLVMFunctionStorageName(t *testing.T) { } } +func TestLLVMGoObjBuiltinReferenceName(t *testing.T) { + oldLinkshared := base.Ctxt.Flag_linkshared + base.Ctxt.Flag_linkshared = false + t.Cleanup(func() { base.Ctxt.Flag_linkshared = oldLinkshared }) + + s := base.Ctxt.LookupABI("runtime.panicdivide", obj.ABIInternal) + want, ok := goobj.BuiltinSymbolName(s.Name, int(s.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) + } + + 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) + } + 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.Set(obj.AttrLinkname, true) + if got := llvmGoObjReferenceName(linkname); got != linkname.Name { + t.Fatalf("non-builtin linkname reference name = %q, want %q", got, linkname.Name) + } + + base.Ctxt.Flag_linkshared = true + if got := llvmGoObjReferenceName(s); got != s.Name { + t.Fatalf("linkshared builtin reference name = %q, want %q", got, s.Name) + } +} + +func TestEmitLateGoObjBuiltinDeclarations(t *testing.T) { + oldModule := CurrentModule + oldCompilerUsed := goObjCompilerUsed + oldCompilerUsedNames := goObjCompilerUsedNames + oldLinkshared := base.Ctxt.Flag_linkshared + module := GlobalCtxt.NewModule("late_goobj_builtins") + CurrentModule = module + goObjCompilerUsed = nil + goObjCompilerUsedNames = make(map[string]bool) + base.Ctxt.Flag_linkshared = false + t.Cleanup(func() { + base.Ctxt.Flag_linkshared = oldLinkshared + goObjCompilerUsedNames = oldCompilerUsedNames + goObjCompilerUsed = oldCompilerUsed + CurrentModule = oldModule + module.Dispose() + }) + + emitLateGoObjBuiltinDeclarations() + lateCount := 0 + for i := 0; i < goobj.NBuiltin(); i++ { + if !goobj.BuiltinIsLate(i) { + continue + } + lateCount++ + name, abi := goobj.BuiltinName(i) + storageName, ok := goobj.BuiltinSymbolName(name, abi) + if !ok { + t.Fatalf("late builtin %s has no encoded name", name) + } + storageName = llvmFunctionStorageName(storageName, llvmCallConv(obj.ABI(abi))) + if fn := module.NamedFunction(storageName); fn.IsNil() { + t.Errorf("late builtin declaration %q is absent", storageName) + } + } + if got := len(goObjCompilerUsed); got != lateCount { + t.Fatalf("compiler-used late builtin count = %d, want %d", got, lateCount) + } + memmove, ok := goobj.BuiltinSymbolName("runtime.memmove", int(obj.ABIInternal)) + if !ok { + t.Fatal("runtime.memmove is absent from GoObj builtin table") + } + if fn := module.NamedFunction(memmove); !fn.IsNil() { + t.Fatalf("ordinary builtin %q was declared eagerly", memmove) + } +} + func TestLLVMAMD64MapPackedByteLowering(t *testing.T) { oldTypes := type2lTypes oldModule := CurrentModule diff --git a/src/cmd/internal/goobj/builtin.go b/src/cmd/internal/goobj/builtin.go index aa665fde99a6a2..131ec8d602ef27 100644 --- a/src/cmd/internal/goobj/builtin.go +++ b/src/cmd/internal/goobj/builtin.go @@ -4,7 +4,12 @@ package goobj -import "internal/buildcfg" +import ( + "internal/buildcfg" + "strconv" +) + +const BuiltinSymbolSuffixPrefix = "", true +} + +// BuiltinIsLate reports whether LLVM's machine passes may introduce a +// reference after ordinary IR call lowering. The classification is generated +// beside builtins so the serialized index and the declaration stay in sync. +func BuiltinIsLate(i int) bool { + return i >= 0 && i < len(lateBuiltins) && lateBuiltins[i] +} + //go:generate go run mkbuiltin.go var builtinMap map[string]int diff --git a/src/cmd/internal/goobj/builtin_test.go b/src/cmd/internal/goobj/builtin_test.go new file mode 100644 index 00000000000000..fc46fa7a2e83ae --- /dev/null +++ b/src/cmd/internal/goobj/builtin_test.go @@ -0,0 +1,57 @@ +// 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 goobj + +import ( + "strconv" + "testing" +) + +func TestBuiltinSymbolName(t *testing.T) { + i := BuiltinIdx("runtime.morestack", 0) + if i < 0 { + t.Fatal("runtime.morestack ABI0 is absent from builtin table") + } + want := "runtime.morestack" + if got, ok := BuiltinSymbolName("runtime.morestack", 0); !ok || got != want { + t.Fatalf("BuiltinSymbolName(runtime.morestack, ABI0) = (%q, %v), want (%q, true)", got, ok, want) + } + if got, ok := BuiltinSymbolName("runtime.notBuiltin", 1); ok || got != "runtime.notBuiltin" { + t.Fatalf("BuiltinSymbolName(non-builtin) = (%q, %v)", got, ok) + } +} + +func TestLateBuiltins(t *testing.T) { + want := map[string]int{ + "runtime.gcWriteBarrier1": 1, + "runtime.gcWriteBarrier2": 1, + "runtime.gcWriteBarrier3": 1, + "runtime.gcWriteBarrier4": 1, + "runtime.gcWriteBarrier5": 1, + "runtime.gcWriteBarrier6": 1, + "runtime.gcWriteBarrier7": 1, + "runtime.gcWriteBarrier8": 1, + "runtime.morestack": 0, + "runtime.morestackc": 0, + "runtime.morestack_noctxt": 0, + } + for i := 0; i < NBuiltin(); i++ { + name, abi := BuiltinName(i) + wantABI, late := want[name] + if got := BuiltinIsLate(i); got != late { + t.Errorf("BuiltinIsLate(%d /* %s */) = %v, want %v", i, name, got, late) + } + if late && abi != wantABI { + t.Errorf("late builtin %s ABI = %d, want %d", name, abi, wantABI) + } + delete(want, name) + } + if len(want) != 0 { + t.Fatalf("late builtins absent from generated table: %v", want) + } + if BuiltinIsLate(-1) || BuiltinIsLate(NBuiltin()) { + t.Fatal("out-of-range builtin index reported as late") + } +} diff --git a/src/cmd/internal/goobj/builtinlist.go b/src/cmd/internal/goobj/builtinlist.go index 1d4f09ae6ff8e1..ae902cc6c81b2f 100644 --- a/src/cmd/internal/goobj/builtinlist.go +++ b/src/cmd/internal/goobj/builtinlist.go @@ -306,3 +306,17 @@ var builtins = [...]struct { {"type:func(error) string", 0}, {"type:*func(error) string", 0}, } + +var lateBuiltins = [...]bool{ + 234: true, + 235: true, + 236: true, + 237: true, + 238: true, + 239: true, + 240: true, + 241: true, + 244: true, + 245: true, + 246: true, +} diff --git a/src/cmd/internal/goobj/mkbuiltin.go b/src/cmd/internal/goobj/mkbuiltin.go index 7e1c425c9d18bd..39a80d15d9883d 100644 --- a/src/cmd/internal/goobj/mkbuiltin.go +++ b/src/cmd/internal/goobj/mkbuiltin.go @@ -59,6 +59,8 @@ func mkbuiltin(w io.Writer) { } decls := make(map[string]bool) + builtinIndex := 0 + var lateBuiltins []int fmt.Fprintf(w, "var builtins = [...]struct{ name string; abi int }{\n") for _, decl := range f.Decls { @@ -73,6 +75,7 @@ func mkbuiltin(w io.Writer) { declName := pkg + "." + decl.Name.Name decls[declName] = true fmt.Fprintf(w, "{%q, 1},\n", declName) // functions are ABIInternal (1) + builtinIndex++ case *ast.GenDecl: if decl.Tok == token.IMPORT { continue @@ -89,6 +92,7 @@ func mkbuiltin(w io.Writer) { declName := pkg + "." + name.Name decls[declName] = true fmt.Fprintf(w, "{%q, 0},\n", declName) // variables are ABI0 + builtinIndex++ } } default: @@ -111,6 +115,16 @@ func mkbuiltin(w io.Writer) { log.Fatalf("%q already added -- mkbuiltin.go out of sync?", name) } fmt.Fprintf(w, "{%q, %d},\n", name, b.abi) + if b.late { + lateBuiltins = append(lateBuiltins, builtinIndex) + } + builtinIndex++ + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + fmt.Fprintln(w, "var lateBuiltins = [...]bool{") + for _, i := range lateBuiltins { + fmt.Fprintf(w, "%d: true,\n", i) } fmt.Fprintln(w, "}") } @@ -128,8 +142,8 @@ func enumerateBasicTypes() []extra { "func(error) string"} result := []extra{} for _, n := range names { - result = append(result, extra{"type:" + n, 0}) - result = append(result, extra{"type:*" + n, 0}) + result = append(result, extra{name: "type:" + n, abi: 0}) + result = append(result, extra{name: "type:*" + n, abi: 0}) } return result } @@ -137,47 +151,48 @@ func enumerateBasicTypes() []extra { type extra struct { name string abi int + late bool } var fextras = [...]extra{ // compiler frontend inserted calls (sysfunc) - {"deferproc", 1}, - {"deferprocStack", 1}, - {"deferreturn", 1}, - {"newproc", 1}, - {"panicoverflow", 1}, - {"sigpanic", 1}, + {name: "deferproc", abi: 1}, + {name: "deferprocStack", abi: 1}, + {name: "deferreturn", abi: 1}, + {name: "newproc", abi: 1}, + {name: "panicoverflow", abi: 1}, + {name: "sigpanic", abi: 1}, // compiler backend inserted calls - {"gcWriteBarrier1", 1}, - {"gcWriteBarrier2", 1}, - {"gcWriteBarrier3", 1}, - {"gcWriteBarrier4", 1}, - {"gcWriteBarrier5", 1}, - {"gcWriteBarrier6", 1}, - {"gcWriteBarrier7", 1}, - {"gcWriteBarrier8", 1}, - {"duffzero", 1}, - {"duffcopy", 1}, + {name: "gcWriteBarrier1", abi: 1, late: true}, + {name: "gcWriteBarrier2", abi: 1, late: true}, + {name: "gcWriteBarrier3", abi: 1, late: true}, + {name: "gcWriteBarrier4", abi: 1, late: true}, + {name: "gcWriteBarrier5", abi: 1, late: true}, + {name: "gcWriteBarrier6", abi: 1, late: true}, + {name: "gcWriteBarrier7", abi: 1, late: true}, + {name: "gcWriteBarrier8", abi: 1, late: true}, + {name: "duffzero", abi: 1}, + {name: "duffcopy", abi: 1}, // assembler backend inserted calls - {"morestack", 0}, // asm function, ABI0 - {"morestackc", 0}, // asm function, ABI0 - {"morestack_noctxt", 0}, // asm function, ABI0 - {"retpolineAX", 0}, // asm function, ABI0, amd64 only - {"retpolineCX", 0}, // asm function, ABI0, amd64 only - {"retpolineDX", 0}, // asm function, ABI0, amd64 only - {"retpolineBX", 0}, // asm function, ABI0, amd64 only - {"retpolineBP", 0}, // asm function, ABI0, amd64 only - {"retpolineSI", 0}, // asm function, ABI0, amd64 only - {"retpolineDI", 0}, // asm function, ABI0, amd64 only - {"retpolineR8", 0}, // asm function, ABI0, amd64 only - {"retpolineR9", 0}, // asm function, ABI0, amd64 only - {"retpolineR10", 0}, // asm function, ABI0, amd64 only - {"retpolineR11", 0}, // asm function, ABI0, amd64 only - {"retpolineR12", 0}, // asm function, ABI0, amd64 only - {"retpolineR13", 0}, // asm function, ABI0, amd64 only - {"retpolineR14", 0}, // asm function, ABI0, amd64 only - {"retpolineR15", 0}, // asm function, ABI0, amd64 only - {"tls_g", 0}, // asm variable, amd64 and 386 only + {name: "morestack", abi: 0, late: true}, // asm function, ABI0 + {name: "morestackc", abi: 0, late: true}, // asm function, ABI0 + {name: "morestack_noctxt", abi: 0, late: true}, // asm function, ABI0 + {name: "retpolineAX", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineCX", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineDX", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineBX", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineBP", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineSI", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineDI", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR8", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR9", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR10", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR11", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR12", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR13", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR14", abi: 0}, // asm function, ABI0, amd64 only + {name: "retpolineR15", abi: 0}, // asm function, ABI0, amd64 only + {name: "tls_g", abi: 0}, // asm variable, amd64 and 386 only } diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go index bc7588c5363cb4..063f5a270564f3 100644 --- a/test/codegen/arithmetic.go +++ b/test/codegen/arithmetic.go @@ -12,7 +12,7 @@ package codegen // LLVM-DAG: getelementptr i8, ptr {{%.*}}, i64 16 // LLVM-DAG: sub i64 // LLVM-DAG: store i64 -// LLVM-DAG: call goabiinternal void @runtime.goPanicIndex +// LLVM-DAG: call goabiinternal void @"runtime.goPanicIndex" // This file contains codegen tests related to arithmetic // simplifications and optimizations on integer types. diff --git a/test/codegen/interface_assertion.go b/test/codegen/interface_assertion.go index 62c7e833267be6..e0cb64c5881beb 100644 --- a/test/codegen/interface_assertion.go +++ b/test/codegen/interface_assertion.go @@ -20,8 +20,7 @@ func (v llvmAssertionValue) Value(delta int) int { // LLVM: @codegen..typeAssert.0 = internal global <{ ptr, ptr, [8 x i8] }> // LLVM-LABEL: define goabiinternal { { ptr, ptr }, i8 } @codegen.assertLLVMInterface( // LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst -// LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal ptr @runtime.typeAssert(ptr, ptr) +// LLVM: call goabiinternal ptr @"runtime.typeAssert"(ptr @codegen..typeAssert.0, ptr // LLVM-LABEL: define goabiinternal { i64, i8 } @codegen.assertLLVMConcrete( // LLVM: extractvalue { ptr, ptr } // LLVM: icmp eq ptr @@ -37,10 +36,8 @@ func assertLLVMInterface(v any) (llvmAssertionInterface, bool) { return x, ok } -// LLVM-DAG: call goabiinternal void @runtime.panicdottypeE( -// LLVM-DAG: call goabiinternal void @runtime.panicnildottype( -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.panicdottypeE(ptr, ptr, ptr) -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.panicnildottype(ptr) +// LLVM-DAG: call goabiinternal void @"runtime.panicdottypeE"( +// LLVM-DAG: call goabiinternal void @"runtime.panicnildottype"( func mustLLVMConcrete(v any) llvmAssertionValue { return v.(llvmAssertionValue) } diff --git a/test/codegen/interface_conversion.go b/test/codegen/interface_conversion.go index 0e8188623b1558..a31a97cb00976a 100644 --- a/test/codegen/interface_conversion.go +++ b/test/codegen/interface_conversion.go @@ -10,8 +10,7 @@ package codegen // LLVM-LABEL: define goabiinternal { ptr, ptr } @codegen.convertLLVMInterface( // LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst // LLVM: ptrtoint ptr -// LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal ptr @runtime.typeAssert(ptr, ptr) +// LLVM: call goabiinternal ptr @"runtime.typeAssert"(ptr @codegen..typeAssert.0, ptr // LLVM: !goobj.gotype = !{ // LLVM-DAG: !{ptr @codegen..typeAssert.0, ptr @ diff --git a/test/codegen/interface_equality.go b/test/codegen/interface_equality.go index c11dd88678538a..d8a15a56dcb03a 100644 --- a/test/codegen/interface_equality.go +++ b/test/codegen/interface_equality.go @@ -17,15 +17,13 @@ func (v llvmEqualityValue) Value() int { } // LLVM-DAG: define goabiinternal i8 @codegen.equalLLVMEmpty( -// LLVM-DAG: call goabiinternal i8 @runtime.efaceeq(ptr -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal i8 @runtime.efaceeq(ptr, ptr, ptr) +// LLVM-DAG: call goabiinternal i8 @"runtime.efaceeq"(ptr func equalLLVMEmpty(a, b any) bool { return a == b } // LLVM-DAG: define goabiinternal i8 @codegen.equalLLVMNonEmpty( -// LLVM-DAG: call goabiinternal i8 @runtime.ifaceeq(ptr -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal i8 @runtime.ifaceeq(ptr, ptr, ptr) +// LLVM-DAG: call goabiinternal i8 @"runtime.ifaceeq"(ptr func equalLLVMNonEmpty(a, b llvmEqualityInterface) bool { return a == b } diff --git a/test/codegen/interface_switch.go b/test/codegen/interface_switch.go index 5d700b13b9e651..bb1fa9f012d0f8 100644 --- a/test/codegen/interface_switch.go +++ b/test/codegen/interface_switch.go @@ -28,11 +28,10 @@ func (v llvmDoubleSwitchValue) Double() int { // LLVM-DAG: @codegen..interfaceSwitch.0 = internal global <{ ptr, [8 x i8], ptr, ptr }> // LLVM-DAG: load atomic ptr, ptr @codegen..interfaceSwitch.0 seq_cst -// LLVM-DAG: call goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr @codegen..interfaceSwitch.0, ptr -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr, ptr) +// LLVM-DAG: call goabiinternal { i64, ptr } @"runtime.interfaceSwitch"(ptr @codegen..interfaceSwitch.0, ptr // LLVM-DAG: icmp eq ptr // LLVM-DAG: @"type:codegen.llvmSwitchValue" -// LLVM-DAG: @"type:string" +// LLVM-DAG: @"type:string" // LLVM-DAG: ret i64 -1 // LLVM: !goobj.gotype = !{ // LLVM-DAG: !{ptr @codegen..interfaceSwitch.0, ptr @ diff --git a/test/codegen/issue52635.go b/test/codegen/issue52635.go index 60379d79baabbb..e5d3ca9cba05d7 100644 --- a/test/codegen/issue52635.go +++ b/test/codegen/issue52635.go @@ -10,10 +10,10 @@ package codegen // LLVM-DAG: define goabiinternal void @"codegen.(*T).f"(ptr %t) -// LLVM-DAG: call goabiinternal void @runtime.memclrNoHeapPointers(ptr {{%.*}}, i64 80) +// LLVM-DAG: call goabiinternal void @"runtime.memclrNoHeapPointers"(ptr {{%.*}}, i64 80) // LLVM-DAG: extractvalue { ptr, i64, i64 } {{%.*}}, 1 // LLVM-DAG: shl i64 {{%.*}}, 3 -// LLVM-DAG: call goabiinternal void @runtime.memclrNoHeapPointers(ptr {{%.*}}, i64 {{%.*}}) +// LLVM-DAG: call goabiinternal void @"runtime.memclrNoHeapPointers"(ptr {{%.*}}, i64 {{%.*}}) type T struct { a *[10]int diff --git a/test/codegen/issue63332.go b/test/codegen/issue63332.go index ee9515ec6bbc50..649b8eb750582d 100644 --- a/test/codegen/issue63332.go +++ b/test/codegen/issue63332.go @@ -9,7 +9,7 @@ package codegen // LLVM-LABEL: define goabiinternal void @codegen.issue63332(ptr %c) // LLVM: alloca i64, align 8 // LLVM: store i64 2, ptr {{%.*}}, align 4 -// LLVM: call goabiinternal void @runtime.chansend1(ptr %c, ptr {{%.*}}) +// LLVM: call goabiinternal void @"runtime.chansend1"(ptr %c, ptr {{%.*}}) func issue63332(c chan int) { x := 0 diff --git a/test/codegen/llvm_defer.go b/test/codegen/llvm_defer.go index e0299b23d92cf9..4c8903ed96e704 100644 --- a/test/codegen/llvm_defer.go +++ b/test/codegen/llvm_defer.go @@ -19,7 +19,7 @@ var llvmDeferSink int // LLVM: store volatile ptr {{.*}}, ptr [[POINTER_SLOT0]] // LLVM: store volatile i8 1, ptr [[POINTER_BITS]] // LLVM: [[RECOVER]]: -// LLVM-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"() // LLVM-NEXT: {{.*}} = load volatile ptr, ptr [[RESULT]] // LLVM-OPT-LABEL: define goabiinternal ptr @codegen.llvmDeferPointerResult( // LLVM-OPT-SAME: ptr{{.*}} %pointer){{.*}} #[[LLVM_NOINLINE:[0-9]+]] gc "goallc" @@ -31,7 +31,7 @@ var llvmDeferSink int // LLVM-OPT: store volatile ptr {{.*}}, ptr [[POINTER_SLOTS_OPT]] // LLVM-OPT: store volatile i8 1, ptr [[POINTER_BITS_OPT]] // LLVM-OPT: [[RECOVER_OPT]]: -// LLVM-OPT-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-OPT-NEXT: call goabiinternal void @"runtime.deferreturn"() // LLVM-OPT-NEXT: {{.*}} = load volatile ptr, ptr [[RESULT_OPT]] // LLVM-LABEL: define goabiinternal i64 @codegen.llvmDeferStack(i64 %value) @@ -42,12 +42,12 @@ var llvmDeferSink int // LLVM: store volatile i64 0, ptr [[STACK_RESULT]] // LLVM: callbr void @llvm.go.defer.edge() // LLVM-NEXT: to label %[[STACK_NORMAL:.*]] [label %[[STACK_RECOVER:.*]]] -// LLVM-NOT: call goabiinternal void @runtime.deferprocStack +// LLVM-NOT: call goabiinternal void @"runtime.deferprocStack"() // LLVM: load volatile i64, ptr [[STACK_RESULT]] // LLVM-OPT-LABEL: define goabiinternal i64 @codegen.llvmDeferStack(i64 %value) // LLVM-OPT: [[STACK_OPT_SLOTS:%.*]] = alloca [1 x ptr], align 8, !goallc.open_defer_slots @@ -57,40 +57,40 @@ var llvmDeferSink int // LLVM-OPT: callbr void @llvm.go.defer.edge() // LLVM-OPT-NEXT: to label %{{.*}} [label %[[STACK_OPT_RECOVER:.*]]] // LLVM-OPT: load volatile i64, ptr [[STACK_OPT_RESULT]] -// LLVM-OPT-NOT: call goabiinternal void @runtime.deferprocStack +// LLVM-OPT-NOT: call goabiinternal void @"runtime.deferprocStack"() // LLVM-LABEL: define goabiinternal void @codegen.llvmDeferHeap(i64 %count) // LLVM: [[HEAP_NORMAL_RETURN:[A-Za-z0-9_.]+]]: -// LLVM: call goabiinternal void @runtime.deferreturn() +// LLVM: call goabiinternal void @"runtime.deferreturn"() // LLVM: ret void // LLVM: [[HEAP_RECOVER:[A-Za-z0-9_.]+]]: -// LLVM-NEXT: call goabiinternal void @runtime.deferreturn() -// LLVM: call goabiinternal void @runtime.deferproc( +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"() +// LLVM: call goabiinternal void @"runtime.deferproc"( // LLVM: callbr void @llvm.go.defer.edge() // LLVM-NEXT: to label %{{.*}} [label %[[HEAP_RECOVER]]] // LLVM: define goabiinternal void @codegen.llvmDeferHeap.deferwrap1({{.*}}) {{.*}}!goobj.func.info ![[WRAPPER_INFO:[0-9]+]] // LLVM: define goabiinternal {{.*}} @codegen.llvmRecover(){{.*}} #[[LLVM_NOINLINE]] gc "goallc" -// LLVM: call goabiinternal {{.*}} @runtime.gorecover( +// LLVM: call goabiinternal {{.*}} @"runtime.gorecover"( // LLVM-OPT-LABEL: define goabiinternal void @codegen.llvmDeferHeap(i64 %count) // LLVM-OPT: [[HEAP_OPT_RECOVER:common.ret]]: -// LLVM-OPT-NEXT: call goabiinternal void @runtime.deferreturn() -// LLVM-OPT: call goabiinternal void @runtime.deferproc( +// LLVM-OPT-NEXT: call goabiinternal void @"runtime.deferreturn"() +// LLVM-OPT: call goabiinternal void @"runtime.deferproc"( // LLVM-OPT: callbr void @llvm.go.defer.edge() // LLVM-OPT-NEXT: to label %{{.*}} [label %[[HEAP_OPT_RECOVER]]] // LLVM-OPT: define goabiinternal void @codegen.llvmDeferHeap.deferwrap1({{.*}}) {{.*}}!goobj.func.info ![[WRAPPER_OPT_INFO:[0-9]+]] // LLVM-OPT: define goabiinternal {{.*}} @codegen.llvmRecover(){{.*}} #[[LLVM_NOINLINE]] gc "goallc" -// LLVM-OPT: call goabiinternal {{.*}} @runtime.gorecover( +// LLVM-OPT: call goabiinternal {{.*}} @"runtime.gorecover"( // An unnamed result still has a recovery-visible home. If evaluating a return // expression panics, defer recovery must return the last committed value. // LLVM-LABEL: define goabiinternal i64 @codegen.llvmDeferUnnamedResult(i64 %value) // LLVM: [[UNNAMED_RESULT:%.*]] = alloca i64, align 8{{$}} // LLVM: store volatile i64 0, ptr [[UNNAMED_RESULT]] -// LLVM: call goabiinternal void @runtime.deferreturn() +// LLVM: call goabiinternal void @"runtime.deferreturn"() // LLVM-NEXT: {{.*}} = load volatile i64, ptr [[UNNAMED_RESULT]] // LLVM: attributes #[[LLVM_NOINLINE]] = { {{.*}}noinline // LLVM: ![[WRAPPER_INFO]] = !{i8 23, i8 0} @@ -100,7 +100,7 @@ var llvmDeferSink int // LLVM-OPT: [[UNNAMED_OPT_RETURN:common.ret]]: // LLVM-OPT-NEXT: {{.*}} = load volatile i64, ptr [[UNNAMED_OPT_RESULT]] // LLVM-OPT: open.defer.recovery: -// LLVM-OPT-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-OPT-NEXT: call goabiinternal void @"runtime.deferreturn"() // LLVM-OPT-NEXT: br label %[[UNNAMED_OPT_RETURN]] // LLVM-OPT: attributes #[[LLVM_NOINLINE]] = { {{.*}}noinline // LLVM-OPT: ![[WRAPPER_OPT_INFO]] = !{i8 23, i8 0} diff --git a/test/codegen/llvm_defer_noreturn.go b/test/codegen/llvm_defer_noreturn.go index 59c5fa55010b75..614aefe07b3882 100644 --- a/test/codegen/llvm_defer_noreturn.go +++ b/test/codegen/llvm_defer_noreturn.go @@ -8,18 +8,18 @@ package codegen // LLVM-LABEL: define goabiinternal void @codegen.llvmDeferNoReturn(i64 %count) // LLVM-NOT: !goallc.open_defer_slots -// LLVM: call goabiinternal void @runtime.deferproc( +// LLVM: call goabiinternal void @"runtime.deferproc"( // LLVM: callbr void @llvm.go.defer.edge() // LLVM-NEXT: to label %{{.*}} [label %[[RECOVERY:[A-Za-z0-9_.]+]]] // LLVM: [[RECOVERY]]: -// LLVM-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"() // LLVM-OPT-LABEL: define goabiinternal void @codegen.llvmDeferNoReturn(i64 %count) // LLVM-OPT-NOT: !goallc.open_defer_slots -// LLVM-OPT: call goabiinternal void @runtime.deferproc( +// LLVM-OPT: call goabiinternal void @"runtime.deferproc"( // LLVM-OPT: callbr void @llvm.go.defer.edge() // LLVM-OPT-NEXT: to label %{{.*}} [label %[[RECOVERY_OPT:[A-Za-z0-9_.]+]]] // LLVM-OPT: [[RECOVERY_OPT]]: -// LLVM-OPT-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-OPT-NEXT: call goabiinternal void @"runtime.deferreturn"() func llvmDeferNoReturn(count int) { for i := 0; i < count; i++ { diff --git a/test/codegen/llvm_memops.go b/test/codegen/llvm_memops.go index 97666d49f8a947..258f6cd2402564 100644 --- a/test/codegen/llvm_memops.go +++ b/test/codegen/llvm_memops.go @@ -76,28 +76,26 @@ func llvmMovePointerToStack(src *llvmPointerStackZero) *int { // LLVM-DAG: define goabiinternal void @codegen.llvmMoveOverlapSized( // LLVM-ARM64-DAG: call void @llvm.memmove.p0.p0.i64(ptr align 1 %dst, ptr align 1 %src, i64 32, i1 false) -// LLVM-AMD64-DAG: call goabiinternal void @runtime.memmove(ptr %dst, ptr %src, i64 32) #{{[0-9]+}} +// LLVM-AMD64-DAG: call goabiinternal void @"runtime.memmove"(ptr %dst, ptr %src, i64 32) #{{[0-9]+}} func llvmMoveOverlapSized(dst, src *[32]byte) { *dst = *src } // LLVM-DAG: define goabiinternal void @codegen.llvmMoveAligned( // LLVM-ARM64-DAG: call void @llvm.memmove.p0.p0.i64(ptr align 8 %dst, ptr align 8 %src, i64 24, i1 false) -// LLVM-AMD64-DAG: call goabiinternal void @runtime.memmove(ptr %dst, ptr %src, i64 24) #{{[0-9]+}} +// LLVM-AMD64-DAG: call goabiinternal void @"runtime.memmove"(ptr %dst, ptr %src, i64 24) #{{[0-9]+}} func llvmMoveAligned(dst, src *[3]uint64) { *dst = *src } // LLVM-DAG: define goabiinternal void @codegen.llvmMoveLarge( -// LLVM-DAG: call goabiinternal void @runtime.memmove(ptr %dst, ptr {{%.*}}, i64 128) #{{[0-9]+}} -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.memmove(ptr, ptr, i64) +// LLVM-DAG: call goabiinternal void @"runtime.memmove"(ptr %dst, ptr {{%.*}}, i64 128) #{{[0-9]+}} func llvmMoveLarge(dst *[128]byte, src [128]byte) { *dst = src } // LLVM-DAG: define goabiinternal i8 @codegen.llvmMemEq( -// LLVM-DAG: call goabiinternal i8 @runtime.memequal(ptr {{%.*}}, ptr {{%.*}}, i64 {{%.*}}) #{{[0-9]+}} -// LLVM-DAG: declare !goobj.builtin !{{[0-9]+}} goabiinternal i8 @runtime.memequal(ptr, ptr, i64) +// LLVM-DAG: call goabiinternal i8 @"runtime.memequal"(ptr {{%.*}}, ptr {{%.*}}, i64 {{%.*}}) #{{[0-9]+}} func llvmMemEq(a, b string) bool { return a == b } diff --git a/test/codegen/llvm_newproc.go b/test/codegen/llvm_newproc.go index 1dbd483ea70cd1..f2c07141b98e74 100644 --- a/test/codegen/llvm_newproc.go +++ b/test/codegen/llvm_newproc.go @@ -7,10 +7,9 @@ package codegen // LLVM-LABEL: define goabiinternal void @codegen.llvmNewproc( -// LLVM: call goabiinternal void @runtime.newproc(ptr -// LLVM-NOT: call goabiinternal void @runtime.newproc(i64 +// LLVM: call goabiinternal void @"runtime.newproc"(ptr +// LLVM-NOT: call goabiinternal void @"runtime.newproc"(i64 // LLVM: ret void -// LLVM: declare !goobj.builtin !{{[0-9]+}} goabiinternal void @runtime.newproc(ptr) var llvmNewprocSink int func llvmNewproc(value int) { diff --git a/test/codegen/llvm_opendefer.go b/test/codegen/llvm_opendefer.go index 67ac2b38dd9264..b9e8ab334892ca 100644 --- a/test/codegen/llvm_opendefer.go +++ b/test/codegen/llvm_opendefer.go @@ -16,7 +16,7 @@ package codegen // LLVM: store volatile ptr {{.*}}, ptr [[SLOT0]] // LLVM: store volatile ptr {{.*}}, ptr [[SLOT1]] // LLVM: [[RECOVERY]]: -// LLVM-NEXT: call goabiinternal void @runtime.deferreturn() +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"() // LLVM: ![[SLOTS_MD]] = !{i32 2} // LLVM-OPT-LABEL: define goabiinternal i64 @codegen.llvmOpenDeferTwo(i64 %value) // LLVM-OPT: [[SLOTS_OPT:%.*]] = alloca [2 x ptr], align 8, !goallc.open_defer_slots ![[SLOTS_OPT_MD:[0-9]+]] @@ -27,7 +27,7 @@ package codegen // LLVM-OPT: store volatile ptr {{.*}}, ptr [[SLOTS_OPT]] // LLVM-OPT: store volatile ptr {{.*}}, ptr [[SLOT1_OPT]] // LLVM-OPT: [[RECOVERY_OPT]]: -// LLVM-OPT: call goabiinternal void @runtime.deferreturn() +// LLVM-OPT: call goabiinternal void @"runtime.deferreturn"() // LLVM-OPT: ![[SLOTS_OPT_MD]] = !{i32 2} func llvmOpenDeferTwo(value int) (result int) { diff --git a/test/codegen/llvm_writebarrier.go b/test/codegen/llvm_writebarrier.go index 59e95889d2c1d5..1485c6e40e97d9 100644 --- a/test/codegen/llvm_writebarrier.go +++ b/test/codegen/llvm_writebarrier.go @@ -9,7 +9,7 @@ package codegen // LLVM-LABEL: define goabiinternal ptr @codegen.llvmWriteBarrierStore( // LLVM-SAME: ptr{{[^%]*}}%dst, ptr{{[^%]*}}%[[VALUE:[a-zA-Z0-9._]+]]) // LLVM-SAME: #[[WBATTR:[0-9]+]] gc "goallc" {{.*}}{ -// LLVM: load i32, ptr @runtime.writeBarrier +// LLVM: load i32, ptr @"runtime.writeBarrier" // LLVM: br i1 // LLVM: call ptr @llvm.go.gc.write.barrier(i32 2) // LLVM: store ptr %[[VALUE]] diff --git a/test/codegen/mathbits.go b/test/codegen/mathbits.go index f72d86ae378b11..0f94cd6655136b 100644 --- a/test/codegen/mathbits.go +++ b/test/codegen/mathbits.go @@ -1003,9 +1003,9 @@ func Div32(hi, lo, x uint32) (q, r uint32) { // LLVM-AMD64-LABEL: define goabiinternal { i64, i64 } @codegen.Div64(i64 %hi, i64 %lo, i64 %x) // LLVM-AMD64: icmp ne i64 %x, 0 // LLVM-AMD64: icmp ult i64 %hi, %x -// LLVM-AMD64-DAG: call goabiinternal void @runtime.panicdivide() +// LLVM-AMD64-DAG: call goabiinternal void @"runtime.panicdivide"() // LLVM-AMD64-DAG: call { i64, i64 } @llvm.x86.go.udivrem.i128.i64(i64 %hi, i64 %lo, i64 %x) -// LLVM-AMD64-DAG: call goabiinternal void @runtime.panicoverflow() +// LLVM-AMD64-DAG: call goabiinternal void @"runtime.panicoverflow"() // LLVM-DAG: call i64 @llvm.cttz.i64(i64 %{{.*}}, i1 false) // LLVM-DAG: call i16 @llvm.ctpop.i16(i16 %{{.*}}) func Div64(hi, lo, x uint64) (q, r uint64) { diff --git a/test/codegen/memops.go b/test/codegen/memops.go index 8dfb3bb35d37fe..3b1985fb17460b 100644 --- a/test/codegen/memops.go +++ b/test/codegen/memops.go @@ -13,7 +13,7 @@ package codegen // LLVM-DAG: icmp eq i64 {{%.*}}, 7 // LLVM-DAG: define goabiinternal void @codegen.idxInt8({ ptr, i64, i64 } %x // LLVM-DAG: icmp ult i64 -// LLVM-DAG: call goabiinternal void @runtime.goPanicIndex +// LLVM-DAG: call goabiinternal void @"runtime.goPanicIndex" // LLVM-DAG: define goabiinternal { i64, i8 } @codegen.compMem3(ptr %x, ptr %y) // LLVM-DAG: icmp slt i64 From 7674c7af00c4e4a24b97b803b92477df5fbfad04 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 14 Aug 2026 21:50:55 +0800 Subject: [PATCH 3/5] ci: use LLVM builtin-symbol release --- .github/workflows/goallc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/goallc.yml b/.github/workflows/goallc.yml index 758cd26bbd6ffb..29b9b2df2f096a 100644 --- a/.github/workflows/goallc.yml +++ b/.github/workflows/goallc.yml @@ -25,8 +25,8 @@ concurrency: cancel-in-progress: true env: - PINNED_LLVM_RELEASE: goallc-llvm23.1.0-20260813T171727Z - PINNED_LLVM_REVISION: 1d142acb4dab7262347b57ac341235913b210f40 + PINNED_LLVM_RELEASE: goallc-llvm23.1.0-20260814T092516Z + PINNED_LLVM_REVISION: 3710742685729c697378ab13a456484e5693aba9 jobs: llvm-payload: From 35c98477c2642f458de031bb7ea79ab250c64c6c Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 01:20:02 +0800 Subject: [PATCH 4/5] cmd/compile: trust closure call code provenance --- src/cmd/compile/internal/ssa/ssa2llvm.go | 63 ++----------------- src/cmd/compile/internal/ssa/ssa2llvm_test.go | 22 ------- src/cmd/internal/testdir/llvm_abi_test.go | 10 ++- 3 files changed, 13 insertions(+), 82 deletions(-) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index adb71d68642f11..085bf6209c9848 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -2047,46 +2047,6 @@ func llvmFunctionUsesClosureContext(f *Func) bool { return hasContext } -// llvmRuntimeConstructedClosure reports whether call uses the runtime's -// trusted hand-built funcval shape. The runtime receives some callback entry -// points as unsafe.Pointer and stores that code word into a local funcval. -// Scalar replacement can forward the pointer-to-uintptr Convert directly to -// the closure call instead of reloading the funcval's first word. -// -// Follow the call's memory chain and require the exact forwarded code value to -// have been stored at offset zero of the context. This keeps arbitrary integer -// indirect calls fail-closed and preserves the code/context identity that the -// ordinary Load form establishes structurally. -func llvmRuntimeConstructedClosure(call, code, context *Value) bool { - if code.Op != OpConvert || !code.Type.IsUintptr() || len(code.Args) != 2 || - !code.Args[0].Type.IsUnsafePtr() || !code.Args[1].Type.IsMemory() || code.Uses != 2 || - context == nil || !context.Type.IsPtr() || len(call.Args) == 0 { - return false - } - for mem, steps := call.Args[len(call.Args)-1], 0; mem != nil && steps < 32; steps++ { - switch mem.Op { - case OpStore: - if len(mem.Args) != 3 { - return false - } - addr := mem.Args[0] - if mem.Args[1] == code && (addr == context || - (addr.Op == OpOffPtr && auxIntToInt64(addr.AuxInt) == 0 && len(addr.Args) == 1 && addr.Args[0] == context)) { - return true - } - mem = mem.Args[2] - case OpVarDef, OpVarLive: - if len(mem.Args) != 1 { - return false - } - mem = mem.Args[0] - default: - return false - } - } - return false -} - func llvmLocalName(v *Value) (*ir.Name, llvmLocalKey) { sym := auxToSym(v.Aux) name, ok := sym.(*ir.Name) @@ -3160,25 +3120,12 @@ func LLVMCompile(f *Func) { } if (v.Op == OpClosureCall || v.Op == OpClosureLECall) && len(v.Args) >= 2 { code := v.Args[0] - switch code.Op { - case OpLoad: - if !code.Type.IsUintptr() || code.Uses != 1 { - v.Fatalf("closure call code pointer is not a single-use uintptr load") - } - if len(code.Args) == 0 || code.Args[0] != v.Args[1] { - v.Fatalf("closure call code pointer was not loaded from its funcval context") - } + if code.Op == OpLoad { + // A nil func value faults while loading its code word. Record + // loads only to preserve that behavior in LLVM; ClosureCall + // itself already defines the code/context contract and does not + // constrain how either value was produced. FCtxt.ClosureCodeLoads[code.ID] = true - case OpAddr: - if !code.Type.IsUintptr() { - v.Fatalf("direct closure call code address has type %v", code.Type) - } - case OpConvert: - if !base.Flag.CompilingRuntime || !llvmRuntimeConstructedClosure(v, code, v.Args[1]) { - v.Fatalf("closure call has unsupported converted code pointer") - } - default: - v.Fatalf("closure call code pointer has unsupported form %s", code.Op) } } else if v.Op == OpClosureCall || v.Op == OpClosureLECall { v.Fatalf("closure call has %d SSA arguments, want at least code, context, and memory", len(v.Args)) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_test.go b/src/cmd/compile/internal/ssa/ssa2llvm_test.go index c04a391207ed35..6dd54e3519e954 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_test.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_test.go @@ -104,28 +104,6 @@ func TestLLVMUntypedABI0FunctionAddressCreatesFunctionDeclaration(t *testing.T) } } -func TestLLVMRuntimeConstructedClosure(t *testing.T) { - mem := &Value{ID: 1, Op: OpInitMem, Type: types.TypeMem} - rawCode := &Value{ID: 2, Op: OpArg, Type: types.Types[types.TUNSAFEPTR]} - code := &Value{ID: 3, Op: OpConvert, Type: types.Types[types.TUINTPTR], Args: []*Value{rawCode, mem}, Uses: 2} - context := &Value{ID: 4, Op: OpLocalAddr, Type: types.NewPtr(types.Types[types.TUINTPTR])} - codeAddress := &Value{ID: 5, Op: OpOffPtr, Type: types.NewPtr(types.Types[types.TUINTPTR]), Args: []*Value{context}} - codeStore := &Value{ID: 6, Op: OpStore, Type: types.TypeMem, Args: []*Value{codeAddress, code, mem}} - otherStore := &Value{ID: 7, Op: OpStore, Type: types.TypeMem, Args: []*Value{context, context, codeStore}} - argument := &Value{ID: 8, Op: OpArg, Type: types.Types[types.TUNSAFEPTR]} - call := &Value{ID: 9, Op: OpClosureLECall, Type: types.TypeMem, Args: []*Value{code, context, argument, otherStore}} - - if !llvmRuntimeConstructedClosure(call, code, context) { - t.Fatal("runtime-constructed funcval was not recognized") - } - - wrongCode := &Value{ID: 10, Op: OpConvert, Type: types.Types[types.TUINTPTR], Args: []*Value{rawCode, mem}, Uses: 2} - call.Args[0] = wrongCode - if llvmRuntimeConstructedClosure(call, wrongCode, context) { - t.Fatal("code value not stored in the funcval context was accepted") - } -} - func TestLLVMJumpTableDefaultIsUnreachable(t *testing.T) { module := GlobalCtxt.NewModule("jump_table_default") builder := GlobalCtxt.NewBuilder() diff --git a/src/cmd/internal/testdir/llvm_abi_test.go b/src/cmd/internal/testdir/llvm_abi_test.go index 328a7a21846798..f03076c3911d4c 100644 --- a/src/cmd/internal/testdir/llvm_abi_test.go +++ b/src/cmd/internal/testdir/llvm_abi_test.go @@ -6,6 +6,7 @@ package testdir_test import ( "bytes" + "cmd/internal/goobj" "encoding/json" "os" "os/exec" @@ -436,6 +437,11 @@ func runLLVMAMD64ArgsPointerMapDifferentialTest(t *testing.T, gorootTestDir stri machineIR := runLLVMABICommand(t, nil, llc, "-load-pass-plugin="+plugin, "-stop-after=prolog-epilog", "-o", "-", goallcIR) + morestackName, ok := goobj.BuiltinSymbolName("runtime.morestack_noctxt", 0) + if !ok { + t.Fatal("runtime.morestack_noctxt ABI0 is absent from the builtin table") + } + morestackPattern := `(?m)^.*CALL64pcrel32 &"?` + regexp.QuoteMeta(morestackName+"") + `"?[^\n]*$` machinePatterns := map[string][]string{ "p.initializedPointerResult": { `STATEPOINT -[0-9]+,[^\n]*\$rsp, 0,`, @@ -452,8 +458,8 @@ func runLLVMAMD64ArgsPointerMapDifferentialTest(t *testing.T, gorootTestDir stri } for name, patterns := range machinePatterns { body := llvmABIMachineFunction(t, machineIR, name) - if !regexp.MustCompile(`(?m)^.*CALL64pcrel32 &"?runtime\.morestack_noctxt"?[^\n]*$`).Match(body) { - t.Fatalf("%s PEI MIR has no raw ABI0 morestack call\n%s", name, body) + if !regexp.MustCompile(morestackPattern).Match(body) { + t.Fatalf("%s PEI MIR has no builtin-encoded ABI0 morestack call\n%s", name, body) } if regexp.MustCompile(`(?m)^.*STATEPOINT[^\n]*runtime\.morestack_noctxt[^\n]*$`).Match(body) { t.Fatalf("%s PEI MIR still represents morestack as a statepoint\n%s", name, body) From 7537c51abbb9a72373d180ec6135caead8e24504 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 01:45:06 +0800 Subject: [PATCH 5/5] cmd/compile: reuse parameter homes for cgo unsafe args --- src/cmd/compile/internal/ssa/ssa2llvm.go | 62 +++++++----------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 085bf6209c9848..c2eb1be100793b 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -1540,18 +1540,18 @@ func (lfc *LLVMFuncContext) FinishPhi() { } func (lfc *LLVMFuncContext) paramForArg(v *Value) llvm.Value { - param, typ := lfc.paramForArgNameAndType(v, v.Aux.(*ir.Name)) + param, typ := lfc.paramForArgNameAndType(v.Aux.(*ir.Name)) return lfc.llvmValueFromABI(v, param, typ, v.Type, v.String()+".arg") } -func (lfc *LLVMFuncContext) paramForArgNameAndType(v *Value, name *ir.Name) (llvm.Value, *types.Type) { +func (lfc *LLVMFuncContext) paramForArgNameAndType(name *ir.Name) (llvm.Value, *types.Type) { key := llvmLocalKeyForName(name) for i, param := range lfc.F.OwnAux.ABIInfo().InParams() { if param.Name != nil && llvmLocalKeyForName(param.Name) == key { return lfc.LF.Param(i), lfc.F.OwnAux.TypeOfArg(int64(i)) } } - v.Fatalf("could not find LLVM parameter for %v", name) + lfc.F.fe.Fatalf(name.Pos(), "could not find LLVM parameter for %v", name) return llvm.Value{}, nil } @@ -3248,27 +3248,20 @@ func LLVMCompile(f *Func) { init.SetAlignment(int(name.Type().Alignment())) init.SetVolatile(true) } - var parameterHomes []*Value + var parameterHomes []*ir.Name var parameterLifetimeSlots []llvmStackSlot - type cgoUnsafeParameterHome struct { - index int - name *ir.Name - typ *types.Type - slot llvmStackSlot - } - var cgoUnsafeParameterHomes []cgoUnsafeParameterHome if cgoUnsafeArgs { - for i, param := range inParams { + // Reuse the ordinary parameter-home path established for the modeled + // ABI0 frame. CgoUnsafeArgs exposes the complete contiguous input area + // through one parameter address, so every non-empty input needs a home + // even when Go SSA contains no OpLocalAddr for it. + for _, param := range inParams { if param.Name == nil || param.Type.Size() == 0 { continue } - slot, _ := preallocateLocal(param.Name, param.Name.Sym().Name+".cgo") - cgoUnsafeParameterHomes = append(cgoUnsafeParameterHomes, cgoUnsafeParameterHome{ - index: i, - name: param.Name, - typ: param.Type, - slot: slot, - }) + if _, created := preallocateLocal(param.Name, param.Name.Sym().Name+".cgo"); created { + parameterHomes = append(parameterHomes, param.Name) + } } } for _, BB := range f.Blocks { @@ -3282,7 +3275,7 @@ func LLVMCompile(f *Func) { continue } if name.Class == ir.PPARAM { - parameterHomes = append(parameterHomes, v) + parameterHomes = append(parameterHomes, name) if name.Type().HasPointers() && !cgoUnsafeArgs { parameterLifetimeSlots = append(parameterLifetimeSlots, FCtxt.Locals[key]) } @@ -3388,33 +3381,12 @@ func LLVMCompile(f *Func) { // argument store from SelectionDAG and prevents a wholly stack-assigned // parameter home from being folded back to its incoming fixed stack slot. FCtxt.b.SetInsertPointAtEnd(FCtxt.BBs[f.Entry.ID]) - if len(cgoUnsafeParameterHomes) != 0 { - var owner *Value - for _, block := range f.Blocks { - if len(block.Values) != 0 { - owner = block.Values[0] - break - } - } - if owner == nil { - f.fe.Fatalf(f.Entry.Pos, "cgo unsafe argument function has no SSA value for diagnostics") - } - for _, home := range cgoUnsafeParameterHomes { - param := FCtxt.LF.Param(home.index) - param = FCtxt.llvmValueFromABI(owner, param, home.typ, home.slot.Type, home.name.Sym().Name+".cgo.home") - if param.Type() != getLLVMType(home.slot.Type) { - f.fe.Fatalf(home.name.Pos(), "cgo unsafe argument home changes LLVM representation") - } - init := FCtxt.b.CreateStore(param, home.slot.Value) - init.SetAlignment(int(home.slot.Type.Alignment())) - } - } - for _, v := range parameterHomes { - name, key := llvmLocalName(v) + for _, name := range parameterHomes { + key := llvmLocalKeyForName(name) slot := FCtxt.Locals[key] - param, paramType := FCtxt.paramForArgNameAndType(v, name) + param, paramType := FCtxt.paramForArgNameAndType(name) if paramType.Size() != slot.Type.Size() || param.Type() != getLLVMABIType(slot.Type) { - v.Fatalf("parameter home has incompatible physical ABI carrier") + f.fe.Fatalf(name.Pos(), "parameter home has incompatible physical ABI carrier") } init := FCtxt.b.CreateStore(param, slot.Value) init.SetAlignment(int(slot.Type.Alignment()))