Comparison of automatic differentiation tools from 2016 still accurate?

I’m not sure what you mean by “breaking SSA” – this kind of loop would be represented using phi nodes (though supporting mutable slots is actually fine too). If you want an intuition for how control flow like this might be handled, tangent’s docs do a good job.

Oh, I didn’t know “SSA-form” refers to a specific technique in compiler optimizations, now I see your point. Indeed, a compiler handling inputs, outputs and variable assignments greatly simplifies the task. Yet, some bookkeeping is still needed.

I was referring to “SSA” in a sense of mathematical variable that can’t be modified or overwritten. In general, if you have a recurrence x_i = f(x_{i-1}), you need to keep all the x_k to backpropagate through them. Tangent does it by pushing values of re-assigned variable onto a stack, which I personally dislike since it breaks further optimizations based on dependency graph.

In Julia SSA-form, as far as I understand, the mutable part of a loop is hidden inside \phi-node. E.g. in:

function foo(x)
    while x < 100
        x = x * 2
    return x

@code_typed foo(4)   # is it the right way to get SSA form? 

which generates:

  1 ─      nothing::Nothing                                                                                                                                                                                  │ 
2 2 ┄ %2 = φ (#1 => _2, #3 => %5)::Int64                                                                                                                                                                     │ 
  │   %3 = (Base.slt_int)(%2, 100)::Bool                                                                                                                                                                     │╻ <
  └──      goto #4 if not %3                                                                                                                                                                                 │ 
3 3 ─ %5 = (Base.mul_int)(%2, 2)::Int64                                                                                                                                                                      │╻ *
  └──      goto #2                                                                                                                                                                                           │ 
5 4 ─      return %2

I understand that this is slot _2 inside φ (#1 => _2, #3 => %5) that accumulates changes, right? If so, you need to keep track of its values.

There also should be a variable to count loop iterations and, if we take ML applications into account, the generated code should be GPU-friendly, and some other complications, but it would be very interesting to see it implemented in Zygote.jl!