`SVector` type unstable for Julia v1.9 and `--check-bounds=no`
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
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
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})
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)