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:
Sind eine Erweiterung der SSAInstruction und werden benötigt, wenn ein Knoten 2 oder mehr direkte Vorgänger hat. Mittels der Phi Funktion werden die Locals in den ExitSets der Vorgänger Zusammengeführt. Das Resultat wird dann im EntrySet des aktuellen Knotens abgelegt.
Spezialfall Loopheader:
Beim der ersten maTraversierung werden alle Phi-Funktionen angelegt, wobei noch nicht alle Parameter bekannt sind. Diese werden bei der zweiten Traversierung dann hinzugefügt.
Eliminieren von redundanten Phi-Funktionen:
Gemäss {Moessenboeck00} gibt es 2 Fälle von redundanten Phi Funktionen.
Fall 1:
x = [y,x,x,...,x]
hier sind alle bis auf einen Operand gleich wie auf der linke Seite des Gleichheitszeichen. Diese Phi-Funktionen können in Loopheaders auftreten wenn eine Variable in der Schleife nicht verändert wird. Somit kann diese mit y ersetzt werden.
Fall 2:
x = [x,x,...,x]
hier sind alle Operanden gleich wie auf der Linken Seite des Gleichheitszeichen. Diese Phi Funktionen können durch das Eliminieren von Phi- Funktionen entstehen. Diese könne direkt mit x ersetzt werden.
Die Phi-Funktion selbst kann aber nur als gelöscht markiert werden. Da sie bei der Registerreservierung dennoch gebraucht werden.
Jede lokale Variable muss zugewiesen sein, bevor sie verwendet werden kann. Ein Parameter wird jedoch beim Aufruf der Methode übergeben, somit existiert in der SSA keine Zuweisung. Daher muss direkt vor dem ersten Gebrauch des Parameters eine SSAInstruction „loadLoacal“ eingefügt werden. Diese sorgt dann bei der Codegenerierung, dass die Variable geladen wird.
Spezialfall Phi-Funktion:
Benutzt diese einen Parameter welcher noch nicht geladen ist, so wird die SSAInstruction „loadLocal“ am Ende des zugehörigen direkten Vorgängers eingefügt. Existiert dieser nicht (z.B. bei einem nicht implementiertem else-Block einer if-Anweisung), so muss dieser Knoten erzeugt und eingefügt werden.
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.