Dies ist eine alte Version des Dokuments!
The Static Single Assignment Form (SSA) is an intermediate form which allows various optimizations and facilitates register allocation. The main point of the SSA is that each variable is assigned a value only once. Each new assignment leads to a new instance of this variable (indicated by i1 and i2). When optimizing, subsequent instuctions can simply use the definition and the value of these variables (e.g. for constant folding). The register allocation can be made more efficient as the living ranges of the instances are generally very short. More information about the creation and usage of the SSA can be found in H. Mössenböck.
The SSA is stored into nodes similar to the CFG nodes. For this purpose we define SSANode as an extension of CFGNode.
When generating the CFG we already create instances of type SSANode.
All Bytecode instructions of all nodes are translated into SSA instructions. The SSA instructions can be categorized as follows:
As the figure shows, the following types are available
Each instruction has an opcode (ssaOpcode). This can be of
Storing to fields (sCstoreToField) can be of type MonadicRef or DiadicRef. MonadicRef is used when accessing class fields. Here the reference is the field whereas the operand is the value to store. DyadicRef is used when accessing instance fields. Here the reference is the field whereas the first operand contains the reference to the object and the second operand is the value to store. The reference to the field is used to hold information about the type of the field and the offset within the object. A similar case is sCloadFromField.
Some SSA instructions don't produce a result, such as sCstoreToField. In this case, the SSA instruction allocates a result of type void.
For each SSANode a state array is carried along with information about local variables and the operand stack. The state of this array at the beginning of a node is the entrySet, at the end of a node the exitSet. The state array holds references to SSAValues. This values are results of previous SSA instructions.
The state array shows the state of the operand stack and indicates which instruction created a certain local variable. When generating SSA instructions the following rules apply:
We use phi-functions whenever a node has more than one predecessors. A phi-function combines a local variable from the exit sets of these predecessors. The resulting value (result of the phi-function) goes into the entry set of the actual node.
During the first traverse all necessary phi-functions will be generated. At this stage, not all parameters are known. Such parameters are added during the second traverse.
Two cases have to be considered: Case 1:
x = [y,x,x,...,x]
here, all operands except one are the same as the assigned value. Such phi-function can occur in loop headers, if the value of a variable does not change in the loop. The value can be replaced by y.
Case 2:
x = [x,x,...,x]
here, all operands are the same as the assigned value. such phi-functions can occur when other phi-functions are eliminated. Simply replace with the value x. Phi-functions are never really deleted. They are marked as deletet, because even deleted phi-functions are used for register allocation.
Every local variable must be assigned before it can be used. However, a parameter has no assignment instruction in the SSA. Therefore, before the first use of a parameter a SSA-instruction loadLoacal must be inserted.
If a phi-function uses a parameter which is not yet loaded, the SSA-instruction loadLocal must be inserted at the end of the appropriate predecessors. If these do not exist (e.g. if an else block is not present) a node must be created and inserted.
Beim Bilden der SSA ist die Reihenfolge, in der die Knoten traversiert werden, zu berücksichtigen. Dabei gilt:
Die Phi-Funktionen müssen aufgelöst werden. Das heisst, alle ihre Operanden und ihr Resultat erhalten das gleiche Register zugewiesen. Dazu wird der Index in den locals verwendet.
Üblicherweise haben die Operanden und das Resultat auch den gleichen Index. Es gibt aber zwei Fälle, wo das nicht zutrifft. Für diese Fälle muss in allen Vorgängerknotens des Knotens mit der phi-Funktion ein Registermoves eingefügt werden. Ebenso müssen die Exit-Sets dieser Vorgängerknoten angepasst werden.
boolean a = i > 10? b : c;
Hier belegen alle drei Variablen vom Typ Boolean (a,b,c) je einen anderen Index im Exit-Set.
if (condition) a = 1; else a = 2; b = a;
Hier hat a z.B. den Index 0 und b den Index 1. Die SSA-Form von a = 1 lädt die Konstante 1. b = a taucht in der SSA-Form nicht auf, resp. b ist das Resultat der phi-Funktion mit den Konstanten 1 und 2 als Operanden.
Für das Source-Lookup des Debuggers muss nachher bekannt sein, welche Instruktion zu welcher Sourcezeile gehört. Diese Information findet sich im Bytecode in der LineNumberTable. Diese Information muss auch in die SSA übernommen werden. Achtung: gelöschte oder neu eingefügte Knoten müssen speziell behandelt werden.