Why does JET seemingly not flag dynamic dispatch in this string interpolation?

julia> using LinearAlgebra, Cthulhu, JET

julia> D = Diagonal(rand(2));

julia> @descend setindex!(D, 2, 1, 2) # Cthulhu
setindex!(D::Diagonal, v, i::Int64, j::Int64) @ LinearAlgebra ~/.julia/juliaup/julia-1.11.0-alpha2+0.x64.linux.gnu/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:188
188 function setindex!(D::Diagonal{Float64, Vector{Float64}}::Diagonal, v::Int64, i::Int64::Int, j::Int64::Int)::Int64
189     @boundscheck checkbounds(D::Diagonal{Float64, Vector{Float64}}, i::Int64, j::Int64)
190     if (i::Int64 == j::Int64)::Bool
191         @inbounds D::Diagonal{Float64, Vector{Float64}}.diag::Vector{Float64}[i::Int64] = v::Int64
192     elseif !(iszero(v::Int64)::Bool)::Bool
193         throw(ArgumentError("cannot set off-diagonal entry ($i::Int64, $j::Int64) to a nonzero value ($v::Int64)"::Any)::Any)
194     end
195     return v::Int64
196 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 β€’ %5 = checkbounds(::Diagonal{Float64, Vector{Float64}},::Int64,::Int64)::Any
    i::Int64 == j::Int64
   D::Diagonal{Float64, Vector{Float64}}.diag
   %10 = setindex!(::Vector{Float64},::Int64,::Int64)::Any
   iszero(v::Int64)
    !iszero(v::Int64)::Bool
   runtime "cannot set off-diagonal entry ($i::Int64, $j::Int64) to a nonzero value ($v::Int64)"
   runtime ArgumentError("cannot set off-diagonal entry ($i::Int64, $j::Int64) to a nonzero value ($v::Int64)"::Any)
   ↩

julia> @report_opt setindex!(D, 2, 1, 2) # JET
No errors detected

julia> VERSION
v"1.11.0-alpha2"

Cthulhu marks the string interpolation in the ArgumentError message as a runtime dispatch, but JET doesn’t report this (although it does report similar calls elsewhere).

Without having run the code, I believe runtime dispatch is collected by @report_call when using JET. Does that report what you want?

Kind regards

The reason for this behavior is due to the skip_unoptimized_throw_blocks::Bool=true setting. The Julia compiler deliberately skips inference on code paths that are known to throw (for better latency), and JET follows suit by not reporting runtime dispatches on such code paths. If you switch to skip_unoptimized_throw_blocks=false, you should see the errors:

julia> @report_opt skip_unoptimized_throw_blocks=false setindex!(D, 2, 1, 2)
═════ 2 possible errors found ═════
β”Œ setindex!(D::Diagonal{Float64, Vector{Float64}}, v::Int64, i::Int64, j::Int64) @ LinearAlgebra /Users/aviatesk/julia/julia2/usr/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:193
β”‚ runtime dispatch detected: string("cannot set off-diagonal entry (", i::Int64, ", ", j::Int64, ") to a nonzero value (", v::Int64, ")")::Any
└────────────────────
β”Œ setindex!(D::Diagonal{Float64, Vector{Float64}}, v::Int64, i::Int64, j::Int64) @ LinearAlgebra /Users/aviatesk/julia/julia2/usr/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:193
β”‚ runtime dispatch detected: LinearAlgebra.ArgumentError(%49::Any)::Any
└────────────────────

See also: Optimization Analysis Β· JET.jl

4 Likes

I’m a bit confused, as the following does report similar dynamic dispatches in the error paths. Is this related to inlining, and are the throw blocks being optimized here?

julia> using LinearAlgebra, JET

julia> D = Diagonal(rand(2));

julia> @report_opt D * D
═════ 4 possible errors found ═════
β”Œ *(Da::Diagonal{Float64, Vector{Float64}}, Db::Diagonal{Float64, Vector{Float64}}) @ LinearAlgebra /cache/build/builder-amdci4-1/julialang/julia-release-1-dot-11/usr/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:304
β”‚β”Œ _muldiag_size_check(A::Diagonal{Float64, Vector{Float64}}, B::Diagonal{Float64, Vector{Float64}}) @ LinearAlgebra /cache/build/builder-amdci4-1/julialang/julia-release-1-dot-11/usr/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:284
β”‚β”‚β”Œ (::LinearAlgebra.var"#throw_dimerr#251")(::Diagonal{Float64, Vector{Float64}}, nA::Int64, mB::Int64) @ LinearAlgebra /cache/build/builder-amdci4-1/julialang/julia-release-1-dot-11/usr/share/julia/stdlib/v1.11/LinearAlgebra/src/diagonal.jl:282
β”‚β”‚β”‚β”Œ string(::String, ::Int64, ::String, ::Int64) @ Base ./strings/io.jl:189
β”‚β”‚β”‚β”‚β”Œ print_to_string(::String, ::Int64, ::String, ::Int64) @ Base ./strings/io.jl:150
β”‚β”‚β”‚β”‚β”‚β”Œ _unsafe_take!(io::IOBuffer) @ Base ./iobuffer.jl:494
β”‚β”‚β”‚β”‚β”‚β”‚β”Œ wrap(::Type{Array}, m::MemoryRef{UInt8}, l::Int64) @ Base ./array.jl:3101
β”‚β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: wrap(::Type{Array}, ::MemoryRef{UInt8}, ::Int64)
││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”Œ print_to_string(::String, ::Int64, ::String, ::Vararg{Any}) @ Base ./strings/io.jl:143
β”‚β”‚β”‚β”‚β”‚β”‚ runtime dispatch detected: Base._str_sizehint(%17::Any)::Int64
│││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”Œ print_to_string(::String, ::Int64, ::String, ::Vararg{Any}) @ Base ./strings/io.jl:148
β”‚β”‚β”‚β”‚β”‚β”‚ runtime dispatch detected: print(%59::IOBuffer, %97::Any)::Any
│││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”Œ string(::String, ::Int64, ::String, ::Tuple{Int64}, ::String, ::Int64, ::String, ::Int64, ::String) @ Base ./strings/io.jl:189
β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: string(::String, ::Int64, ::String, ::Tuple{Int64}, ::String, ::Int64, ::String, ::Int64, ::String)
│││││└──

The throw block deoptimization might not kick in under certain conditions, particularly when the method being analyzed still keeps a positive effect, so that’s possible.

Having said that this deoptimization does complicate the compiler’s behavior, so it’s slated for removal in the future.

1 Like