SSA is functional programming
SSA is functional programming
Static Single-Assignment (SSA) form is an intermedi- able name for each assignment to the variable. For ex-
ate language designed to make optimization clean and ample, we convert the program at left into the single-
efficient for imperative-language (Fortran, C) compil- assignment program at right. At left, a use of a at any
ers. Lambda-calculus is an intermediate language that point refers to the most recent definition, so we know
makes optimization clean and efficient for functional- where to use al, a2, or a3, in the program at right.
language (Scheme, ML, Haskell) compilers. The SSA For a program with no jumps this is easy. But where
community draws pictures of graphs with basic blocks two control-flow edges join together, carrying different
and flow edges, and the functional-language community values of some variable i, we must somehow merge the
writes lexically nested functions, but (as Richard Kelsey two values. In SSA form this is done by a notational
recently pointed out [9]) they're both doing exactly the trick, the C-function. In some node with two in-edges,
same thing in different notation. the expression ¢(al, a2) has the value al if we reached
this node on the first in-edge, and a2 if we came in on the
SSA form. Many dataflow analyses need to find the second in-edge.
use-sites of each defined variable or the definition-sites Let's use the following program to illustrate:
of each variable used in an expression. The def-use chain
is a data structure that makes this efficient: for each state- i4-1
ment in the flow graph, the compiler can keep a list of j4-1
pointers to all the use sites of variables defined there, and k4-0
a list of pointers to all definition sites of the variables used while k < 100
there. But when a variable has N definitions and M uses, i f j < 20
we might need N • M pointers to connect them. j+--i
The designers of SSA form were trying to make an im- k4-k+l
proved form of def-use chains that didn't suffer from this else
problem. Also, they were concerned with "getting the j4-k
right number of names:" the programmer might use some k+-k+2
variable i for several unrelated purposes in the same pro- return j
cedure- for example, as the loop counter for two different
loops - and we can do more optimization if we split i into First we tum this into a control-flow graph (CFG):
different variables il and i2.
In SSA, each variable in the program has only one defi-
nition - it is assigned to only once. The assignment might
be in a loop, which is executed many times; so single-
assignment is a static property of the program text, not a //~[ifk < 100 l
~-.~
dynamic property of program execution.
/ [ i f j < 2 0 " ] 3 [return j j4
a 4- x+y al 4- xTy
b 4- a-1 bl 4- a~-i I[ [ j < - - i -":re
I [j<--k
6
a ~ y + b a2 4- y + b~
/Ik~---k+l ] [ke-k+2
b 4- x.4 b2 4- x.4
a 4- a+b a3 4- a2+b2
To achieve single-assignment, we make up a new vari-
17
Programming
Now, the question is, where to put the C-functions and and function-calls
how to rename the variables. A really crude approach is
to split every variable at every basic-block boundary, and f2(.. • ,j7,-..) f2(.-.,jl,.-.)
put C-functions for every variable in every block: in the functional program. We see that the left-hand side
of the ¢ assignment is the formal parameter of the corre-
sponding function; and each right-hand side argument of
the ¢ assignment is the actual parameter of some call to
the corresponding function. That's what I mean when I
2 ~ O ( b , !1) 2 say that SSA form is a kind of functional programming.
l 0 07, The "C-functions" are not really functions, but they do
k2 ~ ¢ (k7, correspond (in an inside-out way) to the real functions.
if k2 < 100
We can express this functional program in a nicer way
using the idea of nested scope. Then the inner-nested
i3 ¢--¢(i2) 13 i4 ~.._¢t(i2) 4
functions won't all need so many parameters; they can
J3 <'-'¢(J2) [ j4/"'¢i(J2)
k3 ~¢~(kz) h ~¢(kz) use non-local variables from the functions in which they
if j3 < 20 return j4 are nested. This idea will be familiar to Pascal program-
mers (and Scheme, ML, Haskell programmers), and (if
i5':~---¢(i3) I~5 I i6 +--0(i3) 16 there are any of you left) Algol-60 programmers as well.
J5 ~"-0(j3) I I J6/~"-¢(J3) I
let il = 1, jl ..7- 1, kl = 0
k: I O(k3)l in let function f2 (j2, k2) =
J8 ~ 15 [ ] J9 e---k 6 if kz < 100
k8 ~- ks+l I Ik9 ~ k6+2
then let function fT(j4, k4) =
i7 ~._ O (i5, i6) 7 :2 (j4, k4)
in ifj2 < 20
J7 e-- ~ (J8,J9) then let j3 = il, k3 = k2 + 1
x~_~/¢~ (ks'k9)
in f7 (j3, k3)
else let j5 = k2, ks = k2 + 1
Yuck! This isn't "the right number of names!" There in fz (js, ks)
are too many variables and useless copies. More about else return j2
this later. in f2 (jl, kl)
Meanwhile, we can view this program as a set of mu-
But what's the algorithm for finding the best way of
tually recursive functions, where each function takes ar-
nesting the functions to eliminate unnecessary argument-
guments/, j, k: passing? The algorithm is the one for converting pro-
functionfl 0 = grams to SSA form!
let il = 1, j l = 1, kl = 1 in f2(il,jl,kl)
function f2 (i2, j2, k2) =
if k2 < 100 then f3(i2,j2, k2) else f4(i2,j2, k2)
function f3 (i3, j3, k3) =
if j3 < 20 then f~(i3,j3, k3) else f6(i3,j3, k3) J2 ~'-- : (J4, Jkl~) 2
function f4 (i4, j4, k4) = j4 2 ~-~(k4,
function :5 (i5, j5, k5) = ' [ if k2 < I 0 0
let js = i5, ks = ks + 1 in fz(is,js,ks)
function f6(i6, j6, k6) = [ifj2<2013 [retumj2 14
let j9 = k6, k9 = k6 + 1 in fT(i6,j9, k9)
function f~(ir, jr, kT) = f2 (i7, jr, kT) ] ~ +... il 5 <__..k2 6
This gives us some insight into what, exactly, is a "C- e-k2+1 [:~ ~- k2+2
function." Compare the expression j2 ~ ¢(j7, jl ) (in the
really crude SSA program) with the function-declaration
J4 ~ ¢ (J3, J5) 7
(k3,ks)
f~(..., j~,...) . . . .
18
Functional
This is the Static Single-Assignment form of the pro- dominance frontier of 5 is reachable from two different
gram with optimal placement of C-functions. It's much definitions of z; one in node 5 and one in the start node.
nicer than the crude version that had too many variables (We assume that every variable has an initializing defi-
and too many C-functions. This program has "the right nition in the start node.) Therefore, the rule for placing
number of names?' And notice how it corresponds ex- ¢ functions is: Whenever node n contains a definition of
actly to the nested functional program - function fi cor- some variable z, then any node in the dominance frontier
responds to block i, parameter ji corresponds to variable o f n needs a C-function for x.
ji, and so on. Wherever there is a formal parameter of a Efficient algorithms for computing the dominator tree
function (in the functional form), there is a ¢ (in the SSA and dominance frontiers can be found in any good com-
form). Wherever the functional form refers to a non-local piler textbook [3, 4, 5, 10, 15]
variable, the SSA form has avoided the need for a ¢.
Once we have the SSA form, we can make appropriate
linked data structures connecting the uses of each variable
Algorithm for optimal placement of ¢'s. The only to the definition, and the definition to all the uses. Then
place we really need a C-function in SSA form is where we can run efficient optimization algorithms: instead of
two different definitions reach (along control-flow edges) using costly bit-vector dataflow analysis, we can follow
the same point. For example, in the original CFG (the first links to quickly find the uses for each definition, and vice
diagram above), only one definition of i reaches block 2, versa, as needed.
so we don't need a C-function for i in that block. This is
true even though there are two edges leading into block Functional programming in Fortran? So now we
2 - it's because the definition of i (in block 1) dominates know what the SSA conversion algorithm is really do-
block 2. Any path to block 2 must go through block 1. ing with its dominance frontiers: it is automatically con-
We use the notion of dominance and dominance fron- verting a Fortran or C procedure into a well-structured
tiers to calculate the minimum set of C-functions. In gen- functional program with nested scope. Actually, I've only
eral, node a in a flowgraph dominates node b when any shown what to do with the scalar variables. Arrays are
path from the start node to b must go through a. Now, handled in high-powered (parallelizing) compilers using
consider the region of the graph dominated by a; imagine sophisticated dependence analysis techniques [ 15], which
that this region has a "border" or "frontier" separating it is another way of extracting the functional program hid-
from the rest of the graph. We call this the dominance ing inside the imperative one.
frontier of a. In particular, whenever there is an edge
b --+ c from a node b dominated by a to a node c not
What SSA users can learn from functional program-
strictly dominated by a, we say that c is in the dominance ming. An important property of SSA form is that the
frontier of a. definition of a variable dominates every use (or, in the
case of a uses within a C-function, dominates the a prede-
cessor of the use node). This property is often unstated in
explanations of SSA, but it is necessary for many of the
analyses and optimizations on SSA - it is part of SSA's
semantics. In a functional program with nested scope,
this restriction is explicitly and statically encoded into
the structure of function nesting. The notion of scopes
of variables helps us to structure the intermediate form.
19
Programming
History and literature. SSA form was developed by References
Wegman, Zadeck, Alpern, and Rosen [1, 11] for efficient
computation of dataflow problems such as global value [1] Bowen Alpern, Mark N. Wegman, and E Kenneth Zadeck.
numbering, congruence of variables, aggressive dead- Detecting equality of variables in programs. In Proc. 15th
code removal, and constant propagation with conditional ACM Syrup. on Principles of Programming Languages,
pages 1-11, New York, January 1988. ACM Press.
branches [14]. Cytron et al. [7] describe the efficient
computation of SSA form using dominance frontiers. [2] Andrew W. Appel. Compiling with Continuations. Cam-
bridge University Press, New York, 1992.
Wolfe [15] describes several optimization algorithms
[3] Andrew W. Appel. Modern Compiler Implementation in
on SSA (which he calls factored use-defchains). C. Cambridge University Press, New York, 1998.
Church [6] invented A-calculus, a language of func- [4] Andrew W. Appel. Modern Compiler Implementation in
tions with nested scope. Strachey [13] showed how Java. Cambridge University Press, New York, 1998.
to encode control flow as function calls to continua- [5] Andrew W. Appel. Modern Compiler Implementation in
tion functions. Steele [12] showed how to use continu- ML. Cambridge University Press, New York, 1998.
ations as the intermediate representation of a compiler. [6] Alonzo Church. The Calculi of Lambda Conversion.
Kelsey [9] showed the correspondence between SSA and Princeton University Press, Princeton, 1941.
continuation-passing style (CPS), and gave algorithms for [7] Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N.
converting each to the other. Wegman, and F. Kenneth Zadeck. Efficiently computing
Appel [2] improved upon CPS by binding every non- static single assignment form and the control dependence
graph. ACM Trans. on Programming Languages and Sys-
trivial value explicitly to a variable. Flanagan et al. [8]
tems, 13(4):451---490, October 1991.
showed Administrative-Normal Form (A-Normal Form
or ANF), which binds every nontrivial value to a variable [8] Cormac Flanagan, Amr Sabry, Bruce E Duba, and
Matthias Felleisen. The essence of compiling with contin-
without being full CPS. The functional notation I have
uations. In Proceedings of the ACM SIGPLAN '93 Confer-
used in this paper is a variant of ANF or CPS.
ence on Programming Language Design and Implementa-
tion, pages 237-247, New York, 1993. ACM Press.
Advertisement. Chapter 19 of my new Modern Com- [9] Richard A. Kelsey. A correspondence between continua-
piler Implementation textbooks [3, 4, 5] has readable and tion passing style and static single assignment form. In
detailed coverage of many relevant topics: Proceedings ACM SIGPLAN Workshop on Intermediate
Representations, vol. 30, pages 13-22, March 1995.
[10] Steven S. Muchnick. Advanced Compiler Design and Im-
• SSA form and its rationale;
plementation. Morgan Kaufmann, San Francisco, 1997.
• Dominance frontiers and calculation of SSA form; [11] Barry K. Rosen, Mark N. Wegman, and E Kenneth
Zadeck. Global value numbers and redundant computa-
• The Lengauer-Tarjan algorithm for efficient calcula-
tions. In Proc. 15th ACM Symp. on Principles of Pro-
tion of dominators; gramming Languages, pp. 12-27, New York, Jan. 1988.
• Optimization algorithms using SSA: dead-code [12] Guy L. Steele. Rabbit: a compiler for Scheme. Technical
elimination, conditional constant propagation; con- Report AI-TR-474, MIT, Cambridge, MA, 1978.
trol dependence; construction of register interfer- [13] C. Strachey and C. Wadsworth. Continuations: A mathe-
ence graphs; matical semantics which can deal with full jumps. Techni-
cal Monograph PRG-11, Programming Research Group,
• Structural properties of SSA form; Oxford University, 1974.
• Functional intermediate representations (CPS, ANF) [14] Mark N. Wegman and E Kenneth Zadeck. Constant prop-
and their relation to SSA. agation with conditional branches. ACM Trans. on Pro-
gramming Languages and Systems, 13(2):181-210, 1991.
For more information about the book, visit [15] Michael Wolfe. High Performance Compilers for Parallel
http://www.cs.princeton.edu/- appel/modern. Computing. Addison Wesley, Redwood City, CA, 1996.
20