Skip to content

Keep abstractly-typed values away from generic Base code - #702

Merged
maleadt merged 3 commits into
mainfrom
tb/invalidations
Oct 22, 2026
Merged

maleadt merged 3 commits into
mainfrom
tb/invalidations

Conversation

@maleadt

@maleadt maleadt commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Packages loaded before LLVM.jl that extend Base functions like promote_rule or * (ChainRulesCore does both) invalidate precompiled code in LLVM.jl and its users, which Julia then recompiles on first use (JuliaGPU/GPUCompiler.jl#1048).

Much of LLVM.jl is compiled with @nospecialize, so values like alignments, enum values and operand bundle vectors are abstractly typed there. Code that formats or iterates them is compiled with edges to generic Base methods (!=(::Unsigned, ::Int64) through integer printing, range iteration through collect, convert(Vector{String}, ::Vector{Any})), and those are what such packages invalidate. This PR keeps these values away from generic code: typed literals for the module-flag MDNode and the pass-name vectors, a lazily formatted alignment error, enum names formatted to a String before printing, an assertion that enum values are 32-bit, and operand bundle views collected behind an inference barrier. Behaviour and error messages are unchanged.

A separate commit refers to llvmcall through Core.Intrinsics in generated calls. That one is cosmetic: on Julia 1.13, edges to Base's lazily created re-export show up as spurious invalidations (JuliaLang/julia#59135, fixed in 1.14).

First kernel on Julia 1.13.2, with the downstream fixes (JuliaGPU/GPUCompiler.jl#1052, maleadt/CompilerCaching.jl#33 etc.) applied and only LLVM.jl varied:

main this PR
KernelAbstractions CPU, ChainRulesCore loaded first 1.94 s 1.00 s
CUDA, ChainRulesCore loaded first 3.53 s 2.70 s
KernelAbstractions CPU / CUDA, nothing loaded first 0.80 / 2.45 s 0.73 / 2.34 s

DataFrames loaded first is addressed by the follow-up, #703.

An untyped literal like `[id, MDString(name), val]` with `val::Metadata` isn't
inferred concretely, so `MDNode` and its conversion to `Vector{Metadata}` were
compiled for any `AbstractVector`, including ranges, whose methods packages like
ChainRulesCore invalidate by extending `*`. The empty `[]` vectors passed to the
`PassBuilder` and `PassManager` constructors similarly went through a generic
`convert(Vector{String}, ::Vector{Any})`.
@codecov

codecov Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##             main     #702      +/-   ##
==========================================
- Coverage   94.35%   94.35%   -0.01%     
==========================================
  Files          59       59              
  Lines        7850     7849       -1     
==========================================
- Hits         7407     7406       -1     
  Misses        443      443              
Files with missing lines Coverage Δ
src/core/module.jl 98.78% <100.00%> (ø)
src/core/value/constant.jl 97.01% <ø> (ø)
src/enums.jl 98.70% <100.00%> (-0.02%) ⬇️
src/interop/generated.jl 96.25% <100.00%> (ø)
src/irbuilder.jl 97.11% <100.00%> (ø)
src/passes.jl 94.15% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Much of LLVM.jl is compiled with `@nospecialize`, so values like alignments,
enums and operand bundle vectors are abstractly typed. Code that formats or
iterates them is compiled with edges to generic Base methods, which packages
loaded before LLVM.jl (e.g. ChainRulesCore) invalidate by adding methods for
promotion or arithmetic, discarding LLVM.jl's and its users' precompiled code.

Format alignment errors lazily, format enum names to a string before printing
them, assert the width of enum values and the type of pass parameter names, and
collect views of operand bundles behind an inference barrier. With ChainRulesCore
loaded first, this brings the first KernelAbstractions CPU kernel from 1.9 s to
1.1 s, and the first CUDA kernel from 3.5 s to 2.9 s.
Base only re-exports `llvmcall`, and its binding there is created lazily. Code
that references `Base.llvmcall` through a `GlobalRef` therefore has an edge to a
binding that may not exist yet, which Julia 1.13 reports as an invalidation every
time the code is loaded (JuliaLang/julia#59135). Nothing is recompiled, but the
spurious entries clutter SnoopCompile's output.
@maleadt
maleadt merged commit 75b9274 into main Oct 22, 2026
63 checks passed
@maleadt
maleadt deleted the tb/invalidations branch October 22, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant