Skip to content

all: add LLVM 22 support#5527

Open
deadprogram wants to merge 7 commits into
devfrom
llvm-22-ci
Open

all: add LLVM 22 support#5527
deadprogram wants to merge 7 commits into
devfrom
llvm-22-ci

Conversation

@deadprogram

Copy link
Copy Markdown
Member

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.

dgryski and others added 5 commits July 14, 2026 09:19
…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>
@github-actions

Copy link
Copy Markdown

Size difference with the dev branch:

Binary size difference
 flash                          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>
@deadprogram deadprogram marked this pull request as ready for review July 15, 2026 17:56
@deadprogram

Copy link
Copy Markdown
Member Author

All tests passing using LLVM22 on the CI!

This PR incorporates the commits from @dgryski #5505

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +9 to +26
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)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dgryski

dgryski commented Jul 15, 2026

Copy link
Copy Markdown
Member

Are we dropping support for any of the older llvm versions or just keeping everything forever?

@deadprogram

Copy link
Copy Markdown
Member Author

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.

@deadprogram

Copy link
Copy Markdown
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants