Skip to content

decrease max_methods world-splitting setting for some functions#59377

Closed
nsajko wants to merge 10 commits intoJuliaLang:masterfrom
nsajko:decrease_max_methods_setting_for_various_interface_functions
Closed

decrease max_methods world-splitting setting for some functions#59377
nsajko wants to merge 10 commits intoJuliaLang:masterfrom
nsajko:decrease_max_methods_setting_for_various_interface_functions

Conversation

@nsajko
Copy link
Copy Markdown
Member

@nsajko nsajko commented Aug 24, 2025

WIP, not ready for review.

A function which is meant to have methods added to it by users should have a small max_methods value, as world-splitting just leads to unnecessary invalidation in that case, in the context of the package ecosystem, where users are allowed to add arbitrarily many methods to such functions.

xref PR #57884

xref PR #58788

xref PR #58829

xref PR #59091

@nsajko nsajko added design Design of APIs or of the language itself invalidations latency Latency labels Aug 24, 2025
@nsajko
Copy link
Copy Markdown
Member Author

nsajko commented Aug 24, 2025

This must be causing tangible inference issues, because the trimming CI fails. However, the logs don't show what the issue is.

@nsajko
Copy link
Copy Markdown
Member Author

nsajko commented Aug 24, 2025

Ah, there's a bunch of inference failures in the usual test suite, too. Clearly more work is necessary.

nsajko added a commit to nsajko/julia that referenced this pull request Aug 27, 2025
The method return type is known concretely as `OneTo{Int}`, however the
compiler can't tell because there are too many methods matching
`length(::Array)`, over the world-splitting threshold, `max_methods`.

