1.39k likes | 1.51k Views
Optimierungstechniken in modernen Compilern. Grundlagen. Grundlagen. Aufbau eines Compilers Überblick über die Analysephase. Aufbau eines Compilers. Kontext- prüfung. Zielcodeabhängige Optimierungen. Backend. Quell-text. Zwischencode und Symbol- tabelle. Ziel-code. Scanner. Parser.
E N D
Optimierungstechniken in modernen Compilern Grundlagen
Grundlagen Aufbau eines Compilers Überblick über die Analysephase
Aufbau eines Compilers Kontext- prüfung Zielcodeabhängige Optimierungen Backend Quell-text Zwischencode und Symbol- tabelle Ziel-code Scanner Parser Zielcode- erzeugung Zielcodeunab- hängige Optimierungen Frontend Steuerfluss im Compiler Datenfluss im Compiler
Zeichenketten • Es sei eine abzählbare Menge von Zeichen, Alphabet genannt. • Eine Zeichenkettew ist eine totale Funktion: • Für w schreiben wir auch: w(0)…w(n-1) • n ist die Länge der Zeichenkette. • Es gibt genau eine Zeichenkette der Länge 0; diese wird mit bezeichnet. • Eine Zeichenkette wird auch Wort genannt.
Formale Sprache • n:Menge aller Zeichenketten über mit der Länge n: • *: Menge aller endlichen Zeichenfolgen über einem Alphabet : • +: Menge aller nicht leeren endlichen Zeichenfolgen über einem Alphabet : • Formale Sprache L: L *
Grammatik • Mittel zur konstruktiven Definition einer Sprache. • Wichtig für den Compilerbau: Neben den Worten, die zu einer Sprache gehören, wird auch eine Struktur der Worte definiert. • Grammatik G = (, M, R, S) mit • M = , • S M und • R (( M)* - *) ( M)* endlich • Bezeichnungen: • Grundsymble/Terminalsymbole/Terminals • MMetasymbole/Nicht-Terminalsymbole/Nonterminals • V := MVokabular • Worte * sind terminale Worte (Zeichenketten) • Worte ( M)* - * sind nichtterminale Worte (Zeichenketten) • Jede Regel (l,r) R besitzt eine linke Seitel und eine rechte Seiter (auch Alternative genannt)
Ableitungsrelation • Durch die Regeln R einer Grammatik ist eine Ableitungsrelation definiert: • Statt (a, b) schreiben wir auch a b für den Ableitungsschritt von anachb • Eine Folge von Ableitungsschritten a1 a2, a2 a3,…, an-1 an wird kurz als a1 a2 a3 …an-1 an geschrieben und als Ableitung bezeichnet. • Durch Bildung des reflexiven und transitiven Abschlusses * von kann die durch G erzeugte Sprache definiert werden als: • Mit n wird eine Ableitung mit genau n Schritten bezeichnet • Insbesondere folgt aus 0, dass = • Mit + wird eine Ableitung mit mindestens einem Schritt bezeichnet.
Geordneter Baum • Eine Knotenmenge B * ist ein geordneter Baum, falls gilt: • B ist endlich, • Wenn i B, dann auch ( ist der Vater des Knotens i), mit * und i , • Wenn (i+1) B, dann auch i (i ist der linke Bruder des Knotens (i+1)), mit * und i . • Beispiel: 1 1 0 0 0.0 0.1 0.2 1.0 1.1 1.2 B = {, 0, 1,1.0, 1.1, 1.2} B = {, 0, 1,0.0, 0.1, 0.2}
Syntaxbaum • Sei : B V eine Markierung der Knoten eines geordneten Baums mit den Symbolen einer kfG G = (, M, S, R). Dann ist (B, ) genau dann ein Syntaxbaum, falls gilt: • () = S • B: Falls () = A M dann i mit (A,[A,i]) R so dass für alle j mit 0 j < |[A,i]| gilt: • (.j) = [A,i,j] • .|[A,i]| B • B: Falls () = a , dann gilt .0 B • Achtung: Nicht jedes Blatt im konkreten Syntaxbaum ist mit einem Terminalsymbol beschriftet!
Beispiel Syntaxbaum Grammatik G = ({+, –, *, /, n, (, )}, A, M, T}, R, A), wobei R = { (A, A+M), (A, A–M), (A, M), (M, M*T), (M, M / T), (M, T), (T, n), (T, ( A ))} A A + M M M * T n T T n n
Bedeutung eines Programms • Jedem syntaktisch korrekten Programm P der Sprache L ist durch eine Semantikfunktion .Leine Bedeutung PL = BP zugeordnet. • Eine Bedeutung kann im Sinne der denotationalen Semantik als eine Funktion BP verstanden werden, die einer gegebenen Startbelegung der Programmvariablen im Programm P ihre Belegung zuordnet nachdem das Programm terminierte, falls das Programm unter der gegebenen Startbelegung terminiert. • Für eine Eingangsbelegung e der Programmvariablen ist BP(e) = a die Belegung dieser Variablen nachdem P terminiert. • Für eine Programmvariable v kann durch e(v) bzw. a(v) ihre Eingangs-/Ausgangsbelegung ermittelt werden. • Beispiel: Programm P: BP(e) = a, wobei folgender Zusammenhang gilt: main() { if(t) c = c+1 }
Analysephase - Frontend • Aufgabe des Frontends: • Q und Z seien zwei Alphabete • Q Q*sei eine Quellsprache • Z Z* sei eine Zwischensprache • Das Frontend berechnet eine Funktion comp : Q* Z {error} mit • Eine mögliche Zwischensprache ist die Menge der Syntaxbäume.
Synthesephase • Im Backend werden in der Regel viele verschieden Zwischensprachen verwendet. • Mögliche Transformationen der Zwischensprachenformate im Backend: • In klassischen Compilern transformiert das Backend bedeutungserhaltend ein Zwischensprachenprogramm irp in ein Zielprogramm zp. D.h. z.B.: irp3AC = zpZC • Klassische Optimierungsverfahren transformieren den Zwischencode ebenfalls bedeutungserhaltend. • Das kann eine sehr starke Einschränkung für Optimierungen sein. Abschwächung: Nur für die interessierenden Ausgabevariablen v müssen die Semantikfunktionen bedeutungsgleich für alle Eingabebelegungen e sein: pQ(e)(v) = pZ(e)(v) . Optimierungen Optimierungen 3-Adress- Code Steuer- fluss- graph .3AC Optimierungen Optimierungen Syntax- baum Zielcode .ZC Optimierungen SSA- Code DAG … .DAG .SSA Optimierungen
Beispiel Ursprüngliches Programm P Transformiertes Programm P' main() { for(i = 0; i++; i < 10) p = p + c; } main() { for(i = 10; i--; i) p = p + c; } Für eine Eingangsbelegung e und BP(e) = a gilt der Zusammenhang: Für eine Eingangsbelegung e und BP'(e) = a gilt der Zusammenhang: Sollte oben angegebene Transformation bedeutungserhaltend sein, ist sie unzulässig. Falls sie nur bedeutungserhaltend für die interessierenden Ausgabevariablen c und p sein soll, ist sie zulässig.
Grundlagen Zwischencodeformate
3-Adress-Code (3AC) • Folge von 3-Adress-Code-Anweisungen mit Markierungen. • Anweisungsarten: • Zuweisungen: • Binäranweisungen: x := y z • Unäranweisungen: x := y • Kopieranweisungen: x:=y, x:=k, @x:=y, x:=@y, x:=&y • Castoperationen: x := (Type) y • Sprunganweisungen: • Unbedingte Sprünge: goto index • Bedingte Sprünge: if x then Label • Funktionsaufrufe: x := call FLabel(y1,…,yn) • Funktionsbeendigung: return x, return • Markierungen: • Funktionslabel: Function Label: • Sprunglabel (mehrere Label hintereinander zulässig): Label1: Labe2:... • Dabei sind: k Konstante, x, y, yi und z Variablen, wobei Variablen unterschieden werden in: • Programmvariablen (lokal, global) • Temporäre Variablen (immer lokal)
Beispiel: 3AC Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11 int f(int n){ int fak = 1; while(n > 0) { fak = fak * n; n = n – 1; } return fak; }
Unterschiedliche 3AC-Abstraktionsniveaus • Hohes Abstraktionsniveau • Feldzugriffe bleiben erhalten. • Ermöglicht z.B. Abhängigkeitsanalyse von Feldzugriffen. • Mittleres Abstraktionsniveau • Feldzugriffe sind in elementare Adressoperationen aufgelöst. • Ermöglicht einfache Registerallokation und Codeauswahl. • Niedriges Abstraktionsniveau • Variablen wurde auf verfügbare Register abgebildet. • Parameterübergaben in Stapeloperationen umgewandelt. • Ermöglicht direkte Übersetzung in Zielcodeerzeugung; bereits architekturabhängig. t0 = 3 t1 = 2*j t2 = i+t1 t3 = j t4 = &a t5 = t4 + t2 t6 = @t5 t7 = &b t8 = t7 + t3 t9 = @t8 t10 = t6 + t9 t11 = t10 + t0 @t5 = t1 t0 = 3 t10 = a[i+2*j] + b[j] a[i+2*j] = t10 + t0 t0 = 3 t1 = 2*j t2 = i+t1 t3 = j t10 = a[t2] + b[t3] a[t2] = t10 + t0 Abstraktionsniveau
Prinzip der Transformation Syntaxbaum in 3-Adress-Code Syntaxbaum für den Ausdruck c := a+2*b: • Für jede Knotenart (jede Art entspricht einer Regel in der Grammatik) im Syntaxbaum gibt es ein Schema, wie aus den 3-Adress-Code-Sequenzen der Söhne die 3-Adress-Code-Sequenz des Vaters gebildet wird. • Generierung des 3-Adress-Codes kann dann Bottom-Up erfolgen. Assign ir= t0:=a t1:=2 t2:=b t3:=t1*t2 t4:=t0+t3 c:=t4 ir=( t0:=a t1:=2 t2:=b t3:=t1*t2 t4:=t0+t3,t4) LVal Expr Quelltextfragment: id=c { int a,b,c; … c := a+2*b; … } IDENT Expr id=c Expr ir=(t0:=a,t0) ir=( t1:=2 t2:=b t3:=t1*t2,t3) IDENT Expr Expr id=a ir=(t1:=2,t1) ir=(t2:=b,t2) INTLIT IDENT iVal=2 id=b
Steuerflussgraph • Modellierung aller potentiell möglichen Abarbeitungsfolgen der Anweisungen einer Funktion. • Steuerflussgraph S = (N,E,q,s): • q ist die Anweisung über die die Funktion betreten wird. • s ist die Anweisung über die die Funktion verlassen wird (Transformation notwendig, falls mehrere return-Anweisungen in einer Funktion existieren). • N ist Knotenmenge: Zu jeder Anweisung der Funktion gibt es genau einen Knoten, • Kantenmenge E N N {0,1} mit • (a,b,0) E gdw. b folgt im 3AC direkt auf a und a ist Zuweisung oder bedingter Sprung oder Funktionsaufruf, • (a,b,1) E gdw. a ist bedingter oder unbedingter Sprung zum Label l und b ist mit Sprunglabel l markiert.
Beispiel Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11 t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Basisblöcke • Ein Basisblock ist eine Folge maximaler Länge von Anweisungen im 3-Adress-Code, für die gilt: • Nur die erste Anweisung darf mit einem Sprunglabel markiert sein (d.h., dass ein Sprung in einen Basisblock nur zu seiner ersten Anweisung führen kann) und • nur die letzte Anweisung darf eine Sprunganweisung sein (d.h., dass alle Anweisungen des Basisblocks ausgeführt werden, wenn die erste Anweisung ausgeführt wird). Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11
Bilden von Basisblöcken im Steuerflussgraphen • Dabei sind: • Pred(n) = {m | x {0,1} und (m,n,x) E} • Succ(n) = {m | x {0,1} und (n,m,x) E} • Basisblöcke bi mit 0 i nsind Äquivalenzklassen, die eine Äquivalenzrelation bb definieren mit abbb gdw. a biund b bi. Eingabe: Steuerflussgraph (N,E,q,s) Ausgabe: Zerlegung der Knoten in Basisblöcke B = {b0,…,bn} currBB = 0; B = while(es ex. k N, das noch keinem Basisblock zugeordnet wurde) do Wähle ein k N, das noch keinem Basisblock zugeordnet wurde bcurrBB = {k} while(es ex. ein Knoten m bcurrBBundn bcurrBBund Pred(n) = {m} und Succ(m) = {n}) do bcurrBB = bcurrBB {m} od while(es ex. ein Knoten m bcurrBBundn bcurrBBund Succ(n) = {m} und Pred(m) = {n}) do bcurrBB = bcurrBB {m} od B := B {bcurrBB} currBB = currBB + 1; od
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 0 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Klassischer Steuerflussgraph • Im Steuerflussgraphen (N,E,q,s) wird N durch die Äquivalenzrelation bb in Äquivalenzklassen [a] = {b | abbb} zerlegt. • Dadurch ergibt sich der klassische Steuerflussgraph (N/bb,E/bb,[q],[s]) mit: • N/bb = {[a] | a N} ist die Menge der Basisblöcke, • (p,q) E/bb gdw. p,q N/bb und a p b q mit (a,b) E, • [q] ist der Basisblock, der q enthält, • [s] ist der Basisblock, der s enthält.
Beispiel t0 := 1 t0 := 1 fak = t0 fak = t0 t1 := n t2 := 0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 1 0 t5 := fak t11 := fak t6 := n return t11 t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond t11 := fak return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond
Notationen • N/bb = {b0,...,bn}: • über b0 wird die Funktion betreten, • über b1 wird die Funktion verlassen. • |bi| Anzahl der Anweisungen in Basisblock i. • Basisblock bi enthält die Anweisungsfolge ir1(i),...,ir|bi|(i). Falls bi aus dem Kontext hervorgeht, auch kurz ir1,...,ir|bi|, • Eine Programmposition ist ein Tupel (i,j), wobei damit die Position nach der j-ten (vor der (j+1)-ten) Anweisung im Basisblock i gemeint ist: • (0,0) Position vor der ersten Anweisung einer Funktion. • (1,|b1|) Position nach der letzten Anweisung einer Funktion. • Ein Anweisungsfolge a1,...,ak ist ein Pfad im klassischen Steuerflussgraphen (N/bb, E/bb,[q],[s]) von Programmposition (i,j) zur Programmposition (m,n), falls: • k: 1 k < k (ak,ak+1) E und • irj+1(i) = a1 und irn(m) = ak.
Beispiel b0 t0 := 1 fak = t0 |b2| = 5 b2 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end ir2(2),ir3(2),ir4(2) = t2:=0,t3:=t1>t2,t4:=not t3 b3 t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond b1 t11 := fak return t11 Programmposition (1,1) Programmposition (3,7) Anweisungen auf dem Pfad von Programmposition (3,7) nach Position (1,1)
Qualität des Modells • Steuerflussgraph modelliert alle möglichen Abarbeitungspfade des Programms, unabhängig davon, ob alle diese Pfade bei der Ausführung des Programms betreten werden können. • Falls Turingmaschine i auf Eingabe i nach n Schritten stoppt, dann gibt es eine Eingabe n, so dass 4 nach 2 ausgeführt wird, sonst nicht. 0 Eingabe von n 2 if (TMi(i)n) then 4 3 … 4 … 1 …
Grundlagen Datenflussanalyse
Definition/Verwendung einer Variablen • Verwendung einer Variablenv:Eine Variable v wird in der Zwischencodeanweisung iri(j) verwendet, falls iri(j) eine der Formen x := v, x := v, x := y v, x := v y, x := @v, @v := x, return v, if v then goto label, x := (Type) v, x := call f(…,v,…) hat. • Definition einer Variablenv:Eine Variable v wird in der Zwischencodeanweisung iri(j) definiert, falls iri(j) die Form v := … hat. • Eine definierende Anweisung für v ist eine Anweisung, die v definiert. • Eine verwendende Anweisung für v ist eine Anweisung, die v verwendet. • Erreichende Definition: Die Definition einer Programmvariablen v erreicht eine Verwendung von v, falls es einen Pfad im Steuerflussgraphen von dieser Definition zur Verwendung gibt, auf dem keine andere Definition von v liegt.
Beispiel Erreichende Definitionen t0 := 1 fak = t0 t1 := n 0 t2 := 0 3 t3 := t1 > t2 t0 := 1 t4 := not t3 4 fak = t0 6 if t4 then while_0_end t1 := n 7 t2 := 0 t11 := fak t5 := fak 8 t3 := t1 > t2 t6 := n return t11 9 t4 := not t3 t7 := t5 * t6 10 if t4 then while_0_end fak := t7 Definition zu einer Verwendung ist in unverzweigtem Steuerfluss einfach zu finden. t8 := n t9 := 1 t10 := t8 – t9 n := t10 Definition zu einer Verwendung ist bei Verzweigungen im Steuerfluss schwieriger zu finden.
Grundidee Datenflussanalyse am Beispiel der Erreichenden Definitionen Es gibt eingehende Informationen I in einen Knoten: I = in(2) 0 t2 := 2 Durch die Anweisung(en) in einem Knoten werden Informationen zerstört: I := I – Kill(2) 1 t1 := 1 in(2) = {(t2,0),(t1,1)} Kill(2) = {(t1,i) | i } 2 t1 := n Gen(2) = {(t1,2)} Durch die Anweisung(en) in einem Knoten werden neue Informationen generiert: I := IGen(2) out(2) = {(t2,0),(t1,2)} 3 t3 := t1 > t2 Es entstehen ausgehende Informationen an einen Knoten: out(2) 4 if t3 then while_0_end
Transferfunktion für einen Basisblock • Steuerfluss in einem Basisblock i mit den Anweisungen ir0(i),…,ir#i(i) ist bekannt. • Damit ist die Transferfunktion für diesen Basisblock: • Vereinfachung der Transferfunktion zu out(i) = (in(i) – kill(i)) gen(i). in(i) - Kill(0) Gen(0) 0 t2 := 2 1 - Kill(1) Gen(1) t1 := 1 2 - Kill(2) Gen(2) t1 := n - Kill(3) Gen(3) 3 t3 := t1 > t2 - Kill(4) Gen(4) 4 if t3 then while_0_end out(i)
Datenflussanalyse bei Verzweigungen im Steuerflussgraphen • Ausgehende Informationen out(i) gelangen zu jedem Steuerflussnachfolger von i. • Treffen Informationen von mehreren Steuerflussvorgängern zusammen, müssen diese zu einer eingehenden Information zusammengefasst werden. Transformiert eingehende Informationen … t3 := t1 + t2 … Ausgehende Informationen I I Transformiert eingehende Informationen I … t0 := t1 – t9 … … t2 := t3 * t5 … Transformiert eingehende Informationen Hier müssen Informationen kombiniert werden
Grundprinzip Datenflussanalyse • Informationen I breiten sich entweder mit oder gegen den Steuerfluss aus. • Für jeden Knoten b gibt es: • Eingehenden Informationen: in(b), • ausgehende Informationen: out(b), • erzeugte Informationen: gen(b), • zerstörte Informationen: kill(b). • Abhängig von der Ausbreitungsrichtung der Informationen sind: • Vorwärtsanalyse: • in(b) = out(b1) out(b2) … out(bn), wobei die bi die Steuerflussvorgänger von b sind und • out(b) = (in(b) – kill(b)) gen(b) (Transferfunktion genannt) • Rückwärtsanalyse: • out(b) = in(b1) in(b2) … in(bn), wobei die bi die Steuerflussnachfolger von b sind und • in(b) = (out(b) – kill(b)) gen(b) (Transferfunktion genannt) • Durch den Steuerflussgraphen wird eine Mengengleichungssystem definiert. • Falls der Steuerflussgraph Zyklen enthält, ist das Mengengleichungssystem rekursiv; Lösung durch Fixpunktiteration.
Beispiel: Erreichende Definitionen • Bei Verwendung einer Programmvariablen an Position (i,j) interessiert, an welchen Programmpositionen der Wert der Variablen definiert wurde. • Menge aller Informationen: (()V) • Vorwärtsanalyse mit := • gen(bi) := {((i,j),v) | irj(i) ist letzte Definition von v in bi} • kill(bi) := {((m,n),v) | m, n und bi enthält eine Definition von v} gen(b2) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} kill(b2) = {((m,n),t0), ((m,n),t1), ((m,n),t2)} in(b2) = in(b3) = in(b4) = in(b1) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,1),t1), ((2,2),t2), ((3,1),t0), ((2,0),t0), ((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((3,1),t0)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} b0 t0 := a t1 := b t2 := t0 + t1 b2 gen(b3) = {((3,1),t0)} kill(b3) = {((m,n),t0)} out(b0) = out(b2) = out(b3) = out(b4) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((3,1),t0)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,1),t1), ((2,2),t2), ((3,1),t0)} {((2,0),t0), ((2,1),t1)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} b3 t0 := t2 – t0 t0 := b b4 t3 := t2 t4 := t3 * t2 t2 := t2 – t4 b1 gen(b4) = {((4,0),t3), ((4,1),t4), ((4,2),t2)} kill(b4) = {((m,n),t3), ((m,n),t4), ((m,n),t2)}
Beispiel: Lebendigkeit • An einer Programmposition (i,j) interessiert für eine Variable v, ob es einen Pfad zu einer Programmposition gibt, an der v verwendet wird, ohne auf diesem Pfad definiert zu werden. • Menge aller Informationen: (V) • Rückwärtsanalyse mit := • gen(bi) := {v | irj(i) ist Verwendung von v und für alle 0 k < j gilt: irk(i) ist keine Defintion von v} • kill(bi) := {v | v wird in bi definiert} gen(b2) = {a,b} kill(b2) = {t0,t1,t2} b0 in(b0) = in(b2) in(b2) = (in(b3) in(b4) – kill(b2)) gen(b2) in(b3) = (in(b1) – kill(b3)) gen(b3) in(b4) = ((in(b4) in(b1)) – kill(b4)) gen(b4) b2 t0 := a t1 := b t2 := t0 + t1 gen(b4) = {t2} kill(b4) = {t2,t3,t4} gen(b3) = {b, t2, t0} kill(b3) = {t0} b3 t0 := t2 – t0 t0 := b b4 t3 := t2 t4 := t3 * t2 t2 := t2 – t4 b1 gen(b1) = kill(b1) =
Grundlagen Zwischencodeformate (fortgesetzt)
SSA-Code • Eigenschaften: • Jede Variable wird statisch genau einmal definiert. Vorteil für viele Analysen: Jede Benutzung einer Variablen hat nur genau eine Definition. • An solchen Punkten, an denen der Steuerfluss zusammentrifft, müssen -Funktionen eingefügt werden, die verschiedene Instanzen derselben Variablen zusammenführen. • Zwei Schritte zur Konstruktion von SSA-Code: • Programmpositionen finden, an denen eine -Funktion eingefügt werden muss. • Variablenumbenennung (Indizierung), um eindeutige Namen für jede Definition zu erzeugen. v := … v4 := … v := … v1 := … v := … v2 := … … := v … := v4 … := v v := … := v v3 := (v1,v2) … := v3 … := v v := … := v v5 := (v3,v4) … := v5
Dominator • Block x dominiert Block y (x dom y) genau dann im Steuerflussgraphen, wenn jeder Pfad vom Startknoten zum Block y durch Block x führt. • Doms(y) := {x | x dom y} • Block x dominiert Block y streng (x sdom y), wenn • x dom y und • x y. • Beispiel: • 5 dominiert 5, 6, 7, 8 • 5 dominiert streng 6, 7, 8 • 6 dominiert 6 • 6 dominiert streng • nicht dominiert von 5: 0, 1, 2, 3, 4, 9 • nicht streng dominiert von 5: 0, 1, 2, 3, 4, 5, 9 • Doms(3) = {0, 2, 3} • Doms(4) = {0, 4} 0 2 5 3 6 7 4 8 9 1
Berechnung von Doms • Berechnung der Information Doms(x) für Steuerflussgraphen S = (N,E,q,s) durch eine Datenflussanalyse mit den Parametern: • Menge aller Informationen: (N) • Vorwärtsanalyse mit := • gen(bi) := {i} • kill(bi) := • Starten mit in(bi) = N für i > 0 und in(b0) =