mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 20:43:40 +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:
+3
-2
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -260,7 +261,7 @@ func callValueEscapesAt(call, value llvm.Value, allowReturn bool, visiting map[l
|
||||
if called.IsAFunction().IsNil() {
|
||||
return escapeResult{escapeAt: call}
|
||||
}
|
||||
kindNoCapture := llvm.AttributeKindID("nocapture")
|
||||
kindNoCapture := llvm.AttributeKindID(llvmutil.NoCaptureAttrName())
|
||||
kindReturned := llvm.AttributeKindID("returned")
|
||||
matched := false
|
||||
var result escapeResult
|
||||
@@ -270,7 +271,7 @@ func callValueEscapesAt(call, value llvm.Value, allowReturn bool, visiting map[l
|
||||
}
|
||||
matched = true
|
||||
index := i + 1 // param attributes start at 1
|
||||
nocapture := !called.GetEnumAttributeAtIndex(index, kindNoCapture).IsNil()
|
||||
nocapture := llvmutil.IsNoCapture(called.GetEnumAttributeAtIndex(index, kindNoCapture))
|
||||
returnedParam := !called.GetEnumAttributeAtIndex(index, kindReturned).IsNil()
|
||||
if returnedParam {
|
||||
result.returned = true
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -99,6 +100,29 @@ func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Modu
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
|
||||
// which LLVM printed before any co-occurring attribute such as
|
||||
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
|
||||
// equivalent 'captures(none)' instead, and after such attributes (e.g.
|
||||
// "ptr readonly captures(none)"). Normalize both name and position back
|
||||
// to the old spelling to keep a single golden file working across LLVM
|
||||
// versions.
|
||||
s1 = normalizeCapturesAttr(s1)
|
||||
s2 = normalizeCapturesAttr(s2)
|
||||
|
||||
// LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to
|
||||
// certain intrinsic declarations (e.g. llvm.umin) that were implicitly
|
||||
// assumed not to create undef/poison before. It's unrelated to the
|
||||
// escape-analysis behavior under test, so ignore it for comparison.
|
||||
s1 = strings.ReplaceAll(s1, "nocreateundeforpoison ", "")
|
||||
s2 = strings.ReplaceAll(s2, "nocreateundeforpoison ", "")
|
||||
|
||||
// LLVM 22 dropped the (redundant) i64 size argument from
|
||||
// llvm.lifetime.start/end. Normalize away that argument so golden files
|
||||
// written against the two-argument form still match.
|
||||
s1 = lifetimeSizeArgRe.ReplaceAllString(s1, "$1")
|
||||
s2 = lifetimeSizeArgRe.ReplaceAllString(s2, "$1")
|
||||
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
@@ -114,6 +138,25 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
|
||||
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
|
||||
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
|
||||
// around).
|
||||
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
|
||||
|
||||
// lifetimeSizeArgRe matches the i64 size argument of an
|
||||
// llvm.lifetime.start/end call or declaration, which LLVM 22 removed.
|
||||
var lifetimeSizeArgRe = regexp.MustCompile(`(@llvm\.lifetime\.(?:start|end)\.p0\()i64(?: immarg| \d+), `)
|
||||
|
||||
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
|
||||
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
|
||||
// written against LLVM <21 keep matching.
|
||||
func normalizeCapturesAttr(s string) string {
|
||||
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
|
||||
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
|
||||
return s
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
||||
// stripped out.
|
||||
|
||||
Reference in New Issue
Block a user