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

Type instability with Base.promote_type and Static.jl types for Julia v1.10-beta2 and --check-bounds=no #50985

Open
sloede opened this issue Aug 20, 2023 · 7 comments
Labels
compiler:inference Type inference regression Regression in behavior compared to a previous version
Milestone

Comments

@sloede
Copy link
Contributor

sloede commented Aug 20, 2023

When using Julia v1.10-beta2 with --check-bounds=no, using Static.jl types together with native types causes a type instability. In fact, it looks like Base.promote_type is not able to infer a concrete type at all anymore and just returns Any.

MWE (with julia-1.10-beta2 --check=bounds=no):

julia> using Pkg

julia> Pkg.activate(; temp=true, io=devnull)

julia> Pkg.add(name="Static", version="0.8.8", io=devnull)

julia> using Static

julia> i = 1; s = static(2);

julia> @code_warntype Base.promote_type(typeof(i), typeof(s))
MethodInstance for promote_type(::Type{Int64}, ::Type{StaticInt{2}})
  from promote_type(::Type{T}, ::Type{S}) where {T, S} @ Base promotion.jl:306
Static Parameters
  T = Int64
  S = StaticInt{2}
Arguments
  #self#::Core.Const(promote_type)
  _::Core.Const(Int64)
  _::Core.Const(StaticInt{2})
Body::Any
1nothing%2 = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %3 = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %4 = Base.promote_rule($(Expr(:static_parameter, 1)), $(Expr(:static_parameter, 2)))::Core.Const(Union{})
│   %5 = Base.promote_rule($(Expr(:static_parameter, 2)), $(Expr(:static_parameter, 1)))::Core.Const(Union{})
│   %6 = Base.promote_result(%2, %3, %4, %5)::Any
└──      return %6

The error does not occur if I use Julia v1.10 without --check-bounds=no or when using Julia v1.9 (with or without --check-bounds=no). On those cases, we get the following output for @code_warntype:

julia> @code_warntype Base.promote_type(typeof(i), typeof(s))
MethodInstance for promote_type(::Type{Int64}, ::Type{StaticInt{2}})
  from promote_type(::Type{T}, ::Type{S}) where {T, S} @ Base promotion.jl:306
Static Parameters
  T = Int64
  S = StaticInt{2}
Arguments
  #self#::Core.Const(promote_type)
  _::Core.Const(Int64)
  _::Core.Const(StaticInt{2})
Body::Type{Number}
1nothing%2 = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %3 = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %4 = Base.promote_rule($(Expr(:static_parameter, 1)), $(Expr(:static_parameter, 2)))::Core.Const(Union{})
│   %5 = Base.promote_rule($(Expr(:static_parameter, 2)), $(Expr(:static_parameter, 1)))::Core.Const(Union{})
│   %6 = Base.promote_result(%2, %3, %4, %5)::Core.Const(Number)
└──      return %6

