From 63e194f6e86bca651860f44cfb27ef86f3e8ed3b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 20 Mar 2023 10:37:59 -0500 Subject: [PATCH 1/5] Add iterator for method specializations There have been longstanding reasons to want an API for extracting all extant specializations of a method, but this is even more true after #49071. --- base/reflection.jl | 27 +++++++++++++++++++++++++++ test/precompile.jl | 25 ++++++++++--------------- test/worlds.jl | 17 +++-------------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 0e3d5e1fb82a7..a873d21d834e1 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1107,6 +1107,33 @@ function visit(f, d::Core.TypeMapEntry) end nothing end +struct MethodSpecializations + specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} +end +""" + specializations(m::Method) → itr + +Return an iterator `itr` of all compiler-generated specializations of `m`. +""" +specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) +function iterate(specs::MethodSpecializations) + s = specs.specializations + s === nothing && return nothing + isa(s, Core.MethodInstance) && return (s, false) + return iterate(specs, 0) +end +iterate(specs::MethodSpecializations, ::Bool) = nothing +function iterate(specs::MethodSpecializations, i::Int) + s = specs.specializations + n = length(s) + i >= n && return nothing + item = nothing + while i < n && item === nothing + item = s[i+=1] + end + item === nothing && return nothing + return (item, i) +end function length(mt::Core.MethodTable) n = 0 diff --git a/test/precompile.jl b/test/precompile.jl index 2b5405a06c88d..de15171c8138c 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -665,10 +665,8 @@ precompile_test_harness("code caching") do dir # size(::Vector) has an inferred specialization for Vector{X} msize = which(size, (Vector{<:Any},)) hasspec = false - msizespecs = msize.specializations::Core.SimpleVector - for i = 1:length(msizespecs) - mi = msizespecs[i] - if isa(mi, Core.MethodInstance) && mi.specTypes == Tuple{typeof(size),Vector{Cacheb8321416e8a3e2f1.X}} + for mi in Base.specializations(msize) + if mi.specTypes == Tuple{typeof(size),Vector{Cacheb8321416e8a3e2f1.X}} if isdefined(mi, :cache) && isa(mi.cache, Core.CodeInstance) && mi.cache.max_world == typemax(UInt) && mi.cache.inferred !== nothing hasspec = true break @@ -786,9 +784,7 @@ precompile_test_harness("code caching") do dir MB = getfield(@__MODULE__, RootB) M = getfield(MA, RootModule) m = which(M.f, (Any,)) - mspecs = m.specializations - mspecs isa Core.SimpleVector || (mspecs = Core.svec(mspecs)) - for mi in mspecs + for mi in Base.specializations(m) mi === nothing && continue mi = mi::Core.MethodInstance if mi.specTypes.parameters[2] === Int8 @@ -925,8 +921,7 @@ precompile_test_harness("code caching") do dir # Reporting test (ensure SnoopCompile works) @test all(i -> isassigned(invalidations, i), eachindex(invalidations)) m = only(methods(MB.call_nbits)) - for mi in m.specializations::Core.SimpleVector - mi === nothing && continue + for mi in Base.specializations(m) hv = hasvalid(mi, world) @test mi.specTypes.parameters[end] === Integer ? !hv : hv end @@ -1088,13 +1083,13 @@ precompile_test_harness("invoke") do dir end m = get_method_for_type(M.h, Real) - @test m.specializations === Core.svec() + @test isempty(Base.specializations(m)) m = get_method_for_type(M.hnc, Real) - @test m.specializations === Core.svec() + @test isempty(Base.specializations(m)) m = only(methods(M.callq)) - @test m.specializations === Core.svec() || nvalid(m.specializations::Core.MethodInstance) == 0 + @test isempty(Base.specializations(m)) || nvalid(m.specializations::Core.MethodInstance) == 0 m = only(methods(M.callqnc)) - @test m.specializations === Core.svec() || nvalid(m.specializations::Core.MethodInstance) == 0 + @test isempty(Base.specializations(m)) || nvalid(m.specializations::Core.MethodInstance) == 0 m = only(methods(M.callqi)) @test (m.specializations::Core.MethodInstance).specTypes == Tuple{typeof(M.callqi), Int} m = only(methods(M.callqnci)) @@ -1112,7 +1107,7 @@ precompile_test_harness("invoke") do dir m_any, m_int = sort(collect(methods(invokeme)); by=m->(m.file,m.line)) @test precompile(invokeme, (Int,), m_any) @test (m_any.specializations::Core.MethodInstance).specTypes === Tuple{typeof(invokeme), Int} - @test m_int.specializations === Core.svec() + @test isempty(Base.specializations(m_int)) end # test --compiled-modules=no command line option @@ -1581,7 +1576,7 @@ precompile_test_harness("issue #46296") do load_path """ module CodeInstancePrecompile - mi = first(methods(identity)).specializations[1] + mi = first(Base.specializations(first(methods(identity)))) ci = Core.CodeInstance(mi, Any, nothing, nothing, zero(Int32), typemin(UInt), typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00) diff --git a/test/worlds.jl b/test/worlds.jl index ff6b0197e8051..b5a8f1c5449ac 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -233,21 +233,10 @@ function method_instance(f, types=Base.default_tt(f)) m = which(f, types) inst = nothing tt = Base.signature_type(f, types) - specs = m.specializations - if isa(specs, Core.SimpleVector) - for i = 1:length(specs) - mi = specs[i] - if mi isa Core.MethodInstance - if mi.specTypes <: tt && tt <: mi.specTypes - inst = mi - break - end - end - end - else - mi = specs::Core.MethodInstance - if mi.specTypes === tt + for mi in Base.specializations(m) + if mi.specTypes <: tt && tt <: mi.specTypes inst = mi + break end end return inst From 7bd187717d3724fb28780e2641c5654471e19afa Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 20 Mar 2023 11:34:33 -0500 Subject: [PATCH 2/5] Also update docs --- doc/src/manual/performance-tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 1eee23e163a77..ffb84333e8e78 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -584,7 +584,7 @@ h_vararg(x::Vararg{Any, N}) where {N} = tuple(x...) Note that [`@code_typed`](@ref) and friends will always show you specialized code, even if Julia would not normally specialize that method call. You need to check the [method internals](@ref ast-lowered-method) if you want to see whether specializations are generated -when argument types are changed, i.e., if `(@which f(...)).specializations` contains specializations +when argument types are changed, i.e., if `Base.specializations(@which f(...))` contains specializations for the argument in question. ## Break functions into multiple definitions From a3ca979224706a94d2bc48808683bbcefd9ee4a3 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 23 Mar 2023 10:25:03 -0500 Subject: [PATCH 3/5] Update base/reflection.jl Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/reflection.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index a873d21d834e1..a43cc134b7dc9 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1119,10 +1119,10 @@ specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations function iterate(specs::MethodSpecializations) s = specs.specializations s === nothing && return nothing - isa(s, Core.MethodInstance) && return (s, false) + isa(s, Core.MethodInstance) && return (s, nothing) return iterate(specs, 0) end -iterate(specs::MethodSpecializations, ::Bool) = nothing +iterate(specs::MethodSpecializations, ::Nothing) = nothing function iterate(specs::MethodSpecializations, i::Int) s = specs.specializations n = length(s) From 1a24724fd0777301dc49d81becd91e019383ba7a Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 20 Mar 2023 13:10:56 -0500 Subject: [PATCH 4/5] Support length --- base/reflection.jl | 1 + test/reflection.jl | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/base/reflection.jl b/base/reflection.jl index a43cc134b7dc9..7c9cf2a120f6d 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1134,6 +1134,7 @@ function iterate(specs::MethodSpecializations, i::Int) item === nothing && return nothing return (item, i) end +length(specs::MethodSpecializations) = count(Returns(true), specs) function length(mt::Core.MethodTable) n = 0 diff --git a/test/reflection.jl b/test/reflection.jl index e926cdf0a2b9f..8115fc00cc424 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1039,3 +1039,10 @@ ambig_effects_test(a, b) = 1 end @test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] + +@testset "specializations" begin + f(x) = 1 + f(1) + f("hello") + @test length(Base.specializations(only(methods(f)))) == 2 +end From 15c048c9e7f44faafbb7530e069c72143da19b2b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 28 Mar 2023 12:19:32 -0400 Subject: [PATCH 5/5] Update base/reflection.jl --- base/reflection.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reflection.jl b/base/reflection.jl index 7c9cf2a120f6d..b020c3c87a892 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1124,7 +1124,7 @@ function iterate(specs::MethodSpecializations) end iterate(specs::MethodSpecializations, ::Nothing) = nothing function iterate(specs::MethodSpecializations, i::Int) - s = specs.specializations + s = specs.specializations::Core.SimpleVector n = length(s) i >= n && return nothing item = nothing