Cross-reference PR JuliaLang#57627, which might help here in another way by
decreasing the number of unnecessary methods, however merging that PR
would also cause regressions if world-splitting is not disabled first
for `length` (xref PR JuliaLang#59377).

Fix that by asserting the return type of the `length` call as `Int`.

The `typeassert` should improve abstract return type inference, and
consequently make the sysimage more resistant to invalidation.
nsajko added a commit to nsajko/julia that referenced this pull request Aug 27, 2025
The method return type is known concretely as `OneTo{Int}`, however the
compiler can't tell because there are too many methods matching
`length(::Array)`, over the world-splitting threshold, `max_methods`.

Cross-reference WIP PR JuliaLang#57627, which might help here in another way by
decreasing the number of unnecessary methods, however merging that PR
would also cause regressions if world-splitting is not disabled first
for `length` (xref WIP PR JuliaLang#59377).

Fix that by asserting the return type of the `length` call as `Int`.

The `typeassert` should improve abstract return type inference, and
consequently make the sysimage more resistant to invalidation.
@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from f9ddf54 to a47559f Compare August 27, 2025 21:22
@nsajko

This comment was marked as outdated.

@nsajko

This comment was marked as outdated.

nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`,
such as PRs:

* JuliaLang#59091

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 28, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
nsajko added a commit to nsajko/julia that referenced this pull request Aug 29, 2025
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from a563401 to e7c44cc Compare August 29, 2025 20:30
@nsajko
Copy link
Copy Markdown
Member Author

nsajko commented Aug 29, 2025

This now temporarily includes the commit from PR #59421, however I expect that one will be merged before this one will be considered.

@nsajko
Copy link
Copy Markdown
Member Author

nsajko commented Aug 29, 2025

The only test failure is curious:

boundscheck                                     (10) |        started at 2025-08-29T14:05:54.274
      From worker 10: Error During Test at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/test/boundscheck_exec.jl:287
      From worker 10:   Test threw exception
      From worker 10:   Expression: getindex_42115(R, -1:3, :) == CartesianIndices((-1:3, 1:5))
      From worker 10:   BoundsError: attempt to access 5×5 CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}} at index [-1:3, :]
      From worker 10:   Stacktrace:
      From worker 10:    [1] throw_boundserror(A::CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, I::Tuple{UnitRange{Int64}, Colon})
      From worker 10:      @ Base ./essentials.jl:13
      From worker 10:    [2] checkbounds
      From worker 10:      @ ./abstractarray.jl:698 [inlined]
      From worker 10:    [3] getindex(::CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, ::UnitRange{Int64}, ::Colon)
      From worker 10:      @ Base.IteratorsMD ./multidimensional.jl:413
      From worker 10:    [4] getindex_42115(r::CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, i::UnitRange{Int64}, j::Function)
      From worker 10:      @ Main.TestBoundsCheck /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/test/boundscheck_exec.jl:271
      From worker 10:    [5] top-level scope
      From worker 10:      @ /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/test/boundscheck_exec.jl:287
      From worker 10:    [6] macro expansion
      From worker 10:      @ /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/stdlib/v1.13/Test/src/Test.jl:745 [inlined]
      From worker 10: ERROR: LoadError: There was an error during testing
      From worker 10: in expression starting at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/test/boundscheck_exec.jl:3
boundscheck                                     (10) |         failed at 2025-08-29T14:05:57.069
Error During Test at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/share/julia/test/testdefs.jl:26
  Got exception outside of a @test
  LoadError: boundscheck test failed, cmd : `/Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/bin/julia -C native -J/Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-R17H3W25T9.0/build/default-honeycrisp-R17H3W25T9-0/julialang/julia-master/julia-e7c44cc7ba/lib/julia/sys.dylib --depwarn=error --check-bounds=yes -g1 --startup-file=no --check-bounds=auto --depwarn=error --startup-file=no boundscheck_exec.jl`

CI log link:

The test on line 287 here is the one that fails:

# Given this is a sub-processed test file, not using @testsets avoids
# leaking the report print into the Base test runner report
begin # Pass inbounds meta to getindex on CartesianIndices (#42115)
@inline getindex_42115(r, i) = @inbounds getindex(r, i)
@inline getindex_42115(r, i, j) = @inbounds getindex(r, i, j)
R = CartesianIndices((5, 5))
if bc_opt == bc_on
@test_throws BoundsError getindex_42115(R, -1, -1)
@test_throws BoundsError getindex_42115(R, 1, -1)
else
@test getindex_42115(R, -1, -1) == CartesianIndex(-1, -1)
@test getindex_42115(R, 1, -1) == CartesianIndex(1, -1)
end
if bc_opt == bc_on
@test_throws BoundsError getindex_42115(R, CartesianIndices((6, 6)))
@test_throws BoundsError getindex_42115(R, -1:3, :)
else
@test getindex_42115(R, CartesianIndices((6, 6))) == CartesianIndices((6, 6))
@test getindex_42115(R, -1:3, :) == CartesianIndices((-1:3, 1:5))
end
end

Perhaps a compiler bug?

@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from e7c44cc to 78499ab Compare August 30, 2025 10:47
nsajko added a commit to nsajko/julia that referenced this pull request Jan 25, 2026
* Introducing new types and methods for a callable can invalidate
  already compiled method instances of a function for which
  world-splitting is enabled (`max_methods`).

* Invalidation of sysimage or package precompiled code worsens latency
  due to requiring recompilation.

* Lowering the `max_methods` setting for a function often causes
  inference issues for existing code that is not completely
  type-stable (which is a lot of code). In many cases this is easy to
  fix by avoiding method proliferation, such as by merging some methods
  and introducing branching into the merged method.

This PR aims to fix the latter issue for some `Tuple`-related methods
of some functions where decreasing `max_methods` might be interesting.

Seeing as branching was deliberately avoided in the bodies of many of
these methods, I opted for the approach of introducing local functions
which preserve the dispatch logic as before, without branching. Thus
there should be no regressions, except perhaps because of changed
inlining costs.

This PR is a prerequisite for PRs which try to decrease `max_methods`
for select functions, such as PR:

* JuliaLang#59377
@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from 388a7c6 to 31cd591 Compare January 29, 2026 14:08
@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from 3ff5e67 to ff224cc Compare January 30, 2026 08:28
A function which is meant to have methods added to it by users should
have a small `max_methods` value, as world-splitting just leads to
unnecessary invalidation in that case, in the context of the package
ecosystem, where users are allowed to add arbitrarily many methods to
such functions.

xref PR JuliaLang#57884

xref PR JuliaLang#58788

xref PR JuliaLang#58829

xref PR JuliaLang#59091
@nsajko nsajko force-pushed the decrease_max_methods_setting_for_various_interface_functions branch from ff224cc to 6b81847 Compare January 30, 2026 10:18
Some of these lines still need to be adjusted to `max_methods = 2`, if
the function requires a method for the bottom type.

Some of these lines need to be deleted outright, as not all of these
functions are intended to allow package authors to add methods to them.
Delete each line corresponding to a function which is not supposed to
have methods added to it by users or by package authors.
A function which takes a type argument and requires a method for the
bottom type may reasonably require a `max_methods` value greater than
one.
@nsajko
Copy link
Copy Markdown
Member Author

nsajko commented Jan 30, 2026

Giving up on this, bootstrapping concerns make this too difficult. Someone else feel free to take over.

@nsajko nsajko closed this Jan 30, 2026
@nsajko nsajko deleted the decrease_max_methods_setting_for_various_interface_functions branch January 30, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

design Design of APIs or of the language itself invalidations latency Latency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant