all: add LLVM 22 support#5527
Conversation
…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.
Written entirely by Claude (Anthropic's Claude Code), at the request of and under the direction of dgryski, on the dgryski/llvm23 branch (LLVM 22 support, in preparation for the eventual LLVM 23 release; the corresponding go-llvm branch of the same name adds the matching binding support this depends on). Found while running real-world Go test suites through a tinygo built against LLVM 22 (as a smoke test of the earlier captures(none)/ nocapture and lifetime-intrinsic fixes on this branch): `tinygo test` on github.com/dgryski/go-rc5 panicked at compile time with "unknown value" inside interp.(*runner).getValue, while compiling (*sync.Once).Do. Bisecting against LLVM 21 (unaffected) narrowed it to a change introduced in LLVM 22 specifically, unrelated to the earlier captures/lifetime work. Root cause: LLVM 22 stopped exposing switch-instruction case values as regular instruction operands -- only the condition and destination-block operands remain. interp/compiler.go's `case llvm.Switch:` handling walked raw operands assuming the old alternating (value, label) layout, so under LLVM 22 it read a destination *block* where it expected an integer case value, eventually panicking deep inside a getValue call it couldn't resolve. This only manifested on functions actually compiled by the interp package (TinyGo's compile-time evaluator for global initializers) that happen to reach a switch-based dispatch, such as sync.Once's defer-based Do method -- hence it needed a real, moderately complex package (crypto/cipher via go-rc5) to surface, and never showed up in smaller smoke tests. Fix: use the new version-independent Value.SuccessorsCount()/ Value.Successor(i) and Value.GetSwitchCaseValue(i) helpers just added to go-llvm, instead of raw Operand() indexing. Also applies the same captures(none)/nocapture golden-IR normalization already used in transform/transform_test.go and compiler/compiler_test.go to interp/interp_test.go's separate copy of the same comparator helper, which was missed in the earlier pass and started failing two of its subtests once actually run against LLVM 22. Verified: `tinygo test` on go-rc5 now passes reliably (repeated runs) against both LLVM 20 (default) and LLVM 22; full transform/compiler/ interp/cgo/goenv test suites pass on both versions. The `builder` package has pre-existing, environment-related test failures (stale GOROOT cache, local clang/target-feature drift) confirmed identical against an unmodified LLVM 20 build, so unrelated to this change.
Signed-off-by: deadprogram <ron@hybridgroup.com>
Builder: - Update clang.cpp for LLVM 22 API changes: DiagnosticOptions is no longer ref-counted, TextDiagnosticPrinter and DiagnosticsEngine take references instead of pointers, createDiagnostics() signature changed. - Update cc1as.cpp: clang/Driver/Options.h moved to clang/Options/Options.h, namespace changed from clang::driver::options to clang::options, MCInstPrinter now passed as unique_ptr. - Add new LLVM 22 libraries to GNUmakefile: clangAnalysisLifetimeSafety, clangOptions, LLVMDTLTO, and dtlto component. - Add llvm22 build tag to all build/test commands. CGo: - Handle CXType_Unexposed in libclang.go by resolving via canonical type. LLVM 22 reports builtin type aliases (e.g. __size_t) as Unexposed instead of Typedef. - Always make C typedefs into Go type aliases. LLVM 22 changed getTypedefDeclUnderlyingType to return CXType_Enum directly instead of wrapping in an elaborated type. Compileopts: - Add build-tag-guarded ClangTriple() to substitute wasm32-unknown-wasi with wasm32-unknown-wasip1 for LLVM 22 (deprecated triple). - Add build-tag-guarded patchFeatures() to map renamed Xtensa features (atomctl, memctl, timerint, esp32s3) for LLVM 22. Targets: - Remove -zca from RISC-V target feature strings (esp32c3, esp32c6, fe310, k210, riscv-qemu, tkey). LLVM 22 now implies +zca from +c. - Remove -zcd from k210. LLVM 22 now implies +zcd from +c,+d. Tests: - Change TestClangAttributes to check individual feature flags instead of exact string match, allowing new LLVM features without failures. - Update TestBinarySize expected values for LLVM 22 codegen. Signed-off-by: deadprogram <ron@hybridgroup.com>
|
Size difference with the dev branch: Binary size differenceflash ram before after diff before after diff |
cc1as: change AssemblerInvocation.Triple from std::string to llvm::Triple, matching upstream LLVM 22 and fixing deprecation warnings for lookupTarget, createMCRegInfo, createMCAsmInfo, and createMCSubtargetInfo. interp: always use ConstNamedStruct in rawValue.toLLVMValue instead of falling back to ConstStruct for unnamed structs. LLVM 22 uses anonymous identified struct types where previous versions used literal structs; ConstStruct creates a literal type that doesn't match, causing an "initializer type mismatch" panic for globals like fmt.ppFree. compileopts: add build-tag-guarded feature patching for pre-LLVM 20 (strip +bulk-memory-opt and +call-indirect-overlong from wasm features) and restructure into three files covering all LLVM version ranges. targets: strip redundant negative features from RISC-V target JSONs (esp32c3, esp32c6, fe310, k210, riscv-qemu, tkey). These ~190 negative features per target listed every extension LLVM knows about that the target doesn't use, but LLVM disables them by default. They became stale across LLVM versions, causing "not a recognized feature" warnings. Keep only positive features and -relax. Signed-off-by: deadprogram <ron@hybridgroup.com>
Clang on i386 mingw emits calls to _alloca (decorated as __alloca) for stack probing. This was provided by chkstk2.S, not chkstk.S which only provides __chkstk_ms. Signed-off-by: deadprogram <ron@hybridgroup.com>
There was a problem hiding this comment.
Pull request overview
This PR updates TinyGo’s toolchain integration and tests to support LLVM 22, adapting to upstream LLVM/Clang API and IR-printing changes while keeping existing golden files and target configurations working across LLVM versions.
Changes:
- Update builder (clang driver + cc1as) for LLVM 22 API/ABI changes and add required LLVM/Clang libs + build tags.
- Normalize IR comparisons in tests for LLVM 21+ attribute printing changes and LLVM 22 lifetime intrinsic signature changes.
- Adjust compile options/targets for LLVM 22 (WASI triple substitution, Xtensa feature renames, RISC-V feature string updates) and bump go-llvm.
Reviewed changes
Copilot reviewed 39 out of 40 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| transform/transform_test.go | Normalizes IR diffs across LLVM versions for golden-file stability. |
| transform/allocs.go | Uses version-aware nocapture/captures handling via llvmutil. |
| targets/tkey.json | Simplifies/updates RISC-V features for LLVM 22 behavior changes. |
| targets/riscv-qemu.json | Simplifies/updates RISC-V features for LLVM 22 behavior changes. |
| targets/k210.json | Simplifies/updates RISC-V features for LLVM 22 behavior changes. |
| targets/fe310.json | Simplifies/updates RISC-V features for LLVM 22 behavior changes. |
| targets/esp32c6.json | Simplifies/updates RISC-V features and reformats JSON. |
| targets/esp32c3.json | Simplifies/updates RISC-V features and reformats JSON. |
| interp/memory.go | Preserves struct type identity when materializing LLVM constants. |
| interp/interp_test.go | Normalizes IR diffs across LLVM versions for golden-file stability. |
| interp/compiler.go | Updates switch lowering for LLVM 22 operand/successor API changes. |
| go.sum | Updates module sums for the bumped go-llvm version. |
| go.mod | Bumps tinygo.org/x/go-llvm dependency to a newer revision. |
| GNUmakefile | Adds LLVM 22 components/libs and applies llvm22 build tags. |
| compiler/symbol.go | Uses llvmutil to emit correct nocapture/captures attribute by LLVM version. |
| compiler/llvmutil/llvm.go | Adds LLVM-version guards for lifetime intrinsics + nocapture/captures helpers. |
| compiler/compiler_test.go | Normalizes IR diffs across LLVM versions for golden-file stability. |
| compileopts/triple_pre22.go | Keeps pre-LLVM22 clang triple behavior unchanged. |
| compileopts/triple_llvm22.go | Substitutes deprecated WASI triple for LLVM 22 clang invocation. |
| compileopts/features_pre20.go | Strips unsupported wasm feature flags for LLVM 19 and earlier. |
| compileopts/features_llvm22.go | Adds LLVM 22 Xtensa feature rename/removal mapping. |
| compileopts/features_default.go | Default feature mapping behavior for LLVM 20/21. |
| compileopts/config.go | Centralizes feature/triple patching in compile configuration. |
| cgo/libclang.go | Adapts to LLVM 22 libclang type-kind changes and typedef handling. |
| cgo/libclang_config_llvm22.go | Adds build-tagged libclang include/link flags for LLVM 22 installs. |
| cgo/libclang_config_llvm21.go | Adds build-tagged libclang include/link flags for LLVM 21 installs. |
| cgo/libclang_config_llvm20.go | Adjusts default libclang config build tags to exclude LLVM 21/22. |
| builder/sizes_test.go | Updates expected binary sizes for LLVM 22 codegen differences. |
| builder/library.go | Uses ClangTriple when invoking clang for target triple compatibility. |
| builder/clang.cpp | Updates to LLVM 22 Clang diagnostics API changes. |
| builder/cc1as.h | Updates AssemblerInvocation triple type for LLVM 22 expectations. |
| builder/cc1as.cpp | Updates cc1as implementation for LLVM 22 header/namespace/API changes. |
| builder/builtins.go | Updates Windows 386 builtins list to include chkstk variants. |
| builder/builder_test.go | Makes feature checking resilient to LLVM version feature-string growth. |
| .github/workflows/windows.yml | Updates LLVM 22 cache keys for CI. |
| .github/workflows/sizediff.yml | Updates LLVM 22 cache keys for CI. |
| .github/workflows/nix.yml | Updates LLVM 22 cache keys for CI. |
| .github/workflows/linux.yml | Updates LLVM 22 cache keys for CI. |
| .github/workflows/compat.yml | Updates LLVM 22 cache keys for CI. |
| .github/workflows/build-macos.yml | Updates LLVM 22 cache keys for CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func patchFeatures(features string) string { | ||
| // Xtensa feature renames in LLVM 22: | ||
| // atomctl → (removed, no direct replacement) | ||
| // memctl → (removed, no direct replacement) | ||
| // esp32s3 → esp32s3ops | ||
| // timerint → timers3 (for esp32/esp32s3) or timers1 (for esp8266) | ||
| // Since we can't distinguish which timer variant at this level, | ||
| // just remove the obsolete features. The CPU definition already | ||
| // implies the correct features in LLVM 22. | ||
| replacer := strings.NewReplacer( | ||
| "+atomctl,", "", | ||
| "+memctl,", "", | ||
| "+esp32s3,", "+esp32s3ops,", | ||
| "+timerint,", "", | ||
| ",+timerint", "", | ||
| ) | ||
| return replacer.Replace(features) | ||
| } |
There was a problem hiding this comment.
The ",+timerint" entry in the replacer already handles the end-of-string case. It matches ,+timerint at the end of ...+rvector,+timerint and produces ...+rvector. I verified this works correctly for the esp8266 target. The two patterns together cover both positions: "+timerint," for mid-string and ",+timerint" for end-of-string.
|
Are we dropping support for any of the older llvm versions or just keeping everything forever? |
At the moment we do not need to drop any support. The code for LLVM19 and below is quite modest. However we should consider that once it becomes very inconvenient. |
|
Due to our limited cache capacity on GH actions, it would be a lot faster to review/change/merge this PR before we will have to rebuild LLVM again just to run the CI on this PR any any others using LLVM 22. |
Builder:
longer ref-counted, TextDiagnosticPrinter and DiagnosticsEngine take
references instead of pointers, createDiagnostics() signature changed.
clang/Options/Options.h, namespace changed from clang::driver::options
to clang::options, MCInstPrinter now passed as unique_ptr.
clangOptions, LLVMDTLTO, and dtlto component.
CGo:
type. LLVM 22 reports builtin type aliases (e.g. __size_t) as
Unexposed instead of Typedef.
getTypedefDeclUnderlyingType to return CXType_Enum directly instead
of wrapping in an elaborated type.
Compileopts:
with wasm32-unknown-wasip1 for LLVM 22 (deprecated triple).
(atomctl, memctl, timerint, esp32s3) for LLVM 22.
Targets:
fe310, k210, riscv-qemu, tkey). LLVM 22 now implies +zca from +c.
Tests:
of exact string match, allowing new LLVM features without failures.