Repository navigation
Convert arguments to the C API without inferring generic conversions - #703
Merged
Merged
Conversation
This was referenced Oct 21, 2026
Merged
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## tb/invalidations #703 +/- ##
====================================================
- Coverage 94.35% 94.31% -0.05%
====================================================
Files 59 59
Lines 7853 7910 +57
====================================================
+ Hits 7410 7460 +50
- Misses 443 450 +7
🚀 New features to boost your workflow:
|
maleadt
force-pushed
the
tb/invalidations
branch
from
October 21, 2026 12:54
c3f37d9 to
570e785
Compare
maleadt
force-pushed
the
tb/api-conversions
branch
from
October 21, 2026 12:54
73147f7 to
cd7f945
Compare
Callers that don't specialize, like the IRBuilder, pass abstractly-typed
vectors to `value_refs` and friends. Those were then inferred for any
`AbstractVector`, making them depend on generic iteration methods like
`IteratorSize`, which packages invalidate by extending them for their own
arrays (e.g. SparseArrays, StructArrays and FillArrays).
Index vectors with the hierarchy's abstract element type (e.g. `Vector{Value}`,
what callers usually pass) directly, and convert other vectors behind an
inference barrier. That also avoids a dynamic call for the common case, making
`call!` through the IRBuilder about 20% faster.
The IRBuilder and other methods that don't specialize take names and other strings as `AbstractString`s, so passing them to a `ccall` made the conversion to a `Cstring` depend on generic string conversion methods, which packages adding string types (like InlineStrings, through DataFrames) invalidate. Convert `AbstractString` arguments for `Cstring` parameters of the C API wrappers to a `String` before the `ccall`, behind an inference barrier unless they're a `String` already. That's what `cconvert(Cstring, ::AbstractString)` does too; pointers and other values are passed unchanged. Methods that compute a string's length for the C API, or compare it, convert it first, and so do builders that also build a vector of references, as a dynamic call after building it keeps Julia from eliding its allocation.
Alignments and aggregate indices are often abstractly-typed `Integer`s, as the IRBuilder doesn't specialize, so checking them (and converting them to a `Cuint` again when calling the C API) depended on generic integer methods, like promotion, that packages such as ChainRulesCore invalidate. Check them with a function that specializes on their type and returns the converted value, and call it behind an inference barrier unless the value is an `Int` (or, for alignments, a `Cuint`). Callers pass the converted value to the C API. This makes `extract_value!` with an `Int` index twice as fast. Address spaces (of globals, functions and pointer types) and clause counts are converted the same way.
Metadata nodes are often created from an untyped literal, e.g. `MDNode([a, b])`
with elements of different metadata types, so `MDTuple` was compiled for an
abstract vector, and converting its elements to `Vector{Metadata}` depended on
generic methods for any vector, like iterating ranges, which ChainRulesCore
invalidates by extending `*`. That discarded, e.g., the precompiled Metal back-end
of GPUCompiler when ChainRulesCore was loaded first.
maleadt
force-pushed
the
tb/api-conversions
branch
from
October 21, 2026 20:57
41a8c7a to
9833c23
Compare
maleadt
force-pushed
the
tb/invalidations
branch
from
October 21, 2026 20:57
570e785 to
e4f20d1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #702. LLVM.jl's C API wrappers and IRBuilder are compiled with
@nospecialize, so the strings, integers and vectors they pass to the C API are abstractly typed, and converting them was inferred for anyAbstractString,IntegerorAbstractVector. That makes LLVM.jl's (and its users') precompiled code depend on generic Base methods likecconvert(Cstring, ::AbstractString),UInt32(::Integer)orIteratorSize(::AbstractVector), which packages invalidate when they add string, integer or array types: InlineStrings, SentinelArrays and PooledArrays (all loaded by DataFrames), StructArrays, FillArrays, ChainRulesCore, ForwardDiff, etc.This PR converts those values to concrete types once, with a fast path for the common type and an inference barrier for the others:
Vector{Value}(and the like) are indexed directly, other vectors are converted behind a barrier;AbstractStringarguments forCstringparameters to aString(whatcconvert(Cstring, ::AbstractString)does too); pointers are passed unchanged;Cuintby one call that specializes on their type, directly forInts and behind a barrier otherwise; address spaces and clause counts are converted the same way;First kernel on Julia 1.13.2, with the downstream fixes applied and only LLVM.jl varied (mean of 3 runs):
Per commit, the string and integer conversions account for nearly all of the gain; the vector conversions remove about 80 of the rejected method instances without a measurable effect on their own. Warm IRBuilder throughput improves slightly: building a call is about 20% faster than on main (132 vs. 170 ns, 213 vs. 244 bytes), and
extract_value!with anIntindex twice as fast. Converting aVector{ConstantInt}now takes the barrier path (+32 bytes per call).