I missed some references to corresponding papers in the source code. For instance, is the register allocator an implementation of "Linear Scan Register Allocation on SSA Form" ?
Also some facts mention in comments  are a bit scary but I guess this will be resolved over time.
It happens that LLVM does instruction selection and scheduling on SSA but not regalloc (it does phi elimination before regalloc, though uses ssa based liveness info in regalloc)
My take: Who cares? If someone wants to make register allocation SSA based, and demonstrates code or speed or maintenance or whatever benefits, great.
There are theoretical benefits, but in practice, LLVM does pretty well with it's current scheme.
Because of this, it's not really near the top of any todo list, nor should it be.
libfirm never goes out of SSA (since it uses a graph-based representation it's simply impossible: the data dependency edges need one target). It converts the programm to CSSA and assigns the same register to all phi operands. When finally emitting the program, phi nodes emit nothing.
Note that your claim "(since it uses a graph-based representation it's simply impossible: the data dependency edges need one target)"
is wrong :)
SSA also requires that things have a single, unique, reaching definition.
The fact that the graph edges have one target does not guarantee this (it is instead a representation that requires this for correctness)
Open64 is another compiler with factored use-def chains like this (though the rest is not graph based), and has the same issue -
If you hoist something to various valid points (IE across existing live ranges), it's possible to generate multiple reaching definitions. This is because the dataflow definition of reaching definitions is not "whatever is on the end of this graph edge". So the fact that the graph edges only point to one of those definitions just makes the graph edges wrong.
So it's not impossible, but libfirm avoids it or performs the necessary phi insertion in various places to avoid creating invalid SSA for the representation it has.
(Note: It is possible to have IR's where the only ordering is a data dependence ordering, and so the graph is the source of truth and where it points is where it points. There are also representations where the control flow is implied by the graph nodes. In these representations, what you say would be correct. From what i know of libfirm, it has basic blocks and control flow edges, and it uses that to give some ordering. In this world, it's possible to screw up the SSA properties with pretty simple operations, and make the graph no longer correct)
It's impossible to have one edge pointing to multiple reaching definitions.
In short: Your argument is "it's SSA because the data structures only allow for a single reaching definition".
This is not right. If i don't insert phi nodes, and just have the IR point at random reaching definitions, it's not magically SSA, because there is not actually a unique reaching definition for each point, it just happens you've pointed the edges at arbitrarily selected reaching definitions (IE made the graph wrong :P)
I think having a decoupled spilling phase is nice feature of the SSA-based scheme. However, I don't say that LLVM (or GCC) should switch to the SSA-based scheme. I just appreciate that the Go compiler switched to the SSA-based scheme, because it may bring some new insights to the research community.
In fact, the guy who wrote LLVM's current register allocation did his thesis on decoupled SSA register allocation - http://www.theses.fr/2012ENSL0777
But he still decided not to go that way in LLVM when decoupling regalloc from spilling.
"because it may bring some new insights to the research community."
No offense, but this seems pretty unlikely. It's been 10 years and the number of production optimizing compilers that use SSA based regalloc (for example) is still hovering close to 0.
Sad (because i helped fund a ton of that research), but true.
The same thing is sadly true of libfirm. While a really impressive piece of work, it hasn't spurred much that i can see ...
(FWIW: I had the same hopes)
I’m not entirely convinced that stages 2 & 3 are
necessary, maybe we stop at stage 1. It all depends on
what optimizations we’d like to do that can’t be done
because the IR is in the wrong form. Proceeding with
stages 2 and 3 might gain some efficiency in the compiler
itself because then we don’t have to generate the old IR
at all. I suspect that effect will be small, however.
I'm interested in why LLVM was disqualified. Was it simply never considered or is it incompatible with the Go type system, calling convention, etc.?
They simply used what they knew best:
> If step one had been "learn the GCC or LLVM toolchains well enough to add segmented stacks", I'm not sure we'd have gotten to step two.
> Honestly, if we'd built on GCC or LLVM, we'd be moving so slowly I'd probably have left the project years ago.
If we were using LLVM...
Also LLVM requires you to either write your IR in SSA or add another expensive optimisation pass to make it SSA (mem2reg). Perhaps they thought writing an SSA generator would be too much of a headache.
From the end of
> Proven and well tested: clang uses this technique for local mutable variables. As such, the most common clients of LLVM are using this to handle a bulk of their variables. You can be sure that bugs are found fast and fixed early.
You don't need to add every optimization known to man to a compiler, so you can sometimes keep a few of the important ones and then skip every other optimization. A priori, I'd guess SSA would speed up the compiler, which means you end up having a better budget for the more expensive optimizations.
SSA is a conversion of the program into a representation of its data flow. I've used it in multiple compilers and found it to be a big win for easing other analyses (e.g., induction variable recognition become trivial) and reducing bugs due to the update problem.