JuliaSIMD / StrideArrays.jl

Library supporting the ArrayInterface.jl strided array interface.
MIT License
54 stars 9 forks source link

Type instability when using `PtrArray` with Julia v1.10 and `--check-bounds=no` #78

Open sloede opened 1 year ago

sloede commented 1 year ago

With Julia v1.10-beta2 and --check-bounds=no, indexing into a PtrArray seems to be type unstable. When running the following MWE (mwe.jl),

using Pkg
Pkg.activate(; temp=true, io=devnull)
Pkg.add(name="StrideArrays", version="0.1.26", io=devnull)

using StrideArrays, InteractiveUtils

a = [1, 2, 3]
p = PtrArray(a)

@code_warntype p[1]

with julia-1.10 --check-bounds=no mwe.jl, we get the following output:

MethodInstance for getindex(::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}}, ::Int64)
  from getindex(A::PtrArray{T, 1}, i::Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8, StaticInt}) where T @ StrideArraysCore ~/.julia/packages/StrideArraysCore/COJRJ/src/ptr_array.jl:957
Static Parameters
  T = Int64
Arguments
  #self#::Core.Const(getindex)
  A::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}}
  i::Int64
Body::Any
1 ─       nothing
│   %2  = StrideArraysCore.boundscheck()::Core.Const(false)
└──       goto #3 if not %2
2 ─       Core.Const(:(goto %6 if not $(Expr(:boundscheck))))
│         Core.Const(:(StrideArraysCore.checkbounds(A, i)))
└──       Core.Const(:(goto %7))
3 ┄ %7  = StrideArraysCore.pointer(A)::Ptr{Int64}
│   %8  = StaticArrayInterface.offset1::Core.Const(StaticArrayInterface.offset1)
│   %9  = (%8)(A)::Core.Const(static(1))
│   %10 = (i - %9)::Any
│   %11 = LayoutPointers.bytestrides::Core.Const(LayoutPointers.bytestrides)
│   %12 = (%11)(A)::Core.Const((static(8),))
│   %13 = StrideArraysCore.only(%12)::Core.Const(static(8))
│   %14 = (%10 * %13)::Any
│   %15 = (%7 + %14)::Any
│   %16 = StrideArraysCore.pload(%15)::Any
└──       return %16

This does not appear to happen with Julia v1.9 or without using --check-bounds=no:

`julia-1.10 mwe.jl` ```julia MethodInstance for getindex(::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}}, ::Int64) from getindex(A::PtrArray{T, 1}, i::Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8, StaticInt}) where T @ StrideArraysCore ~/.julia/packages/StrideArraysCore/COJRJ/src/ptr_array.jl:957 Static Parameters T = Int64 Arguments #self#::Core.Const(getindex) A::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}} i::Int64 Body::Int64 1 ─ nothing │ %2 = StrideArraysCore.boundscheck()::Core.Const(false) └── goto #3 if not %2 2 ─ Core.Const(:(goto %6 if not $(Expr(:boundscheck)))) │ Core.Const(:(StrideArraysCore.checkbounds(A, i))) └── Core.Const(:(goto %7)) 3 ┄ %7 = StrideArraysCore.pointer(A)::Ptr{Int64} │ %8 = StaticArrayInterface.offset1::Core.Const(StaticArrayInterface.offset1) │ %9 = (%8)(A)::Core.Const(static(1)) │ %10 = (i - %9)::Int64 │ %11 = LayoutPointers.bytestrides::Core.Const(LayoutPointers.bytestrides) │ %12 = (%11)(A)::Core.Const((static(8),)) │ %13 = StrideArraysCore.only(%12)::Core.Const(static(8)) │ %14 = (%10 * %13)::Int64 │ %15 = (%7 + %14)::Ptr{Int64} │ %16 = StrideArraysCore.pload(%15)::Int64 └── return %16 ```
`julia-1.9 --check-bounds=no mwe.jl` ```julia MethodInstance for getindex(::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}}, ::Int64) from getindex(A::PtrArray{T, 1}, i::Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8, StaticInt}) where T @ StrideArraysCore ~/.julia/packages/StrideArraysCore/COJRJ/src/ptr_array.jl:957 Static Parameters T = Int64 Arguments #self#::Core.Const(getindex) A::PtrArray{Int64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, Tuple{StaticInt{1}}} i::Int64 Body::Int64 1 ─ nothing │ %2 = StrideArraysCore.boundscheck()::Core.Const(false) └── goto #3 if not %2 2 ─ Core.Const(:(goto %6 if not $(Expr(:boundscheck)))) │ Core.Const(:(StrideArraysCore.checkbounds(A, i))) └── Core.Const(:(goto %7)) 3 ┄ %7 = StrideArraysCore.pointer(A)::Ptr{Int64} │ %8 = StaticArrayInterface.offset1::Core.Const(StaticArrayInterface.offset1) │ %9 = (%8)(A)::Core.Const(static(1)) │ %10 = (i - %9)::Int64 │ %11 = LayoutPointers.bytestrides::Core.Const(LayoutPointers.bytestrides) │ %12 = (%11)(A)::Core.Const((static(8),)) │ %13 = StrideArraysCore.only(%12)::Core.Const(static(8)) │ %14 = (%10 * %13)::Int64 │ %15 = (%7 + %14)::Ptr{Int64} │ %16 = StrideArraysCore.pload(%15)::Int64 └── return %16 ```

Unfortunately, this regression makes StrideArrays.jl currently unusably slow on Julia v1.10 with bounds checking disabled globally.

cc @ranocha

chriselrod commented 1 year ago

PRs welcome.

│   %9  = (%8)(A)::Core.Const(static(1))
│   %10 = (i - %9)::Any

For starters, %9 has no reason to be a static Int. Maybe convert to Int first? Or, perhaps look at that for a more minimal example.

sloede commented 1 year ago

@ranocha made it a more minimum MWE and it turns out it is not related to StrideArrays.jl but rather Static.jl and Base.promote_type, so we reported a more focused example in https://github.com/JuliaLang/julia/issues/50985.