# Strange type instability

Iβm encountering a strange type instability that I could distill to the following minimal code:

``````function unstable(rv,x0)
x1=copy(x0)
for t in 1:10
p::Int = prod(x0[j] for j in rv; init=1)
x1[1] = p == 1 ? 0 : 1
x0,x1 = x1,x0
end
return x0
end
``````

`@code_warntype` gives the following. The instability disappears if:

• If I set `p=1` instead of the `p=prod(... )`
• If I typeassert `x0` and `x1` in the assignment, i.e. `x0::Vector{Int},x1::Vector{Int} = x1,x0` instead of `x0,x1 = x1,x0`.

Any idea or explanation?

``````julia> @code_warntype unstable(fill(1,10),fill(1,10))
MethodInstance for unstable(::Vector{Int64}, ::Vector{Int64})
from unstable(rv, x0) @ Main REPL[103]:1
Arguments
#self#::Core.Const(unstable)
rv::Vector{Int64}
x0@_3::Vector{Int64}
Locals
@_4::Union{Nothing, Tuple{Int64, Int64}}
x1::Any
#109::var"#109#110"
t::Int64
p::Int64
x0@_9::Union{}
x0@_10::Union{}
x0@_11::Union{}
x0@_12::Union{Core.Box, Vector{Int64}}
@_13::Int64
Body::Any
1 ββ       (x0@_12 = x0@_3)
β          (x0@_12 = Core.Box(x0@_12::Vector{Int64}))
β          Core.NewvarNode(:(@_4))
β          Core.NewvarNode(:(x1))
β    %5  = Core.isdefined(x0@_12::Core.Box, :contents)::Bool
ββββ       goto #3 if not %5
2 ββ       goto #4
3 ββ       Core.NewvarNode(:(x0@_9))
ββββ       x0@_9
4 ββ %10 = Core.getfield(x0@_12::Core.Box, :contents)::Any
β          (x1 = Main.copy(%10))
β    %12 = (1:10)::Core.Const(1:10)
β          (@_4 = Base.iterate(%12))
β    %14 = (@_4::Core.Const((1, 1)) === nothing)::Core.Const(false)
β    %15 = Base.not_int(%14)::Core.Const(true)
ββββ       goto #13 if not %15
5 ββ %17 = @_4::Tuple{Int64, Int64}
β          (t = Core.getfield(%17, 1))
β    %19 = Core.getfield(%17, 2)::Int64
β          (#109 = %new(Main.:(var"#109#110"), x0@_12::Core.Box))
β    %21 = #109::var"#109#110"
β    %22 = Base.Generator(%21, rv)::Base.Generator{Vector{Int64}, var"#109#110"}
β    %23 = (:init,)::Core.Const((:init,))
β    %24 = Core.apply_type(Core.NamedTuple, %23)::Core.Const(NamedTuple{(:init,)})
β    %25 = Core.tuple(1)::Core.Const((1,))
β    %26 = (%24)(%25)::Core.Const((init = 1,))
β    %27 = Core.kwcall(%26, Main.prod, %22)::Any
β    %28 = Base.convert(Main.Int, %27)::Any
β          (p = Core.typeassert(%28, Main.Int))
β    %30 = (p == 1)::Bool
ββββ       goto #7 if not %30
6 ββ       (@_13 = 0)
ββββ       goto #8
7 ββ       (@_13 = 1)
8 ββ %35 = @_13::Int64
β          Base.setindex!(x1, %35, 1)
β    %37 = x1::Any
β    %38 = Core.isdefined(x0@_12::Core.Box, :contents)::Bool
ββββ       goto #10 if not %38
9 ββ       goto #11
10 β       Core.NewvarNode(:(x0@_10))
ββββ       x0@_10
11 β %43 = Core.getfield(x0@_12::Core.Box, :contents)::Any
β          Core.setfield!(x0@_12::Core.Box, :contents, %37)
β          (x1 = %43)
β          (@_4 = Base.iterate(%12, %19))
β    %47 = (@_4 === nothing)::Bool
β    %48 = Base.not_int(%47)::Bool
ββββ       goto #13 if not %48
12 β       goto #5
13 β %51 = Core.isdefined(x0@_12::Core.Box, :contents)::Bool
ββββ       goto #15 if not %51
14 β       goto #16
15 β       Core.NewvarNode(:(x0@_11))
ββββ       x0@_11
16 β %56 = Core.getfield(x0@_12::Core.Box, :contents)::Any
ββββ       return %56

``````

Thatβs the infamous Julia#15276. `x0[j] for j in rv` is a closure that captures `x0`, but `x0` has complicated lifetime and gets re-assigned. You can fix it (and remove the `::Int` assertion) with:

``````p = let x0=x0; prod(x0[j] for j in rv; init=1); end
``````
full code
``````julia> function stable(rv,x0)
x1=copy(x0)
for t in 1:10
p = let x0=x0; prod(x0[j] for j in rv; init=1); end
x1[1] = p == 1 ? 0 : 1
x0,x1 = x1,x0
end
return x0
end
stable (generic function with 1 method)

julia> @code_warntype stable(fill(1,10),fill(1,10))
MethodInstance for stable(::Vector{Int64}, ::Vector{Int64})
from stable(rv, x0) @ Main REPL[21]:1
Arguments
#self#::Core.Const(stable)
rv::Vector{Int64}
x0@_3::Vector{Int64}
Locals
@_4::Union{Nothing, Tuple{Int64, Int64}}
x1::Vector{Int64}
t::Int64
p::Int64
#5::var"#5#6"{Vector{Int64}}
x0@_9::Vector{Int64}
x0@_10::Vector{Int64}
@_11::Int64
Body::Vector{Int64}
1 β       (x0@_10 = x0@_3)
β         (x1 = Main.copy(x0@_10))
β   %3  = (1:10)::Core.Const(1:10)
β         (@_4 = Base.iterate(%3))
β   %5  = (@_4::Core.Const((1, 1)) === nothing)::Core.Const(false)
β   %6  = Base.not_int(%5)::Core.Const(true)
βββ       goto #7 if not %6
2 β %8  = @_4::Tuple{Int64, Int64}
β         (t = Core.getfield(%8, 1))
β   %10 = Core.getfield(%8, 2)::Int64
β   %11 = x0@_10::Vector{Int64}
β         (x0@_9 = %11)
β   %13 = Main.:(var"#5#6")::Core.Const(var"#5#6")
β   %14 = Core.typeof(x0@_9)::Core.Const(Vector{Int64})
β   %15 = Core.apply_type(%13, %14)::Core.Const(var"#5#6"{Vector{Int64}})
β         (#5 = %new(%15, x0@_9))
β   %17 = #5::var"#5#6"{Vector{Int64}}
β   %18 = Base.Generator(%17, rv)::Base.Generator{Vector{Int64}, var"#5#6"{Vector{Int64}}}
β   %19 = (:init,)::Core.Const((:init,))
β   %20 = Core.apply_type(Core.NamedTuple, %19)::Core.Const(NamedTuple{(:init,)})
β   %21 = Core.tuple(1)::Core.Const((1,))
β   %22 = (%20)(%21)::Core.Const((init = 1,))
β         (p = Core.kwcall(%22, Main.prod, %18))
β   %24 = (p == 1)::Bool
βββ       goto #4 if not %24
3 β       (@_11 = 0)
βββ       goto #5
4 β       (@_11 = 1)
5 β %29 = @_11::Int64
β         Base.setindex!(x1, %29, 1)
β   %31 = x1::Vector{Int64}
β   %32 = x0@_10::Vector{Int64}
β         (x0@_10 = %31)
β         (x1 = %32)
β         (@_4 = Base.iterate(%3, %10))
β   %36 = (@_4 === nothing)::Bool
β   %37 = Base.not_int(%36)::Bool
βββ       goto #7 if not %37
6 β       goto #2
7 β       return x0@_10
``````
7 Likes

I seeβ¦ Thanks a lot!