Why is this simple code type unstable?

julia> function test()
       for i=1:1 end
       end
test (generic function with 1 method)

julia> @code_warntype test()
MethodInstance for test()
  from test() in Main at REPL[1]:1
Arguments
  #self#::Core.Const(test)
Locals
  @_2::Union{Nothing, Tuple{Int64, Int64}}
  i::Int64
Body::Nothing
1 ─ %1  = (1:1)::Core.Const(1:1)
│         (@_2 = Base.iterate(%1))
│   %3  = (@_2::Core.Const((1, 1)) === nothing)::Core.Const(false)
│   %4  = Base.not_int(%3)::Core.Const(true)
└──       goto #4 if not %4
2 ─ %6  = @_2::Core.Const((1, 1))
│         (i = Core.getfield(%6, 1))
│   %8  = Core.getfield(%6, 2)::Core.Const(1)
│         (@_2 = Base.iterate(%1, %8))
│   %10 = (@_2::Core.Const(nothing) === nothing)::Core.Const(true)
│   %11 = Base.not_int(%10)::Core.Const(false)
└──       goto #4 if not %11
3 ─       Core.Const(:(goto %6))
4 ┄       return nothing


julia>

The for loop turns into calls to iterate(...), which returns the next value/state or nothing if the iterator is exhausted. It’s not problematic because of small union optimization – that’s why it’s not printed in red but orange.

See Interfaces · The Julia Language

5 Likes

It’s worth observing that a non-trivial version of this test is completely type stable:

julia> function test2()
       x = rand(1,10)
       y = rand(5,10)
       for i=1:y
          x += y
       end
       x
       end
test2 (generic function with 1 method)

julia> @code_warntype test2()
MethodInstance for test2()
  from test2() @ Main REPL[14]:1
Arguments
  #self#::Core.Const(test2)
Locals
  @_2::Union{}
  y::Matrix{Float64}
  x::Matrix{Float64}
  i::Union{}
Body::Union{}
1 ─     (x = Main.rand(1, 10))
│       (y = Main.rand(5, 10))
│       (1:y)
│       Core.Const(:(@_2 = Base.iterate(%3)))
│       Core.Const(:(@_2 === nothing))
│       Core.Const(:(Base.not_int(%5)))
│       Core.Const(:(goto %17 if not %6))
│       Core.Const(:(@_2))
│       Core.Const(:(i = Core.getfield(%8, 1)))
│       Core.Const(:(Core.getfield(%8, 2)))
│       Core.Const(:(x = x + y))
│       Core.Const(:(@_2 = Base.iterate(%3, %10)))
│       Core.Const(:(@_2 === nothing))
│       Core.Const(:(Base.not_int(%13)))
│       Core.Const(:(goto %17 if not %14))
│       Core.Const(:(goto %8))
└──     Core.Const(:(return x))

The original is dead code:

julia> @code_llvm test()
;  @ REPL[8]:1 within `test`
define void @julia_test_1195() #0 {
top:
;  @ REPL[8]:2 within `test`
  ret void
}

With live code, the small union optimization is applied, making the body of the loop type-stable.

1 Like