The issue first appeared when trying to use PtrArray from StrideArrays.jl (see also JuliaSIMD/StrideArrays.jl#78). There, the failure to determine a usable promotion type propagates into a regular array access returning values of type Any, making the code unusably slow.

Thanks to @ranocha for creating a more minimum MWE. Maybe @vchuravy has an idea how to fix this (since he resolved a similar issue for v1.9 and StaticArrays.jl)?

cc @chriselrod

@brenhinkeller
Copy link
Contributor

I take it this is distinct from (though possibly indirectly related to) the issue discussed in #50239?

@ranocha ranocha added the regression Regression in behavior compared to a previous version label Aug 21, 2023
@vchuravy
Copy link
Member

vchuravy commented Aug 21, 2023

On 1.9 with --check-bounds=no

 • %2 = < pure > typejoin(::Type{…},::Type{…})::Core.Const(Number) (+c,+e,+n,+t,+s,+m,+i)

On 1.10 with --check-bounds=no

 • %1 = invoke typejoin(::Type{Int64},::Type{StaticInt{2}})::Any (+c,+e,!n,+t,+s,+m,+i)

This might be #48684 cc: @aviatesk

I hoped #50561 would improve the situation, but it doesn't seem enough.

@brenhinkeller
Copy link
Contributor

Is this another consequence of the removal of @pure from some internals?

@vchuravy
Copy link
Member

Kinda, #48684 replaced special handling of the almost intrinsics. So it's more than just a removal of pure.

@vchuravy vchuravy added this to the 1.10 milestone Aug 22, 2023
@vchuravy
Copy link
Member

Putting this on the milestone since we will need to fix this in either 1.10.0 or 1.10.x

@KristofferC
Copy link
Member

Talking with @Keno, it seems that this cannot really be fixed in 1.10 and has to wait for 1.11, see e.g. #50641. So I will take this off the milestone.

@mbauman
Copy link
Member

mbauman commented Feb 3, 2025

Can we promise that typejoin is no throw? I'd wager this would resolve many of the biggest regressions here:

▶ julia +nightly --check-bounds=no -q
julia> using Static

julia> f(::Type{T}, ::Type{S}) where {T,S} = typejoin(T,S)
f (generic function with 1 method)

julia> @code_warntype f(Int, StaticInt{2})
# ...
Body::Any
# ...

julia> @eval Base @assume_effects :foldable :nothrow function typejoin(@nospecialize(a), @nospecialize(b))
# ... whole body here

julia> @code_warntype f(Int, StaticInt{2})
# ...
Body::Type{Number}
# ...

julia> @code_warntype promote_type(Int, StaticInt{2})
# ...
Body::Type{Number}
# ...
│   %13 = (%2)(%3, %4, %8, %12)::Core.Const(Number)
└──       return %13
complete session
▶ julia +nightly --check-bounds=no -q
julia> using Static

julia> f(::Type{T}, ::Type{S}) where {T,S} = typejoin(T,S)
f (generic function with 1 method)

julia> @code_warntype f(Int, StaticInt{2})
MethodInstance for f(::Type{Int64}, ::Type{StaticInt{2}})
  from f(::Type{T}, ::Type{S}) where {T, S} @ Main REPL[2]:1
Static Parameters
  T = Int64
  S = StaticInt{2}
Arguments
  #self#::Core.Const(Main.f)
  _::Core.Const(Int64)
  _::Core.Const(StaticInt{2})
Body::Any
1 ─ %1 = Main.typejoin::Core.Const(typejoin)
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %3 = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %4 = (%1)(%2, %3)::Any
└──      return %4


julia> @eval Base @assume_effects :foldable :nothrow function typejoin(@nospecialize(a), @nospecialize(b))
           @_nospecializeinfer_meta
           if isa(a, TypeVar)
               return typejoin(a.ub, b)
           elseif isa(b, TypeVar)
               return typejoin(a, b.ub)
           elseif a <: b
               return b
           elseif b <: a
               return a
           elseif isa(a, UnionAll)
               return UnionAll(a.var, typejoin(a.body, b))
           elseif isa(b, UnionAll)
               return UnionAll(b.var, typejoin(a, b.body))
           elseif isa(a, Union)
               return typejoin(typejoin(a.a, a.b), b)
           elseif isa(b, Union)
               return typejoin(a, typejoin(b.a, b.b))
           end
           # a and b are DataTypes
           # We have to hide Constant info from inference, see #44390
           a, b = inferencebarrier(a)::DataType, inferencebarrier(b)::DataType
           if a <: Tuple
               if !(b <: Tuple)
                   return Any
               end
               ap, bp = a.parameters, b.parameters
               lar = length(ap)
               lbr = length(bp)
               if lar == 0
                   return Tuple{Vararg{tailjoin(bp, 1)}}
               end
               if lbr == 0
                   return Tuple{Vararg{tailjoin(ap, 1)}}
               end
               laf, afixed = full_va_len(ap)
               lbf, bfixed = full_va_len(bp)
               if laf < lbf
                   if isvarargtype(ap[lar]) && !afixed
                       c = Vector{Any}(undef, laf)
                       c[laf] = Vararg{typejoin(unwrapva(ap[lar]), tailjoin(bp, laf))}
                       n = laf-1
                   else
                       c = Vector{Any}(undef, laf+1)
                       c[laf+1] = Vararg{tailjoin(bp, laf+1)}
                       n = laf
                   end
               elseif lbf < laf
                   if isvarargtype(bp[lbr]) && !bfixed
                       c = Vector{Any}(undef, lbf)
                       c[lbf] = Vararg{typejoin(unwrapva(bp[lbr]), tailjoin(ap, lbf))}
                       n = lbf-1
                   else
                       c = Vector{Any}(undef, lbf+1)
                       c[lbf+1] = Vararg{tailjoin(ap, lbf+1)}
                       n = lbf
                   end
               else
                   c = Vector{Any}(undef, laf)
                   n = laf
               end
               for i = 1:n
                   ai = ap[min(i,lar)]; bi = bp[min(i,lbr)]
                   ci = typejoin(unwrapva(ai), unwrapva(bi))
                   c[i] = i == length(c) && (isvarargtype(ai) || isvarargtype(bi)) ? Vararg{ci} : ci
               end
               return Tuple{c...}
           elseif b <: Tuple
               return Any
           end
           while !(b === Any)
               if a <: b.name.wrapper
                   while !(a.name === b.name)
                       a = supertype(a)::DataType
                   end
                   if a.name === Type.body.name
                       ap = a.parameters[1]
                       bp = b.parameters[1]
                       if ((isa(ap,TypeVar) && ap.lb === Bottom && ap.ub === Any) ||
                           (isa(bp,TypeVar) && bp.lb === Bottom && bp.ub === Any))
                           # handle special Type{T} supertype
                           return Type
                       end
                   end
                   aprimary = a.name.wrapper
                   # join on parameters
                   n = length(a.parameters)
                   if n == 0
                       return aprimary
                   end
                   vars = []
                   for i = 1:n
                       ai, bi = a.parameters[i], b.parameters[i]
                       if ai === bi || (isa(ai,Type) && isa(bi,Type) && ai <: bi && bi <: ai)
                           aprimary = aprimary{ai}
                       else
                           aprimary = aprimary::UnionAll
                           # pushfirst!(vars, aprimary.var)
                           _growbeg!(vars, 1)
                           vars[1] = aprimary.var
                           aprimary = aprimary.body
                       end
                   end
                   for v in vars
                       aprimary = UnionAll(v, aprimary)
                   end
                   return aprimary
               end
               b = supertype(b)::DataType
           end
           return Any
       end
typejoin (generic function with 6 methods)

julia> @code_warntype f(Int, StaticInt{2})
MethodInstance for f(::Type{Int64}, ::Type{StaticInt{2}})
  from f(::Type{T}, ::Type{S}) where {T, S} @ Main REPL[2]:1
Static Parameters
  T = Int64
  S = StaticInt{2}
Arguments
  #self#::Core.Const(Main.f)
  _::Core.Const(Int64)
  _::Core.Const(StaticInt{2})
Body::Type{Number}
1 ─ %1 = Main.typejoin::Core.Const(typejoin)
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %3 = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %4 = (%1)(%2, %3)::Core.Const(Number)
└──      return %4


julia> @code_warntype promote_type(Int, StaticInt{2})
MethodInstance for promote_type(::Type{Int64}, ::Type{StaticInt{2}})
  from promote_type(::Type{T}, ::Type{S}) where {T, S} @ Base promotion.jl:310
Static Parameters
  T = Int64
  S = StaticInt{2}
Arguments
  #self#::Core.Const(promote_type)
  _::Core.Const(Int64)
  _::Core.Const(StaticInt{2})
Body::Type{Number}
1 ─       nothing
│   %2  = Base.promote_result::Core.Const(Base.promote_result)
│   %3  = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %4  = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %5  = Base.promote_rule::Core.Const(promote_rule)
│   %6  = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %7  = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %8  = (%5)(%6, %7)::Core.Const(Union{})
│   %9  = Base.promote_rule::Core.Const(promote_rule)
│   %10 = $(Expr(:static_parameter, 2))::Core.Const(StaticInt{2})
│   %11 = $(Expr(:static_parameter, 1))::Core.Const(Int64)
│   %12 = (%9)(%10, %11)::Core.Const(Union{})
│   %13 = (%2)(%3, %4, %8, %12)::Core.Const(Number)
└──       return %13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:inference Type inference regression Regression in behavior compared to a previous version
Projects
None yet
Development

No branches or pull requests

8 participants