Appropriate warning for size using code_warntype

I tried profiling some code using JET. When I used size() on a matrix of type Any, the warning came back that the output of the function size is of type Any. This confused me because I thought size could only output of type Int. I created a MWE and duplicated the effect with @code_warntype as shown below

julia> function mwe()
           a = [1 2.0; true "Yes"]
           b = size(a, 1)
       end
mwe (generic function with 1 method)

julia> typeof(mwe())
Int64

julia> @code_warntype mwe()
MethodInstance for mwe()
  from mwe() @ Main REPL[10]:1
Arguments
  #self#::Core.Const(mwe)
Locals
  b::Any
  a::Any
Body::Any
1 ─ %1 = Core.tuple(2, 2)::Core.Const((2, 2))
│        (a = Base.hvcat(%1, 1, 2.0, true, "Yes"))
│   %3 = Main.size(a, 1)::Any
│        (b = %3)
└──      return %3

If I read this right, size returns an Int64, but @code_warntype shows that it returns a type of Any. JET shows the same thing.

Does this mean the compiler does not know what the type of size(a::Any,1) is and I should make the statement Int(size(a,1)) in my code or is this a bug in @code_warntype?

Firstly, it’s worth noting that what’s considered as problematic can differ based on the situation. However, in terms of performance of this function itself, the real issue isn’t the return type of size(a, 1) being non-inferred, but it is that the type of a isn’t inferred as Matrix{Any}. This causes a runtime dispatch to size(a, 1), eventually leading to inaccurate return type of this function itself as a whole. I’m guessing you’ve used JET’s @report_opt, which should’ve reported this runtime dispatch. In contrast, @report_call doesn’t highlight such concern by default, since it’s designed to be permissive as runtime dispatches in general doesn’t necessarily mean MethodError (so JET doesn’t consider it as very problematic in terms of error detection point of view). Anyway, circling back, a quick fix here could be to use a type annotation like a = [1 2.0; true "Yes"]::Matrix{Any}:

julia> function mwe()
           a = [1 2.0; true "Yes"]::Matrix{Any} # help the compiler
           b = size(a, 1)
       end
mwe (generic function with 1 method)

julia> @code_warntype mwe()
MethodInstance for mwe()
  from mwe() @ Main REPL[6]:1
Arguments
  #self#::Core.Const(mwe)
Locals
  b::Int64
  a::Matrix{Any}
Body::Int64
1 ─ %1 = Core.tuple(2, 2)::Core.Const((2, 2))
│   %2 = Base.hvcat(%1, 1, 2.0, true, "Yes")::Any
│   %3 = Core.apply_type(Main.Matrix, Main.Any)::Core.Const(Matrix{Any})
│        (a = Core.typeassert(%2, %3))
│   %5 = Main.size(a, 1)::Int64
│        (b = %5)
└──      return %5

Although tweaking the implementation of Base.typed_hvcat is an alternative and probably better option, it’d involve changes to Julia Base itself.

3 Likes

This isn’t strictly necessary, as BigInt ranges have a BigInt size, and infinite arrays have an infinite size.

1 Like