Definition und Aufbau aus dem AST

Transcription

Definition und Aufbau aus dem AST
SSA
Definition und Aufbau aus dem AST
Autor: Simone Forster
Betreuer: Florian Liekweg
Ausarbeitung zum Seminarvortrag am 02.6.2003 im Rahmen des Seminars
„Zwischensprachen und Codegenerierung“
am Institut für Programmstrukturen und Datenorganisation
Lehrstuhl Prof. Dr. Goos
der Fakultät für Informatik an der Universität Karlsruhe(TH)
Inhaltsverzeichnis:
0. Einleitung
3
1. SSA-Definition
3
2. PHI-Funktionen
4
3. SSA-Variationen
5
4. Transformation nach SSA
6
5. Dominanzgrenzen
6
6. Algorithmus nach Cytron et al.
7
7. Verbesserter Algorithmus nach Cytron/Ferrante
8
8. Weitere Algorithmen
8
9. Vorteile von SSA
9
10. AST
9
11. Zusammenfassung
11
12. Quellenangabe
12
13. Zitat-Quellenangabe
12
Simone Forster
2
02.6.2003
0. Einleitung
Betrachtet man das Anwendungsgebiet von SSA, so befindet man sich auf der
Zwischendarstellungsebene eines Übersetzers. Die Zwischendarstellung stellt die
Struktur des betreffenden Programms möglichst unabhängig von Quell- und
Zielsprache dar. Sie befindet sich zwischen Frontend und Backend. Im Frontend wird
das Programm in Quellsprache eingelesen und entsprechend der Struktur und
Semantik der Quellsprache in die Zwischendarstellung überführt. Im Backend
dagegen wird dann das Programm in der Zielsprache generiert.
SSA bietet sich im Zusammenhang mit Graphen an, da die Anschaulichkeit der
Graphen gewahrt wird und SSA sowohl auf Quelltext als auch auf den Inhalt von
Knoten in einem Graph anwendbar ist.
Aktuelle Zwischendarstellungen sind immer mehr graphbasiert, da man versucht, in
der Zwischendarstellung nur die Datenabhängigkeiten darzustellen. Dies hat
gleichzeitig den positiven Effekt, daß alle für die späteren Optimierungen wichtigen
Informationen erhalten bleiben.
SSA vereinfacht zudem die Optimierungen auf der Zwischendarstellung erheblich.
Dies erklärt sich durch die im folgenden dargestellte Struktur von SSA.
So bietet sich mit SSA
Zwischenrepräsentation.
also
eine
aktuelle
und
elegante
Form
einer
1. SSA-Definition
SSA bedeutet ausgeschrieben „Static Single Assignment“ (englisch), zu deutsch
„Statische Einmal-Zuweisung“.
Im folgenden wird verkürzend immer von SSA gesprochen.
Die Definition von SSA lautet:
Ein Programm ist dann in SSA-Form, wenn jeder Variable im Programm
jeweils nur einmal ein Wert zugewiesen wird.
Dadurch ist in einem Programm in SSA-Form immer klar, welchen Wert die jeweilige
Variable zum Zeitpunkt ihrer Benutzung hat.
SSA an sich ist keine Zwischendarstellung; es ist nur eine Eigenschaft, der eine
Zwischendarstellung genügen kann.
Deshalb wird das weitere Ziel auch sein, darzustellen, wie man eine
Zwischendarstellung in eine Form bringen kann, die den SSA-Anforderungen genügt.
Simone Forster
3
02.6.2003
2. PHI-Funktionen
Um ein Programm in SSA-Form zu bringen, benötigt man noch ein Utensil:
die Φ-Funktion.
Wozu man Φ-Funktionen benötigt, soll am folgenden Beispiel klar werden:
func foo
{
int x = 0;
if (x == 0)
then do {x = 5;}
else do {x = 7;}
int y = x+1;
}
Dieses Programm ist noch nicht in SSA-Form, da x dreimal einen Wert zugewiesen
bekommt. Gäbe es mehrere Zuweisungen in einem Grundblock (in der Grafik als
Rechteck dargestellt), so könnte das Programm durch einfache Wertnumerierung in
SSA-Form gebracht werden (x bekäme dann die Namen x1, x2, ...).
Hier jedoch tritt das Problem auf, daß im letzten Knoten (y = x+1) nicht sicher
bestimmbar ist, welchen Wert x hat, da der Knoten im Ablaufgraph von mehreren
Grundblöcken aus erreichbar ist. Wurde der Knoten über den „then“-Teil erreicht, so
hat x den Wert 5, folgte man dem Ablaufgraph über den „else“-Teil zu dem besagten
Knoten, so hat x den Wert 7.
Das heißt, es fließen mehrere Werte einer Variablen ein und es sind
Zusatzinformationen notwendig, den aktuell gültigen Wert zu ermitteln.
Genau dieses Problem klärt eine Φ-Funktion. Eine Φ-Funktion hat so viele
Operanden, wie der Knoten Vorgänger im Ablaufgraph hat. Der j-te Operand
entspricht dem Wert der Variable im j-ten Vorgänger des Knotens im Ablaufgraph.
Das heißt, Φ(x1, x2, x3, ...) liefert den Wert der Variable x in dem Vorgänger zurück,
über den der aktuelle Knoten erreicht wurde.
Simone Forster
4
02.6.2003
Beispiel:
Programm:
Programm in SSA-Form:
func foo
{
int x = 0;
if (x == 0)
then do {x = 5;}
else do {x = 7;}
int y = x+1;
}
func foo
{
int x1 = 0;
if (x1 == 0)
then do {x2 = 5;}
else do {x3 = 7;}
x4 = Φ(x2, x3);
int y1 = x4+1;
}
Φ-Funktionen sind grundsätzlich immer am Eingang eines Grundblocks zu finden.
Sie werden vor allen anderen „normalen“ Anweisungen ausgeführt. Zudem werden
alle Φ-Funktionen eines Grundblocks gleichzeitig ausgeführt.
3. SSA-Variationen
Es gibt verschiedene Arten von SSA, die sich im wesentlichen darin unterscheiden,
wo und wie viele Φ-Funktionen gesetzt werden.
Zum einen gibt es die „minimale“ SSA-Form, wie es nach Cytron [CEtAl] genannt
wird. Dabei werden so wenige Φ-Funktionen wie möglich gesetzt. Darauf wird auch
später noch eingegangen, wenn der zugehörige Algorithmus vorgestellt wird.
Dann gibt es noch eine Form, die „Pruned SSA“ [CCF] genannt wird. „Pruned“ ist
englisch und bedeutet zu deutsch „abgeschnitten“. Theorie dieser Form ist, daß nur
so weit nach unten im Ablaufgraph Φ-Funktionen für Variablen gebildet werden, wie
die Variable noch tiefer verwendet wird (weiter unten bzw. tiefer im Graph bedeutet
hier weiter entfernt von der Wurzel). Besser gesagt: Es wird nur dann eine ΦFunktion gebildet, wenn die Variable tiefer im Graph noch lebendig ist.
Schließlich gibt es noch eine dritte Form, bei der Φ-Funktionen nur auf Anfrage
generiert werden. Dies wird auch Aufbau „on Demand“ (englisch) genannt [AR].
Simone Forster
5
02.6.2003
4. Transformation nach SSA
Im allgemeinen ist es das Ziel, ein Programm, das sich in der Zwischendarstellung
befindet, in SSA-Form umzuwandeln. Essentiell sind dabei die Algorithmen, nach
denen die Φ-Funktionen generiert werden. Im folgenden wird ein Algorithmus von
Ron Cytron et al. (1991) [CEtAl] vorgestellt und anschließend ein verbesserter
Algorithmus von Ron Cytron und Jeanne Ferrante (1995) [CF].
5. Dominanzgrenzen
Um den Algorithmus von Cytron et al. [CEtAl] und den von Cytron/Ferrante [CF] zu
verstehen, muß zuvor als Grundlage erklärt werden, was Dominanz bedeutet.
Sei ein Ablaufgraph mit, unter anderem, zwei Knoten X und Y gegeben.
Dann gilt:
-
X dominiert Y ⇔
X kommt auf jedem Pfad von der Wurzel bis zu Y vor ⇔
Formel: X ≥ Y (gilt auch reflexiv und transitiv)
-
X dominiert Y strikt ⇔
X dominiert Y und X ≠ Y
Formel: X > Y
-
Y liegt in der Dominanzgrenze von X ⇔
Y ist der erste Knoten, der von X nicht strikt dominiert wird; die Vorgänger von
Y wurden von X aber dominiert
Formel: DG(X) = { Y | (∃P ∈ Vorg(Y)) (X ≥ P und X > Y)}
mit Vorg(Y) = Vorgänger von Y
-
Dominanzgrenze einer Knotenmenge =
Vereinigung der Dominanzgrenzen aller Knoten der Knotenmenge
Formel: DG(M) = UX∈M DG(X)
-
Iterierte Dominanzgrenze =
maximale Menge der Dominanzgrenzen einer Menge von Knoten, wobei die
Φ-Knoten (Knoten mit Φ-Funktionen) nach und nach der Menge zugefügt
wurden, so daß die iterierte Dominanzgrenze auch die Dominanzgrenzen aller
Φ-Knoten enthält
Formel: DG+ = Maximum der ansteigenden Knotenmenge
DG1 = DG(M),
DGi+1 = DG(M ∪ DGi)
Simone Forster
6
02.6.2003
Beispiel:
-
S dominiert alles und S dominiert
alles strikt
A dominiert B, C und D strikt
B und C dominieren nichts
D dominiert nichts
E dominiert nichts
F liegt in der Dominanzgrenze
von A
wäre A gleich D, so würde
gelten:
o A dominiert B, C und D
o A dominiert B und C strikt,
aber D nicht
Dominanzgrenzen werden zu folgendem Zweck benötigt.
Würde im Beispiel bei Knoten A „x=5“ zugewiesen und bei Knoten E „x=7“, so würde
man bei F eine Φ-Funktion benötigen. Dies ist soweit verallgemeinerbar, als daß
man davon ausgehen kann, daß an jeder solchen festgestellten Dominanzgrenze
eine Φ-Funktion benötigt wird, wenn man die Dominanzgrenzen der Knoten
bestimmt, die eine Variablenzuweisung enthalten.
Das heißt, durch das Finden der iterierten Dominanzgrenzen werden alle Stellen
ermittelt, an denen eine Φ-Funktion benötigt wird.
6. Algorithmus nach Cytron et al.
Cytron et al. [CEtAl] gehen grob zusammengefaßt folgendermaßen vor.
Zuerst werden die Dominanzgrenzen der Knoten mit Variablenzuweisungen ermittelt.
Dann wird mit den zuvor festgestellten Dominanzgrenzen die iterierte
Dominanzgrenze ermittelt.
Da jetzt die Platzierung der Φ-Funktionen feststeht, ist das Ziel fast erreicht: es
müssen nur noch die Variablen umbenannt werden. Dazu wird iterativ über den
Quelltext gegangen, es werden Wertnummern vergeben und auf alle Nennungen der
jeweiligen Variable übertragen.
Damit wurde die SSA-Form hergestellt und der Algorithmus terminiert.
Simone Forster
7
02.6.2003
7. Verbesserter Algorithmus nach Cytron/Ferrante
Der Vorteil des verbesserten Algorithmus ist, daß er im schlimmsten Fall nahezu
lineare Zeit in Bezug auf die Anzahl der Anweisungen benötigt, während der oben
genannte Algorithmus sich quadratisch verhält. Darauf wird aber später noch
genauer eingegangen.
Cytron/Ferrante [CF] gehen im Prinzip genauso vor, wie Cytron et al. [CEtAl]. Sie
haben allerdings das Finden der Dominanzgrenzen vereinfacht und damit
beschleunigt.
Um den Ansatz hier zu verstehen, muß vorher geklärt sein, was ein unmittelbarer
Dominator ist.
-
X ist unmittelbarer (direkter) Dominator von Y ⇔
X ist auf allen Pfaden der Knoten, der sich am nächsten bei Y befindet und
gleichzeitig Y strikt dominiert
Formel: ddom(Y)
Beim Verfahren von Cytron/ Ferrante [CF] werden dann Mengen von Knoten
gebildet, die alle den selben unmittelbaren Dominator besitzen
(⇔ equidom(X) = { Y | ddom(Y) = ddom(X) } ).
Der Algorithmus unterteilt diese Menge dann wiederum in Mengen von Knoten, die in
der iterierten Dominanzgrenze der jeweils anderen Knoten liegen. Dazu werden die
„Strongly Connected Components“ (engl., zu deutsch: „Starke Zusammenhangskomponenten“) nach Aho et al. [AEtAl] aus dem Ablaufgraph ermittelt.
Da nach der Bildung dieser Mengen klar ist, wo sich die iterierten Dominanzgrenzen
befinden, ist schließlich offensichtlich, wo die Φ-Funktionen gesetzt werden müssen.
Die Umbenennung der Variablen muß dann ebenfalls noch durchgeführt werden und
wird wie in Cytron et al. [CEtAl] realisiert.
8. Weitere Algorithmen
Bleibt schließlich noch zu betrachten, was genau „Pruned“ SSA [CCF] ist und was
„Aufbau auf Anfrage“ [AR] bedeutet.
„Pruned“ (oder auch „abgeschnittenes“) SSA hat wie oben schon beschrieben die
Eigenschaft, daß Φ-Funktionen nur so weit nach unten im Graph gebildet werden,
wie die Variable tiefer noch verwendet wird (weiter unten bzw. tiefer im Graph
bedeutet hier weiter entfernt von der Wurzel). Besser gesagt: Es wird nur dann eine
Φ-Funktion gebildet, wenn die Variable tiefer im Graph noch lebendig ist. Das hat
folgenden Sinn.
Simone Forster
8
02.6.2003
Ist Variable tiefer im Graph nicht mehr lebendig, so wäre es völlig überflüssig noch
Aufwand in die Bildung einer Φ-Funktion zu stecken, da nie auf den dort berechneten
Wert zugegriffen werden würde.
In der Praxis bedeutet dies, daß während des Aufbaus der Φ-Funktionen eine
Vorausschau gemacht wird, ob die betreffende Variable tiefer im Graph noch
vorkommt oder nicht. Wird die Variable gefunden, so wird für sie eine Φ-Funktion
eingefügt, ist dies nicht der Fall, so wird auch keine Φ-Funktion gebildet.
Beim Aufbau auf Anfrage dagegen wird zunächst erst einmal gar keine Φ-Funktion
gebildet. Kommt man später bei der Ausführung an eine Stelle, wo nicht klar ist,
welchen Wert eine Variable hat, wird nachträglich durch die Rückverfolgung des
Ablaufgraphen eine Φ-Funktion generiert und damit der richtige Wert ermittelt.
9. Vorteile von SSA
Der Prozeß, ein Programm in SSA-Form zu bringen, bringt zwar Aufwand mit sich,
dieser zahlt sich aber dadurch aus, daß SSA viele Vorteile bietet.
Zunächst vereinfacht die SSA-Form deutlich Optimierungen auf der
Zwischensprachenebene. Zwar gilt es auch hier abzuwägen, welche
Optimierungsverfahren in welcher Reihenfolge angewendet das beste Ergebnis
ergeben, jedoch sind Optimierungen aufgrund der klaren Struktur von SSA einfach
auf das jeweilige Programm anzuwenden.
Des weiteren wurde festgestellt, daß sich SSA gut dazu eignet, ProgrammÄquivalenzen zu erkennen [AWZ, YHR], Parallelismus in imperativen Programmen
zu erhöhen [CF2] und einfache Datenflußinformationen kompakter darzustellen [RL].
Für Leser, die sich genauer über diese Gegebenheiten informieren möchten, sei auf
die angegebenen Quellen verwiesen.
10. AST
Das Thema dieser Ausarbeitung lautet ja „SSA - Definition und Aufbau aus dem
AST“. Warum hier bisher nicht direkt auf das Thema AST eingegangen wurde, hat
vor allen Dingen damit zu tun, daß Cytron et al. [CEtAl] und Cytron/Ferrante [CF] als
Grundlage für ihre Arbeiten immer von einem Ablaufgraph ausgehen. Diese Quellen
waren die Grundlage für die Seminararbeit.
Die Übertragung der Algorithmen auf den AST geschieht ohne Komplikationen: sie
können genauso angewandt werden.
Simone Forster
9
02.6.2003
AST bedeutet „Abstract Syntax Tree“ (engl.), zu deutsch „Abstrakter Strukturbaum“.
Um SSA aus einem AST aufzubauen, muß nur darauf geachtet werden, daß von
links nach rechts eine Tiefensuche angewandt wird.
Der Unterschied zwischen einem Ablaufgraph und einem AST ist folgender. Wenn in
einem AST irgendwo eine Variable x einen Wert zugewiesen bekommt und später
diese Variable verwendet wird, so wird im AST an der Stelle, wo die Variable
verwendet wird ein Verweis auf die Stelle gesetzt, wo der Wert der Variable steht.
Um den Unterschied von Ablaufgraph und AST darzustellen, sei hier noch ein
Beispiel gegeben:
Ablaufgraph:
AST:
Simone Forster
10
02.6.2003
11. Zusammenfassung
Was in dieser Arbeit gezeigt wurde, ist, wie SSA aufgebaut wird. Dazu wurden
verschiedene Algorithmen vorgestellt.
Abschließend soll noch etwas über den Aufwand dieser Algorithmen gesagt werden.
Der Algorithmus nach Cytron et al. [CEtAl] konstruiert die iterierten
Dominanzgrenzen in O(E + N²), wobei E für die Kanten (engl.: edges) und N für die
Knoten (engl.: nodes) im Ablaufgraph steht. Dies bedeutet im schlechtesten Fall ein
quadratisches Verhalten.
Der Algorithmus nach Cytron / Ferrante dagegen läuft in O(E + N + T), wobei T die
Zeit ist, die benötigt wird, um festzustellen, ob ein gegebener Knoten Y sich in der
Dominanzgrenze eines schon betrachteten Knotens X befindet.
Durch Pfadkompression kann ein verbessertes Verhalten von O(E + N + Eα(E))
erreicht werden, wobei α die inverse Ackermann-Funktion [CorEtAl] darstellt, die sehr
langsam wächst. Dadurch wurde auch im schlechtesten Fall ein nahezu lineares
Verhalten erreicht.
„Pruned“ SSA und „Aufbau auf Anfrage“ laufen in der Praxis in linearer Zeit in Bezug
auf die Anzahl der Anweisungen. Dies gilt aber auch für die oben genannten
Algorithmen; der Algorithmus nach Cytron et al. [CEtAl] zum Beispiel läuft in der
Praxis in linearer Zeit, nur im schlechtesten Fall hätte er eine quadratische Laufzeit,
wobei der schlechteste Fall in der Praxis so gut wie nie erreicht wird.
Simone Forster
11
02.6.2003
12. Quellenangabe
•
Übersetzerbau - Ein kleiner Überblick
o Rubino Geiß, Sebastian Hack
o IPD Lehrstuhl Goos, Universität Karlsruhe (TH) 2003
•
Efficiently Computing Static Single Assignment Form and the Control
Dependence Graph
o Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman,
F.Kenneth Zadeck
o ACM Transactions on Programming Languages and Systems, Vol.13,
No.4, October 1991, pp. 451-490
•
Efficiently Computing Φ-Nodes On-The-Fly
o Ron K. Cytron, Jeanne Ferrante
o ACM Transactions on Programming Languages and Systems, Vol.17,
No.3, May 1995, pp. 487-506
13. Zitat-Quellenangabe
[CEtAl]:
Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman, F. Kenneth
Zadeck
Efficiently Computing Static Single Assignment Form and the Control
Dependence Graph
ACM Transactions on Programming Languages and Systems, Vol.13, No.4,
October 1991, pp. 451-490
[CCF]:
-
J. Choi, R. Cytron, J. Ferrante
Automatic construction of sparse data flow evaluation graphs
Conference Record of the 18th ACM Symposium on Principles of
Programming Languages, January 1991, ACM, New York, pp. 55-66
[AR]:
-
M. Armbruster, C. von Roques
Entwurf und Implementierung eines Sather-K Übersetzers
Diplomarbeit am IPD, Lehrstuhl Prof. Dr. Goos, Fakultät für Informatik,
Universität Karlsruhe (TH), 1996
[AWZ]:
B. Alpern, M. N. Wegman, F. K. Zadeck
Detecting equality of values in programs
Conference Record of the 15th ACM Symposium on Principles of
Programming Languages, January 1988, ACM, New York, pp. 1-11
Simone Forster
12
02.6.2003
[YHR]:
-
W. Yang, S. Horwitz, T. Reps
Detecting program components with equivalent behaviors
Tech Report 840, Department of Computer Science, University of Wisconsin
at Madison, Madison, April 1989
[CF]:
-
R. Cytron, J. Ferrante
Efficiently Computing Φ-Nodes On-The-Fly
ACM Transactions on Programming Languages and Systems, Vol.17, No.3,
May 1995, pp. 487-506
[CF2]:
-
R. Cytron, J. Ferrante
What‘s in a name?
Proceedings of the 1987 International Conference on Parallel Processing,
August 1987, pp. 19-27
[RL]:
-
J. H. Reif, H. R. Lewis
Efficient symbolic analysis of programs
J. Comput. Syst. Sci. 32, June 1986, pp. 280-313
[CorEtAl]:
T. H. Cormen, C. E. Leiserson, R. L. Rivest
Introduction to Algorithms
The MIT Press, Cambridge, Mass., 1990
[AEtAl]:
A. Aho, J. Hopcroft, J. Ullman
The Design and Analysis of Computer Algorithms
Addison-Wesley, Reading, Mass., 1974
Simone Forster
13
02.6.2003