Closure bug (#15276) hits again with multi-threading, but why?!

#1

Hello,

Below is a MWE of what I am seeing in an application:

function foo(v)
    #uncomment for Box
    size(v) == (10,10) && error()
    ss=zeros(Float64,Threads.nthreads())
    
    #this does not lead to Box
    #size(v) == (10,10) && error()
    
    Threads.@threads for i2 in 1:size(v,2)
        @simd for i1 in 1:size(v,1)
            tid=Threads.threadid()
            @inbounds ss[tid]+=v[i1,i2]
        end
    end
    return sum(ss)
end

@code_warntype foo(rand(100,100))Body::Any
│╻     Type3 1 ── %1  = %new(Core.Box)::Core.Box
│╻     size  │    %2  = (Base.arraysize)(v, 1)::Int64
││      │    %3  = (Base.arraysize)(v, 2)::Int64
││╻╷    _eq  │    %4  = (%2 === 10)::Bool
││╻     _eq  │    %5  = (%4 === false)::Bool
│││     └───       goto #3 if not %5
│││     2 ──       goto #7
│││╻╷    _eq  3 ── %8  = (%3 === 10)::Bool
│││╻     _eq  │    %9  = (%8 === false)::Bool
││││    └───       goto #5 if not %9
││││    4 ──       goto #6
││││    5 ──       goto #6
│││     6 ┄─ %13 = φ (#4 => false, #5 => true)::Bool
│││     └───       goto #7
││      7 ┄─ %15 = φ (#2 => false, #6 => %13)::Bool
││      └───       goto #8
│       8 ──       goto #10 if not %15
│       9 ──       invoke Main.error()
│       └───       $(Expr(:unreachable))
│╻     nthreads4 10 ┄ %20 = (Base.Threads.cglobal)(:jl_n_threads, Base.Threads.Cint)::Ptr{Int32}
││╻     unsafe_load  │    %21 = (Base.pointerref)(%20, 1, 1)::Int32
│││╻     toInt64  │    %22 = (Core.sext_int)(Core.Int64, %21)::Int64
││╻╷╷   zeros  │    %23 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), :(:ccall), 2, Array{Float64,1}, :(%22), :(%22)))::Array{Float64,1}
│││     │    %24 = invoke Base.fill!(%23::Array{Float64,1}, 0.0::Float64)::Array{Float64,1}
│       │          (Core.setfield!)(%1, :contents, %24)
│╻╷    macro expansion9 │    %26 = (Base.arraysize)(v, 2)::Int64
││╻╷╷╷  Colon  │    %27 = (Base.sle_int)(1, %26)::Bool
│││╻     Type  │          (Base.sub_int)(%26, 1)
││││┃     unitrange_last  │    %29 = (Base.ifelse)(%27, %26, 0)::Int64
││││    │    %30 = %new(UnitRange{Int64}, 1, %29)::UnitRange{Int64}
│╻     macro expansion  │    %31 = %new(getfield(Main, Symbol("##1134#threadsfor_fun#280")){Array{Float64,2},UnitRange{Int64}}, v, %1, %30)::getfield(Main, Symbol("##1134#threadsfor_fun#280")){Array{Float64,2},UnitRange{Int64}}
││╻     threadid  │    %32 = $(Expr(:foreigncall, :(:jl_threadid), Int16, svec(), :(:ccall), 0))::Int16
│││╻     +  │    %33 = (Base.sext_int)(Int64, %32)::Int64
│││╻     +  │    %34 = π (1, Int64)
││││╻     +  │    %35 = (Base.add_int)(%33, %34)::Int64
│││╻     ==  │    %36 = (%35 === 1)::Bool
│││╻     !  │    %37 = (Base.not_int)(%36)::Bool
││      └───       goto #12 if not %37
││      11 ─       goto #13
││      12 ─ %40 = Base.Threads.in_threaded_loop::Core.Compiler.Const(Base.RefValue{Bool}(false), false)
│││╻     getproperty  └─── %41 = (Base.getfield)(%40, :x)::Bool
││      13 ┄ %42 = φ (#11 => %37, #12 => %41)::Bool
││      └───       goto #15 if not %42
││      14 ─       invoke %31(true::Bool)
││      └───       goto #16
││      15 ─ %46 = Base.Threads.in_threaded_loop::Core.Compiler.Const(Base.RefValue{Bool}(false), false)
│││╻     setproperty!  │          (Base.setfield!)(%46, :x, true)
│╻     macro expansion  │          $(Expr(:foreigncall, :(:jl_threading_run), Ref{Nothing}, svec(Any), :(:ccall), 1, :(%31), getfield(Main, Symbol("##1134#threadsfor_fun#280")){Array{Float64,2},UnitRange{Int64}}, :(v), :(%1)))
││      │    %49 = Base.Threads.in_threaded_loop::Core.Compiler.Const(Base.RefValue{Bool}(false), false)
││╻     setindex!  └───       (Base.setfield!)(%49, :x, false)
│╻     macro expansion  16 ┄ %51 = (Core.isdefined)(%1, :contents)::Bool
││      └───       goto #18 if not %51
││      17 ─       goto #19
││      18 ─       $(Expr(:throw_undef_if_not, :ss, false))
││      19 ┄ %55 = (Core.getfield)(%1, :contents)::Any
││      │    %56 = (Main.sum)(%55)::Any
││      └───       return %56
│       20 ─       goto #10

I understand that this is 15276, but why is the error check before ss causing it (checking after does not). I appreciate any help understanding this so I can catch it in the future (and other users may too).

Thanks!

#2

Consider KissThreading.jl if it fits your use case. 15276 has been properly fought under the hood.