1 / 138

Optimierungstechniken in modernen Compilern

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.

rafal
Download Presentation

Optimierungstechniken in modernen Compilern

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Optimierungstechniken in modernen Compilern Grundlagen

  2. Grundlagen Aufbau eines Compilers Überblick über die Analysephase

  3. 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

  4. 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.

  5. 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  *

  6. 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)

  7. 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.

  8. 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}

  9. 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!

  10. 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

  11. Bedeutung eines Programms • Jedem syntaktisch korrekten Programm P der Sprache L ist durch eine Semantikfunktion .Leine Bedeutung PL = 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 }

  12. 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.

  13. 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.: irp3AC = zpZC • 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: pQ(e)(v) = pZ(e)(v) . Optimierungen Optimierungen 3-Adress- Code Steuer- fluss- graph .3AC Optimierungen Optimierungen Syntax- baum Zielcode .ZC Optimierungen SSA- Code DAG … .DAG .SSA Optimierungen

  14. 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.

  15. Grundlagen Zwischencodeformate

  16. 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)

  17. 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; }

  18. 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

  19. 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

  20. 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.

  21. 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

  22. 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

  23. 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

  24. 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

  25. 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

  26. 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

  27. 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

  28. 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

  29. 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

  30. 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

  31. 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

  32. 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

  33. 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.

  34. 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

  35. 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.

  36. 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)

  37. 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 …

  38. Grundlagen Datenflussanalyse

  39. 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.

  40. 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.

  41. 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 := IGen(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

  42. 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)

  43. 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

  44. 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.

  45. 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)}

  46. 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) = 

  47. Grundlagen Zwischencodeformate (fortgesetzt)

  48. 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

  49. 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

  50. 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) = 

More Related