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