Why does JET say this leads to a dispatch error?

Hello!

I am using JET to improve my package. I have a function looking like this:

function ResetArrays!(arrays...)
    @inbounds for array in arrays
        fill!(array,zero(eltype(array)))
    end
end

Which I feed in vectors of different types, for example Vector{Float64} and Vector{SVector{3,Float64}}. I then get the warning:

═════ 2 possible errors found ═════
┌ ResetArrays!(::Vector{Float64}, ::Vector{SVector{3, Float64}}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{SVector{3, Float64}}, ::Vector{SVector{3, Float64}}) @ SPHExample.SimulationDataArrays c:\git\SPHExample\src\SimulationDataArrays.jl:20
│┌ fill!(dest::Vector{SVector{3, Float64}}, x::Float64) @ Base ./array.jl:393
││ runtime dispatch detected: convert(::SVector{3, Float64}, x::Float64)
│└────────────────────
│┌ fill!(dest::Vector{Float64}, x::SVector{3, Float64}) @ Base ./array.jl:393
││ runtime dispatch detected: convert(::Float64, x::SVector{3, Float64})
│└────────────────────

What is a runtime dispatch and why does this happen, and how would I fix it in Julia?

Kind regards

Okay I realized that it is because that Julia is not able to at runtime determine the eltype(array), when it gets mixed input. It works fine when all inputs are of the same type, then no error is detected.

Any smart way to fix it or I just have to split into two function calls?

EDIT: My current solution:

Enforce the type of variable arguments through Vararg.

function ResetArrays!(arrays::Vararg{Vector{T}}) where T
    @inbounds for array in arrays
        fill!(array,zero(eltype(array)))
    end
end

Split call into two function calls:

            # Clean up arrays, Vector{T} and Vector{SVector{3,T}} must be cleansed individually,
            # to avoid run time dispatch errors
            ResetArrays!(Kernel, dρdtI,dρdtIₙ⁺)
            ResetArrays!(KernelGradient, dvdtI, Acceleration)

Kind regards

  • Is that @inbounds really doing anything? It’s best to avoid those if you don’t really need them.

  • I think you can just do fill!.((k,d,p), 0)

  • Is that @inbounds really doing anything? It’s best to avoid those if you don’t really need them.

I think you are right, probably has no effect.

I think you can just do fill!.((k,d,p), 0)

Yes, that is in a sense what I am doing, just having a function more? I think at least

Kind regards

1 Like

You can also say

ResetArrays!(arrays...) = foreach(a -> fill!(a, zero(eltype(a))), arrays)

This seems to be faster than broadcasting.

Thank you. I am unsure why, but your solution reports no errors and seems to be able to dispatch on type correctly, so I marked it as solution.

Kind regards

I am unsure why, but your solution reports no errors and seems to be able to dispatch on type correctly

a -> fill!(a, zero(eltype(a))) is an anonymous function, that acts as a function barrier.

EDIT: This was true, but definitely not the explanation (see @matthias314 comment below). The following still triggers a “runtime dispatch detected” by JET.

In a less compact way, one could create a named function

function reset_array!(a)
    fill!(a, zero(eltype(a)))
end

and use it as

for a in arrays
    reset_array!(a)
end

This is not the same. The point is that foreach (like map, reduce and friends) can unroll a loop over a tuple.

2 Likes

You are right, it looks like the function barrier is not enough;
thanks a lot for the correction.

julia> using JET

julia> function reset_array!(a)
           fill!(a, zero(eltype(a)))
       end

julia> function reset_all!(arrays)
           for a in arrays
               reset_array!(a)
           end
       end

julia> @report_opt reset_all!(Any[[1, 2], [1.0, 2.0]])
═════ 1 possible error found ═════
┌ reset_all!(arrays::Vector{Any}) @ Main ./REPL[14]:3
│ runtime dispatch detected: reset_array!(%19::Any)::Any
└────────────────────

But the foreach does not seem to work for JET either ?


julia> function reset_all_2!(arrays)
           foreach(reset_array!, arrays)
       end

julia> @report_opt reset_all_2!(Any[[1, 2], [1.0, 2.0]])
┌ reset_all_2!(arrays::Vector{Any}) @ Main ./REPL[27]:2
│┌ foreach(f::typeof(reset_array!), itr::Vector{Any}) @ Base ./abstractarray.jl:3094
││ runtime dispatch detected: f::typeof(reset_array!)(%19::Any)::Any
│└────────────────────

It’s crucial to use a tuple and not a vector. Try

reset_all_2!(([1, 2], ["a", "b"]))

(Remember that argument slurping in Julia gives a tuple.)

It also works, if one uses arrays... i.e. making it varargs. I guess that is because the varargs is represented as a tuple internally?

Right. This is exactly argument slurping. If you define a method of the form function f(arrays...), then in the function body arrays is a tuple.

1 Like