StaticArrays.jl icon indicating copy to clipboard operation
StaticArrays.jl copied to clipboard

`SVector` type unstable for Julia v1.9 and `--check-bounds=no`

Open sloede opened this issue 2 years ago • 3 comments

As described in https://github.com/JuliaLang/julia/issues/49472, using StaticArrays.jl with Julia v1.9 and --check-bounds=no causes the SVector constructor to be type unstable even for the simplest invocations.

To reproduce, start Julia v1.9 (RC1 or later were tested and reproduce this problem) with --check-bounds=no and then run the following:

julia> using StaticArrays

julia> @code_warntype SVector(1)
MethodInstance for (SVector)(::Int64)
  from (::Type{SA})(x...) where SA<:StaticArray @ StaticArrays ~/hackathon/perf/mwe-staticarrays/rc1/tmp-depot/packages/StaticArrays/4uslg/src/convert.jl:160
Static Parameters
  SA = SVector
Arguments
  #self#::Type{SVector}
  x::Tuple{Int64}
Body::Any
1 ─      nothing
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(SVector)
│   %3 = StaticArrays.Args(x)::StaticArrays.Args{Tuple{Int64}}
│   %4 = StaticArrays.construct_type(%2, %3)::Any
│   %5 = (%4)(x)::Any
└──      return %5

Without --check-bounds=no, we get the expected, type stable output:

julia> using StaticArrays

julia> @code_warntype SVector(1)
MethodInstance for (SVector)(::Int64)
  from (::Type{SA})(x...) where SA<:StaticArray @ StaticArrays ~/hackathon/perf/mwe-staticarrays/rc1/tmp-depot/packages/StaticArrays/4uslg/src/convert.jl:160
Static Parameters
  SA = SVector
Arguments
  #self#::Type{SVector}
  x::Tuple{Int64}
Body::SVector{1, Int64}
1 ─      nothing
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(SVector)
│   %3 = StaticArrays.Args(x)::StaticArrays.Args{Tuple{Int64}}
│   %4 = StaticArrays.construct_type(%2, %3)::Core.Const(SVector{1, Int64})
│   %5 = (%4)(x)::SVector{1, Int64}
└──      return %5

This regression renders StaticArrays.jl virtually unusable with Julia v1.9 for many HPC workloads (hot kernels that make generous use of SVector basically grind to a halt). In the linked julia issue above it was hinted that this is not going to be fixed in Julia base, and @vchuravy suggested that I should rather file an issue here (keeping fingers crossed 🙏).

cc @ranocha

sloede avatar Apr 24 '23 15:04 sloede

using Cthulhu with remarks, and effects on.

Check-bounds=0

∘ ─ %0 = invoke StaticArray(::Int64)::Any (!c,!e,!n,!t,!s,!m,+i)′
1 ─      nothing::Core.Const(nothing)
│   @ /home/vchuravy/.julia/packages/StaticArrays/4uslg/src/convert.jl:160 within `StaticArray`
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(SVector) (+c,+e,+n,+t,+s,+m,+i)
│   %3 = StaticArrays.Args(x)::StaticArrays.Args{Tuple{Int64}} (+c,+e,+n,+t,+s,+m,+i)
│   %4 = StaticArrays.construct_type(%2, %3)::Any (!c,!e,!n,!t,!s,!m,+i)′ Call inference reached maximally imprecise information. Bailing on. [constprop] Disabled by method instance heuristic
│   %5 = (%4)(x)::Any (!c,!e,!n,!t,!s,!m,+i)′ Could not identify method table for call
└──      return %5

Check-bounds=auto

∘ ─ %0 = invoke StaticArray(::Int64)::SVector{1, Int64} (+c,+e,+n,+t,+s,+m,+i)
1 ─      nothing::Core.Const(nothing)
│   @ /home/vchuravy/.julia/packages/StaticArrays/4uslg/src/convert.jl:160 within `StaticArray`
│   %2 = $(Expr(:static_parameter, 1))::Core.Const(SVector) (+c,+e,+n,+t,+s,+m,+i)
│   %3 = StaticArrays.Args(x)::StaticArrays.Args{Tuple{Int64}} (+c,+e,+n,+t,+s,+m,+i)
│   %4 = StaticArrays.construct_type(%2, %3)::Core.Const(SVector{1, Int64}) (+c,+e,+n,+t,+s,+m,+i) [constprop] No more information to be gained
│   %5 = (%4)(x)::SVector{1, Int64} (+c,+e,+n,+t,+s,+m,+i) [constprop] Disabled by argument and rettype heuristics
└──      return %5

vchuravy avatar Apr 24 '23 16:04 vchuravy

The failure comes from:

@code_warntype StaticArrays.adapt_size(SVector,StaticArrays.Args((1,)))

Check-bounds=0

9 ┄─ %63 = Base.typeintersect::Core.Const(typeintersect) (+c,+e,+n,+t,+s,+m,+i)
│    %64 = $(Expr(:static_parameter, 1))::Core.Const(SVector) (+c,+e,+n,+t,+s,+m,+i)
│    %65 = StaticArrays.StaticArrayNoEltype::Core.Const(StaticArray{S, T, N} where {S, N, T}) (+c,+e,+n,+t,+s,+m,+i)
│    %66 = SZ::Core.Const(Tuple{1})
│    %67 = StaticArrays.tuple_length(SZ::Core.Const(Tuple{1}))::Core.Const(1) (+c,+e,+n,+t,+s,+m,+i)
│    %68 = Core.apply_type(%65, %66, %67)::Core.Const(StaticArray{Tuple{1}, T, 1} where T) (+c,+e,+n,+t,+s,+m,+i)
│          (SA′ = (%63)(%64, %68))::Any (+c,+e,+n,+t,+s,+m,+i) Call inference reached maximally imprecise information. Bailing on.
│    %70 = SA′::Any

Check-bounds=auto

9 ┄─ %63 = Base.typeintersect::Core.Const(typeintersect) (+c,+e,+n,+t,+s,+m,+i)
│    %64 = $(Expr(:static_parameter, 1))::Core.Const(SVector) (+c,+e,+n,+t,+s,+m,+i)
│    %65 = StaticArrays.StaticArrayNoEltype::Core.Const(StaticArray{S, T, N} where {S, N, T}) (+c,+e,+n,+t,+s,+m,+i)
│    %66 = SZ::Core.Const(Tuple{1})
│    %67 = StaticArrays.tuple_length(SZ::Core.Const(Tuple{1}))::Core.Const(1) (+c,+e,+n,+t,+s,+m,+i)
│    %68 = Core.apply_type(%65, %66, %67)::Core.Const(StaticArray{Tuple{1}, T, 1} where T) (+c,+e,+n,+t,+s,+m,+i)
│          (SA′ = (%63)(%64, %68))::Core.Const(SVector{1}) (+c,+e,+n,+t,+s,+m,+i)
│    %70 = SA′::Core.Const(SVector{1})

vchuravy avatar Apr 24 '23 17:04 vchuravy

cc: @aviatesk

Noteworthy difference is:

--check-bounds=auto

   %69 = < concrete eval > typeintersect(::Core.Const(SVector),::Core.Const(StaticArray{Tuple{1}, T, 1} where T))::… (+c,+e,+n,+t,+s,+m,+i)

--check-bounds=false

   %69 = < constprop > typeintersect(::Core.Const(SVector),::Core.Const(StaticArray{Tuple{1}, T, 1} where T))::Any (+c,+e,+n,+t,+s,+m,+i)

vchuravy avatar Apr 24 '23 17:04 vchuravy