Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to ensure that @nospecialize really does avoid specialization #40312

Closed
bkamins opened this issue Apr 2, 2021 · 2 comments · Fixed by #41931
Closed

Add an option to ensure that @nospecialize really does avoid specialization #40312

bkamins opened this issue Apr 2, 2021 · 2 comments · Fixed by #41931
Assignees

Comments

@bkamins
Copy link
Member

bkamins commented Apr 2, 2021

In JuliaData/DataFrames.jl#2691 I needed to use Ref{Any} trick to avoid excessive specialization. Unfortunately I was not able to achieve the required level of despecialization using @nospecialize. The issue is quite complex and we discussed with @nalimilan a lot what to do with it. Since we really do not understand in full how to avoid specialization I ask the question here (as on Slack it probably will get quickly lost).

The problem is that @nospecialize does not guarantee that the argument is not specialized. There are cases when not having this is prohibitive (as each additional compilation of method instance is costly in itself). Here is an MWE using MethodAnalysis.jl:

julia> using MethodAnalysis
julia> @noinline f(@nospecialize(x)) = map(x, [1,2,3])
f (generic function with 1 method)
julia> g(x) = f(x)
g (generic function with 1 method)
julia> g(sin);
julia> methodinstances(f)
2-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
julia> g(Int);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
 MethodInstance for f(::Type)
julia> g(Float64);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
 MethodInstance for f(::Type)

and I get 3 method instances of f instead of 1 as I wanted.

Now in a fresh session additionally this happens:

julia> using MethodAnalysis
julia> @noinline f(@nospecialize(x)) = map(x, [1,2,3])
f (generic function with 1 method)
julia> g(x::Base.Callable) = f(x)
g (generic function with 1 method)
julia> g(sin);
julia> methodinstances(f)
2-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
julia> g(Int);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
 MethodInstance for f(::Type{Int64})
julia> g(Float64);
julia> methodinstances(f)
4-element Vector{Core.MethodInstance}:
 MethodInstance for f(::Function)
 MethodInstance for f(::Any)
 MethodInstance for f(::Type{Int64})
 MethodInstance for f(::Type{Float64})

and this time it is even worse - for each type passed a new method instance is generated.

I have more examples if needed, but they all boil down to one issue: how to tell the compiler that unconditionally only one method instance should be generated for a given argument (as commented above in JuliaData/DataFrames.jl#2691 we ended up using Ref{Any} to guarantee this, but this is kind of ugly and maybe there is a better way to do it).

@timholy
Copy link
Sponsor Member

timholy commented Jul 25, 2021

I've been caught by the same thing (see #35131). @nospecialize blocks deeper forms of specialization (e.g., LLVM IR) but not inference. If you want to also block inference, you can do this:

g(x::Base.Callable) = f(Base.inferencebarrier(x))

All inferencebarrier currently does is the equivalent of this:

g(x::Base.Callable) = f(Ref{Any}(x)[])

Just pushing it into a Ref{Any} and immediately popping it out again is enough to have inference give up trying to infer the argument type that you're calling callee with. Perhaps in the long run inferencebarrier might become an intrinsic. (The fear might be, what if inference gets so smart it can figure out the type of x even after temporarily stashing in a Ref{Any}?)

@aviatesk
Copy link
Sponsor Member

Close as a dup of #35131.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants