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

Core.ifelse calls to avoid invalidations from defining custom Base.ifelse methods #46366

Closed
wants to merge 1 commit into from

Conversation

chriselrod
Copy link
Contributor

@chriselrod chriselrod commented Aug 16, 2022

Addresses ifelse invalidations reported here:
SciML/Static.jl#77 (comment)

After this PR:

julia> using SnoopCompileCore

julia> invals = @snoopr using Static
104-element Vector{Any}:
  MethodInstance for to_indices(::AbstractArray, ::Tuple{Any, CartesianIndex{0}})
 1
  MethodInstance for setindex!(::AbstractArray, ::Any, ::Any, ::CartesianIndex{0})
 2
  MethodInstance for to_indices(::AbstractArray, ::Any, ::Tuple{Any, CartesianIndex{0}})
  "jl_method_table_insert"
  to_indices(A, inds, I::Tuple{AbstractArray{NDIndex{N, J}}, Vararg{Any}}) where {N, J} @ Static ~/.julia/dev/Static/src/Static.jl:919
  "jl_method_table_insert"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
 
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for promote_rule(::Type, ::Type)
  "invalidate_mt_cache"
  MethodInstance for Base._cutdim(::Any, ::CartesianIndex{0})
 1
  MethodInstance for Base.IteratorsMD.split(::Any, ::Val{0})
  "jl_method_table_insert"
  split(i::NDIndex, V::Val) @ Static ~/.julia/dev/Static/src/Static.jl:855
  "jl_method_table_insert"
  MethodInstance for (::Function, ::Function)
  "invalidate_mt_cache"

julia> using SnoopCompileCore

julia> using SnoopCompile

julia> tree = invalidation_trees(invals)
2-element Vector{SnoopCompile.MethodInvalidations}:
 inserting split(i::NDIndex, V::Val) @ Static ~/.julia/dev/Static/src/Static.jl:855 invalidated:
   backedges: 1: superseding split(t, V::Val) @ Base.IteratorsMD multidimensional.jl:480 with MethodInstance for Base.IteratorsMD.split(::Any, ::Val{0}) (1 children)
   44 mt_cache

 inserting to_indices(A, inds, I::Tuple{AbstractArray{NDIndex{N, J}}, Vararg{Any}}) where {N, J} @ Static ~/.julia/dev/Static/src/Static.jl:919 invalidated:
   backedges: 1: superseding to_indices(A, inds, I::Tuple{Any, Vararg{Any}}) @ Base indices.jl:352 with MethodInstance for to_indices(::AbstractArray, ::Any, ::Tuple{Any, CartesianIndex{0}}) (2 children)

@chriselrod
Copy link
Contributor Author

Alternatively, I could call Core.ifelse in these places instead.

@oscardssmith
Copy link
Member

Neither of these approaches seem great, but there might not be a better solution.

@chriselrod
Copy link
Contributor Author

Perhaps I should do Bool(x)::Bool instead, so things like Static.False would still work here.

This breaks things returning VectorizationBase.Mask being used here, which I am okay with.
@shashi does this impact anything in the Symbolics ecosystem?
Note that these locations aren't going to be hit if methods like max are defined preventing these from being reached.

@Tokazama
Copy link
Contributor

Thanks for taking the lead on this. I looked at it and wasn't sure what the best path forward was. It seems like we shouldn't have this much trouble inferring Bool.

@chriselrod
Copy link
Contributor Author

Jeff suggested Core.ifelse instead of type asserts.

@JeffBezanson
Copy link
Member

It seems like we shouldn't have this much trouble inferring Bool.

The trouble is we don't have a way to enforce types like isless(::Any, ::Any)::Bool, but maybe we will one day.

@chriselrod chriselrod changed the title typeassert Bool for ifelse calls to reduce invalidations from defining custom ifelse methods Core.ifelse calls to avoid invalidations from defining custom Base.ifelse methods Aug 17, 2022
@chriselrod chriselrod changed the title Core.ifelse calls to avoid invalidations from defining custom Base.ifelse methods Core.ifelse calls to avoid invalidations from defining custom Base.ifelse methods Aug 17, 2022
@chriselrod
Copy link
Contributor Author

The trouble is we don't have a way to enforce types like isless(::Any, ::Any)::Bool, but maybe we will one day.

That would be nice.
But, for now, I'm using Core.ifelse here as you suggested.

Copy link
Member

@aviatesk aviatesk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds reasonable to use Core.ifelse in place of ifelse(c, x, y) where the types of c/x/y are known to be such types that are owned by Base, but otherwise it sounds against Julia's design pattern.

E.g. max(x, y) = Core.ifelse(isless(y, x), x, y) disallows the possibility to overload Base.ifelse(c, x::S, y::T) to customize max(::S, ::T)/min(::S, ::T), where S and T are owned by users.

But it seems reasonable to me to define max(x::Float64, y::Float64) = Core.ifelse(isless(y, x), x, y) as users aren't supposed to override the definitions of isless and ifelse for Float64 types.

@KristofferC
Copy link
Member

E.g. max(x, y) = Core.ifelse(isless(y, x), x, y) disallows the possibility to overload Base.ifelse(c, x::S, y::T) to customize max(::S, ::T)

On the other hand, you probably shouldn't rely on overloading ifelse in this case since someone might rewrite this to islexx(y,x) ? x : y and if you needed ifelse overload for this to work correctly, it would all of a sudden be broken.

@Tokazama
Copy link
Contributor

E.g. max(x, y) = Core.ifelse(isless(y, x), x, y) disallows the possibility to overload Base.ifelse(c, x::S, y::T) to customize max(::S, ::T)

On the other hand, you probably shouldn't rely on overloading ifelse in this case since someone might rewrite this to islexx(y,x) ? x : y and if you needed ifelse overload for this to work correctly, it would all of a sudden be broken.

IIRC, the reason Base.ifelse was overloaded was for internal purposes in VectorizationBase, not public usage. The broader issue we're trying to address is that underlying we keep running into a fresh batch of invalidations. For example, we're now trying to address a huge amount of invalidations with ! now SciML/Static.jl#78.

I kind of agree that we can (maybe should) revert overloading Base.ifelse to get by, but given that this is just one of many related issues it seems like we need something to help prevent this sort of thing from popping up with every release.

@brenhinkeller brenhinkeller added the compiler:latency Compiler latency label Nov 17, 2022
@chriselrod chriselrod closed this May 1, 2024
@chriselrod chriselrod deleted the ifelseboolassert branch May 1, 2024 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:latency Compiler latency
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants