The FixedPointNumbers package is causing many invalidations, even though it does not engage in type piracy. For details, see: Loading FixedPointNumbers invalidates ~7,700 precompiled method instances (7x slower first mtkcompile) · Issue #331 · JuliaMath/FixedPointNumbers.jl · GitHub
My question: What is causing these invalidations? Should that be fixed in Julia itself, or in FixedPointNumbers, or is there no way to fix it?
I encounter this problem mainly when using GLMakie, which depends on FixedPointNumbers.
UPDATE: GeometryBasics has a similar problem: Loading GeometryBasics invalidates ~11,000 precompiled method instances (7x slower first mtkcompile) · Issue #284 · JuliaGeometry/GeometryBasics.jl · GitHub . Both issues must be fixed before loading GLMakie does not create long delays for MTK any more.
Benny
September 6, 2026, 10:21pm
2
Might be case 4 mentioned in Type piracy · Aqua.jl , not explained like the others though. I know of invalidations where additional methods violate a prior assumption of a function only having a few (defaulting to 3 according to ?Base.Experimental.@max_methods) methods when abstract types are inferred for inputs, but a cursory glance at methods(-, Tuple{Real}) shows 9 in Base, so I doubt that assumption was made.
Probably unimportant, but I don’t see how the Union{} type described in case 3 and mentioned in the issue is practically relevant when nobody can instantiate it and won’t instantiate empty Union{}-parametric types, that is these call signatures aren’t worth compiling in the first place.
Julia v1.13 (v1.13.1) has been released. Updating the information on how the situation has changed might be helpful to developers.
As mentioned in Loading FixedPointNumbers invalidates ~7,700 precompiled method instances (7x slower first mtkcompile) · Issue #331 · JuliaMath/FixedPointNumbers.jl · GitHub , I suspect that SymbolicUtils.jl is partly to blame.
Of course, SymbolicUtils.jl might literally be the “victim”.
No improvement using Julia 1.13.1:
ufechner@framework:~/tmp$ julia --startup-file=no --project=. mwe.jl
mtkcompile: 0.04 s
ufechner@framework:~/tmp$ julia --startup-file=no --project=. mwe.jl
mtkcompile: 0.04 s
ufechner@framework:~/tmp$ LOAD_FPN=1 julia --startup-file=no --project=. mwe.jl
mtkcompile: 28.24 s
ufechner@framework:~/tmp$ LOAD_FPN=1 julia --startup-file=no --project=. mwe.jl
mtkcompile: 28.2 s
Thank you for updating the information.
Regarding the checked_mul issue in FixedPointNumbers v0.9, there are limits to what can be addressed within the package itself.
I have filed two issues regarding workarounds.
opened 08:45AM - 28 Sep 26 UTC
Currently, checked arithmetic functions such as `checked_add` and `checked_mul` … do not have fallback implementations.
For this reason, if type inference fails, the method cannot be resolved.
```julia
julia> which(Base.Checked.checked_mul, Tuple{Integer, Any})
ERROR: Calling invoke(f, t, args...) would throw:
MethodError: checked_mul(::Integer, ::Any) is ambiguous.
Candidates:
checked_mul(a::BigInt, b::BigInt)
@ Base.GMP gmp.jl:864
checked_mul(x::T, y::T) where T<:Integer
@ Base.Checked checked.jl:292
checked_mul(x::Integer, y::Integer)
@ Base.Checked checked.jl:28
To resolve the ambiguity, try making one of the methods more specific, or adding a new method more specific than any of the existing applicable methods.
```
This carries the risk of triggering invalidation when adding new methods for new types.
In fact, FixedPointNumbers.jl began using `checked_mul` etc. in v0.9, and (although the situation is somewhat unusual), ModelingToolkit.jl (or its dependencies) is encountering a critical invalidation issue. (cf. https://github.com/JuliaMath/FixedPointNumbers.jl/issues/331)
A demo is shown below.
```julia
julia> using SnoopCompileCore
julia> import Base.Checked: checked_mul
julia> checked_mul(x, y) = checked_mul(convert(Integer, x), convert(Integer, y)); # fallback
julia> using ModelingToolkit # v11.45.1
julia> invs = @snoop_invalidations Base.Checked.checked_mul(::Real, T::Type) = T(); # invalidation trigger
julia> using SnoopCompile
julia> length(uinvalidated(invs))
4206 # without fallback
0 # with fallback
Julia Version 1.13.1
Commit 96ca370cf0 (2026-09-25 19:34 UTC)
Build Info:
Official https://julialang.org release
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: 8 × 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
WORD_SIZE: 64
LLVM: libLLVM-20.1.8 (ORCJIT, tigerlake)
GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 8 virtual cores)
```
Of course, I believe the issue of type instability should be addressed on the package side.
In addition, end users can mitigate the problem by changing the order in which packages are loaded.
There is another, independent workaround for this issue (xref: #63455).
opened 10:11AM - 28 Sep 26 UTC
For background information, please refer to issue #63452.
One of the (potential… ) direct backedges of `checked_mul(::Integer, ::Any)` is `//(::Rational, ::Integer)` (and its variants).
```julia
julia> code_warntype(//, (Rational, Integer))
MethodInstance for //(::Rational, ::Integer)
from //(x::Rational, y::Integer) @ Base rational.jl:93
Arguments
#self#::Core.Const(//)
x::Rational
y::Integer
Locals
@_4::Int64
yn::Any
xn::Any
Body::Rational
1 ─ %1 = Base.divgcd::Core.Const(Base.divgcd)
│ %2 = Base.promote::Core.Const(promote)
│ %3 = Base.getproperty(x, :num)::Integer
│ %4 = (%2)(%3, y)::Tuple{Any, Any}
│ %5 = Core._apply_iterate(Base.iterate, %1, %4)::Tuple{Any, Any}
│ %6 = Base.indexed_iterate(%5, 1)::Core.PartialStruct(Tuple{Any, Int64}, Union{Nothing, Bool}[0, 0], Any[Any, Core.Const(2)])
│ (xn = Core.getfield(%6, 1))
│ (@_4 = Core.getfield(%6, 2))
│ %9 = @_4::Core.Const(2)
│ %10 = Base.indexed_iterate(%5, 2, %9)::Core.PartialStruct(Tuple{Any, Int64}, Union{Nothing, Bool}[0, 0], Any[Any, Core.Const(3)])
│ (yn = Core.getfield(%10, 1))
│ %12 = Base.checked_den::Core.Const(Base.checked_den)
│ %13 = xn::Any
│ %14 = Base.checked_mul::Core.Const(Base.Checked.checked_mul)
│ %15 = Base.getproperty(x, :den)::Integer
│ %16 = yn::Any
│ %17 = (%14)(%15, %16)::Any
│ %18 = (%12)(%13, %17)::Rational
└── return %18
```
The `Base.divgcd` is defined with only one method that takes two `Integer` arguments, and it is expected to return two `Integer`s. However, it actually may returns `(Any, Any)` for abstract type arguments, as showed above.
https://github.com/JuliaLang/julia/blob/e4624f55020e91cdd43465f6f6e87430f45bd535/base/rational.jl#L52-L62
I think it’s a reasonable choice to give up on type inference early on for methods that aren’t worth specializing.
On the other hand, (though this may be an edge case), since the method is actually precompiled, I think it’s acceptable to have some degree of type assertions.
To begin with, is `::Tuple{TX, TY}` generally correct? I don't think there's any guarantee that the results will be in `TX` and `TY`. (cf. PR #51944)
The `Base.divgcd` is a internal function and AFAK only used in rational.jl.
```julia
julia> versioninfo()
Julia Version 1.13.1
Commit 96ca370cf0 (2026-09-25 19:34 UTC)
Build Info:
Official https://julialang.org release
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: 8 × 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
WORD_SIZE: 64
LLVM: libLLVM-20.1.8 (ORCJIT, tigerlake)
GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 8 virtual cores)
```
@ufechner7
SymbolicUtils.jl v4.50.0 has been released, incorporating:
master ← pankgeorg:pow-rational-inference
merged 02:45PM - 02 Oct 26 UTC
Follow-up to the type-stability note in https://github.com/JuliaSymbolics/Symbol… icUtils.jl/issues/1101#issuecomment-5884776264.
The `sqrt`/`cbrt` branches of `^` computed `b::Rational // 2` (and `// 3`). In the `^(::BasicSymbolic, ::Number)` specialization, that makes inference go through `//(::Rational, ::Int64)` with an abstract `Rational`. That call has a backedge to `checked_mul(::Integer, ::Any)` (JuliaLang/julia#63455), and FixedPointNumbers 0.9 adds a `checked_mul` method, so loading it invalidated `^` and everything inferred through it. `b // 2` behaves the same at runtime, since it dispatches on the concrete `Rational`, but inference no longer specializes `//` on the abstract type: SymbolicUtils compiles with `max_methods=1`, and `(Number, Int)` matches three `//` methods, so the call is left dynamic.
**Measured** with SnoopCompileCore and FixedPointNumbers 0.9.1 against current master (v4.49.0): distinct SymbolicUtils method instances invalidated by `using FixedPointNumbers` after a small `^` workload.
| Julia | master | this PR |
|---|---|---|
| 1.12.7 | 83 (70 of them in the `checked_mul` tree, all entering at `^(::BasicSymbolic, ::Number)` → `//(::Rational, ::Int64)`) | 13 |
| 1.13.1 | 1163 | 1163 |
On 1.13.1 the `checked_mul` tree is gone too, but a separate tree rooted at FixedPointNumbers' `length(::StepRange{X, X})` invalidates 1159 method instances through `Dict{…, StepRange}` operations, `^` included. This PR doesn't address that one.
**Tests:** none added, because behaviour is unchanged (the `sqrt`/`cbrt` folds give identical results before and after). There's no automated invalidation test either, since the counts depend on the Julia version and on what has been compiled. On the merged branch I ran `basics`, `methods` and `rulesets` on Julia 1.10.12 and 1.12.7; before the merge, `polyform` (both versions) and `fuzz` (1.12.7) passed too. I did not run the full suite.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
How does it work when combined with FixedPointNumbers.jl v0.9.2?
Working much better now! I updated the issue for FixedPointNumbers.
Now we only need some progress with the invalidations caused by GeometryBasics .