Skip to content

feat(jit): move the LLVM backend from LLVM 15 to LLVM 21 - #311

Open
AregGevorgyan wants to merge 2 commits into
integrate/autoresearch-2026-08-19from
feat/llvm21-upgrade
Open

feat(jit): move the LLVM backend from LLVM 15 to LLVM 21#311
AregGevorgyan wants to merge 2 commits into
integrate/autoresearch-2026-08-19from
feat/llvm21-upgrade

Conversation

@AregGevorgyan

Copy link
Copy Markdown
Collaborator

Moves the LLVM JIT backend from LLVM 15 to LLVM 21. Stacked on #310 — review with
git diff integrate/autoresearch-2026-08-19...feat/llvm21-upgrade to see only the LLVM work.

inkwell was never the constraint

inkwell 0.9 — already the pinned version — carries feature flags for LLVM 11 through 22.
The version was one feature string, llvm15-0-prefer-dynamic. Bumping to inkwell 0.10 would
lose a version (it supports 12–22), not gain one.

So the non-CUDA half of this needed zero source changes: it builds and runs on LLVM 18 and
21 as-is. The only thing that genuinely broke is CUDA.

What actually breaks: the CUDA pass pipeline

LLVM 17 removed the legacy PassManager, so --features cuda did not merely warn — it failed
to compile with four no method named add_*_pass errors. run_libdevice_cleanup_passes is
rewritten onto Module::run_passes with the same four passes in the same order.

Verified on hardware (2× RTX 3090, LLVM 21.1.8)

  • scripts/verify_cuda_on_gpu.sh: 9 passed, 0 failed, 0 skipped; 19 GPU tests with no
    silent CPU fallback; compute-sanitizer memcheck / racecheck / initcheck / synccheck all
    ERROR SUMMARY: 0.
  • PTX diffed against an LLVM 15 build of the same tree, 14-kernel battery (poly, sin, cos,
    tan, exp, log, sqrt, abs, three pow forms, all_funcs, deep_nest, wide): identical set of
    out-of-line libdevice functions in all 14, zero __nv_* and zero tanh survivors in either
    build, 14/14 assemble under ptxas -arch=sm_86, and 896/896 device results bit-for-bit
    identical
    .
  • The PTX does differ, but only in backend codegen — typeless .b64 registers, .ptr .align 1
    param annotations, param loads hoisted above the bounds check, 2–12 % fewer instructions.
    poly, which touches no libdevice at all, shows the same class of diff, which is what
    establishes it as LLVM version noise rather than a pipeline regression.
  • Negative control: with the cleanup disabled, all 14 cases fail ptxas on LLVM 21 with
    exactly the documented signature (Parsing error near '.nvvm' on
    llvm.nvvm.tanh.approx.f32, inside __nv_fast_tanhf), and PTX goes 822 KB → 975 bytes when
    it runs. The hazard is fully alive on LLVM 21, and this pipeline is what removes it.

Note nothing in CI builds cuda (cuda_nightly.yml is workflow_dispatch with no registered
runner), so that verification is a manual GPU-box step and this PR does not change that.

CI

Ubuntu 24.04's universe stops at llvm-19-dev, so LLVM 21 comes from apt.llvm.org via a new
.github/actions/setup-llvm composite action — nine jobs across five workflows build jit,
which is why it is an action and not nine copies. It reads the codename from /etc/os-release
so it survives the runner image moving off noble, and it fails loudly at apt-get update if
the pinned suite is ever retired. macOS pins brew install llvm@21; Homebrew's unversioned
llvm is already 22.1.8.

Two job-level LLVM_SYS_211_PREFIX: /usr overrides were removed from release-build.yml:
workflow-file env: beats $GITHUB_ENV, and /usr is wrong for an apt.llvm.org install (its
debs put llvm-config solely at /usr/lib/llvm-21/bin; nothing lands directly in /usr/bin).
Those jobs are release-gated, so this would have passed every PR and failed at the tag.

User-facing consequence

The +jit and +full release wheels move ubuntu-22.04ubuntu-24.04, raising those two
wheels'
glibc floor from 2.35 to 2.39. They are linux_x86_64-tagged and attached to GitHub
Releases only, never PyPI. The default PyPI wheel is untouched and stays on 22.04. The
attach-optional-linux-wheels artifact glob is updated to match, which would otherwise have
silently stopped matching anything.

cargo semver-checks reports no semver update required — there are no public API changes.

