mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-11 06:23:39 +00:00
transform, compiler: support LLVM 21's captures(none) attribute; add LLVM 22 build support
Written entirely by Claude (Anthropic's Claude Code), at the request of and under the direction of dgryski, as part of an effort to get TinyGo building against upcoming LLVM releases (this branch currently targets LLVM 22, verified against real LLVM 22.1.8; a corresponding go-llvm branch of the same name adds the matching binding support). LLVM 21 replaced the boolean 'nocapture' enum attribute with the more expressive 'captures' int attribute, where captures(none) (value 0) is the equivalent of the old nocapture. This matters for transform.OptimizeAllocs, which relies on reading this attribute for its interprocedural escape analysis, and for compiler/symbol.go, which emits it on a number of runtime/generated functions. Confirmed empirically (via `opt -passes=function-attrs`) that the cutoff is LLVM 20 emits/expects nocapture, LLVM 21+ emits/expects captures(none). Since TinyGo must keep working with LLVM 20, both sites now go through new version-gated helpers in compiler/llvmutil (NoCaptureAttrName/IsNoCapture) rather than switching unconditionally. Also fixes a second, unrelated but load-bearing break found while testing against LLVM 22: llvm.lifetime.start/end dropped their i64 size argument (confirmed via `opt -passes=verify`, the cutoff here is one version later, at LLVM 22). compiler/llvmutil now builds the right call signature based on version. Adds llvm21 and llvm22 build-tag config files to the cgo package, which parses cgo fragments via libclang and had never been updated past LLVM 20 even though the compiler package itself already gained LLVM 21 support previously -- a latent gap that would have caused a version mismatch between the cgo preprocessor and the rest of the compiler when building with -tags llvm21 or llvm22. Finally, updates the golden-IR test comparators in transform/transform_test.go and compiler/compiler_test.go to normalize a few cosmetic LLVM 21/22 output differences (the captures(none) rename/reordering, a new 'nocreateundeforpoison' intrinsic attribute, and the lifetime intrinsic arity change) so a single golden file continues to match output from either LLVM version. Verified by building a full (non-byollvm) tinygo binary against real LLVM 22.1.8 and running a compiled Go program end-to-end (exercising OptimizeAllocs' stack-allocation path), and by running the transform/compiler/cgo test suites against both LLVM 20 (default) and LLVM 22. Not yet addressed: the byollvm embedded-clang/lld build path hits separate, unrelated Clang C++ API breakage against LLVM 22 (DiagnosticOptions reference-to-pointer change, missing headers) -- that is a larger follow-up effort.
This commit is contained in:
@@ -44,7 +44,7 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
|
||||
alloca = CreateEntryBlockAlloca(builder, t, name)
|
||||
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
fnType, fn := getLifetimeStartFunc(mod)
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
|
||||
builder.SetInsertPointBefore(inst)
|
||||
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
fnType, fn := getLifetimeStartFunc(mod)
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||
builder.SetInsertPointBefore(next)
|
||||
} else {
|
||||
builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
}
|
||||
fnType, fn = getLifetimeEndFunc(mod)
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
return alloca
|
||||
}
|
||||
|
||||
@@ -74,7 +74,27 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
|
||||
// createTemporaryAlloca.
|
||||
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
|
||||
fnType, fn := getLifetimeEndFunc(mod)
|
||||
builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, ptr), "")
|
||||
}
|
||||
|
||||
// lifetimeCallArgs returns the arguments to pass to a call of the
|
||||
// llvm.lifetime.start/end intrinsics. LLVM 22 dropped the (redundant,
|
||||
// already required to match the alloca size) i64 size argument, so the
|
||||
// intrinsic now only takes the pointer.
|
||||
func lifetimeCallArgs(size, ptr llvm.Value) []llvm.Value {
|
||||
if Version() >= 22 {
|
||||
return []llvm.Value{ptr}
|
||||
}
|
||||
return []llvm.Value{size, ptr}
|
||||
}
|
||||
|
||||
// lifetimeFuncType returns the function type of the llvm.lifetime.start/end
|
||||
// intrinsics, which lost their i64 size parameter in LLVM 22.
|
||||
func lifetimeFuncType(ctx llvm.Context, ptrType llvm.Type) llvm.Type {
|
||||
if Version() >= 22 {
|
||||
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ptrType}, false)
|
||||
}
|
||||
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
@@ -84,7 +104,7 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -98,7 +118,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -218,6 +238,36 @@ func Version() int {
|
||||
return major
|
||||
}
|
||||
|
||||
// NoCaptureAttrName returns the name of the LLVM attribute kind that marks a
|
||||
// pointer parameter as guaranteed not to escape by capture.
|
||||
//
|
||||
// LLVM 21 removed the old boolean 'nocapture' enum attribute in favor of the
|
||||
// more expressive 'captures' int attribute, where 'captures(none)' (encoded
|
||||
// as the value 0) is the equivalent of the old 'nocapture'. LLVM 20 supports
|
||||
// both, but its own optimizer still emits 'nocapture', so the cutoff for
|
||||
// reading/writing the new name is LLVM 21.
|
||||
func NoCaptureAttrName() string {
|
||||
if Version() >= 21 {
|
||||
return "captures"
|
||||
}
|
||||
return "nocapture"
|
||||
}
|
||||
|
||||
// IsNoCapture reports whether attr (looked up using the kind returned by
|
||||
// NoCaptureAttrName) indicates that the pointer it is attached to does not
|
||||
// escape by capture. It returns false for a nil attribute.
|
||||
func IsNoCapture(attr llvm.Attribute) bool {
|
||||
if attr.IsNil() {
|
||||
return false
|
||||
}
|
||||
if Version() >= 21 {
|
||||
// captures(none) is encoded as the value 0; any other value permits
|
||||
// some form of capture.
|
||||
return attr.GetEnumValue() == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ByteOrder returns the byte order for the given target triple. Most targets are little
|
||||
// endian, but for example MIPS can be big-endian.
|
||||
func ByteOrder(target string) binary.ByteOrder {
|
||||
|
||||
Reference in New Issue
Block a user