From 73278f395e368dface2415af3d90ed1c73a614ee Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Wed, 5 Aug 2026 17:13:49 +0800 Subject: [PATCH] cmd/compile: preserve KeepAlive through LLVM inlining --- src/cmd/compile/internal/ssa/ssa2llvm.go | 16 +++++--- .../vendor/github.com/goallc/go-llvm/ir.go | 38 +++++++++++++++++++ test/codegen/llvm_memory_order.go | 10 +++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 8e634434960832..4c4b39325271d5 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -282,10 +282,14 @@ func (lfc *LLVMFuncContext) llvmLifetimeStart(slot llvmStackSlot) { lfc.b.CreateCall(sig, fn, []llvm.Value{slot.Value}, "") } -func (lfc *LLVMFuncContext) llvmFakeUse(value llvm.Value) { - sig := llvm.FunctionType(GlobalCtxt.VoidType(), nil, true) - fn := getOrInsertLLVMIntrinsic("llvm.fake.use", sig) - lfc.b.CreateCall(sig, fn, []llvm.Value{value}, "") +func (lfc *LLVMFuncContext) llvmKeepAlive(value llvm.Value) { + // An operand bundle is a real SSA use that follows the call through + // inlining. llvm.donothing survives long enough for the statepoint pass to + // consume that liveness, then code generation emits no instruction for it. + fn := getLLVMIntrinsicDeclaration("llvm.donothing") + bundle := llvm.NewOperandBundle("go.keepalive", []llvm.Value{value}) + lfc.b.CreateCallWithOperandBundles(fn.GlobalValueType(), fn, nil, []llvm.OperandBundle{bundle}, "") + bundle.Dispose() } func (lfc *LLVMFuncContext) llvmUnaryIntrinsic(v *Value, name string) llvm.Value { @@ -1577,12 +1581,12 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = arg0() if name, ok := v.Aux.(*ir.Name); ok { if slot, ok := lfc.Locals[llvmLocalKeyForName(name)]; ok && slot.Type.HasPointers() { - lfc.llvmFakeUse(slot.Value) + lfc.llvmKeepAlive(slot.Value) } } case OpKeepAlive: lVal = arg1() - lfc.llvmFakeUse(arg0()) + lfc.llvmKeepAlive(arg0()) case OpLocalAddr: if v.Uses == 0 { break diff --git a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go index be27442bbf2781..9d471a44fab3a9 100644 --- a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go +++ b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go @@ -67,6 +67,9 @@ type ( Attribute struct { C C.LLVMAttributeRef } + OperandBundle struct { + C C.LLVMOperandBundleRef + } Opcode C.LLVMOpcode AtomicRMWBinOp C.LLVMAtomicRMWBinOp AtomicOrdering C.LLVMAtomicOrdering @@ -92,6 +95,7 @@ func (c PassManager) IsNil() bool { return c.C == nil } func (c Use) IsNil() bool { return c.C == nil } func (c Attribute) IsNil() bool { return c.C == nil } func (c Metadata) IsNil() bool { return c.C == nil } +func (c OperandBundle) IsNil() bool { return c.C == nil } // helpers func llvmTypeRefPtr(t *Type) *C.LLVMTypeRef { return (*C.LLVMTypeRef)(unsafe.Pointer(t)) } @@ -99,6 +103,9 @@ func llvmValueRefPtr(t *Value) *C.LLVMValueRef { return (*C.LLVMValueRef)(unsafe func llvmMetadataRefPtr(t *Metadata) *C.LLVMMetadataRef { return (*C.LLVMMetadataRef)(unsafe.Pointer(t)) } +func llvmOperandBundleRefPtr(t *OperandBundle) *C.LLVMOperandBundleRef { + return (*C.LLVMOperandBundleRef)(unsafe.Pointer(t)) +} func llvmBasicBlockRefPtr(t *BasicBlock) *C.LLVMBasicBlockRef { return (*C.LLVMBasicBlockRef)(unsafe.Pointer(t)) } @@ -145,6 +152,27 @@ func llvmMetadataRefs(mds []Metadata) (*C.LLVMMetadataRef, C.unsigned) { return pt, ptlen } +func llvmOperandBundleRefs(bundles []OperandBundle) (*C.LLVMOperandBundleRef, C.unsigned) { + var pt *C.LLVMOperandBundleRef + ptlen := C.unsigned(len(bundles)) + if ptlen > 0 { + pt = llvmOperandBundleRefPtr(&bundles[0]) + } + return pt, ptlen +} + +func NewOperandBundle(tag string, args []Value) (bundle OperandBundle) { + ctag := C.CString(tag) + defer C.free(unsafe.Pointer(ctag)) + values, nvalues := llvmValueRefs(args) + bundle.C = C.LLVMCreateOperandBundle(ctag, C.size_t(len(tag)), values, nvalues) + return +} + +func (bundle OperandBundle) Dispose() { + C.LLVMDisposeOperandBundle(bundle.C) +} + //------------------------------------------------------------------------- // llvm.Opcode //------------------------------------------------------------------------- @@ -1913,6 +1941,16 @@ func (b Builder) CreateCall(t Type, fn Value, args []Value, name string) (v Valu return } +func (b Builder) CreateCallWithOperandBundles(t Type, fn Value, args []Value, bundles []OperandBundle, name string) (v Value) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + values, nvalues := llvmValueRefs(args) + operandBundles, nbundles := llvmOperandBundleRefs(bundles) + v.C = C.LLVMBuildCallWithOperandBundles(b.C, t.C, fn.C, values, nvalues, + operandBundles, nbundles, cname) + return +} + func (b Builder) CreateSelect(ifv, thenv, elsev Value, name string) (v Value) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) diff --git a/test/codegen/llvm_memory_order.go b/test/codegen/llvm_memory_order.go index 1c5c8a4553ac7d..e67fc60001a636 100644 --- a/test/codegen/llvm_memory_order.go +++ b/test/codegen/llvm_memory_order.go @@ -6,8 +6,14 @@ package codegen +import "runtime" + var llvmMemoryOrderRoot *int +func llvmSemanticKeepAlive(value *int) { + runtime.KeepAlive(value) +} + //go:noinline func llvmMemoryOrderStore(value *int) { llvmMemoryOrderRoot = value @@ -22,6 +28,10 @@ func llvmMemoryOrderStore(value *int) { // LLVM-OPT: call ptr @llvm.go.gc.write.barrier(i32 2) // LLVM-OPT: store ptr %[[VALUE]], ptr @codegen.llvmMemoryOrderRoot // LLVM-OPT: ret ptr %[[VALUE]] +// LLVM-LABEL: define goabiinternal void @codegen.llvmSemanticKeepAlive( +// LLVM: call void @llvm.donothing() [ "go.keepalive"(ptr %{{.*}}) ] +// LLVM-OPT-LABEL: define goabiinternal void @codegen.llvmSemanticKeepAlive( +// LLVM-OPT: call void @llvm.donothing() [ "go.keepalive"(ptr %{{.*}}) ] func llvmMemoryOrderLoad(value *int) *int { llvmMemoryOrderStore(value) return llvmMemoryOrderRoot