diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 260d28956f7a98..96f727264b9336 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -171,10 +171,10 @@ use 的 local 不生成 LLVM 栈槽,避免无意义地增大 frame。 GoObj stack growth、SP 恢复和当前 stack-map 路径只支持编译期固定 frame。 因此 amd64 和 arm64 GoObj target 对 variable-sized `alloca` 都确定性报错, -不能把动态栈分配交给 LLVM 通用 lowering。当前 GoObj writer 的 args/locals -pointer maps 仍为空;本实现只保证现有固定栈槽的 dominance、frame placement -和 stack-growth 约束,不代表已经支持完整 precise-GC stack maps、goroutine、 -defer 或 panic unwind。 +不能把动态栈分配交给 LLVM 通用 lowering。当前 GoObj writer 已为所有 GoObj Go +函数生成类型推导的入口 ArgsPointerMaps,并为已支持的普通 statepoint 和固定 +`alloca` 生成 LocalsPointerMaps;这仍不代表已经支持完整 precise-GC stack maps、 +goroutine、defer 或 panic unwind。 对应回归包括: @@ -273,9 +273,10 @@ pass plugin 默认从 `llc` 所属 LLVM payload 的 wrapper 覆盖的 compile action 重新编译。 插件的功能性源码和测试位于 Go 仓库 -`src/cmd/llvmplugin`,不放入 LLVM 源码树。LLVM 只提供通用的 -`-load-pass-plugin` 和 pre-codegen callback;GoALLC statepoint rewrite 及其 -pass 顺序都应继续在这个 Go-owned 工程中实现。当前 +`src/cmd/llvmplugin`,不放入 LLVM 源码树。LLVM 提供通用的 +`-load-pass-plugin`、pre-codegen callback,以及 GoObj 对 Machine StackMaps +的对象格式适配;GoALLC statepoint rewrite 及其 pass 顺序都应继续在这个 +Go-owned 工程中实现。当前 `runPreCodeGenPipeline` 调用 Go-owned statepoint pass:它对 Go ABI 函数执行 CFG 逆向数据流活跃性分析,为普通调用分配稳定 callsite ID,生成 `gc.statepoint` / `gc.relocate`,并识别 `gc-leaf-function`。受管指针分类当前 @@ -285,21 +286,21 @@ aggregate、`invoke`、`musttail` 和非 leaf inline asm fail closed。未来 compile 进程内集成 LLVM 时直接复用该 core 入口,不经过 plugin adapter。 机器位置不通过修改 LLVM 通用 `StackMaps.cpp` 截获。SSA→LLVM IR 前端为 Go ABI -函数声明 `gc "goallc"` 和 `go-stack-growth-statepoint`,插件只负责注册对应的 -GC strategy 与 `GCMetadataPrinter::emitStackMaps`,并消费这些前端标记。在 +函数声明 `gc "goallc"`;GoObj Go 函数默认采用原生 Go 的可扩栈策略,只有 +`go-nosplit`、`go-systemstack` 这样的例外策略需要额外属性。插件负责注册对应的 +GC strategy、执行 statepoint rewrite 并消费这些前端标记。在 LLVM GoObj AsmPrinter 模块收尾阶段 读取标准 `FnInfos/CSInfos`,跳过 statepoint 的 CC、flags 和 deopt 前缀后,把 原始 GC locations 写入 MCContext。GoObj writer 在最终 layout 后完成 SP 校验、`Direct`/`Indirect` 解释、LocalsPointerMaps 和 PCDATA_StackMapIndex -编码。GoALLC 要求 StackMaps 记录 CALL 起点;map 从 CALL 开始。前端添加的 -`go-stack-growth-statepoint` 属性使 LLVM 在 PEI 阶段把 -`runtime.morestack` 调用生成为物理 MIR `STATEPOINT`。其 deopt 和 GC alloca -区为空,GC pointer 区则记录类型推导出的入口参数 home。它从 morestack CALL -起点选择入口 ArgsPointerMaps 和空 locals bitmap,因此普通调用与栈增长调用 -走同一 Machine StackMaps 链路,且不依赖 return PC 反推调用范围。已有 -Machine `STATEPOINT` 但缺少该前端属性时, -LLVM target lowering 会 fail closed;不再使用 slow-path reset label 兼容普通 -morestack CALL。GoObj 先写索引 0 的 +编码。GoALLC 要求 StackMaps 记录 CALL 起点;map 从 CALL 开始。LLVM target +formal lowering 为每个 GoObj Go 函数推导入口参数 home,并用一个独立、零字节的 +`EntryArgsStackMapID` 表达函数级 ArgsPointerMaps;该记录不是调用点,不生成 +PCDATA。非 nosplit 函数在 PEI 阶段额外把 `runtime.morestack` 调用生成为物理、 +root-free 的 `StackGrowthStatepointID`,从 CALL 起点选择入口 ArgsPointerMaps 和 +空 locals bitmap。nosplit 函数不得包含这个 statepoint。这样普通调用与栈增长 +调用仍走同一 Machine StackMaps 链路,且不依赖 return PC 反推调用范围,同时 +函数级入口图不再伪装成 morestack 调用。GoObj 先写索引 0 的 `PCDATA_UnsafePoint`(当前恒为 safe 的 `-1`),再写索引 1 的 `PCDATA_StackMapIndex`;不能只写后一张表,否则 linker 会把它误认成索引 0。 `Direct SP+offset` 是栈地址本身,不表示该 slot 存有 pointer,因此不会设置 diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index d73f9909cf7eba..3cdb622def25a9 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -4,6 +4,7 @@ package ssa import ( "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/internal/goobj" "cmd/internal/obj" @@ -18,16 +19,55 @@ import ( "github.com/goallc/go-llvm" ) +type llvmGoObjSymbolKey struct { + name string + abi obj.ABI +} + +var llvmGoObjLocalDefinitions map[llvmGoObjSymbolKey]bool + +func llvmGoObjSymbolKeyFor(s *obj.LSym) llvmGoObjSymbolKey { + return llvmGoObjSymbolKey{name: s.Name, abi: s.ABI()} +} + +// initLLVMGoObjLocalDefinitions records the distinction needed only by +// linkname: a declaration without a local definition is a pull, while a local +// function or data definition is a push. Builtin references do not consult +// this set. +func initLLVMGoObjLocalDefinitions() { + llvmGoObjLocalDefinitions = make(map[llvmGoObjSymbolKey]bool) + for _, fn := range typecheck.Target.Funcs { + if fn == nil || fn.Nname == nil || len(fn.Body) == 0 { + continue + } + s := fn.LinksymABI(fn.ABI) + llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true + } + for _, name := range typecheck.Target.Externs { + if name == nil || name.Op() != ir.ONAME || name.Class != ir.PEXTERN { + continue + } + s := name.Linksym() + llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true + } +} + +func llvmGoObjLinknameReference(s *obj.LSym) bool { + return s != nil && (s.IsLinkname() || s.IsLinknameStd()) && + !llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] +} + // llvmGoObjReferenceName is the single naming boundary for undefined Go -// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only -// suffix tells the GoObj writer to serialize a surviving relocation through -// the predefined builtin index table. +// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only name +// records whether the GoObj writer must serialize a surviving relocation as a +// builtin-index or linkname pull. func llvmGoObjReferenceName(s *obj.LSym) string { if s == nil { base.Fatalf("nil GoObj symbol reference") } - if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) { - base.Fatalf("Go symbol name %q uses reserved LLVM builtin suffix", s.Name) + if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) || + strings.Contains(s.Name, goobj.LinknameSymbolSuffix) { + base.Fatalf("Go symbol name %q uses reserved LLVM reference suffix", s.Name) } if base.Ctxt.Flag_linkshared { return s.Name @@ -35,10 +75,9 @@ func llvmGoObjReferenceName(s *obj.LSym) string { if name, ok := goobj.BuiltinSymbolName(s.Name, int(s.ABI())); ok { return name } - // Linkname references currently retain their ordinary linker name. A - // runtime implementation may itself be linknamed while compiler-generated - // references to the same logical symbol still use the builtin table, so the - // builtin lookup above deliberately takes precedence over this attribute. + if llvmGoObjLinknameReference(s) { + return s.Name + goobj.LinknameSymbolSuffix + } return s.Name } @@ -69,21 +108,22 @@ func emitGoObjCgoModuleAsm() { } // attachGoObjSymbolRef attaches the part of an undefined imported Go symbol's -// identity that cannot be recovered from an LLVM relocation. Builtin identity -// is carried by the declaration name instead. +// identity that cannot be recovered from an LLVM relocation. Builtin and +// linkname identity is carried by the declaration name instead. func attachGoObjSymbolRef(value llvm.Value, s *obj.LSym) { if value.IsNil() || s == nil { base.Fatalf("invalid LLVM value in GoObj symbol reference") } - if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) { + if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) || + strings.Contains(value.Name(), goobj.LinknameSymbolSuffix) { return } // Linknamed symbols live in GoObj's non-package namespace even when the // compiler learned about them through an imported package. Their export // symbol index addresses that package's ordinary symbol block and must not // be attached to the LLVM declaration as an imported reference. - if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() { + if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() || s.IsLinknameStd() { return } localPkg := objabi.PathToPrefix(base.Ctxt.Pkgpath) @@ -656,6 +696,9 @@ func setGoObjDataFlags(g llvm.Value, s *obj.LSym) { if s.IsLinkname() { flag2 |= 1 << 4 // goobj.SymFlagLinkname } + if s.IsLinknameStd() { + flag2 |= goobj.SymFlagLinknameStd + } if s.ABIWrapper() { flag2 |= 1 << 5 // goobj.SymFlagABIWrapper } diff --git a/src/cmd/compile/internal/ssa/llvmdebug.go b/src/cmd/compile/internal/ssa/llvmdebug.go index de76a44f6178d9..7e0c6810e19dcf 100644 --- a/src/cmd/compile/internal/ssa/llvmdebug.go +++ b/src/cmd/compile/internal/ssa/llvmdebug.go @@ -257,6 +257,22 @@ func (lfc *LLVMFuncContext) setDebugLocation(xpos src.XPos) { } locationScope := llvmDIScopeForPos(scope, pos, 0) - lfc.b.SetCurrentDebugLocationMetadata(GlobalCtxt.CreateDebugLocation( - pos.RelLine(), pos.RelCol(), locationScope, inlinedAt)) + location := GlobalCtxt.CreateDebugLocation( + pos.RelLine(), pos.RelCol(), locationScope, inlinedAt) + lfc.b.SetCurrentDebugLocationMetadata(location) + + // Generic LLVM optimization may combine instructions from different Go + // inline frames and keep only one of their DILocations. Record one complete + // frontend location for every inline node independently of the instruction + // stream. The final machine pass uses this only when an inline edge has + // otherwise disappeared, so optimization remains unconstrained while Go's + // pcinline tree still has a real final-layout PC for every source edge. + if len(chain) != 0 && !lfc.RequiredInlinePos[pos.Base().InliningIndex()] { + lfc.RequiredInlinePos[pos.Base().InliningIndex()] = true + CurrentModule.AddNamedMetadataOperand(goObjDebugInlineRequiredMD, + GlobalCtxt.MDNode([]llvm.Metadata{ + lfc.LF.ConstantAsMetadata(), + location, + })) + } } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index c2eb1be100793b..818e7a1e2b9d3c 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -5,6 +5,7 @@ package ssa import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/src" @@ -25,6 +26,7 @@ type LLVMFuncContext struct { ClosureCodeLoads map[ID]bool DeferResults map[llvmLocalKey]bool DeferResultKeys map[ID]llvmLocalKey + RequiredInlinePos map[int]bool OpenDeferBits llvmLocalKey HasOpenDeferBits bool OpenDeferSlots map[llvmLocalKey]int @@ -69,7 +71,8 @@ const goABI0SymbolSuffix = "" const goResultsTupleAttr = "go_results_tuple" const goGCStrategy = "goallc" const goGCLeafFunctionAttr = "gc-leaf-function" -const goStackGrowthStatepointAttr = "go-stack-growth-statepoint" +const goNoSplitAttr = "go-nosplit" +const goSystemStackAttr = "go-systemstack" const goAsyncUnsafeAttr = "go-async-unsafe" const goWriteBarrierIntrinsic = "llvm.go.gc.write.barrier" const goDeferEdgeIntrinsic = "llvm.go.defer.edge" @@ -79,6 +82,7 @@ const goOpenDeferBitsMD = "goallc.open_defer_bits" const goOpenDeferSlotsMD = "goallc.open_defer_slots" const goObjMarkerRelocMD = "goobj.marker_reloc" const goObjSymbolIndexMD = "goobj.symbol.index" +const goObjDebugInlineRequiredMD = "goobj.debug.inline.required" const llvmFramePointerAttr = "frame-pointer" const llvmFramePointerNonLeaf = "non-leaf" const llvmTargetCPUAttr = "target-cpu" @@ -145,8 +149,9 @@ func llvmTypeContainsABIPad(typ llvm.Type) bool { } // getLLVMABIType makes a non-empty carrier only at a top-level zero-sized ABI -// boundary. The original zero-sized type remains in the wrapper, so DataLayout -// supplies its Go alignment without storing that alignment in an attribute. +// boundary. The original zero-sized layout remains in the wrapper, so +// DataLayout supplies its Go alignment without storing that alignment in an +// attribute. func getLLVMABIType(typ *types.Type) llvm.Type { storage := getLLVMType(typ) if typ.Size() == 0 { @@ -1733,11 +1738,32 @@ func (lfc *LLVMFuncContext) llvmIData(v *Value) llvm.Value { return result } -// reshapeLLVMValue converts between the distinct nominal LLVM aggregate types -// used for a generic shape and one of its concrete instantiations. Go's type -// system already records these as identical for shape-aware operations; keep -// that decision as the authority and rebuild only the affected first-class -// struct or array value. Scalar leaves and memory keep their normal lowering. +func llvmValueTypesCanReshape(from, to *types.Type) bool { + if from == nil || to == nil { + return false + } + if types.Identical(from, to) && !types.IdenticalStrict(from, to) && (from.HasShape() || to.HasShape()) { + return true + } + if from.Kind() != to.Kind() { + return false + } + switch from.Kind() { + case types.TSTRUCT, types.TARRAY: + // The frontend lowers explicit conversions between defined aggregate + // types with identical underlying types (ignoring struct tags) to Copy. + // LLVM still gives each defined struct its own nominal identity. + return types.IdenticalIgnoreTags(from.Underlying(), to.Underlying()) + } + return false +} + +// reshapeLLVMValue converts between distinct nominal LLVM aggregate types +// that Go has already established are representation-preserving: either a +// generic shape and its concrete instantiation, or an explicit conversion +// between aggregates with identical underlying types. Rebuild only the +// affected first-class struct or array value; scalar leaves and memory keep +// their normal lowering. func (lfc *LLVMFuncContext) reshapeLLVMValue(v *Value, value llvm.Value, from, to *types.Type, name string) llvm.Value { if value.IsNil() { v.Fatalf("cannot reshape an empty LLVM value") @@ -1758,14 +1784,14 @@ func (lfc *LLVMFuncContext) reshapeLLVMValue(v *Value, value llvm.Value, from, t return lfc.b.CreateBitCast(value, want, name) } } - if from == nil || to == nil || !types.Identical(from, to) || types.IdenticalStrict(from, to) || (!from.HasShape() && !to.HasShape()) { + if !llvmValueTypesCanReshape(from, to) { v.Fatalf("cannot reshape LLVM value from Go type %v to %v", from, to) } switch from.Kind() { case types.TSTRUCT: if to.Kind() != types.TSTRUCT || value.Type().TypeKind() != llvm.StructTypeKind || want.TypeKind() != llvm.StructTypeKind { - v.Fatalf("shape-identical structs have incompatible LLVM aggregate kinds") + v.Fatalf("representation-identical structs have incompatible LLVM aggregate kinds") } fromElements := value.Type().StructElementTypes() toElements := want.StructElementTypes() @@ -1778,7 +1804,7 @@ func (lfc *LLVMFuncContext) reshapeLLVMValue(v *Value, value llvm.Value, from, t toElementCount++ } if from.NumFields() != to.NumFields() || len(fromElements) != fromElementCount || len(toElements) != toElementCount { - v.Fatalf("shape-identical structs have incompatible field counts") + v.Fatalf("representation-identical structs have incompatible field counts") } result := llvm.Undef(want) for i := 0; i < from.NumFields(); i++ { @@ -1794,7 +1820,7 @@ func (lfc *LLVMFuncContext) reshapeLLVMValue(v *Value, value llvm.Value, from, t case types.TARRAY: if to.Kind() != types.TARRAY || from.NumElem() != to.NumElem() || value.Type().TypeKind() != llvm.ArrayTypeKind || want.TypeKind() != llvm.ArrayTypeKind { - v.Fatalf("shape-identical arrays have incompatible LLVM aggregate layouts") + v.Fatalf("representation-identical arrays have incompatible LLVM aggregate layouts") } result := llvm.Undef(want) for i := int64(0); i < from.NumElem(); i++ { @@ -1806,7 +1832,90 @@ func (lfc *LLVMFuncContext) reshapeLLVMValue(v *Value, value llvm.Value, from, t return result default: - v.Fatalf("shape-identical Go types %v and %v require unsupported LLVM reshaping", from, to) + v.Fatalf("representation-identical Go types %v and %v require unsupported LLVM reshaping", from, to) + return llvm.Value{} + } +} + +// reshapeLLVMABICarrier rebuilds a value between semantically distinct LLVM +// aggregate types that have the same physical Go ABI structure. It is used +// only by callers at function boundaries; definitions and declarations retain +// the named aggregate types in their own Go signatures. +func (lfc *LLVMFuncContext) reshapeLLVMABICarrier(v *Value, value llvm.Value, target llvm.Type, name string) llvm.Value { + if value.Type() == target { + return value + } + + source := value.Type() + // Go ABI analysis may describe a promoted method receiver using its single + // physical register carrier while the generated wrapper definition retains + // the named aggregate receiver type. Peel and rebuild singleton aggregates + // at that caller boundary. This keeps the callee signature semantic without + // introducing an anonymous aggregate signature shared by both sides. + if source.TypeKind() != target.TypeKind() { + switch source.TypeKind() { + case llvm.StructTypeKind: + fields := source.StructElementTypes() + if len(fields) == 1 { + field := lfc.b.CreateExtractValue(value, 0, name+".abi.unwrap") + return lfc.reshapeLLVMABICarrier(v, field, target, name) + } + case llvm.ArrayTypeKind: + if source.ArrayLength() == 1 { + element := lfc.b.CreateExtractValue(value, 0, name+".abi.unwrap") + return lfc.reshapeLLVMABICarrier(v, element, target, name) + } + } + + switch target.TypeKind() { + case llvm.StructTypeKind: + fields := target.StructElementTypes() + if len(fields) == 1 { + field := lfc.reshapeLLVMABICarrier(v, value, fields[0], name) + return lfc.b.CreateInsertValue(llvm.Undef(target), field, 0, name+".abi.wrap") + } + case llvm.ArrayTypeKind: + if target.ArrayLength() == 1 { + element := lfc.reshapeLLVMABICarrier(v, value, target.ElementType(), name) + return lfc.b.CreateInsertValue(llvm.Undef(target), element, 0, name+".abi.wrap") + } + } + } + + switch target.TypeKind() { + case llvm.StructTypeKind: + if source.TypeKind() != llvm.StructTypeKind { + v.Fatalf("cannot reshape LLVM ABI carrier kind %d to struct at %s", source.TypeKind(), name) + } + sourceFields := source.StructElementTypes() + targetFields := target.StructElementTypes() + if len(sourceFields) != len(targetFields) { + v.Fatalf("cannot reshape LLVM ABI structs with different field counts") + } + result := llvm.Undef(target) + for i := range sourceFields { + fieldName := fmt.Sprintf("%s.abi.field%d", name, i) + field := lfc.b.CreateExtractValue(value, i, fieldName+".extract") + field = lfc.reshapeLLVMABICarrier(v, field, targetFields[i], fieldName) + result = lfc.b.CreateInsertValue(result, field, i, fieldName+".insert") + } + return result + + case llvm.ArrayTypeKind: + if source.TypeKind() != llvm.ArrayTypeKind || source.ArrayLength() != target.ArrayLength() { + v.Fatalf("cannot reshape incompatible LLVM ABI arrays") + } + result := llvm.Undef(target) + for i := 0; i < source.ArrayLength(); i++ { + elementName := fmt.Sprintf("%s.abi.element%d", name, i) + element := lfc.b.CreateExtractValue(value, i, elementName+".extract") + element = lfc.reshapeLLVMABICarrier(v, element, target.ElementType(), elementName) + result = lfc.b.CreateInsertValue(result, element, i, elementName+".insert") + } + return result + + default: + v.Fatalf("cannot reshape LLVM ABI carrier from %v to %v", source, target) return llvm.Value{} } } @@ -1819,10 +1928,7 @@ func (lfc *LLVMFuncContext) llvmValueToABI(v *Value, value llvm.Value, from, log return llvm.Undef(abiType) } value = lfc.reshapeLLVMValue(v, value, from, logical, name) - if value.Type() != abiType { - v.Fatalf("Go ABI value has incompatible LLVM carrier") - } - return value + return lfc.reshapeLLVMABICarrier(v, value, abiType, name) } func (lfc *LLVMFuncContext) llvmValueFromABI(v *Value, value llvm.Value, logical, to *types.Type, name string) llvm.Value { @@ -1832,12 +1938,15 @@ func (lfc *LLVMFuncContext) llvmValueFromABI(v *Value, value llvm.Value, logical } return llvm.Undef(getLLVMType(to)) } + value = lfc.reshapeLLVMABICarrier(v, value, getLLVMType(logical), name) return lfc.reshapeLLVMValue(v, value, logical, to, name) } // llvmStaticCallSignature restores semantic pointer types for compiler-built // runtime calls whose AuxCall uses uintptr only to compute physical ABI -// assignments. AuxCall remains the physical ABI authority; the LLVM operands +// assignments. When compiling runtime itself, an ordinary source call to the +// same helper already has its semantic pointer type and needs no rewrite. +// AuxCall remains the physical ABI authority in both cases; the LLVM operands // and runtime helper parameters are pointers. func llvmStaticCallSignature(v *Value, aux *AuxCall, sig llvmFuncSignature) llvmFuncSignature { if aux == nil || aux.Fn == nil { @@ -1865,17 +1974,9 @@ func llvmStaticCallSignature(v *Value, aux *AuxCall, sig llvmFuncSignature) llvm v.Fatalf("%s uses unsupported ABI %v", aux.Fn.Name, aux.ABI().Which()) } 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 - } + v.Fatalf("%s has unexpected call signature: %d arguments, %d results", aux.Fn.Name, aux.NArgs(), aux.NResults()) } + params := append([]llvm.Type(nil), sig.Type.ParamTypes()...) 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) @@ -1891,15 +1992,56 @@ 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) } - } - params := append([]llvm.Type(nil), sig.Type.ParamTypes()...) - for i := int64(0); i < pointerArgs; i++ { - params[i] = GlobalCtxt.PointerType(0) + typ := aux.TypeOfArg(i) + switch { + case typ != nil && typ.IsUintptr(): + params[i] = GlobalCtxt.PointerType(0) + case typ != nil && typ.IsPtrShaped(): + if !pointerShaped { + v.Fatalf("semantic pointer argument %d to %s is not pointer-shaped", i, aux.Fn.Name) + } + if params[i].TypeKind() != llvm.PointerTypeKind { + v.Fatalf("argument %d to %s has non-pointer LLVM type", i, aux.Fn.Name) + } + default: + v.Fatalf("argument %d to %s is neither raw uintptr nor a semantic pointer", i, aux.Fn.Name) + } } sig.Type = llvm.FunctionType(sig.ReturnType, params, false) return sig } +// llvmStaticCalleeAux returns the source signature of a function defined in +// the package currently being compiled. Compiler-built runtime calls use the +// deliberately approximate declarations in the go.runtime pseudo-package; +// their AuxCall can therefore describe a physically equivalent but nominally +// different signature from the real runtime definition. Keep the definition's +// named types in LLVM and make the caller bridge that boundary instead. +func llvmStaticCalleeAux(aux *AuxCall) *AuxCall { + if aux == nil || aux.Fn == nil || aux.ABIInfo() == nil { + return aux + } + if llvmStaticCalleeAuxCache == nil { + llvmStaticCalleeAuxCache = make(map[*obj.LSym]*AuxCall) + } + if callee := llvmStaticCalleeAuxCache[aux.Fn]; callee != nil { + return callee + } + abi := aux.ABI().Which() + for _, fn := range typecheck.Target.Funcs { + if fn == nil || fn.Nname == nil || fn.Type() == nil || fn.ABI != abi { + continue + } + if fn.LinksymABI(abi) != aux.Fn { + continue + } + callee := StaticAuxCall(aux.Fn, aux.ABI().ABIAnalyze(fn.Type(), false)) + llvmStaticCalleeAuxCache[aux.Fn] = callee + return callee + } + return aux +} + func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { aux := auxToCall(v.Aux) if aux == nil || aux.Fn == nil { @@ -1909,9 +2051,26 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { v.Fatalf("static call to %s has %d LLVM arguments, want %d", aux.Fn.Name, got, want) } - sig := llvmStaticCallSignature(v, aux, llvmSignature(aux)) + callerSig := llvmStaticCallSignature(v, aux, llvmSignature(aux)) + calleeAux := llvmStaticCalleeAux(aux) + calleeSig := llvmSignature(calleeAux) + if calleeAux == aux { + calleeSig = llvmStaticCallSignature(v, calleeAux, calleeSig) + } + if got, want := calleeAux.NArgs(), aux.NArgs(); got != want { + v.Fatalf("static call to %s has %d callee arguments, want %d", aux.Fn.Name, got, want) + } + if got, want := calleeAux.ABIInfo().InRegistersUsed(), aux.ABIInfo().InRegistersUsed(); got != want { + v.Fatalf("static call to %s has %d callee input registers, want %d", aux.Fn.Name, got, want) + } + if got, want := calleeAux.ABIInfo().OutRegistersUsed(), aux.ABIInfo().OutRegistersUsed(); got != want { + v.Fatalf("static call to %s has %d callee output registers, want %d", aux.Fn.Name, got, want) + } + if got, want := calleeAux.ArgWidth(), aux.ArgWidth(); got != want { + v.Fatalf("static call to %s has callee argument width %d, want %d", aux.Fn.Name, got, want) + } cc := llvmCallConv(aux.ABI().Which()) - fn := getOrInsertLLVMFunctionRef(aux.Fn, sig, cc) + fn := getOrInsertLLVMFunctionRef(aux.Fn, calleeSig, 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 @@ -1921,26 +2080,34 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { args := make([]llvm.Value, 0, aux.NArgs()) for i := int64(0); i < aux.NArgs(); i++ { arg := lfc.GenLV(v.Args[i]) - if arg.Type() != sig.Type.ParamTypes()[i] { - arg = lfc.llvmValueToABI(v, arg, v.Args[i].Type, aux.TypeOfArg(i), sig.Type.ParamTypes()[i], fmt.Sprintf("%s.arg%d", v, i)) + if arg.Type() != callerSig.Type.ParamTypes()[i] { + arg = lfc.llvmValueToABI(v, arg, v.Args[i].Type, aux.TypeOfArg(i), callerSig.Type.ParamTypes()[i], fmt.Sprintf("%s.arg%d.caller", v, i)) } - if got, want := arg.Type(), sig.Type.ParamTypes()[i]; got != want { + arg = lfc.reshapeLLVMABICarrier(v, arg, calleeSig.Type.ParamTypes()[i], fmt.Sprintf("%s.arg%d.callee", v, i)) + if got, want := arg.Type(), calleeSig.Type.ParamTypes()[i]; got != want { v.Fatalf("argument %d to %s has incompatible LLVM type", i, aux.Fn.Name) } args = append(args, arg) } name := v.String() - if sig.ResultCount == 0 { + if calleeSig.ResultCount == 0 { name = "" } - call := lfc.b.CreateCall(sig.Type, fn, args, name) + call := lfc.b.CreateCall(calleeSig.Type, fn, args, name) call.SetInstructionCallConv(cc) - configureLLVMCall(call, sig) - lfc.materializeAddressedResults(v, call, aux) + configureLLVMCall(call, calleeSig) + if (callerSig.ResultCount == 0) != (calleeSig.ResultCount == 0) { + v.Fatalf("static call to %s has incompatible caller and callee result presence", aux.Fn.Name) + } + result := call + if callerSig.ResultCount != 0 { + result = lfc.reshapeLLVMABICarrier(v, call, callerSig.ReturnType, v.String()+".caller.result") + } + lfc.materializeAddressedResults(v, result, aux) if llvmGCLeaf { markLLVMGCLeafCall(call) } - return call + return result } func (lfc *LLVMFuncContext) indirectCall(v *Value, argStart int, closureContext bool) llvm.Value { @@ -2857,14 +3024,25 @@ func (lfc *LLVMFuncContext) emitOpenDeferRecovery() { deferReturn := getOrInsertLLVMABISymbolRef("runtime.deferreturn", obj.ABIInternal, deferReturnSig, goABIInternalCallConv) lfc.b.SetInsertPointAtEnd(lfc.OpenDeferRecovery) + frontendFunc := lfc.F.Frontend().Func() + if frontendFunc == nil || !frontendFunc.Endlineno.IsKnown() { + lfc.F.fe.Fatalf(lfc.F.Entry.Pos, "open-coded defer recovery has no function-end source position") + } + // Match the native shared deferreturn convention: its synthetic call is + // attributed to the function end, after every source-level defer. Besides + // giving PCLN a stable line, a call in an LLVM debug-info function must + // carry a !dbg location even when it lives in a disconnected recovery block. + lfc.setDebugLocation(frontendFunc.Endlineno) call := lfc.b.CreateCall(deferReturnSig.Type, deferReturn, nil, "") call.SetInstructionCallConv(goABIInternalCallConv) + lfc.b.ClearCurrentDebugLocation() outParams := lfc.F.OwnAux.ABIInfo().OutParams() if len(outParams) != lfc.ResultCount { lfc.F.fe.Fatalf(lfc.F.Entry.Pos, "open-coded defer result count %d does not match LLVM signature result count %d", len(outParams), lfc.ResultCount) } results := make([]llvm.Value, len(outParams)) + reshapeContext := &Value{Block: lfc.F.Entry, Pos: lfc.F.Entry.Pos} for i, result := range outParams { var abiType llvm.Type if lfc.ResultCount == 1 { @@ -2886,6 +3064,7 @@ func (lfc *LLVMFuncContext) emitOpenDeferRecovery() { value := lfc.b.CreateLoad(getLLVMType(result.Type), slot.Value, fmt.Sprintf("open.defer.result%d", i)) value.SetAlignment(int(result.Type.Alignment())) value.SetVolatile(true) + value = lfc.llvmValueToABI(reshapeContext, value, result.Type, lfc.F.OwnAux.TypeOfResult(int64(i)), abiType, fmt.Sprintf("open.defer.result%d.abi", i)) if value.Type() != abiType { lfc.F.fe.Fatalf(lfc.F.Entry.Pos, "open-coded defer result %d has incompatible LLVM ABI type", i) } @@ -2978,6 +3157,11 @@ func llvmFuncCalls(f *Func, target string) bool { return false } +func llvmPreserveRecoverFrame(f *Func) bool { + return f.OwnAux != nil && f.OwnAux.Fn != nil && f.OwnAux.Fn.Name == "runtime.gorecover" || + llvmFuncCalls(f, "runtime.gorecover") +} + func LLVMCompile(f *Func) { if f.OwnAux == nil || f.OwnAux.Fn == nil || f.OwnAux.ABIInfo() == nil { f.fe.Fatalf(f.Entry.Pos, "missing function ABI information in LLVM lowering for %s", f.Name) @@ -2988,20 +3172,21 @@ func LLVMCompile(f *Func) { } cc := llvmCallConv(f.OwnAux.ABI().Which()) FCtxt := &LLVMFuncContext{ - BBs: map[ID]llvm.BasicBlock{}, - Vs: map[ID]llvm.Value{}, - Locals: map[llvmLocalKey]llvmStackSlot{}, - AddressedResults: map[ID][]llvmAddressedResult{}, - ResultSlots: map[ID]llvm.Value{}, - ItabMethods: map[ID]bool{}, - ClosureCodeLoads: map[ID]bool{}, - DeferResults: map[llvmLocalKey]bool{}, - DeferResultKeys: map[ID]llvmLocalKey{}, - OpenDeferSlots: map[llvmLocalKey]int{}, - F: f, - b: GlobalCtxt.NewBuilder(), - ReturnType: sig.ReturnType, - ResultCount: sig.ResultCount, + BBs: map[ID]llvm.BasicBlock{}, + Vs: map[ID]llvm.Value{}, + Locals: map[llvmLocalKey]llvmStackSlot{}, + AddressedResults: map[ID][]llvmAddressedResult{}, + ResultSlots: map[ID]llvm.Value{}, + ItabMethods: map[ID]bool{}, + ClosureCodeLoads: map[ID]bool{}, + DeferResults: map[llvmLocalKey]bool{}, + DeferResultKeys: map[ID]llvmLocalKey{}, + RequiredInlinePos: map[int]bool{}, + OpenDeferSlots: map[llvmLocalKey]int{}, + F: f, + b: GlobalCtxt.NewBuilder(), + ReturnType: sig.ReturnType, + ResultCount: sig.ResultCount, } defer FCtxt.b.Dispose() @@ -3054,8 +3239,20 @@ func LLVMCompile(f *Func) { } } cgoUnsafeArgs := frontendFunc != nil && frontendFunc.Pragma&ir.CgoUnsafeArgs != 0 - frontendNoInline := frontendFunc != nil && (frontendFunc.Pragma&ir.Noinline != 0 || frontendFunc.HasDefer() || cgoUnsafeArgs) - if frontendNoInline || llvmFuncCalls(f, "runtime.gorecover") { + // Native Go performs all inlining before it computes and checks the nosplit + // call graph. Do not let LLVM perform a second, invisible round of inlining + // for a nosplit callee: doing so can inflate a caller's frame after Go's + // budget decisions and make an otherwise valid runtime nosplit chain fail at + // link time. + frontendNoInline := f.NoSplit || frontendFunc != nil && (frontendFunc.Pragma&ir.Noinline != 0 || frontendFunc.HasDefer() || cgoUnsafeArgs) + // Go 1.27's gorecover implementation unwinds physical frames and skips its + // own frame before counting the deferred caller. LLVM is capable of inlining + // this much larger function even though the Go inliner does not; doing so + // removes the frame that the runtime algorithm deliberately skips and can + // make an unrelated active panic recoverable. Preserve both gorecover itself + // and every direct recover caller as physical frame boundaries. + preserveRecoverFrame := llvmPreserveRecoverFrame(f) + if frontendNoInline || preserveRecoverFrame { FCtxt.LF.AddFunctionAttr(llvmNoInlineAttribute()) } if f.OpenDeferBits != nil { @@ -3082,10 +3279,20 @@ func LLVMCompile(f *Func) { } } FCtxt.LF.AddFunctionAttr(GlobalCtxt.CreateStringAttribute(goAsyncUnsafeAttr, "")) - // TODO(goallc): Once LLVM lowering propagates the compiler's precise - // morestack policy, attach this only to functions whose prologue can grow - // the Go stack. - FCtxt.LF.AddFunctionAttr(GlobalCtxt.CreateStringAttribute(goStackGrowthStatepointAttr, "")) + // A //go:nosplit function must not acquire that late morestack edge. Besides + // violating the runtime's nosplit call graph, it would expose a safepoint the + // frontend deliberately prohibited. Give target frame lowering the source + // policy directly instead of asking it to infer a pragma from GoObj metadata. + if f.NoSplit { + FCtxt.LF.AddFunctionAttr(GlobalCtxt.CreateStringAttribute(goNoSplitAttr, "")) + } + // Native Go gives //go:systemstack functions a distinct stack-growth + // prologue: it checks g.stackguard1 and calls runtime.morestackc. Carry the + // source pragma through AttrCFunc so target frame lowering cannot silently + // use the ordinary goroutine stack-growth protocol for runtime code. + if f.OwnAux.Fn.CFunc() { + FCtxt.LF.AddFunctionAttr(GlobalCtxt.CreateStringAttribute(goSystemStackAttr, "")) + } FCtxt.LF.AddFunctionAttr(GlobalCtxt.CreateStringAttribute(llvmFramePointerAttr, llvmFramePointerNonLeaf)) if sig.HasClosureContext { FCtxt.ClosureContext = FCtxt.LF.Param(sig.ClosureContextIndex) @@ -3436,6 +3643,7 @@ var goObjImportsWritten bool var currentLLVMDataLowerer *llvmDataLowerer var goObjCompilerUsed []llvm.Value var goObjCompilerUsedNames map[string]bool +var llvmStaticCalleeAuxCache map[*obj.LSym]*AuxCall var GlobalCtxt = llvm.GlobalContext() @@ -3576,7 +3784,9 @@ func InitModule(pkg *types.Pkg) { currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) goObjCompilerUsed = nil goObjCompilerUsedNames = make(map[string]bool) + initLLVMGoObjLocalDefinitions() emitLateGoObjBuiltinDeclarations() + llvmStaticCalleeAuxCache = make(map[*obj.LSym]*AuxCall) initLLVMDebugInfo(pkg) } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_test.go b/src/cmd/compile/internal/ssa/ssa2llvm_test.go index 6dd54e3519e954..db0e9113d1f0f9 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_test.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "cmd/compile/internal/abi" "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" @@ -22,6 +23,110 @@ import ( "github.com/goallc/go-llvm" ) +type llvmTestTypeName struct { + sym *types.Sym +} + +func (n *llvmTestTypeName) Sym() *types.Sym { return n.sym } +func (*llvmTestTypeName) Pos() src.XPos { return src.NoXPos } +func (*llvmTestTypeName) Type() *types.Type { return nil } + +func TestLLVMABICarrierPreservesNamedAggregateIdentity(t *testing.T) { + pkg := types.NewPkg("runtime", "runtime") + namedSlice := types.NewNamed(&llvmTestTypeName{sym: pkg.Lookup("slice")}) + namedSlice.SetUnderlying(types.NewStruct([]*types.Field{ + types.NewField(src.NoXPos, pkg.Lookup("array"), types.Types[types.TUNSAFEPTR]), + types.NewField(src.NoXPos, pkg.Lookup("len"), types.Types[types.TINT]), + types.NewField(src.NoXPos, pkg.Lookup("cap"), types.Types[types.TINT]), + })) + types.CalcSize(namedSlice) + builtinSlice := types.NewSlice(types.Types[types.TUINT8]) + types.CalcSize(builtinSlice) + + if getLLVMType(namedSlice) == getLLVMType(builtinSlice) { + t.Fatal("semantic LLVM types unexpectedly lost named aggregate identity") + } + if got, want := getLLVMABIType(namedSlice), getLLVMType(namedSlice); got != want { + t.Fatalf("named ABI carrier = %v, want semantic type %v", got, want) + } + if got, other := getLLVMABIType(namedSlice), getLLVMABIType(builtinSlice); got == other { + t.Fatalf("named ABI carrier unexpectedly collapsed to builtin carrier %v", got) + } +} + +func TestLLVMABICarrierBridgesPromotedReceiverAtCaller(t *testing.T) { + module := GlobalCtxt.NewModule("promoted_receiver_carrier") + builder := GlobalCtxt.NewBuilder() + t.Cleanup(module.Dispose) + t.Cleanup(builder.Dispose) + + pointer := GlobalCtxt.PointerType(0) + receiver := GlobalCtxt.StructCreateNamed("goallc.test.promoted.receiver") + receiver.StructSetBody([]llvm.Type{pointer}, false) + + wrap := llvm.AddFunction(module, "wrap", llvm.FunctionType(receiver, []llvm.Type{pointer}, false)) + builder.SetInsertPointAtEnd(llvm.AddBasicBlock(wrap, "entry")) + context := &LLVMFuncContext{b: builder} + value := &Value{ID: 1, Type: types.Types[types.TUNSAFEPTR]} + builder.CreateRet(context.reshapeLLVMABICarrier(value, wrap.Param(0), receiver, "receiver")) + + unwrap := llvm.AddFunction(module, "unwrap", llvm.FunctionType(pointer, []llvm.Type{receiver}, false)) + builder.SetInsertPointAtEnd(llvm.AddBasicBlock(unwrap, "entry")) + builder.CreateRet(context.reshapeLLVMABICarrier(value, unwrap.Param(0), pointer, "receiver")) + + if err := llvm.VerifyModule(module, llvm.ReturnStatusAction); err != nil { + t.Fatalf("LLVM verifier rejected promoted receiver carrier bridge: %v\n%s", err, module.String()) + } + ir := module.String() + for _, want := range []string{ + "insertvalue %goallc.test.promoted.receiver undef, ptr %0, 0", + "extractvalue %goallc.test.promoted.receiver %0, 0", + } { + if !strings.Contains(ir, want) { + t.Errorf("promoted receiver bridge does not contain %q\n%s", want, ir) + } + } +} + +func TestLLVMStaticCalleeAuxUsesPackageDefinition(t *testing.T) { + oldTarget := typecheck.Target + oldCache := llvmStaticCalleeAuxCache + typecheck.Target = new(ir.Package) + llvmStaticCalleeAuxCache = make(map[*obj.LSym]*AuxCall) + t.Cleanup(func() { + typecheck.Target = oldTarget + llvmStaticCalleeAuxCache = oldCache + }) + + pkg := types.NewPkg("runtime", "runtime") + namedSlice := types.NewNamed(&llvmTestTypeName{sym: pkg.Lookup("slice")}) + namedSlice.SetUnderlying(types.NewStruct([]*types.Field{ + types.NewField(src.NoXPos, pkg.Lookup("array"), types.Types[types.TUNSAFEPTR]), + types.NewField(src.NoXPos, pkg.Lookup("len"), types.Types[types.TINT]), + types.NewField(src.NoXPos, pkg.Lookup("cap"), types.Types[types.TINT]), + })) + types.CalcSize(namedSlice) + builtinSlice := types.NewSlice(types.Types[types.TUINT8]) + types.CalcSize(builtinSlice) + + fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup("growslice"), types.NewSignature(nil, nil, []*types.Field{ + types.NewField(src.NoXPos, nil, namedSlice), + })) + typecheck.Target.Funcs = []*ir.Func{fn} + config := abi.NewABIConfig(9, 15, 0, uint8(obj.ABIInternal)) + caller := StaticAuxCall(fn.LinksymABI(obj.ABIInternal), config.ABIAnalyzeTypes(nil, []*types.Type{builtinSlice})) + callee := llvmStaticCalleeAux(caller) + if callee == caller { + t.Fatal("static call retained the caller's approximate runtime signature") + } + if got := callee.TypeOfResult(0); got != namedSlice { + t.Fatalf("callee result type = %v, want named runtime result %v", got, namedSlice) + } + if cached := llvmStaticCalleeAux(caller); cached != callee { + t.Fatal("static callee signature was not cached") + } +} + func TestLLVMGoObjCompilerUsedOnlyKeepsExternalDataRoots(t *testing.T) { oldModule := CurrentModule oldLowerer := currentLLVMDataLowerer @@ -104,6 +209,29 @@ func TestLLVMUntypedABI0FunctionAddressCreatesFunctionDeclaration(t *testing.T) } } +func TestLLVMNamedAggregateConversionCanReshape(t *testing.T) { + pkg := types.NewPkg("runtime", "runtime") + newCacheType := func(name string, scavType *types.Type) *types.Type { + typ := types.NewNamed(&llvmTestTypeName{sym: pkg.Lookup(name)}) + typ.SetUnderlying(types.NewStruct([]*types.Field{ + types.NewField(src.NoXPos, pkg.Lookup("base"), types.Types[types.TUINTPTR]), + types.NewField(src.NoXPos, pkg.Lookup("cache"), types.Types[types.TUINT64]), + types.NewField(src.NoXPos, pkg.Lookup("scav"), scavType), + })) + return typ + } + pageCache := newCacheType("pageCache", types.Types[types.TUINT64]) + exportedPageCache := newCacheType("PageCache", types.Types[types.TUINT64]) + wrongPageCache := newCacheType("WrongPageCache", types.Types[types.TUINT32]) + + if !llvmValueTypesCanReshape(pageCache, exportedPageCache) { + t.Fatal("defined structs with identical underlying types cannot reshape") + } + if llvmValueTypesCanReshape(pageCache, wrongPageCache) { + t.Fatal("defined structs with different underlying types can reshape") + } +} + func TestLLVMJumpTableDefaultIsUnreachable(t *testing.T) { module := GlobalCtxt.NewModule("jump_table_default") builder := GlobalCtxt.NewBuilder() @@ -193,38 +321,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) + } } } @@ -405,3 +555,31 @@ func TestLLVMTargetCPU(t *testing.T) { }) } } + +func TestLLVMPreserveRecoverFrameUsesLinkSymbolName(t *testing.T) { + recoverFn := &Func{ + Name: "gorecover", + OwnAux: &AuxCall{Fn: &obj.LSym{Name: "runtime.gorecover"}}, + } + if !llvmPreserveRecoverFrame(recoverFn) { + t.Fatal("gorecover definition was not recognized from its qualified link symbol") + } + + unrelated := &Func{ + Name: "gorecover", + OwnAux: &AuxCall{Fn: &obj.LSym{Name: "other.gorecover"}}, + } + if llvmPreserveRecoverFrame(unrelated) { + t.Fatal("unqualified SSA function name incorrectly identified another package's gorecover") + } + + caller := &Func{ + OwnAux: &AuxCall{Fn: &obj.LSym{Name: "runtime.preprintpanics.func1"}}, + Blocks: []*Block{{ + Values: []*Value{{Aux: &AuxCall{Fn: &obj.LSym{Name: "runtime.gorecover"}}}}, + }}, + } + if !llvmPreserveRecoverFrame(caller) { + t.Fatal("direct gorecover caller was not preserved as a physical frame") + } +} diff --git a/src/cmd/internal/goobj/builtin.go b/src/cmd/internal/goobj/builtin.go index 131ec8d602ef27..635f3ff401a7d4 100644 --- a/src/cmd/internal/goobj/builtin.go +++ b/src/cmd/internal/goobj/builtin.go @@ -9,7 +9,10 @@ import ( "strconv" ) -const BuiltinSymbolSuffixPrefix = "", "-S", llvmIR, + "-o", optimizedLLVMIR) + machineIR := runLLVMABICommand(t, nil, llc, "-load-pass-plugin="+plugin, "-stop-after=finalize-isel", - "-o", "-", llvmIR) + "-o", "-", optimizedLLVMIR) for _, pattern := range []string{ `(?s)name:\s+main\.liveScalarStackArgument.*?fixedStack:.*?offset:\s+8.*?isImmutable:\s+false.*?stack:\s+\[\].*?STATEPOINT[^\n]*%fixed-stack\.0.*?LDRXui\s+%fixed-stack\.0`, `(?s)name:\s+main\.livePointerSequenceStackArguments.*?fixedStack:.*?stack:\s+\[\].*?STATEPOINT[^\n]*%fixed-stack\.2[^\n]*%fixed-stack\.0.*?LDRXui\s+%fixed-stack\.[02].*?LDRXui\s+%fixed-stack\.[02]`, @@ -206,7 +231,7 @@ func runLLVMAArch64ABIDifferentialTest(t *testing.T, gorootTestDir string) { } goallcAssembly := runLLVMABICommand(t, nil, llc, - "-load-pass-plugin="+plugin, "-filetype=asm", llvmIR, "-o", "-") + "-load-pass-plugin="+plugin, "-filetype=asm", optimizedLLVMIR, "-o", "-") for _, name := range []string{ "main.mixedABI", "main.liveScalarStackArgument", "main.livePointerSequenceStackArguments", @@ -221,7 +246,7 @@ func runLLVMAArch64ABIDifferentialTest(t *testing.T, gorootTestDir string) { checkLLVMABIAssembly(t, nativeAssembly, goallcAssembly) runLLVMABICommand(t, nil, llc, - "-load-pass-plugin="+plugin, "-filetype=obj", llvmIR, "-o", goallcObject) + "-load-pass-plugin="+plugin, "-filetype=obj", optimizedLLVMIR, "-o", goallcObject) native := readLLVMABIObject(t, nativeObject) goallc := readLLVMABIObject(t, goallcObject) @@ -240,7 +265,7 @@ func runLLVMAArch64ABIDifferentialTest(t *testing.T, gorootTestDir string) { // homes in LocalsPointerMaps through locals-only alloca records. goallcArgsMaps: [][]int{{2, 4, 18}, {2}}, nativeStackMaps: []int32{-1, 0, -1}, - goallcStackMaps: []int32{-1, 1, -1}, + goallcStackMaps: []int32{-1, 1, -1, 1}, goallcQueryMaps: [][]int{{2, 4, 18}, {2}, {2}, {2}, {2}}, }, { @@ -339,7 +364,8 @@ func runLLVMAArch64ABIDifferentialTest(t *testing.T, gorootTestDir string) { nativeArgsMaps: [][]int{{4}, nil}, goallcArgsMaps: [][]int{{4}, nil, nil}, nativeStackMaps: []int32{-1, 0, 1, -1}, - goallcStackMaps: []int32{-1, 1, 2, -1, 2}, + goallcStackMaps: []int32{-1, 1, -1, 2}, + goallcQueryMaps: [][]int{{4}, nil, nil, nil}, }, } for _, tc := range cases { @@ -624,10 +650,13 @@ func runLLVMABIArgsPointerMapSourceTest(t *testing.T, gorootTestDir, llc, opt, p } runLLVMABICommand(t, rewrittenIR, opt, "-load-pass-plugin="+plugin, "-passes=verify", "-disable-output", "-") + optimizedGoallcIR := goallcArchive + ".opt.ll" + runLLVMABICommand(t, nil, opt, "-passes=default", "-S", goallcIR, + "-o", optimizedGoallcIR) machineIR := runLLVMABICommand(t, nil, llc, "-load-pass-plugin="+plugin, "-stop-after=finalize-isel", - "-o", "-", goallcIR) + "-o", "-", optimizedGoallcIR) for _, pattern := range []string{ `(?s)name:\s+p\.liveScalarStackArgument.*?fixedStack:.*?isImmutable:\s+false.*?stack:\s+\[\].*?STATEPOINT[^\n]*%fixed-stack\.0.*?LDRXui\s+%fixed-stack\.0`, `(?s)name:\s+p\.liveAggregateStackArgument.*?fixedStack:.*?id:\s+2.*?size:\s+24.*?isAliased:\s+true.*?stack:\s+\[\].*?STATEPOINT[^\n]*%fixed-stack\.1[^\n]*%fixed-stack\.0.*?LDRXui\s+%fixed-stack\.0.*?LDRXui\s+%fixed-stack\.1`, @@ -637,7 +666,7 @@ func runLLVMABIArgsPointerMapSourceTest(t *testing.T, gorootTestDir, llc, opt, p } } runLLVMABICommand(t, nil, llc, "-load-pass-plugin="+plugin, - "-filetype=obj", goallcIR, "-o", goallcObject) + "-filetype=obj", optimizedGoallcIR, "-o", goallcObject) native := readLLVMABIObject(t, nativeObject) goallc := readLLVMABIObject(t, goallcObject) @@ -944,6 +973,42 @@ func findLLVMABISymbol(t *testing.T, document llvmABIDocument, name string) llvm return llvmABISymbol{} } +func llvmABIRelocationTargetName(document llvmABIDocument, target llvmABIReference) string { + if target.Name != "" || target.PkgKind != "none" { + return target.Name + } + for _, member := range document.Members { + if member.GoObject == nil { + continue + } + var nonpackageDefinitions uint32 + for _, symbol := range member.GoObject.Symbols { + if symbol.Class == "nonpackage" && symbol.ClassIndex >= nonpackageDefinitions { + nonpackageDefinitions = symbol.ClassIndex + 1 + } + } + if target.SymIndex < nonpackageDefinitions { + continue + } + classIndex := target.SymIndex - nonpackageDefinitions + for _, reference := range member.GoObject.References { + if reference.Class == "nonpackage_reference" && reference.ClassIndex == classIndex { + return reference.Name + } + } + } + return "" +} + +func llvmABIHasRelocationTo(document llvmABIDocument, symbol llvmABISymbol, name string) bool { + for _, relocation := range symbol.Relocations { + if llvmABIRelocationTargetName(document, relocation.Target) == name { + return true + } + } + return false +} + func checkLLVMABISymbol(t *testing.T, backend string, symbol llvmABISymbol, tc llvmABICase) { t.Helper() if symbol.ABI != 1 { diff --git a/src/cmd/internal/testdir/llvm_stdlib_test.go b/src/cmd/internal/testdir/llvm_stdlib_test.go index e4a1e94670d4e2..2bcb751256c77f 100644 --- a/src/cmd/internal/testdir/llvm_stdlib_test.go +++ b/src/cmd/internal/testdir/llvm_stdlib_test.go @@ -31,6 +31,7 @@ type llvmStdlibTestSet struct { Whitelist map[string]string `json:"whitelist"` Blacklist map[string]string `json:"blacklist"` PlatformBlacklist map[string]map[string]string `json:"platform_blacklist,omitempty"` + DependencyClosure []string `json:"dependency_closure,omitempty"` } type llvmStdlibPolicy struct { @@ -100,8 +101,9 @@ func classifyLLVMStdlibPackage(set llvmStdlibTestSet, name string) llvmStdlibCla func effectiveLLVMStdlibTestSet(set llvmStdlibTestSet, platform string) llvmStdlibTestSet { effective := llvmStdlibTestSet{ - Whitelist: make(map[string]string, len(set.Whitelist)), - Blacklist: make(map[string]string, len(set.Blacklist)), + Whitelist: make(map[string]string, len(set.Whitelist)), + Blacklist: make(map[string]string, len(set.Blacklist)), + DependencyClosure: make([]string, 0, len(set.DependencyClosure)), } for name, reason := range set.Whitelist { effective.Whitelist[name] = reason @@ -113,9 +115,23 @@ func effectiveLLVMStdlibTestSet(set llvmStdlibTestSet, platform string) llvmStdl delete(effective.Whitelist, name) effective.Blacklist[name] = reason } + for _, name := range set.DependencyClosure { + if _, ok := effective.Whitelist[name]; ok { + effective.DependencyClosure = append(effective.DependencyClosure, name) + } + } return effective } +func llvmStdlibUsesDependencyClosure(set llvmStdlibTestSet, name string) bool { + for _, entry := range set.DependencyClosure { + if entry == name { + return true + } + } + return false +} + func validateLLVMStdlibPolicy(t *testing.T, packages map[string]bool, set llvmStdlibTestSet) { t.Helper() failed := false @@ -179,6 +195,22 @@ func validateLLVMStdlibPolicy(t *testing.T, packages map[string]bool, set llvmSt } } } + closureEntries := make(map[string]bool, len(set.DependencyClosure)) + for _, name := range set.DependencyClosure { + if name == "*" || strings.ContainsAny(name, "*?[\\") { + t.Errorf("LLVM standard library dependency-closure entry %q is not an exact package", name) + failed = true + } + if closureEntries[name] { + t.Errorf("LLVM standard library dependency-closure entry %q is duplicated", name) + failed = true + } + closureEntries[name] = true + if _, ok := set.Whitelist[name]; !ok { + t.Errorf("LLVM standard library dependency-closure entry %q is not in the whitelist", name) + failed = true + } + } for name := range packages { if classifyLLVMStdlibPackage(set, name) == llvmStdlibUnclassified { t.Errorf("standard library package %q is not classified as white or black", name) @@ -216,8 +248,9 @@ func TestClassifyLLVMStdlibPackage(t *testing.T) { func TestEffectiveLLVMStdlibTestSet(t *testing.T) { set := llvmStdlibTestSet{ - Whitelist: map[string]string{"bytes": "qualified", "cmp": "qualified"}, - Blacklist: map[string]string{"*": "not yet qualified"}, + Whitelist: map[string]string{"bytes": "qualified", "cmp": "qualified"}, + Blacklist: map[string]string{"*": "not yet qualified"}, + DependencyClosure: []string{"bytes", "cmp"}, PlatformBlacklist: map[string]map[string]string{ "linux/amd64": {"bytes": "known failure"}, }, @@ -232,6 +265,38 @@ func TestEffectiveLLVMStdlibTestSet(t *testing.T) { if got := classifyLLVMStdlibPackage(set, "bytes"); got != llvmStdlibWhite { t.Errorf("platform selection modified the common set: classifyLLVMStdlibPackage(bytes) = %v, want %v", got, llvmStdlibWhite) } + if llvmStdlibUsesDependencyClosure(effective, "bytes") { + t.Error("platform-blacklisted bytes retained dependency-closure qualification") + } + if !llvmStdlibUsesDependencyClosure(effective, "cmp") { + t.Error("effective policy lost cmp dependency-closure qualification") + } +} + +func llvmStdlibDependencyPackages(t *testing.T, packages map[string]bool, name string) []string { + t.Helper() + cmd := testenv.Command(t, llvmStdlibGoTool(t), "list", "-deps", "-f={{.ImportPath}}", name) + cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=", "GOROOT="+testenv.GOROOT(t)) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("list dependencies for standard library package %q: %v\n%s", name, err, out) + } + seen := make(map[string]bool) + var dependencies []string + for _, dependency := range strings.Fields(string(out)) { + if !packages[dependency] { + t.Fatalf("dependency-closure package %q has non-standard dependency %q", name, dependency) + } + if !seen[dependency] { + seen[dependency] = true + dependencies = append(dependencies, dependency) + } + } + if !seen[name] { + t.Fatalf("dependency closure for %q does not contain the package itself", name) + } + sort.Strings(dependencies) + return dependencies } func TestLLVMStdlib(t *testing.T) { @@ -252,13 +317,20 @@ func TestLLVMStdlib(t *testing.T) { set := effectiveLLVMStdlibTestSet(policySet, platform) configureLLVMTestToolchain(t) toolexec := llvmToolexec(t, "default") + runtimeToolexec := llvmToolexecWithNativePackages(t, "default", "runtime_test", "runtime.test") whitelist := make([]string, 0, len(set.Whitelist)) for name := range set.Whitelist { whitelist = append(whitelist, name) } sort.Strings(whitelist) - t.Logf("LLVM standard library entry-package policy: %d white, %d black (%d packages)", len(whitelist), len(packages)-len(whitelist), len(packages)) + t.Logf("LLVM standard library policy: %d white, %d black (%d packages), %d dependency closures", len(whitelist), len(packages)-len(whitelist), len(packages), len(set.DependencyClosure)) + + dependencyPackages := make(map[string][]string, len(set.DependencyClosure)) + for _, name := range set.DependencyClosure { + dependencyPackages[name] = llvmStdlibDependencyPackages(t, packages, name) + t.Logf("LLVM stdlib dependency closure: package=%q packages=%d", name, len(dependencyPackages[name])) + } knownBlacklist := make([]string, 0, len(set.Blacklist)-1) for name := range set.Blacklist { @@ -279,16 +351,40 @@ func TestLLVMStdlib(t *testing.T) { cache := t.TempDir() for _, name := range whitelist { t.Run(name, func(t *testing.T) { + compilePackages := []string{name} + packageToolexec := toolexec + testTimeout := "2m" + processTimeout := 5 * time.Minute + if closure := dependencyPackages[name]; len(closure) != 0 { + compilePackages = closure + testTimeout = "5m" + processTimeout = 8 * time.Minute + if name == "runtime" { + // runtime_test and the generated runtime.test main are test + // scaffolding rather than part of the qualified runtime closure. + packageToolexec = runtimeToolexec + } + } for run := 1; run <= llvmStdlibWhitelistRuns; run++ { - ctx, cancel := stdcontext.WithTimeout(stdcontext.Background(), 5*time.Minute) - cmd := testenv.CommandContext(t, ctx, llvmStdlibGoTool(t), + ctx, cancel := stdcontext.WithTimeout(stdcontext.Background(), processTimeout) + args := []string{ "test", "-count=1", - "-timeout=2m", - "-toolexec="+toolexec, - fmt.Sprintf("-gcflags=%s=-enablellvm -llvmironly", name), - name, - ) + "-timeout=" + testTimeout, + "-toolexec=" + packageToolexec, + } + if name == "runtime" { + // LLVM GoObj does not yet emit the complete per-function + // DWARF carrier set expected by the Go linker. Runtime + // qualification currently covers code generation, GoObj, + // linking, and execution, but not debug information. + args = append(args, "-ldflags=-w") + } + for _, compilePackage := range compilePackages { + args = append(args, fmt.Sprintf("-gcflags=%s=-enablellvm -llvmironly", compilePackage)) + } + args = append(args, name) + cmd := testenv.CommandContext(t, ctx, llvmStdlibGoTool(t), args...) cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=", diff --git a/src/cmd/internal/testdir/llvm_test.go b/src/cmd/internal/testdir/llvm_test.go index 353951f1004078..06ba2e3a09160a 100644 --- a/src/cmd/internal/testdir/llvm_test.go +++ b/src/cmd/internal/testdir/llvm_test.go @@ -15,7 +15,9 @@ import ( "os/exec" "path" "path/filepath" + "regexp" "runtime" + "slices" "sort" "strings" "sync" @@ -189,6 +191,8 @@ func runLLVMInfrastructureTests(t *testing.T, common testCommon) { runLLVMCallerStateTest(t, common.gorootTestDir) }) t.Run("getg-abi0-fail-closed", runLLVMGetGABI0FailClosedTest) + t.Run("nosplit", runLLVMNoSplitTest) + t.Run("nowritebarrierrec", runLLVMNoWriteBarrierRecTest) t.Run("writebarrier-helpers", runLLVMWriteBarrierHelperTest) t.Run("compile-only-regressions", func(t *testing.T) { for _, name := range []string{"cmp.go", "typeparam/issue47684c.go"} { @@ -200,6 +204,121 @@ func runLLVMInfrastructureTests(t *testing.T, common testCommon) { t.Run("writebarrier-ir", runLLVMWriteBarrierIRTests) } +func runLLVMNoSplitTest(t *testing.T) { + t.Helper() + dir := t.TempDir() + source := filepath.Join(dir, "nosplit.go") + program := `package p + +func use(*[32]uintptr) + +//go:nosplit +//go:noinline +func NoSplit(pointer *int) *int { + var words [32]uintptr + use(&words) + return pointer +} + +//go:noinline +func Split(pointer *int) *int { + var words [32]uintptr + use(&words) + return pointer +} +` + if err := os.WriteFile(source, []byte(program), 0o666); err != nil { + t.Fatal(err) + } + archive := filepath.Join(dir, "nosplit.a") + runLLVMABICommand(t, nil, goTool, "tool", "compile", + "-p=p", "-enablellvm", "-llvmironly", "-o", archive, source) + ir, err := os.ReadFile(archive + ".ll") + if err != nil { + t.Fatal(err) + } + attributeLine := func(name string) []byte { + definition := regexp.MustCompile(`(?m)^define goabiinternal .*@` + regexp.QuoteMeta(name) + `\([^\n]*\) #([0-9]+)`).FindSubmatch(ir) + if definition == nil { + t.Fatalf("LLVM IR has no attributed definition for %s\n%s", name, ir) + } + pattern := regexp.MustCompile(`(?m)^attributes #` + string(definition[1]) + ` = \{.*$`) + line := pattern.Find(ir) + if line == nil { + t.Fatalf("LLVM IR has no attribute group for %s\n%s", name, ir) + } + return line + } + noSplitAttrs := attributeLine("p.NoSplit") + if !bytes.Contains(noSplitAttrs, []byte(`"go-nosplit"`)) || + !bytes.Contains(noSplitAttrs, []byte(`noinline`)) { + t.Fatalf("LLVM nosplit attributes do not select the nosplit prologue policy: %s", noSplitAttrs) + } + splitAttrs := attributeLine("p.Split") + if bytes.Contains(splitAttrs, []byte(`"go-nosplit"`)) { + t.Fatalf("LLVM split attributes do not select the stack-growth prologue policy: %s", splitAttrs) + } + if bytes.Contains(ir, []byte(`"go-stack-growth-statepoint"`)) { + t.Fatal("LLVM IR still contains the obsolete stack-growth attribute") + } + + llc := llvmToolPath(t, "llc", "GOALLC_LLC") + plugin := llvmABIPassPlugin(t, llc) + object := filepath.Join(dir, "nosplit.o") + runLLVMABICommand(t, nil, llc, "-load-pass-plugin="+plugin, + "-verify-machineinstrs", "-filetype=obj", "-o", object, archive+".ll") + document := readLLVMABIObject(t, object) + noSplit := findLLVMABISymbol(t, document, "p.NoSplit") + if !slices.Contains(noSplit.FlagNames, "nosplit") { + t.Fatalf("p.NoSplit GoObj flags %v do not contain nosplit", noSplit.FlagNames) + } + if llvmABIHasRelocationTo(document, noSplit, "runtime.morestack_noctxt") { + t.Fatal("p.NoSplit unexpectedly calls runtime.morestack_noctxt") + } + noSplitArgs := llvmABIArgsPointerBitmaps(t, noSplit) + if len(noSplitArgs) == 0 || !slices.Equal(noSplitArgs[0], []int{0}) { + t.Fatalf("p.NoSplit entry ArgsPointerMaps = %v, want pointer bit 0", noSplitArgs) + } + split := findLLVMABISymbol(t, document, "p.Split") + if !llvmABIHasRelocationTo(document, split, "runtime.morestack_noctxt") { + t.Fatal("p.Split has no runtime.morestack_noctxt relocation") + } + splitArgs := llvmABIArgsPointerBitmaps(t, split) + if len(splitArgs) == 0 || !slices.Equal(splitArgs[0], []int{0}) { + t.Fatalf("p.Split entry ArgsPointerMaps = %v, want pointer bit 0", splitArgs) + } +} + +func runLLVMNoWriteBarrierRecTest(t *testing.T) { + t.Helper() + archive := filepath.Join(t.TempDir(), "nowritebarrier.a") + source := filepath.Join(testenv.GOROOT(t), "test", "nowritebarrier.go") + cmd := exec.Command(goTool, "tool", "compile", + "-p=runtime", + "-+", + "-C", + "-e", + "-d=ssa/check/on", + "-enablellvm", + "-llvmironly", + "-o", archive, + source, + ) + cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") + out, err := cmd.CombinedOutput() + if err == nil { + t.Fatalf("LLVM accepted prohibited runtime write barriers\n%s", out) + } + for _, want := range []string{ + "write barrier prohibited by caller; b2", + "write barrier prohibited by caller; d3", + } { + if !bytes.Contains(out, []byte(want)) { + t.Fatalf("LLVM //go:nowritebarrierrec check missed %q: %v\n%s", want, err, out) + } + } +} + func runLLVMGetGABI0FailClosedTest(t *testing.T) { t.Helper() dir := t.TempDir() @@ -798,6 +917,10 @@ func runLLVMWriteBarrierIRTests(t *testing.T) { } func llvmToolexec(t *testing.T, optPasses string) string { + return llvmToolexecWithNativePackages(t, optPasses) +} + +func llvmToolexecWithNativePackages(t *testing.T, optPasses string, nativePackages ...string) string { t.Helper() wrapper := llvmToolexecPath(t) @@ -812,6 +935,9 @@ func llvmToolexec(t *testing.T, optPasses string) string { opt := llvmToolPath(t, "opt", "GOALLC_OPT") args = append(args, "-opt="+opt, "-opt-passes="+optPasses) } + for _, name := range nativePackages { + args = append(args, "-native-package="+name) + } value, err := quoted.Join(args) if err != nil { t.Fatal(err) diff --git a/src/cmd/llvmplugin/CMakeLists.txt b/src/cmd/llvmplugin/CMakeLists.txt index c91bb6715e6b24..b7a465b9ef0f17 100644 --- a/src/cmd/llvmplugin/CMakeLists.txt +++ b/src/cmd/llvmplugin/CMakeLists.txt @@ -21,9 +21,9 @@ add_definitions(${LLVM_DEFINITIONS}) include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) add_llvm_pass_plugin(GoALLCStatepoints + GoALLCGCMetadataPrinter.cpp GoALLCInlineAnchors.cpp GoALLCPreCodeGen.cpp - GoALLCStackMapPrinter.cpp GoALLCStatepoints.cpp GoALLCStatepointPlugin.cpp ) @@ -252,7 +252,7 @@ if(BUILD_TESTING) ) set_tests_properties(GoALLCStatepoints.FrontendMarkersPreserved PROPERTIES PASS_REGULAR_EXPRESSION - "go-stack-growth-statepoint" + "gc \"goallc\"" ) foreach(GOALLC_DEBUG_TARGET IN ITEMS X86 AArch64) diff --git a/src/cmd/llvmplugin/GoALLCGCMetadataPrinter.cpp b/src/cmd/llvmplugin/GoALLCGCMetadataPrinter.cpp new file mode 100644 index 00000000000000..1ffe6b65d19adb --- /dev/null +++ b/src/cmd/llvmplugin/GoALLCGCMetadataPrinter.cpp @@ -0,0 +1,20 @@ +// 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. + +#include "llvm/CodeGen/GCMetadataPrinter.h" + +using namespace llvm; + +namespace { + +// AsmPrinter requires every GC strategy to have a registered metadata +// printer. GoObj itself consumes Machine StackMaps in LLVM's AsmPrinter path, +// so the Go-owned plugin only needs this marker registration. +class GoALLCGCMetadataPrinter final : public GCMetadataPrinter {}; + +} // namespace + +static GCMetadataPrinterRegistry::Add + GoALLCGCMetadataPrinterRegistration("goallc", + "GoALLC GC metadata marker"); diff --git a/src/cmd/llvmplugin/GoALLCInlineAnchors.cpp b/src/cmd/llvmplugin/GoALLCInlineAnchors.cpp index 8f854545f28ede..d74c779ea3e265 100644 --- a/src/cmd/llvmplugin/GoALLCInlineAnchors.cpp +++ b/src/cmd/llvmplugin/GoALLCInlineAnchors.cpp @@ -3,14 +3,17 @@ // license that can be found in the LICENSE file. #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/CodeGen/MachineBasicBlock.h" #include "llvm/CodeGen/MachineFunction.h" #include "llvm/CodeGen/MachineFunctionPass.h" #include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DebugLoc.h" +#include "llvm/IR/Module.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCSymbol.h" #include "llvm/Support/ErrorHandling.h" @@ -71,6 +74,42 @@ class GoALLCInlineAnchorPass final : public MachineFunctionPass { Loc->getAtomRank()); } + SmallVector + requiredInlineLocations(const MachineFunction &MF) const { + SmallVector Required; + const Module *M = MF.getFunction().getParent(); + const NamedMDNode *Locations = + M ? M->getNamedMetadata("goobj.debug.inline.required") : nullptr; + if (!Locations) + return Required; + + for (const MDNode *Entry : Locations->operands()) { + if (Entry->getNumOperands() != 2) + report_fatal_error("expected !goobj.debug.inline.required entries to " + "have two operands"); + const auto *CAM = + dyn_cast_or_null(Entry->getOperand(0)); + const auto *GV = CAM ? dyn_cast(CAM->getValue()) : nullptr; + const auto *Loc = dyn_cast_or_null(Entry->getOperand(1)); + if (!GV || !Loc || !Loc->getInlinedAt()) + report_fatal_error("invalid !goobj.debug.inline.required entry"); + if (GV == &MF.getFunction()) + Required.push_back(Loc); + } + + llvm::stable_sort( + Required, [](const DILocation *LHS, const DILocation *RHS) { + auto Depth = [](const DILocation *Loc) { + unsigned Result = 0; + for (; Loc && Loc->getInlinedAt(); Loc = Loc->getInlinedAt()) + ++Result; + return Result; + }; + return Depth(LHS) > Depth(RHS); + }); + return Required; + } + public: static char ID; @@ -88,6 +127,51 @@ class GoALLCInlineAnchorPass final : public MachineFunctionPass { const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo(); CanonicalSites.clear(); + // LLVM may merge instructions from distinct frontend inline frames (for + // example, SLP-vectorizing adjacent stores) and retain only one debug + // location. Materialize a source NOP for each required inline edge that no + // longer occurs in the optimized MachineFunction. Deeper chains are + // considered first because one such marker also preserves all its parents. + DenseSet SurvivingCallsites; + for (MachineBasicBlock &MBB : MF) + for (MachineInstr &MI : MBB) + if (!MI.isMetaInstruction()) + for (const DILocation *Loc = MI.getDebugLoc().get(); + Loc && Loc->getInlinedAt(); Loc = Loc->getInlinedAt()) + SurvivingCallsites.insert(Loc->getInlinedAt()); + + bool Changed = false; + if (!MF.empty()) { + MachineBasicBlock *InsertBlock = nullptr; + MachineBasicBlock::iterator InsertAt; + for (MachineBasicBlock &MBB : MF) { + auto It = MBB.begin(); + while (It != MBB.end() && (It->isMetaInstruction() || + It->getFlag(MachineInstr::FrameSetup))) + ++It; + if (It != MBB.end()) { + InsertBlock = &MBB; + InsertAt = It; + break; + } + } + for (const DILocation *Loc : requiredInlineLocations(MF)) { + const DILocation *Innermost = Loc->getInlinedAt(); + if (SurvivingCallsites.contains(Innermost)) + continue; + if (!InsertBlock) + report_fatal_error("GoALLC required inline location has no " + "post-prologue insertion point"); + TII.insertNoop(*InsertBlock, InsertAt); + MachineInstr &Marker = *std::prev(InsertAt); + Marker.setDebugLoc(DebugLoc(Loc)); + for (const DILocation *Site = Loc; Site && Site->getInlinedAt(); + Site = Site->getInlinedAt()) + SurvivingCallsites.insert(Site->getInlinedAt()); + Changed = true; + } + } + // Normalize complete inline chains before inspecting them. This keeps the // anchor pass and the later GoObj debug handler on the same edge identity. for (MachineBasicBlock &MBB : MF) @@ -97,7 +181,6 @@ class GoALLCInlineAnchorPass final : public MachineFunctionPass { DebugLoc(canonicalizeLocation(MI.getDebugLoc().get()))); DenseSet AnchoredCallsites; - bool Changed = false; for (MachineBasicBlock &MBB : MF) { for (auto It = MBB.begin(), End = MBB.end(); It != End; ++It) { diff --git a/src/cmd/llvmplugin/GoALLCStackMapPrinter.cpp b/src/cmd/llvmplugin/GoALLCStackMapPrinter.cpp deleted file mode 100644 index d8e88ffc55df66..00000000000000 --- a/src/cmd/llvmplugin/GoALLCStackMapPrinter.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -#include "llvm/ADT/SmallVector.h" -#include "llvm/CodeGen/AsmPrinter.h" -#include "llvm/CodeGen/GCMetadataPrinter.h" -#include "llvm/CodeGen/GoCallingConv.h" -#include "llvm/CodeGen/StackMaps.h" -#include "llvm/MC/MCContext.h" -#include "llvm/Support/ErrorHandling.h" - -#include -#include - -using namespace llvm; - -namespace { - -class GoALLCStackMapPrinter final : public GCMetadataPrinter { -public: - bool emitStackMaps(StackMaps &SM, AsmPrinter &AP) override; -}; - -MCContext::GoObjStackMapLocation::LocationType -convertLocationType(StackMaps::Location::LocationType Type) { - using GoLocation = MCContext::GoObjStackMapLocation; - switch (Type) { - case StackMaps::Location::Unprocessed: - return GoLocation::Unprocessed; - case StackMaps::Location::Register: - return GoLocation::Register; - case StackMaps::Location::Direct: - return GoLocation::Direct; - case StackMaps::Location::Indirect: - return GoLocation::Indirect; - case StackMaps::Location::Constant: - return GoLocation::Constant; - case StackMaps::Location::ConstantIndex: - return GoLocation::ConstantIndex; - } - llvm_unreachable("unknown StackMaps location type"); -} - -uint64_t getNonnegativeConstant(const StackMaps::Location &Location, - StringRef Description) { - if (Location.Type != StackMaps::Location::Constant || Location.Offset < 0) - report_fatal_error("malformed GoALLC stackmap " + Description); - return static_cast(Location.Offset); -} - -} // namespace - -static GCMetadataPrinterRegistry::Add - GoALLCStackMapPrinterRegistration("goallc", - "GoALLC GoObj Machine StackMaps bridge"); - -bool GoALLCStackMapPrinter::emitStackMaps(StackMaps &SM, AsmPrinter &AP) { - // Other object formats can continue to use LLVM's standard stackmap - // serialization while the GoObj bridge is being brought up. - if (!AP.OutContext.isGoObj()) - return false; - - auto &Callsites = SM.getCSInfos(); - auto Callsite = Callsites.begin(); - uint32_t PointerSize = AP.getPointerSize(); - if (!PointerSize) - report_fatal_error("GoALLC statepoint target has no pointer size"); - - for (const auto &[Function, Info] : SM.getFnInfos()) { - for (uint64_t I = 0; I != Info.RecordCount; ++I) { - if (Callsite == Callsites.end()) - report_fatal_error( - "GoALLC statepoint function record count exceeds callsites"); - - // LLVM's statepoint parser flattens CC, flags, deopt count, deopt - // operands, GC base/derived pairs, and GC allocas into Locations. The - // first three entries and the deopt operands are not GC roots. - const StackMaps::CallsiteInfo &CSI = *Callsite++; - if (CSI.Locations.size() < 3) - report_fatal_error("malformed GoALLC statepoint location list"); - (void)getNonnegativeConstant(CSI.Locations[0], "calling convention"); - (void)getNonnegativeConstant(CSI.Locations[1], "flags"); - uint64_t NumDeopts = - getNonnegativeConstant(CSI.Locations[2], "deopt count"); - if (NumDeopts > CSI.Locations.size() - 3) - report_fatal_error("malformed GoALLC statepoint deopt operands"); - if (NumDeopts > std::numeric_limits::max()) - report_fatal_error("GoALLC statepoint has too many deopt operands"); - - MCContext::GoObjStackMapEntry Entry{CSI.CSOffsetExpr, CSI.ID, - CSI.IsIndirectCall, Info.StackSize, - PointerSize, - static_cast(NumDeopts), - {}}; - Entry.Locations.reserve(CSI.Locations.size() - 3); - for (const StackMaps::Location &Location : - ArrayRef(CSI.Locations).drop_front(3)) { - auto Type = convertLocationType(Location.Type); - int64_t Offset = Location.Offset; - if (Location.Type == StackMaps::Location::Constant || - Location.Type == StackMaps::Location::ConstantIndex) { - std::optional Constant = SM.getConstantValue(Location); - if (!Constant) - report_fatal_error( - "GoALLC statepoint contains an invalid constant-pool index"); - Type = MCContext::GoObjStackMapLocation::Constant; - Offset = *Constant; - } - Entry.Locations.push_back( - {Type, Location.Size, Location.Reg, Offset}); - } - AP.OutContext.addGoObjSymbolStackMapEntry(Function, std::move(Entry)); - } - } - - if (Callsite != Callsites.end()) - report_fatal_error( - "GoALLC statepoint callsites exceed function record counts"); - - SM.reset(); - return true; -} diff --git a/src/cmd/llvmplugin/README.md b/src/cmd/llvmplugin/README.md index 1b7b0b954f0095..d1ceffdbce16f4 100644 --- a/src/cmd/llvmplugin/README.md +++ b/src/cmd/llvmplugin/README.md @@ -12,9 +12,11 @@ future in-process `cmd/compile` integration can call the same pipeline without going through `llc`. The SSA-to-LLVM lowering owns the function-level contract: Go ABI definitions -carry `gc "goallc"` and the `go-stack-growth-statepoint` attribute. Loading this -plugin registers the named GC strategy and its metadata printer; the rewrite -pass consumes the frontend markers rather than adding them. +carry `gc "goallc"`, and only source-level exceptions to the native Go stack +policy need target attributes such as `go-nosplit` or `go-systemstack`. Loading +this plugin registers the named GC strategy and the no-op metadata-printer +marker required by AsmPrinter; the rewrite pass consumes the frontend markers +rather than adding them. GoObj stack-map adaptation lives in LLVM core. Entry argument pointer maps do not use an IR marker or intrinsic. After LLVM optimization and inlining, target formal-argument lowering recursively derives @@ -156,7 +158,7 @@ constants in Machine StackMaps. For `gc "goallc"`, a static alloca used only as a deopt layout carrier is not implicitly promoted to a GC root; explicit `gc-live` is the activity signal. The resulting `gc.relocate(alloca)` is `NoRelocate` and rematerializes the same frame index, so no root spill is -created. The Go-owned StackMaps bridge retains the deopt prefix and resolves +created. LLVM's GoObj StackMaps bridge retains the deopt prefix and resolves both inline constants and `ConstantIndex` values. The GoObj writer strictly parses the suffix and maps every layout with a matching direct `gc-live` alloca plus bitmap bit to that callsite's `LocalsPointerMaps`. An unmatched layout is @@ -231,28 +233,28 @@ non-trivial implementation block is copied verbatim, it must live in a separately attributed source file retaining LLVM's Apache-2.0-with-exception notice; BSD-only Go source files should not silently absorb copied code. -`GoALLCStackMapPrinter.cpp` is the Go-owned boundary between LLVM Machine -StackMaps and GoObj. It uses the standard -`AsmPrinter -> GCMetadataPrinter::emitStackMaps` hook, retains deopt and GC -locations in `MCContext`, and resolves StackMaps constant-pool indexes without -adding a parallel serializer. LLVM's generic `StackMaps.cpp` only exposes the -constant resolver; it has no GoALLC grammar or GoObj policy. LLVM records GoObj -statepoint callsites at the CALL +The project-owned LLVM GoObj AsmPrinter path is the boundary between Machine +StackMaps and GoObj. It retains deopt and GC locations in `MCContext` and +resolves StackMaps constant-pool indexes without adding a parallel serializer; +the Go-owned plugin remains responsible for the frontend statepoint rewrite, +not object-format adaptation. LLVM records GoObj statepoint callsites at the CALL start, matching Go's `PCDATA_StackMapIndex` convention without a command-line -mode. The frontend's stack-growth attribute -asks LLVM to express the late-generated `runtime.morestack` call as a physical -MIR `STATEPOINT` with empty deopt and GC-alloca sections. Before frame -allocation, AArch64 formal lowering maps every type-derived input pointer word -onto the existing fixed home reserved for that ABI input. Frame lowering encodes -those homes in the morestack statepoint's GC pointer section as indirect -`SP+offset` locations with base-equals-derived pairs; it never asks the -statepoint machinery to allocate another spill. The GoObj writer recognizes -the stack-growth ID, interprets those offsets in the entry-SP geometry, and -selects pair 0: non-empty `EntryArgs` when present and empty locals. Ordinary -and stack-growth calls use the same Machine StackMaps pipeline without relying -on a return-PC convention. GoObj functions that already contain a Machine -`STATEPOINT` but lack the frontend stack-growth attribute fail closed; there is -no slow-path reset-label fallback for a raw morestack call. +mode. GoObj Go functions use native Go's split-stack policy by default: unless +`go-nosplit` is present, target frame lowering expresses the late-generated +`runtime.morestack` call as an ordinary ABI0 MIR call. Before frame allocation, +formal lowering maps each live type-derived input pointer word onto the existing +fixed home reserved for that ABI input. It still reserves and saves complete ABI +homes for an unused formal so a morestack retry preserves the register +assignment, but does not scan a word that LLVM callers may replace with poison. +Frame lowering records the live homes in a separate zero-byte +`EntryArgsStackMapID` record for every GoObj Go function. This is function +metadata rather than a callsite. The GoObj writer uses it as pair 0: non-empty +`EntryArgs` when present and empty locals, and initializes +`PCDATA_StackMapIndex` to 0. AsmPrinter's PCSP stream has already resolved the +Machine CFG; every transition back to the entry stack depth selects map 0. This +covers the pre-frame morestack path without identifying `runtime.morestack` by +name or manufacturing a statepoint. An ordinary statepoint selects its actual +live map and overrides a same-PC entry-depth transition. GoObj emits the currently constant safe `PCDATA_UnsafePoint` table first and the statepoint-derived `PCDATA_StackMapIndex` table second, as required by their Go ABI indexes 0 and 1. diff --git a/src/cmd/llvmplugin/testdata/aarch64-frame.ll b/src/cmd/llvmplugin/testdata/aarch64-frame.ll index a2e202b7ea6fc2..148506f6ca6135 100644 --- a/src/cmd/llvmplugin/testdata/aarch64-frame.ll +++ b/src/cmd/llvmplugin/testdata/aarch64-frame.ll @@ -136,4 +136,4 @@ entry: ret i64 %regarg } -attributes #0 = { "frame-pointer"="non-leaf" "go-stack-growth-statepoint" } +attributes #0 = { "frame-pointer"="non-leaf" } diff --git a/src/cmd/llvmplugin/testdata/aggregate-call-result-goobj.ll b/src/cmd/llvmplugin/testdata/aggregate-call-result-goobj.ll index bf3ce623032a98..8814b4d72a1aab 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-call-result-goobj.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-call-result-goobj.ll @@ -69,7 +69,7 @@ declare goabiinternal void @leaf_consume_pair(%pair) #0 define goabiinternal ptr @aggregate_call_result_goobj( ptr %seed, i1 %take_call) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %value = call goabiinternal %pair @make_pair(ptr %seed, i64 17) br i1 %take_call, label %call, label %skip diff --git a/src/cmd/llvmplugin/testdata/aggregate-cfg.ll b/src/cmd/llvmplugin/testdata/aggregate-cfg.ll index 30b98fd1942fd4..4e2b94bb6e59da 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-cfg.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-cfg.ll @@ -62,7 +62,7 @@ declare goabiinternal void @leaf_consume_pair(%pair) #0 define goabiinternal ptr @aggregate_diamond_call_skip( i1 %take_call, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_call, label %call, label %skip @@ -81,7 +81,7 @@ merge: define goabiinternal ptr @aggregate_branch_safepoints( i1 %take_left, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_left, label %left, label %right @@ -101,7 +101,7 @@ merge: define goabiinternal ptr @aggregate_sequential_conditional( i1 %take_first, i1 %take_second, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_first, label %first_call, label %first_skip @@ -130,7 +130,7 @@ second_merge: define goabiinternal ptr @aggregate_natural_loop( i32 %count, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br label %header @@ -149,7 +149,7 @@ exit: define goabiinternal ptr @aggregate_irreducible( i1 %enter_b, i1 %leave_a, i1 %leave_b, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %enter_b, label %b, label %a @@ -168,7 +168,7 @@ exit: define goabiinternal ptr @aggregate_phi_edge_use( i1 %take_call, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_call, label %call, label %skip @@ -189,7 +189,7 @@ merge: define goabiinternal ptr @aggregate_phi_duplicate_edge( i32 %which, %pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: call goabiinternal void @safepoint() switch i32 %which, label %merge [ @@ -206,7 +206,7 @@ merge: define goabiinternal ptr @aggregate_call_result_conditional( ptr %seed, i1 %take_call) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %value = call goabiinternal %pair @make_pair(ptr %seed, i64 7) br i1 %take_call, label %call, label %skip @@ -226,7 +226,7 @@ merge: define goabiinternal ptr @aggregate_call_result_loop( ptr %seed, i32 %count) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %value = call goabiinternal %pair @make_pair(ptr %seed, i64 11) br label %header @@ -246,7 +246,7 @@ exit: define goabiinternal ptr @aggregate_call_result_irreducible( ptr %seed, i1 %enter_b, i1 %leave_a, i1 %leave_b) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %value = call goabiinternal %pair @make_pair(ptr %seed, i64 13) br i1 %enter_b, label %b, label %a @@ -265,7 +265,7 @@ exit: } define goabiinternal ptr @aggregate_multiple_safepoints(%pair %value) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: call goabiinternal void @safepoint() call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/aggregate-conditional-stage-b.ll b/src/cmd/llvmplugin/testdata/aggregate-conditional-stage-b.ll index 6c059d34e3f95a..ce42b9425bfcf4 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-conditional-stage-b.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-conditional-stage-b.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal ptr @aggregate_conditional_relocation(i1 %take_call, %pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @aggregate_conditional_relocation(i1 %take_call, %pair %value) gc "goallc" { entry: br i1 %take_call, label %call, label %skip diff --git a/src/cmd/llvmplugin/testdata/aggregate-fixed-vector.ll b/src/cmd/llvmplugin/testdata/aggregate-fixed-vector.ll index 56a9222dfc6542..4ea420f13110cb 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-fixed-vector.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-fixed-vector.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal ptr @fixed_vector(ptr %source) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @fixed_vector(ptr %source) gc "goallc" { entry: %value = load <2 x ptr>, ptr %source, align 8 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/aggregate-loop-stage-b.ll b/src/cmd/llvmplugin/testdata/aggregate-loop-stage-b.ll index 53216f66bd2d13..2cbd66a25159b7 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-loop-stage-b.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-loop-stage-b.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal ptr @aggregate_loop_relocation(i1 %take_call, i1 %again, %pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @aggregate_loop_relocation(i1 %take_call, i1 %again, %pair %value) gc "goallc" { entry: br label %header diff --git a/src/cmd/llvmplugin/testdata/aggregate-scalable-vector-unsupported.ll b/src/cmd/llvmplugin/testdata/aggregate-scalable-vector-unsupported.ll index 44f7936209cbb1..2d36d129a1d385 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-scalable-vector-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-scalable-vector-unsupported.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal ptr @scalable_vector(ptr %source) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @scalable_vector(ptr %source) gc "goallc" { entry: %value = load , ptr %source, align 8 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/aggregate-scalarization.ll b/src/cmd/llvmplugin/testdata/aggregate-scalarization.ll index 48df3f3718e39a..01da4f405e8374 100644 --- a/src/cmd/llvmplugin/testdata/aggregate-scalarization.ll +++ b/src/cmd/llvmplugin/testdata/aggregate-scalarization.ll @@ -59,14 +59,14 @@ declare goabiinternal void @leaf_consume_pair(%pair) #0 declare goabiinternal void @leaf_consume_nested(%nested) #0 declare goabiinternal void @leaf_consume_vector_pair(%vector_pair) #0 -define goabiinternal ptr @pair_across_call(%pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @pair_across_call(%pair %value) gc "goallc" { entry: call goabiinternal void @safepoint() %pointer = extractvalue %pair %value, 0 ret ptr %pointer } -define goabiinternal ptr @triple_across_call(%triple %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @triple_across_call(%triple %value) gc "goallc" { entry: call goabiinternal void @safepoint() %first = extractvalue %triple %value, 1 @@ -76,14 +76,14 @@ entry: ret ptr %result } -define goabiinternal void @nested_across_call(%nested %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @nested_across_call(%nested %value) gc "goallc" { entry: call goabiinternal void @safepoint() call goabiinternal void @leaf_consume_nested(%nested %value) ret void } -define goabiinternal ptr @fixed_vector_across_call(ptr %source) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @fixed_vector_across_call(ptr %source) gc "goallc" { entry: %value = load <2 x ptr>, ptr %source, align 8 call goabiinternal void @safepoint() @@ -91,7 +91,7 @@ entry: ret ptr %result } -define goabiinternal ptr @nested_fixed_vector_across_call(ptr %source, i64 %number) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @nested_fixed_vector_across_call(ptr %source, i64 %number) gc "goallc" { entry: %vector.value = load <2 x ptr>, ptr %source, align 8 %with_vector = insertvalue %vector_pair poison, <2 x ptr> %vector.value, 0 @@ -103,7 +103,7 @@ entry: ret ptr %result } -define goabiinternal ptr @insertvalue_across_call(ptr %pointer, i64 %number) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @insertvalue_across_call(ptr %pointer, i64 %number) gc "goallc" { entry: %with_pointer = insertvalue %pair zeroinitializer, ptr %pointer, 0 %value = insertvalue %pair %with_pointer, i64 %number, 1 @@ -117,7 +117,7 @@ entry: ; pointer roots for poison leaves. Those leaves can be overwritten after the ; safepoint without ever being observed, as happens while reflect.Value is ; assembled for a later call. -define goabiinternal ptr @partial_insertvalue_across_call(ptr %pointer, i64 %number) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @partial_insertvalue_across_call(ptr %pointer, i64 %number) gc "goallc" { entry: %partial = insertvalue %reflect_value poison, ptr %pointer, 0 call goabiinternal void @safepoint() @@ -127,7 +127,7 @@ entry: ret ptr %leaf } -define goabiinternal ptr @phi_across_call(i1 %choose, %pair %left_value, %pair %right_value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @phi_across_call(i1 %choose, %pair %left_value, %pair %right_value) gc "goallc" { entry: br i1 %choose, label %left, label %right @@ -144,7 +144,7 @@ merge: ret ptr %result } -define goabiinternal ptr @select_across_call(i1 %choose, %pair %left_value, %pair %right_value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @select_across_call(i1 %choose, %pair %left_value, %pair %right_value) gc "goallc" { entry: %value = select i1 %choose, %pair %left_value, %pair %right_value call goabiinternal void @safepoint() @@ -152,7 +152,7 @@ entry: ret ptr %result } -define goabiinternal ptr @phi_edge_use(%pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @phi_edge_use(%pair %value) gc "goallc" { entry: call goabiinternal void @safepoint() br label %merge @@ -163,7 +163,7 @@ merge: ret ptr %result } -define goabiinternal ptr @multiple_calls(%pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @multiple_calls(%pair %value) gc "goallc" { entry: call goabiinternal void @safepoint() call goabiinternal void @safepoint() @@ -171,7 +171,7 @@ entry: ret ptr %result } -define goabiinternal ptr @aggregate_call_result(ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @aggregate_call_result(ptr %pointer) gc "goallc" { entry: %value = call goabiinternal %pair @make_pair(ptr %pointer, i64 7) call goabiinternal void @safepoint() @@ -179,13 +179,13 @@ entry: ret ptr %result } -define goabiinternal void @aggregate_current_call_argument(%pair %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @aggregate_current_call_argument(%pair %value) gc "goallc" { entry: call goabiinternal void @consume_pair(%pair %value) ret void } -define goabiinternal void @aggregate_load_store(ptr %source, ptr %destination) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @aggregate_load_store(ptr %source, ptr %destination) gc "goallc" { entry: %value = load %pair, ptr %source, align 8 call goabiinternal void @safepoint() @@ -193,7 +193,7 @@ entry: ret void } -define goabiinternal ptr @alloca_derived_leaf(i64 %number) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @alloca_derived_leaf(i64 %number) gc "goallc" { entry: %slot = alloca i64, align 8 store i64 %number, ptr %slot, align 8 @@ -203,7 +203,7 @@ entry: ret ptr %result } -define goabiinternal void @frozen_aggregate() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @frozen_aggregate() gc "goallc" { entry: %value = freeze %pair poison call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/alloca-derived.ll b/src/cmd/llvmplugin/testdata/alloca-derived.ll index 24073270dc22a6..d6bcd985d96d9d 100644 --- a/src/cmd/llvmplugin/testdata/alloca-derived.ll +++ b/src/cmd/llvmplugin/testdata/alloca-derived.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() -define goabiinternal i64 @selected_stack_address_live_across_call(i1 %choose_a) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal i64 @selected_stack_address_live_across_call(i1 %choose_a) gc "goallc" { entry: %a = alloca i64, align 8 %b = alloca i64, align 8 diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-dynamic-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-dynamic-unsupported.ll index b59d10421aa11e..c8df1c47743dc0 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-dynamic-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-dynamic-unsupported.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal void @dynamic_pointer_alloca(i64 %count) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @dynamic_pointer_alloca(i64 %count) gc "goallc" { entry: %slot = alloca ptr, i64 %count, align 8 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-ambiguous.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-ambiguous.ll index 4eeaed12b2b48f..030c6d5e399730 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-ambiguous.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-ambiguous.ll @@ -4,7 +4,7 @@ declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) declare void @llvm.fake.use(...) declare goabiinternal void @safepoint() -define goabiinternal void @path_local_pointer_alloca_lifetime(i1 %start) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @path_local_pointer_alloca_lifetime(i1 %start) gc "goallc" { entry: %slot = alloca ptr, align 8 br i1 %start, label %live, label %join diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-unsupported.ll index 511f8e20fbf6b0..7e9691262844b2 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-lifetime-unsupported.ll @@ -89,7 +89,7 @@ declare goabiinternal void @safepoint() declare goabiinternal void @observe(ptr) declare goabiinternal void @observe_slice({ ptr, i64, i64 }) -define goabiinternal void @locals_pointer_alloca_with_lifetime() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @locals_pointer_alloca_with_lifetime() gc "goallc" { entry: %slot = alloca ptr, align 8 call goabiinternal void @safepoint() @@ -101,7 +101,7 @@ entry: ret void } -define goabiinternal void @stack_object_alloca_with_lifetime() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @stack_object_alloca_with_lifetime() gc "goallc" { entry: %slot = alloca ptr, align 8 call goabiinternal void @safepoint() @@ -112,7 +112,7 @@ entry: ret void } -define goabiinternal void @loop_reinitialized_pointer_alloca(i1 %again) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @loop_reinitialized_pointer_alloca(i1 %again) gc "goallc" { entry: %slot = alloca ptr, align 8 br label %loop @@ -129,7 +129,7 @@ exit: ret void } -define goabiinternal void @preinitialized_pointer_alloca() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @preinitialized_pointer_alloca() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 call void @llvm.lifetime.start.p0(i64 16, ptr %slot) @@ -140,7 +140,7 @@ entry: } define goabiinternal void @store_initialized_pointer_alloca( - ptr %first, ptr %second) "go-stack-growth-statepoint" gc "goallc" { + ptr %first, ptr %second) gc "goallc" { entry: %slot = alloca %pointer_gap, align 8 call void @llvm.lifetime.start.p0(i64 24, ptr %slot) @@ -154,7 +154,7 @@ entry: } define goabiinternal void @partially_stored_pointer_alloca( - ptr %first, ptr %second) "go-stack-growth-statepoint" gc "goallc" { + ptr %first, ptr %second) gc "goallc" { entry: %slot = alloca %pointer_gap, align 8 call void @llvm.lifetime.start.p0(i64 24, ptr %slot) @@ -168,7 +168,7 @@ entry: } define goabiinternal void @phi_edge_pointer_alloca( - i1 %use_stack, ptr %other) "go-stack-growth-statepoint" gc "goallc" { + i1 %use_stack, ptr %other) gc "goallc" { entry: %slot = alloca ptr, align 8 br i1 %use_stack, label %initialize, label %external @@ -188,7 +188,7 @@ merge: ret void } -define goabiinternal void @hoisted_aggregate_pointer_alloca() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @hoisted_aggregate_pointer_alloca() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 %slice = insertvalue { ptr, i64, i64 } poison, ptr %slot, 0 diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-nonentry-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-nonentry-unsupported.ll index d9e8e78af24960..470f8bbe569779 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-nonentry-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-nonentry-unsupported.ll @@ -5,7 +5,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() define goabiinternal void @nonentry_pointer_alloca( - i1 %allocate) "go-stack-growth-statepoint" gc "goallc" { + i1 %allocate) gc "goallc" { entry: call goabiinternal void @safepoint() br i1 %allocate, label %allocate.block, label %exit diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-realigned-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-realigned-unsupported.ll index 38b61897e42496..d1ada39373384e 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-realigned-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-realigned-unsupported.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal void @realigned_pointer_alloca() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @realigned_pointer_alloca() gc "goallc" { entry: %slot = alloca ptr, align 32 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-roots.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-roots.ll index 4ee65b76db5794..e89c46dbebca71 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-roots.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-roots.ll @@ -132,7 +132,7 @@ declare goabiinternal i64 @readonly_pointer_slot(ptr readonly) memory(read) declare goabiinternal i64 @readnone_callee() memory(none) declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) -define goabiinternal ptr @pointer_slot(ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @pointer_slot(ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -143,7 +143,7 @@ entry: } define goabiinternal ptr @nested_whole_aggregate( - ptr %first, ptr %second, ptr %third) "go-stack-growth-statepoint" gc "goallc" { + ptr %first, ptr %second, ptr %third) gc "goallc" { entry: ; The optimized use graph contains only direct memory operations, so this ; must use fixed homes and remain eligible for SROA. @@ -159,7 +159,7 @@ entry: } define goabiinternal ptr @alloca_call_skip( - i1 %take_call, ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + i1 %take_call, ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -175,7 +175,7 @@ merge: } define goabiinternal ptr @alloca_multiple_calls( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -186,7 +186,7 @@ entry: } define goabiinternal ptr @alloca_partial_initialization() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: ; Each field is initialized from a different safepointing call. The plugin ; must zero the whole object before the first call so the complete bitmap is @@ -204,7 +204,7 @@ entry: } define goabiinternal ptr @alloca_loop( - i1 %again, ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + i1 %again, ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -220,7 +220,7 @@ exit: } define goabiinternal ptr @alloca_gep_address_across_call( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca %pointer_field, align 8 %field = getelementptr inbounds %pointer_field, ptr %slot, i32 0, i32 1 @@ -231,7 +231,7 @@ entry: } define goabiinternal void @alloca_direct_address_across_calls() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr null, ptr %slot, align 8 @@ -242,7 +242,7 @@ entry: } define goabiinternal void @argument_home_address_across_calls(ptr %pointer) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: ; This canonical parameter alloca becomes the argument's fixed ABI home. ; Its last callsite has no direct gc-live base, so the function also needs @@ -257,7 +257,7 @@ entry: } define goabiinternal void @argument_aggregate_home_address_across_calls( - %nested %value) "go-stack-growth-statepoint" gc "goallc" { + %nested %value) gc "goallc" { entry: ; The split aggregate parameter still has one complete fixed home and one ; argp-relative StackObject covering all ABI pieces and padding. @@ -271,7 +271,7 @@ entry: } define goabiinternal void @alloca_gep_value_across_calls() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %slot = alloca %pointer_field, align 8 %field = getelementptr inbounds %pointer_field, ptr %slot, i32 0, i32 1 @@ -283,7 +283,7 @@ entry: } define goabiinternal void @alloca_pointer_free_address_across_calls() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %slot = alloca i64, align 8 store i64 0, ptr %slot, align 8 @@ -294,7 +294,7 @@ entry: } define goabiinternal ptr @alloca_address_passed_to_callee( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: ; The structural call use makes the address observable. %slot = alloca ptr, align 8 @@ -305,7 +305,7 @@ entry: } define goabiinternal void @alloca_marker_free_at_safepoint( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr null, ptr %slot, align 8 @@ -315,7 +315,7 @@ entry: } define goabiinternal ptr @alloca_high_bitmap_word( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca %high_bitmap, align 8 %field = getelementptr inbounds %high_bitmap, ptr %slot, i32 0, i32 1 @@ -326,7 +326,7 @@ entry: } define goabiinternal ptr @alloca_multiple_records( - ptr %first, ptr %second) "go-stack-growth-statepoint" gc "goallc" { + ptr %first, ptr %second) gc "goallc" { entry: %left = alloca ptr, align 8 %right = alloca ptr, align 8 @@ -338,7 +338,7 @@ entry: } define goabiinternal ptr @alloca_select_same_base( - i1 %choose, ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + i1 %choose, ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 %same = getelementptr inbounds i8, ptr %slot, i64 0 @@ -350,7 +350,7 @@ entry: } define goabiinternal ptr @alloca_nocapture_writable( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -360,7 +360,7 @@ entry: } define goabiinternal ptr @alloca_escaped_before_unknown_write( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 @@ -371,7 +371,7 @@ entry: } define goabiinternal i64 @alloca_readonly_and_readnone( - ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { + ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store ptr %pointer, ptr %slot, align 8 diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-select-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-select-unsupported.ll index c66e005c806494..cdff2808f1629d 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-select-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-select-unsupported.ll @@ -3,7 +3,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() define goabiinternal void @select_different_pointer_allocas( - i1 %choose) "go-stack-growth-statepoint" gc "goallc" { + i1 %choose) gc "goallc" { entry: %left = alloca ptr, align 8 %right = alloca ptr, align 8 diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-vector-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-vector-unsupported.ll index ccc415570ef502..efcae4508f524c 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-vector-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-vector-unsupported.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal void @pointer_vector_alloca() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @pointer_vector_alloca() gc "goallc" { entry: %slot = alloca <2 x ptr>, align 8 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/alloca-pointer-volatile-unsupported.ll b/src/cmd/llvmplugin/testdata/alloca-pointer-volatile-unsupported.ll index 65bb822e37b1de..a271948d165312 100644 --- a/src/cmd/llvmplugin/testdata/alloca-pointer-volatile-unsupported.ll +++ b/src/cmd/llvmplugin/testdata/alloca-pointer-volatile-unsupported.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal void @volatile_pointer_alloca(ptr %pointer) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @volatile_pointer_alloca(ptr %pointer) gc "goallc" { entry: %slot = alloca ptr, align 8 store volatile ptr %pointer, ptr %slot, align 8 diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-kind.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-kind.ll index 103ec3196b83fb..cf85824fb9d4e4 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-kind.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-kind.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 15, i64 1, i64 1347703373, i64 11, ptr %slot, i64 0, i64 16, i64 8, i64 8, i64 2, i64 64, i64 1, i64 3, i64 1095519299, i64 15), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-length.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-length.ll index b040f5b94c29d7..68487e456e8a54 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-length.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-bad-length.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 14, i64 1, i64 1095520067, i64 11, ptr %slot, i64 0, i64 16, i64 8, i64 8, i64 2, i64 64, i64 1, i64 3, i64 1095519299, i64 15), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-duplicate.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-duplicate.ll index a62444f1b208b2..9935a0788dbf7b 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-duplicate.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-duplicate.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 26, i64 2, i64 1095520067, i64 11, ptr %slot, i64 0, i64 16, i64 8, i64 8, i64 2, i64 64, i64 1, i64 3, i64 1095520067, i64 11, ptr %slot, i64 0, i64 16, i64 8, i64 8, i64 2, i64 64, i64 1, i64 3, i64 1095519299, i64 26), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-non-direct.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-non-direct.ll index 40f13094dc8bfb..9721ff56160d79 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-non-direct.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-non-direct.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 15, i64 1, i64 1095520067, i64 11, i64 0, i64 0, i64 8, i64 8, i64 8, i64 1, i64 64, i64 1, i64 1, i64 1095519299, i64 15), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-overlap.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-overlap.ll index 2ee72172006279..544d75dfddd824 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-overlap.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-overlap.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 26, i64 2, i64 1095520067, i64 11, ptr %slot, i64 0, i64 8, i64 8, i64 8, i64 1, i64 64, i64 1, i64 1, i64 1095520067, i64 11, ptr %slot, i64 0, i64 16, i64 8, i64 8, i64 2, i64 64, i64 1, i64 3, i64 1095519299, i64 26), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-padding.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-padding.ll index 33c98ae023adc5..7a49b46a587adf 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-padding.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-padding.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 15, i64 1, i64 1095520067, i64 11, ptr %slot, i64 0, i64 8, i64 8, i64 8, i64 1, i64 64, i64 1, i64 3, i64 1095519299, i64 15), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-truncated.ll b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-truncated.ll index c8adc37247705f..1a4cbde8e27059 100644 --- a/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-truncated.ll +++ b/src/cmd/llvmplugin/testdata/alloca-ptrmap-malformed-truncated.ll @@ -3,11 +3,10 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() declare token @llvm.experimental.gc.statepoint.p0(i64 immarg, i32 immarg, ptr, i32 immarg, i32 immarg, ...) -define goabiinternal void @test() #0 gc "goallc" { +define goabiinternal void @test() gc "goallc" { entry: %slot = alloca [2 x ptr], align 8 store [2 x ptr] zeroinitializer, ptr %slot, align 8 %statepoint = call goabiinternal token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 1, i32 0, ptr elementtype(void ()) @callee, i32 0, i32 0, i32 0, i32 0) [ "deopt"(i64 1195461697, i64 15, i64 1, i64 1095520067), "gc-live"(ptr %slot) ] ret void } -attributes #0 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/alloca-zero-pointer-array.ll b/src/cmd/llvmplugin/testdata/alloca-zero-pointer-array.ll index bf0ae1fbeb7131..42720029f9fc9b 100644 --- a/src/cmd/llvmplugin/testdata/alloca-zero-pointer-array.ll +++ b/src/cmd/llvmplugin/testdata/alloca-zero-pointer-array.ll @@ -3,7 +3,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() define goabiinternal void @zero_length_pointer_array() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %slot = alloca [0 x ptr], align 8 call goabiinternal void @safepoint() diff --git a/src/cmd/llvmplugin/testdata/branch-safepoints-relocation.ll b/src/cmd/llvmplugin/testdata/branch-safepoints-relocation.ll index 862f84459f5ed2..05419ab311f681 100644 --- a/src/cmd/llvmplugin/testdata/branch-safepoints-relocation.ll +++ b/src/cmd/llvmplugin/testdata/branch-safepoints-relocation.ll @@ -3,7 +3,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @left_callee() declare goabiinternal void @right_callee() -define goabiinternal i64 @branch_safepoints(ptr %p, i1 %take_left) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal i64 @branch_safepoints(ptr %p, i1 %take_left) gc "goallc" { entry: br i1 %take_left, label %left, label %right diff --git a/src/cmd/llvmplugin/testdata/conditional-relocation.ll b/src/cmd/llvmplugin/testdata/conditional-relocation.ll index 0bb179b01533f1..3afac0e8c5a6e4 100644 --- a/src/cmd/llvmplugin/testdata/conditional-relocation.ll +++ b/src/cmd/llvmplugin/testdata/conditional-relocation.ll @@ -29,7 +29,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() -define goabiinternal i64 @conditional_safepoint(ptr %p, i1 %take_call) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal i64 @conditional_safepoint(ptr %p, i1 %take_call) gc "goallc" { entry: br i1 %take_call, label %call, label %skip @@ -49,7 +49,7 @@ merge: define goabiinternal i64 @conditional_phi_edge_use( ptr %p, ptr %q, i1 %take_call) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_call, label %call, label %skip diff --git a/src/cmd/llvmplugin/testdata/debug-inline.ll b/src/cmd/llvmplugin/testdata/debug-inline.ll index 8546e4dfe2b8bc..e8d9abbc02176e 100644 --- a/src/cmd/llvmplugin/testdata/debug-inline.ll +++ b/src/cmd/llvmplugin/testdata/debug-inline.ll @@ -115,9 +115,28 @@ entry: ret i64 %x, !dbg !55 } +; The optimized instruction stream has no location for erasedInner. Frontend +; required-location metadata must make the final machine pass materialize the +; missing nested inline edge without constraining IR optimization. +define goabiinternal void @main.erased() !dbg !19 { +entry: + ret void, !dbg !65 +} + +define goabiinternal void @main.erasedMid() !dbg !20 { +entry: + ret void, !dbg !66 +} + +define goabiinternal void @main.erasedInner() !dbg !21 { +entry: + ret void, !dbg !67 +} + !llvm.dbg.cu = !{!0} !llvm.module.flags = !{!5, !6} -!goobj.debug.funcs = !{!40, !41, !42, !43, !44, !45, !46, !47, !48} +!goobj.debug.funcs = !{!40, !41, !42, !43, !44, !45, !46, !47, !48, !49, !56, !57} +!goobj.debug.inline.required = !{!58} !0 = distinct !DICompileUnit(language: DW_LANG_Go, file: !1, producer: "goallc-test", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, enums: !2, splitDebugInlining: true, nameTableKind: None) !1 = !DIFile(filename: "outer.go", directory: "/tmp/goobj-inline") @@ -136,6 +155,9 @@ entry: !16 = distinct !DISubprogram(name: "main.shared", linkageName: "main.shared", scope: !1, file: !1, line: 70, type: !3, scopeLine: 70, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) !17 = distinct !DISubprogram(name: "main.sharedLeft", linkageName: "main.sharedLeft", scope: !1, file: !1, line: 80, type: !3, scopeLine: 80, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) !18 = distinct !DISubprogram(name: "main.sharedRight", linkageName: "main.sharedRight", scope: !1, file: !1, line: 90, type: !3, scopeLine: 90, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) +!19 = distinct !DISubprogram(name: "main.erased", linkageName: "main.erased", scope: !1, file: !1, line: 100, type: !3, scopeLine: 100, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) +!20 = distinct !DISubprogram(name: "main.erasedMid", linkageName: "main.erasedMid", scope: !1, file: !1, line: 110, type: !3, scopeLine: 110, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) +!21 = distinct !DISubprogram(name: "main.erasedInner", linkageName: "main.erasedInner", scope: !1, file: !1, line: 120, type: !3, scopeLine: 120, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) !30 = !DILocation(line: 30, column: 3, scope: !12, inlinedAt: !31) !31 = distinct !DILocation(line: 20, column: 3, scope: !11, inlinedAt: !32) @@ -151,6 +173,12 @@ entry: !53 = !DILocation(line: 72, column: 2, scope: !16) !54 = !DILocation(line: 81, column: 2, scope: !17) !55 = !DILocation(line: 91, column: 2, scope: !18) +!60 = !DILocation(line: 121, column: 2, scope: !21, inlinedAt: !61) +!61 = distinct !DILocation(line: 111, column: 2, scope: !20, inlinedAt: !62) +!62 = distinct !DILocation(line: 101, column: 2, scope: !19) +!65 = !DILocation(line: 102, column: 2, scope: !19) +!66 = !DILocation(line: 112, column: 2, scope: !20) +!67 = !DILocation(line: 122, column: 2, scope: !21) !40 = !{!10, ptr @main.outer} !41 = !{!11, ptr @main.mid} @@ -161,3 +189,7 @@ entry: !46 = !{!16, ptr @main.shared} !47 = !{!17, ptr @main.sharedLeft} !48 = !{!18, ptr @main.sharedRight} +!49 = !{!19, ptr @main.erased} +!56 = !{!20, ptr @main.erasedMid} +!57 = !{!21, ptr @main.erasedInner} +!58 = !{ptr @main.erased, !60} diff --git a/src/cmd/llvmplugin/testdata/defer-edge.ll b/src/cmd/llvmplugin/testdata/defer-edge.ll index 43a620ce678feb..1ce15e46817819 100644 --- a/src/cmd/llvmplugin/testdata/defer-edge.ll +++ b/src/cmd/llvmplugin/testdata/defer-edge.ll @@ -26,7 +26,7 @@ declare goabiinternal void @runtime.panicmem() declare void @llvm.go.defer.edge() declare void @llvm.lifetime.start.p0(ptr captures(none)) -define goabiinternal void @defer_edge() #0 gc "goallc" { +define goabiinternal void @defer_edge() gc "goallc" { entry: call goabiinternal void @runtime.deferproc() callbr void @llvm.go.defer.edge() to label %normal [label %recover] @@ -39,7 +39,7 @@ recover: ret void } -define goabiinternal ptr @defer_result(ptr %pointer) #0 gc "goallc" { +define goabiinternal ptr @defer_result(ptr %pointer) gc "goallc" { entry: %result = alloca ptr, align 8, !goallc.defer_result !1 call void @llvm.lifetime.start.p0(ptr %result) @@ -63,7 +63,5 @@ entry: ret void } -attributes #0 = { "go-stack-growth-statepoint" } - !0 = !{i8 23, i8 0} !1 = !{} diff --git a/src/cmd/llvmplugin/testdata/derived-pointer-rematerialization.ll b/src/cmd/llvmplugin/testdata/derived-pointer-rematerialization.ll index 31ce288dcef9f2..0ce9c23b6663d0 100644 --- a/src/cmd/llvmplugin/testdata/derived-pointer-rematerialization.ll +++ b/src/cmd/llvmplugin/testdata/derived-pointer-rematerialization.ll @@ -29,7 +29,7 @@ declare goabiinternal void @callee() ; above the source nil guard. The non-heap value null+96 must not become a Go ; GC root at the intervening safepoint. define goabiinternal i1 @hoisted_null_offset(ptr %base) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %derived = getelementptr i8, ptr %base, i64 96 call goabiinternal void @callee() @@ -42,7 +42,7 @@ entry: ; Rebuild the full address-expression chain from the relocated base, not from ; an independently relocated interior pointer. define goabiinternal i8 @derived_chain(ptr %base) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %field = getelementptr i8, ptr %base, i64 16 %element = getelementptr i8, ptr %field, i64 8 @@ -55,7 +55,7 @@ entry: ; a safepoint: the merge must select the rebuilt address on that path and the ; original address on the other path. define goabiinternal i8 @conditional_derived(ptr %base, i1 %take_call) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %derived = getelementptr i8, ptr %base, i64 32 br i1 %take_call, label %call, label %skip @@ -76,7 +76,7 @@ merge: ; derived pointer. Relocate the vector base as one value, then rebuild the ; vector GEP instead of exposing its interior-pointer lanes as Go GC roots. define goabiinternal <2 x ptr> @derived_vector(<2 x ptr> %base) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %derived = getelementptr i8, <2 x ptr> %base, <2 x i64> @@ -88,7 +88,7 @@ entry: ; base. Keep that scalar base live and rebuild the vector result after it is ; relocated. define goabiinternal <2 x ptr> @derived_vector_from_scalar(ptr %base) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %derived = getelementptr i8, ptr %base, <2 x i64> diff --git a/src/cmd/llvmplugin/testdata/function-marker-inline.ll b/src/cmd/llvmplugin/testdata/function-marker-inline.ll index 2b2ab0831dbee4..be07f764e500f7 100644 --- a/src/cmd/llvmplugin/testdata/function-marker-inline.ll +++ b/src/cmd/llvmplugin/testdata/function-marker-inline.ll @@ -14,14 +14,14 @@ target triple = "x86_64-unknown-linux-goobj" declare void @llvm.sideeffect() define internal goabiinternal void @callee() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: call void @llvm.sideeffect(), !goobj.marker_reloc !0 ret void } define goabiinternal void @caller() - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: call goabiinternal void @callee() ret void diff --git a/src/cmd/llvmplugin/testdata/gc-leaf-markers.ll b/src/cmd/llvmplugin/testdata/gc-leaf-markers.ll index b0db0b1d0111b7..64d9c7c1e5e288 100644 --- a/src/cmd/llvmplugin/testdata/gc-leaf-markers.ll +++ b/src/cmd/llvmplugin/testdata/gc-leaf-markers.ll @@ -3,7 +3,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee_leaf() "gc-leaf-function" declare goabiinternal void @callsite_leaf() -define goabiinternal void @leaf_calls() "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @leaf_calls() gc "goallc" { entry: call goabiinternal void @callee_leaf() call goabiinternal void @callsite_leaf() "gc-leaf-function" diff --git a/src/cmd/llvmplugin/testdata/indirect-callee.ll b/src/cmd/llvmplugin/testdata/indirect-callee.ll index 5f58bd365ad177..652172f1e88554 100644 --- a/src/cmd/llvmplugin/testdata/indirect-callee.ll +++ b/src/cmd/llvmplugin/testdata/indirect-callee.ll @@ -54,4 +54,4 @@ entry: ret void } -attributes #0 = { "frame-pointer"="non-leaf" "go-stack-growth-statepoint" } +attributes #0 = { "frame-pointer"="non-leaf" } diff --git a/src/cmd/llvmplugin/testdata/invalid-gc-leaf-definition.ll b/src/cmd/llvmplugin/testdata/invalid-gc-leaf-definition.ll index e5807484c08e41..542bf9bfbca0f0 100644 --- a/src/cmd/llvmplugin/testdata/invalid-gc-leaf-definition.ll +++ b/src/cmd/llvmplugin/testdata/invalid-gc-leaf-definition.ll @@ -8,4 +8,4 @@ entry: ret void } -attributes #0 = { "gc-leaf-function" "go-stack-growth-statepoint" } +attributes #0 = { "gc-leaf-function" } diff --git a/src/cmd/llvmplugin/testdata/irreducible-relocation.ll b/src/cmd/llvmplugin/testdata/irreducible-relocation.ll index f8e74a22e5d6d8..ca7e378a7edc6a 100644 --- a/src/cmd/llvmplugin/testdata/irreducible-relocation.ll +++ b/src/cmd/llvmplugin/testdata/irreducible-relocation.ll @@ -4,7 +4,7 @@ declare goabiinternal void @callee() define goabiinternal i64 @irreducible_relocation( ptr %p, i1 %enter_b, i1 %leave_a, i1 %leave_b) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %enter_b, label %b, label %a diff --git a/src/cmd/llvmplugin/testdata/live-aggregate.ll b/src/cmd/llvmplugin/testdata/live-aggregate.ll index 96f4e9d4517a80..de743e0d94decf 100644 --- a/src/cmd/llvmplugin/testdata/live-aggregate.ll +++ b/src/cmd/llvmplugin/testdata/live-aggregate.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @callee() -define goabiinternal ptr @live_pointer_aggregate({ ptr, i64 } %value) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal ptr @live_pointer_aggregate({ ptr, i64 } %value) gc "goallc" { entry: call goabiinternal void @callee() %pointer = extractvalue { ptr, i64 } %value, 0 diff --git a/src/cmd/llvmplugin/testdata/loop-relocation.ll b/src/cmd/llvmplugin/testdata/loop-relocation.ll index 5b0293f8f37791..bdc6b1b0b0105c 100644 --- a/src/cmd/llvmplugin/testdata/loop-relocation.ll +++ b/src/cmd/llvmplugin/testdata/loop-relocation.ll @@ -4,7 +4,7 @@ declare goabiinternal void @callee() define goabiinternal i64 @loop_relocation( ptr %p, i1 %take_call, i1 %again) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br label %header diff --git a/src/cmd/llvmplugin/testdata/multiple-calls.ll b/src/cmd/llvmplugin/testdata/multiple-calls.ll index 8b560a5e6c2345..28cf6ee264676b 100644 --- a/src/cmd/llvmplugin/testdata/multiple-calls.ll +++ b/src/cmd/llvmplugin/testdata/multiple-calls.ll @@ -39,7 +39,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @first_callee() declare goabiinternal void @second_callee() -define goabiinternal i64 @different_pointer_sets_across_calls(ptr %p, ptr %q) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal i64 @different_pointer_sets_across_calls(ptr %p, ptr %q) gc "goallc" { entry: call goabiinternal void @first_callee() %first = load i64, ptr %p, align 8 diff --git a/src/cmd/llvmplugin/testdata/nest-param-attr.ll b/src/cmd/llvmplugin/testdata/nest-param-attr.ll index 52bc3bbe2f9130..5fbce840c5a7e2 100644 --- a/src/cmd/llvmplugin/testdata/nest-param-attr.ll +++ b/src/cmd/llvmplugin/testdata/nest-param-attr.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @closure_callee(ptr nest) -define goabiinternal void @nest_param_attr(ptr %context) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @nest_param_attr(ptr %context) gc "goallc" { entry: call goabiinternal void @closure_callee(ptr nest %context) ret void diff --git a/src/cmd/llvmplugin/testdata/open-defer-incomplete.ll b/src/cmd/llvmplugin/testdata/open-defer-incomplete.ll index 31b9109d085c40..08c8a5d26cc1a3 100644 --- a/src/cmd/llvmplugin/testdata/open-defer-incomplete.ll +++ b/src/cmd/llvmplugin/testdata/open-defer-incomplete.ll @@ -4,7 +4,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal void @open_defer_missing_slots() #0 gc "goallc" { +define goabiinternal void @open_defer_missing_slots() gc "goallc" { entry: %bits = alloca i8, align 1, !goallc.open_defer_bits !0 store volatile i8 0, ptr %bits, align 1 @@ -12,6 +12,4 @@ entry: ret void } -attributes #0 = { "go-stack-growth-statepoint" } - !0 = !{} diff --git a/src/cmd/llvmplugin/testdata/open-defer.ll b/src/cmd/llvmplugin/testdata/open-defer.ll index e66d1650026fb6..422d75c9cd2760 100644 --- a/src/cmd/llvmplugin/testdata/open-defer.ll +++ b/src/cmd/llvmplugin/testdata/open-defer.ll @@ -30,7 +30,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @safepoint() -define goabiinternal ptr @open_defer(ptr %value) #0 gc "goallc" { +define goabiinternal ptr @open_defer(ptr %value) gc "goallc" { entry: %bits = alloca i8, align 1, !goallc.open_defer_bits !0 %slots = alloca [2 x ptr], align 8, !goallc.open_defer_slots !1 @@ -47,7 +47,5 @@ entry: ret ptr %result } -attributes #0 = { "go-stack-growth-statepoint" } - !0 = !{} !1 = !{i32 2} diff --git a/src/cmd/llvmplugin/testdata/pointer-address-observation.ll b/src/cmd/llvmplugin/testdata/pointer-address-observation.ll index e41f84427b50b9..b098de3333a6d4 100644 --- a/src/cmd/llvmplugin/testdata/pointer-address-observation.ll +++ b/src/cmd/llvmplugin/testdata/pointer-address-observation.ll @@ -14,7 +14,7 @@ declare i64 @llvm.go.pointer.address.i64.p0(ptr) declare goabiinternal void @callee() define goabiinternal i1 @observe(ptr %pointer) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: %before = call i64 @llvm.go.pointer.address.i64.p0(ptr %pointer) call goabiinternal void @callee() diff --git a/src/cmd/llvmplugin/testdata/sequential-conditional-relocation.ll b/src/cmd/llvmplugin/testdata/sequential-conditional-relocation.ll index 59d8c591cc1c10..5b3e59fb11964a 100644 --- a/src/cmd/llvmplugin/testdata/sequential-conditional-relocation.ll +++ b/src/cmd/llvmplugin/testdata/sequential-conditional-relocation.ll @@ -5,7 +5,7 @@ declare goabiinternal void @second_callee() define goabiinternal i64 @sequential_conditional_safepoints( ptr %p, i1 %take_first, i1 %take_second) - "go-stack-growth-statepoint" gc "goallc" { + gc "goallc" { entry: br i1 %take_first, label %first_call, label %first_skip diff --git a/src/cmd/llvmplugin/testdata/statepoint.ll b/src/cmd/llvmplugin/testdata/statepoint.ll index 9178dd6c4f21a7..0485fd0f9c4f68 100644 --- a/src/cmd/llvmplugin/testdata/statepoint.ll +++ b/src/cmd/llvmplugin/testdata/statepoint.ll @@ -4,14 +4,14 @@ declare goabiinternal void @callee() declare goabiinternal void @leaf_callee() #0 declare goabiinternal ptr @make_pointer() -define goabiinternal i64 @pointer_live_across_call(ptr %p) #1 gc "goallc" { +define goabiinternal i64 @pointer_live_across_call(ptr %p) gc "goallc" { entry: call goabiinternal void @callee() %value = load i64, ptr %p, align 8 ret i64 %value } -define goabiinternal i64 @stack_address_live_across_call() #1 gc "goallc" { +define goabiinternal i64 @stack_address_live_across_call() gc "goallc" { entry: %slot = alloca i64, align 8 store i64 42, ptr %slot, align 8 @@ -20,13 +20,13 @@ entry: ret i64 %value } -define goabiinternal void @explicit_leaf_call() #1 gc "goallc" { +define goabiinternal void @explicit_leaf_call() gc "goallc" { entry: call goabiinternal void @leaf_callee() ret void } -define goabiinternal i64 @pointer_live_across_two_calls(ptr %p) #1 gc "goallc" { +define goabiinternal i64 @pointer_live_across_two_calls(ptr %p) gc "goallc" { entry: call goabiinternal void @callee() call goabiinternal void @callee() @@ -34,7 +34,7 @@ entry: ret i64 %value } -define goabiinternal i64 @pointer_live_into_cfg(ptr %p, i1 %take_left) #1 gc "goallc" { +define goabiinternal i64 @pointer_live_into_cfg(ptr %p, i1 %take_left) gc "goallc" { entry: call goabiinternal void @callee() br i1 %take_left, label %left, label %right @@ -52,7 +52,7 @@ done: ret i64 %value } -define goabiinternal ptr @call_result_live_across_call() #1 gc "goallc" { +define goabiinternal ptr @call_result_live_across_call() gc "goallc" { entry: %pointer = call goabiinternal ptr @make_pointer() call goabiinternal void @callee() @@ -62,7 +62,7 @@ entry: ; Function block layout is intentionally different from CFG dominance. The ; safepoint in %use is visited before the pointer-producing call in %define, ; but its liveness set contains that call result. -define goabiinternal ptr @out_of_layout_call_result() #1 gc "goallc" { +define goabiinternal ptr @out_of_layout_call_result() gc "goallc" { entry: br label %define @@ -76,4 +76,3 @@ define: } attributes #0 = { "gc-leaf-function" } -attributes #1 = { "go-stack-growth-statepoint" } diff --git a/src/cmd/llvmplugin/testdata/supported-param-attrs.ll b/src/cmd/llvmplugin/testdata/supported-param-attrs.ll index a1fb5f2f3fcbd1..cdd741ddc3e7fb 100644 --- a/src/cmd/llvmplugin/testdata/supported-param-attrs.ll +++ b/src/cmd/llvmplugin/testdata/supported-param-attrs.ll @@ -8,4 +8,4 @@ entry: ret void } -attributes #0 = { "frame-pointer"="non-leaf" "go-stack-growth-statepoint" } +attributes #0 = { "frame-pointer"="non-leaf" } diff --git a/src/cmd/llvmplugin/testdata/unsupported-invoke.ll b/src/cmd/llvmplugin/testdata/unsupported-invoke.ll index 8c65eba7e923df..6e84d2eb71cadc 100644 --- a/src/cmd/llvmplugin/testdata/unsupported-invoke.ll +++ b/src/cmd/llvmplugin/testdata/unsupported-invoke.ll @@ -4,7 +4,7 @@ declare goabiinternal void @callee() declare i32 @__gxx_personality_v0(...) define goabiinternal void @unsupported_invoke() - "go-stack-growth-statepoint" gc "goallc" + gc "goallc" personality ptr @__gxx_personality_v0 { entry: invoke goabiinternal void @callee() diff --git a/src/cmd/llvmplugin/testdata/unsupported-param-attr.ll b/src/cmd/llvmplugin/testdata/unsupported-param-attr.ll index c1911175b6bb68..1898ce3c354129 100644 --- a/src/cmd/llvmplugin/testdata/unsupported-param-attr.ll +++ b/src/cmd/llvmplugin/testdata/unsupported-param-attr.ll @@ -2,7 +2,7 @@ target triple = "x86_64-unknown-linux-goobj" declare goabiinternal void @unsupported_callee(ptr noalias) -define goabiinternal void @unsupported_param_attr(ptr %context) "go-stack-growth-statepoint" gc "goallc" { +define goabiinternal void @unsupported_param_attr(ptr %context) gc "goallc" { entry: call goabiinternal void @unsupported_callee(ptr noalias %context) ret void diff --git a/src/cmd/llvmtoolexec/main.go b/src/cmd/llvmtoolexec/main.go index 83adb1e5b2f23c..77266a1f48c38f 100644 --- a/src/cmd/llvmtoolexec/main.go +++ b/src/cmd/llvmtoolexec/main.go @@ -21,18 +21,49 @@ import ( "os/exec" "path/filepath" "runtime" + "sort" "strconv" "strings" ) +type stringSetFlag map[string]struct{} + +func (f *stringSetFlag) Set(value string) error { + if value == "" { + return errors.New("package path must not be empty") + } + if *f == nil { + *f = make(map[string]struct{}) + } + (*f)[value] = struct{}{} + return nil +} + +func (f *stringSetFlag) String() string { + if f == nil { + return "" + } + values := make([]string, 0, len(*f)) + for value := range *f { + values = append(values, value) + } + sort.Strings(values) + return strings.Join(values, ",") +} + var ( llcPath = flag.String("llc", os.Getenv("GOALLC_LLC"), "path to llc") optPath = flag.String("opt", os.Getenv("GOALLC_OPT"), "path to opt") optPasses = flag.String("opt-passes", "", "optional LLVM optimization pipeline to run before llc") passPluginPath = flag.String("pass-plugin", os.Getenv("GOALLC_PASS_PLUGIN"), "path to the GoALLC LLVM pass plugin (default next to llc)") keepIR = flag.Bool("keep-ir", false, "keep the compiler-generated .ll sidecar") + nativePackages stringSetFlag ) +func init() { + flag.Var(&nativePackages, "native-package", "compile this exact -p package with the native Go backend even when inherited gcflags select LLVM (repeatable)") +} + func main() { flag.Parse() if flag.NArg() < 1 { @@ -55,6 +86,10 @@ func main() { run(tool, args...) return } + if useNativeCompiler(args, nativePackages) { + run(tool, withoutLLVMCompileFlags(args)...) + return + } if !isCompileAction(args) { run(tool, args...) return @@ -290,6 +325,8 @@ func printToolIdentity(tool string, args []string, llc, configuredOpt, optPasses identityInput := append([]byte(nil), out...) identityInput = append(identityInput, "\x00opt-passes="...) identityInput = append(identityInput, optPasses...) + identityInput = append(identityInput, "\x00native-packages="...) + identityInput = append(identityInput, nativePackages.String()...) identity, err := backendIdentity(identityInput, append([]string{wrapper}, backendFiles...)...) if err != nil { fatalf("computing backend identity: %v", err) @@ -426,6 +463,27 @@ func toolFlag(args []string, name string) (string, bool) { return "", false } +func useNativeCompiler(args []string, packages stringSetFlag) bool { + pkg, ok := toolFlag(args, "-p") + if !ok { + return false + } + _, ok = packages[pkg] + return ok +} + +func withoutLLVMCompileFlags(args []string) []string { + native := make([]string, 0, len(args)) + for _, arg := range args { + if arg == "-enablellvm" || strings.HasPrefix(arg, "-enablellvm=") || + arg == "-llvmironly" || strings.HasPrefix(arg, "-llvmironly=") { + continue + } + native = append(native, arg) + } + return native +} + func run(path string, args ...string) { cmd := exec.Command(path, args...) cmd.Stdin = os.Stdin diff --git a/src/cmd/llvmtoolexec/main_test.go b/src/cmd/llvmtoolexec/main_test.go index 42468c50a7619b..db873e9e13c71d 100644 --- a/src/cmd/llvmtoolexec/main_test.go +++ b/src/cmd/llvmtoolexec/main_test.go @@ -384,6 +384,27 @@ func TestCompileInvocationClassification(t *testing.T) { } } +func TestNativePackageOverride(t *testing.T) { + packages := stringSetFlag{"runtime_test": {}} + args := []string{ + "-p", "runtime_test", "-enablellvm", "-llvmironly=true", + "-o", "out.a", "callers_test.go", + } + if !useNativeCompiler(args, packages) { + t.Fatal("exact native package was not recognized") + } + native := withoutLLVMCompileFlags(args) + if hasLLVMCompileFlags(native) { + t.Fatalf("LLVM selection survived native override: %q", native) + } + if got, ok := toolFlag(native, "-p"); !ok || got != "runtime_test" { + t.Fatalf("native override changed package flag to %q, %v", got, ok) + } + if useNativeCompiler([]string{"-p=runtime", "-enablellvm", "-llvmironly"}, packages) { + t.Fatal("native package override matched a different package") + } +} + func TestBoolToolFlag(t *testing.T) { tests := []struct { name string diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index c0f1d979061948..7899734573a515 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -607,9 +607,23 @@ func G0StackOverflow() { }) } +var stackOverflowTestState uint32 + func stackOverflow(x *byte) { var buf [256]byte + // Keep this recursion from becoming a tail-recursive loop. The test needs + // real stack growth, but the LLVM backend is otherwise free to optimize tail + // calls more aggressively than the native Go backend. An atomic load makes + // the return path reachable to the optimizer, and the post-call atomic + // operation keeps state from the current frame live across the call. + buf[0] = byte(atomic.Load(&stackOverflowTestState)) + if buf[0] != 0 { + return + } stackOverflow(&buf[0]) + if x != nil { + atomic.Xadd(&stackOverflowTestState, int32(*x)) + } } func RunGetgThreadSwitchTest() { diff --git a/test/codegen/llvm_linkname.go b/test/codegen/llvm_linkname.go new file mode 100644 index 00000000000000..a497cd96dcc71f --- /dev/null +++ b/test/codegen/llvm_linkname.go @@ -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"() +// LLVM: declare goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-NOT: @"runtime.llvmLinknameLocal" +// LLVM-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal"() +// LLVM: call goabiinternal i64 @runtime.llvmLinknameLocal() +// LLVM-LABEL: define goabiinternal i64 @runtime.llvmLinknameLocal() +// LLVM-OPT-LABEL: define goabiinternal i64 @codegen.llvmLinknameCalls() +// LLVM-OPT: call goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-OPT: declare goabiinternal i64 @"runtime.llvmLinknameExternal"() +// LLVM-OPT-NOT: @"runtime.llvmLinknameLocal" +// LLVM-OPT-LABEL: define weak goabi0 i64 @"runtime.llvmLinknameLocal"() +// LLVM-OPT-LABEL: define goabiinternal {{.*}}@runtime.llvmLinknameLocal() +func llvmLinknameCalls() int { + return llvmLinknameExternal() + llvmLinknameLocal() +} diff --git a/test/codegen/llvm_opendefer.go b/test/codegen/llvm_opendefer.go index b9e8ab334892ca..803a3ceb3f2497 100644 --- a/test/codegen/llvm_opendefer.go +++ b/test/codegen/llvm_opendefer.go @@ -6,6 +6,10 @@ package codegen +type llvmOpenDeferNamedResult struct { + value int +} + // LLVM-LABEL: define goabiinternal i64 @codegen.llvmOpenDeferTwo(i64 %value) // LLVM: [[SLOTS:%.*]] = alloca [2 x ptr], align 8, !goallc.open_defer_slots ![[SLOTS_MD:[0-9]+]] // LLVM: [[SLOT0:%.*]] = getelementptr i8, ptr [[SLOTS]], i64 0 @@ -16,8 +20,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: ![[SLOTS_MD]] = !{i32 2} +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"(), !dbg !{{[0-9]+}} // 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]+]] // LLVM-OPT: [[SLOT1_OPT:%.*]] = getelementptr {{.*}}i8, ptr [[SLOTS_OPT]], i64 8 @@ -27,7 +30,20 @@ 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"(), !dbg !{{[0-9]+}} + +// LLVM-LABEL: define goabiinternal %codegen.llvmOpenDeferNamedResult @codegen.llvmOpenDeferNamed( +// LLVM: open.defer.recovery: +// LLVM-NEXT: call goabiinternal void @"runtime.deferreturn"(), !dbg !{{[0-9]+}} +// LLVM: load volatile %codegen.llvmOpenDeferNamedResult +// LLVM: ret %codegen.llvmOpenDeferNamedResult +// LLVM: ![[SLOTS_MD]] = !{i32 2} +// LLVM-OPT-LABEL: define goabiinternal %codegen.llvmOpenDeferNamedResult @codegen.llvmOpenDeferNamed( +// LLVM-OPT: common.ret: +// LLVM-OPT: load volatile %codegen.llvmOpenDeferNamedResult +// LLVM-OPT: ret %codegen.llvmOpenDeferNamedResult +// LLVM-OPT: open.defer.recovery: +// LLVM-OPT: call goabiinternal void @"runtime.deferreturn"(), !dbg !{{[0-9]+}} // LLVM-OPT: ![[SLOTS_OPT_MD]] = !{i32 2} func llvmOpenDeferTwo(value int) (result int) { @@ -39,3 +55,10 @@ func llvmOpenDeferTwo(value int) (result int) { }() return 3 } + +func llvmOpenDeferNamed(value int) (result llvmOpenDeferNamedResult) { + defer func() { + result.value += value + }() + return llvmOpenDeferNamedResult{value: 3} +} diff --git a/test/codegen/statepoint.go b/test/codegen/statepoint.go index 3cbae7970769f5..026c0e98151013 100644 --- a/test/codegen/statepoint.go +++ b/test/codegen/statepoint.go @@ -19,7 +19,8 @@ import "unsafe" // LLVM-NOT: llvm.experimental.stackmap // LLVM-LABEL: define goabiinternal i64 @codegen.goABIStatepointAttributes( // LLVM-SAME: i64 %x) #[[ATTRS:[0-9]+]] gc "goallc" {{.*}}{ -// LLVM: attributes #[[ATTRS]] = { {{.*}}"frame-pointer"="non-leaf"{{.*}}"go-stack-growth-statepoint"{{.*}} } +// LLVM: attributes #[[ATTRS]] = { {{.*}}"frame-pointer"="non-leaf"{{.*}} } +// LLVM-NOT: go-stack-growth-statepoint func goABIStatepointAttributes(x int) int { return x + 1 } diff --git a/test/llvm_stdlib_packages.json b/test/llvm_stdlib_packages.json index da500406b38be2..c6e67306daca50 100644 --- a/test/llvm_stdlib_packages.json +++ b/test/llvm_stdlib_packages.json @@ -58,6 +58,7 @@ "path/filepath": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", "regexp": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", "regexp/syntax": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", + "runtime": "qualified through LLVM O2 compilation of its exact dependency closure, GoObj/archive, stripped link (-w), and runtime package tests with test scaffolding compiled natively; DWARF is not yet qualified", "sort": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", "strconv": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", "strings": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", @@ -68,6 +69,9 @@ "unicode/utf16": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests", "unicode/utf8": "qualified through entry-package LLVM O2 compile, GoObj/archive, link, and package tests" }, + "dependency_closure": [ + "runtime" + ], "blacklist": { "*": "entry package has not yet been qualified through LLVM O2 compile, GoObj/archive, link, and package tests", "compress/flate": "llc/GoObj: X86 and AArch64 SelectionDAG instruction-selection assertions" diff --git a/test/llvm_tests.json b/test/llvm_tests.json index f57350aa5db3cb..0642a9880bac4b 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -55,6 +55,7 @@ "codegen/llvm_trunc.go": "SSA-verified float64 truncation toward zero through the LLVM trunc intrinsic", "codegen/llvm_memops.go": "pointer-free zero and overlap-safe move intrinsics, runtime memequal, and native-int slice masks", "codegen/llvm_memory_order.go": "preserve Go SSA Memory-token order when LLVM emission precedes native scheduling", + "codegen/llvm_linkname.go": "external linkname pulls use a name suffix while local definitions and ABI wrappers remain canonical", "codegen/llvm_newproc.go": "pointer-typed funcval argument for the runtime.newproc raw ABI call", "codegen/llvm_nilcheck.go": "explicit branch to recoverable runtime.panicmem with stable continuation and optimized IR checks", "codegen/llvm_reflect_method.go": "reflect method calls preserve linker reachability flags in LLVM GoObj functions", @@ -71,7 +72,7 @@ "codegen/shortcircuit.go": "empty-interface extraction and concrete type comparison", "codegen/smallintiface.go": "small scalar boxing through runtime.staticuint64s", "codegen/spills.go": "integer and floating-point values preserved across calls", - "codegen/statepoint.go": "frontend-owned Go GC strategy and stack-growth statepoint function markers", + "codegen/statepoint.go": "frontend-owned Go GC strategy and nosplit-only stack-growth policy", "codegen/structs.go": "typed struct initialization and zeroing", "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data",