Also here

  • AGENTS.md's headline build line no longer carries jit: it needed LLVM dev headers the line
    never mentioned, so a fresh contributor's first command failed on an LLVM error, and
    cranelift already provides a JIT with no system dependencies. This is issue #30a of the
    2026-08-19 autoresearch run.
  • The GPU review also caught budget::memory::tests::gmp_accounting_tracks_a_big_rational
    (from fix: the 2026-08-19 autoresearch run — two Critical soundness bugs, a false mathematical claim, and an uncatchable process abort #310) failing 1-in-4 under --workspace: it reads a process-global counter while ~2360
    other tests allocate GMP concurrently. It now samples until one pair is uncontended, with both
    assertions still strict.

Verification

gate result
cargo test --workspace --features "parallel egraph cranelift jit groebner" 2411 passed, 0 failed (incl. 29 doctests)
--features cuda jit groebner compiles; GPU-verified above
cargo +1.98.0 clippy — default / cranelift / jit / cuda+jit, -D warnings clean
cargo doc --workspace with RUSTDOCFLAGS=-D warnings clean
cargo fmt --check clean
cargo semver-checks vs origin/main 223/223, no update required
scripts/check_error_codes.py OK, 217 codes
compile_expr JIT output matches libm to the last bit

Verified against LLVM 21.1.8 on two independent machines (RHEL 10 llvm-devel here, extracted
apt.llvm.org debs on the GPU box). What neither of us could verify is the packaging path
itself
setup-llvm has never run on a real runner, and brew install llvm@21 is asserted
from the formula API rather than observed. This PR's own CI run is the first real test of both,
and both fail loudly rather than silently.

🤖 Generated with Claude Code

AregGevorgyan and others added 2 commits August 24, 2026 16:57
inkwell was never the constraint. inkwell 0.9 — already the pinned version —
carries feature flags for LLVM 11 through 22; the version was one feature
string, `llvm15-0-prefer-dynamic`. (inkwell 0.10 supports 12-22, so bumping the
crate would drop a version rather than add one.)

CI installs LLVM 21 from apt.llvm.org: Ubuntu 24.04's universe stops at
llvm-19-dev. A new `.github/actions/setup-llvm` composite action adds the
repository and exports LLVM_SYS_211_PREFIX; it is an action rather than nine
copies because nine jobs across five workflows build `jit`. The codename is read
from /etc/os-release so it survives the runner image moving off noble. macOS
pins `brew install llvm@21` — Homebrew's unversioned `llvm` is already 22.1.8.
RHEL 10 ships LLVM 21 as `llvm-devel`, so a distro package now gives a
contributor the right version with no extra repository.

The NVPTX libdevice cleanup is rewritten onto the new pass manager. LLVM 17
removed the legacy `PassManager`, so `--features cuda` did not merely warn on
LLVM 21 — it failed to compile with four `no method named add_*_pass` errors.
`run_libdevice_cleanup_passes` now calls `Module::run_passes` with the same four
passes in the same order, run twice as before.

  VERIFIED ON HARDWARE (2x RTX 3090, LLVM 21.1.8), by a separate agent:
  `scripts/verify_cuda_on_gpu.sh` 9 passed / 0 failed / 0 skipped; 19 GPU tests
  with no silent CPU fallback; memcheck, racecheck, initcheck and synccheck all
  ERROR SUMMARY: 0.

  PTX was diffed against the PR #310 base built at LLVM 15 over a 14-kernel
  battery (poly, sin, cos, tan, exp, log, sqrt, abs, three `pow` forms,
  all_funcs, deep_nest, wide): the set of libdevice functions left out-of-line is
  identical in all 14, zero `__nv_*` and zero `tanh` survivors in either build,
  14/14 assemble under `ptxas -arch=sm_86`, and 896/896 device results are
  bit-for-bit identical. The PTX does differ, but only in backend codegen —
  typeless `.b64` registers, `.ptr .align 1` param annotations, param loads
  hoisted above the bounds check, 2-12% FEWER instructions — and `poly`, which
  touches no libdevice at all, shows the same class of diff, which is what shows
  it is LLVM version noise rather than this pipeline change.

  NEGATIVE CONTROL: with the cleanup disabled, all 14 cases fail ptxas on LLVM 21
  with exactly the documented signature (`Parsing error near '.nvvm'` on
  `llvm.nvvm.tanh.approx.f32`, inside `__nv_fast_tanhf`), and PTX goes 822 KB ->
  975 bytes when it runs. The hazard is fully alive on LLVM 21; this pipeline is
  what kills it.

The `+jit` and `+full` release wheels move from ubuntu-22.04 to ubuntu-24.04,
raising those two wheels' glibc floor from 2.35 to 2.39. They are
linux_x86_64-tagged and attached to GitHub Releases only, never PyPI; the
default PyPI wheel is untouched and stays on 22.04. The
`attach-optional-linux-wheels` artifact glob is updated to match, which would
otherwise have silently stopped matching anything.

`AGENTS.md`'s headline build line no longer carries `jit`: it needed LLVM dev
headers the line never mentioned, so a fresh contributor's first command failed
on an LLVM error, and `cranelift` already provides a JIT with no system
dependencies. This is issue #30a of the 2026-08-19 autoresearch run.

Also fixed from that review: two job-level `LLVM_SYS_211_PREFIX: /usr` overrides
in release-build.yml, which would have beaten the prefix `setup-llvm` writes to
$GITHUB_ENV and pointed at a path apt.llvm.org does not populate (its debs put
llvm-config solely at /usr/lib/llvm-21/bin). Those jobs are release-gated, so it
would have passed every PR and failed at the tag. And
`budget::memory::tests::gmp_accounting_tracks_a_big_rational` — which reads a
process-global counter while the rest of the suite allocates GMP on other
threads — now samples until one pair is uncontended instead of trusting a single
sample; it failed once in four `--workspace` runs before.

Verified on LLVM 21.1.8 (RHEL 10 llvm-devel): cargo test --workspace --features
"parallel egraph cranelift jit groebner" -> 2411 passed, 0 failed (incl. 29
doctests), exit 0; cargo clippy --all-targets --features jit -- -D warnings
clean; cargo clippy --features "cuda jit groebner" -- -D warnings clean; cargo
fmt --check clean; check_error_codes OK; all 11 workflows + the new action parse
as YAML; compile_expr JIT output matches libm to the last bit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rade

# Conflicts:
#	.github/workflows/ci-cross.yml
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48035af6-294b-41c7-b550-5bf537878ae7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Aug 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 35 untouched benchmarks
⏩ 49 skipped benchmarks1


Comparing feat/llvm21-upgrade (e115260) with integrate/autoresearch-2026-08-19 (ea0dee8)

Open in CodSpeed

Footnotes

  1. 49 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

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.

1 participant