Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für DSPs und Mikrocontroller.

Ähnliche Präsentationen


Präsentation zum Thema: "Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für DSPs und Mikrocontroller."—  Präsentation transkript:

1 Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für DSPs und Mikrocontroller

2 Optimierungstechniken in modernen CompilernGrundlagen2 Eigenschaften von DSP-Architekturen Irregulärer Datenpfad: Spezialregister Abhängig von der verwendeten Operation wird ein bestimmtes Register benötigt. Eingeschränkte Form von ILP vorhanden Instruktionsformat erzeugt zusätzliche Einschränkungen Pipeline ist eventuell vorhanden X-SpeicherY-Speicher axayafmx my mf ALU +/- ALU armr

3 Optimierungstechniken in modernen CompilernGrundlagen3 Drei Phasen bei Zielcodeerzeugung Codeauswahl (CS): Finden einer Abbildung der Zwischencodebefehle auf die Maschinenbefehle der Zielarchitektur. Registerallokation (RA): Finden geeigneter Speicherklassen für die Variablen des Zwischencodes. Ablaufplanung und Bindung (SC): Finden einer geeigneten Ausführungsreihenfolge der Operationen und einer geeigneten Ausführungseinheit für jede Operation.

4 Optimierungstechniken in modernen CompilernGrundlagen4 Problem der Reihenfolge RA vor SC: RA erzeugt zusätzliche Abhängigkeiten durch die genutzten Register. SC vor RA: Hoher Registerdruck kann entstehen. Evtl. erforderlicher Spill-Code muss erneut geplant werden: Ablaufplanung besteht deshalb aus Prepass und Postpass. CS vor SC: Schlecht parallelisierbarer Code kann entstehen. SC vor CS: Nicht alle durch die Architektur vorgegebenen Einschränkungen können bei der Parallelisierung beachtet werden. Konsequenz: Code ist schlecht parallelisiert; Ressourcen bleiben ungenutzt. RA vor CS: Durch Registerzuordnung können gewisse Zieloperationen nicht mehr genutzt werden, weil sich die Werte in den falschen Registern befinden. CS vor RA: Einschränkung der Registerallokation, weil Ergebnisse in ganz bestimmte Register geschrieben werden. Konsequenz kann hoher Registerdruck sein.

5 Vorlesung, Wintersemester 2009/10M. Schölzel 5 Optimierungstechniken für DSPs und Mikrocontroller Ablaufplanung

6 Optimierungstechniken in modernen CompilernGrundlagen6 Klassifizierung von Planungsalgorithmen Lokal – Planung der Operationen innerhalb eines Basisblocks: Ressourcenbeschränkt: List-Scheduling Zeitbeschränkt: ASAP, ALAP, Force-Directed-Scheduling Global – Planung der Operationen über Basisblockgrenzen hinaus: Azyklisch – Planung der Operationen in schleifenfreiem Code: Strukturorientiert: Region-based-scheduling, percolation-scheduling, global- scheduling Profilorientiert: Trace-Schedling, Superblock-Scheduling, Hyperblock- Schedling Zyklisch – Planung der Operationen in Schleifen: Loop-Unrolling Modulo-Scheduling

7 Optimierungstechniken in modernen CompilernGrundlagen7 Lokale Ablaufplanung Gegeben ist sequentieller Code für einen Basisblock - z.B. DAG - mit abgeschlossener Registerallokation. Ziel: Festlegen eines Ausführungszeitpunktes (v) für jede Operation v, so dass keine Datenabhängigkeiten verletzt werden unter Berücksichtigung einer der folgenden zwei grundsätzlichen Nebenbedingungen: Ressourcenbeschränktes Scheduling (z.B. List-Scheduling): Gegeben ist eine Menge von Ressourcen. Schedule darf zu keinem Zeitpunkt mehr Ressourcen erfordern als vorhanden sind. Optimierungsziel: Minimierung der Schedulelänge. Zeitbeschränktes Scheduling (z.B: ASAP, ALAP, Force-Directed- Scheduling): Gegeben ist eine Schedulelänge. Schedule darf die gegebene Schedulelänge nicht überschreiten. Optimierungsziel: Minimierung des Ressourcenbedarfs.

8 Optimierungstechniken in modernen CompilernGrundlagen8 Modellierung der Abhängigkeiten in einem Basisblock durch einen DAG Totale Ordnung einer Anweisungsfolge im 3-Adress-Code wird zu einer partiellen Ordnung abgeschwächt. G = (N, E, A, ord, label) sei ein gerichteter azyklischer Graph (DAG): Knoten repräsentieren Operationen in den 3-Adress-Code-Anweisungen. Kanten in E repräsentieren durch skalare Variablen entstehenden Flussabhängigkeiten. Kanten in A repräsentieren durch Speicherzugriffe entstehende Datenabhängigkeiten. ord : E modelliert die Reihenfolge der eingehenden Kanten (Operanden) eines Knotens. Bei ord(e) < ord(e') ist e linker und e' rechter Operand. label : N {const k, store, load, write a, read a, | k, a +, ist Operation im 3-Adress-Code} ist eine Beschriftung der Knoten mit Operationen.

9 Optimierungstechniken in modernen CompilernGrundlagen9 Konstruktion eines DAGs zu einem Basisblock Eingabe: Basisblock als Folge von 3-Adress-Code-Anweisungen ir 0,…,ir n Ausgabe: DAG (N, E, A, ord, label) Algorithmus: Hilfsfunktionen: N :=, E :=, A :=, ord :=, label := S := // Enthält für die aktuelle Situation bei der Übersetzung für jede Variable // des Zwischencodes u.a. den Knoten im DAG, der ihren Wert berechnet for i = 0 to n do switch(ir i ) case "x := y z": TranslateBinStmt(ir i ); break; case "x := y : TranslateUnaStmt(ir i ); break; case "x := y" : TranslateCopy(ir i ); break; case := y" : TranslateStore(ir i ); break; case "x : TranslateLoad(ir i ); break; end od Für jedes (a,n,W) S mit a ist Programmvariable erzeuge Knoten m mit label(m) = write a, N := N {m}, E := E {(n,m)}, A := A {(h,m) | label(h) = read a oder label(h) = load oder label(h) = store} findVar(var) if (var,n,x) S then return n else return 0 fi findLabel(label,l,r) if n N mit Beschriftung label und ((l,n) E oder l = 0) und ((r,n) E oder r = 0) then return n else return 0 fi

10 Optimierungstechniken in modernen CompilernGrundlagen10 Übersetzung von Kopieranweisungen TranslateCopy(x := y) if findVar(y) = 0 then Erzeuge Knoten n mit label(n) = read y // passiert nur, wenn y Programmvariable N := N {n} S := S {(y,n,R)} fi l := findVar(y) S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,l,W)} TranslateConst(x := k) if findLabel(const k,0,0) = 0 then Erzeuge Knoten n mit label(n) = const k N := N {n} fi n := findLabel(const k,0,0) S := S {(x,n,W)}

11 Optimierungstechniken in modernen CompilernGrundlagen11 Übersetzung binärer und unärer Operationen TranslateBinStmt(x := y z) l := findVar(y) r := findVar(z) if n N mit label(n) = und (l,n) E und (r,n) E und not (ord(r,n) < ord(l,n)) then m := n else Erzeuge einen Knoten m mit Beschriftung N := N {m} E := E {(l,m),(r,m)} ord((l,m)) := 0; ord((r,m)) := 0, falls kommutativ, sonst ord((r,m)) := 1 fi S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,m,W)} TranslateUnaStmt(x := y) l := findVar(y) // immer erfolgreich if findLabel(, l) then m := findLabel(, l) else Erzeuge neuen Knoten m mit label(m) = N := N {m} E := E {(l,m)} fi S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,m,W)}

12 Optimierungstechniken in modernen CompilernGrundlagen12 Übersetzung von Speicherzugriffen := y) l := findVar(x) r := findVar(y) Erzeuge neuen Knoten n mit label(n)=store N := N {n} E := E {(l,n),(r,n)}; ord((l,n)):=0; ord((r,n)):=1; A := A {(k,n) | k N und label(k)=store oder label(k)=load oder label(k) = read a oder label(k) = write a} TranslateLoad(x l := findVar(y) Erzeuge neuen Knoten n mit label(n)=load N := N {n} E := E {(l,n)} S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,n,W)} A := A {(k,n) | k N und label(k) = store oder label(k) = write a}

13 Optimierungstechniken in modernen CompilernGrundlagen13 Beispiel Beispiel: a[i] = b[i] + a[j] t0 := i t1 := 4 t2 := t1 * t0 t3 := &b t4 := t3 + t2 t5 t6 := j t7 := 4 t8 := t7 * t6 t9 := &a t10 := t9 + t8 t11 t12 := t5 + t11 t13 := i t14 := 4 t15 := t14 * t13 t16 := &a t17 := t16 + := t12 1 read i 2 const 4 3 (t0,1,W) S (t1,2,W) (t2,3,W) * 4 const &b (t3,4,W) 5 + (t4,5,W) 6 load (t5,6,W) 7 read j (i,1,R) (t6,7,W) (j,7,R) (t7,2,W) 8 * (t8,8,W) 9 const &a (t9,9,W) 10 + (t10,10,W) 11 load (t11,11,W) 12 + (t12,12,W) (t13,1,W) (t14,2,W) (t15,3,W) (t16,9,W) 13 + (t17,13,W) 14 store

14 Optimierungstechniken in modernen CompilernGrundlagen14 ASAP/ALAP ASAP (As Soon As Possible) und ALAP (As Late As Possible) liefern keine besonders guten Ergebnisse. Einsatz eher in der High-Level-Synthese. Können beim ressourcenbeschränkten List-Scheduling aber zum steuern der Heuristik verwendet werden:

15 Optimierungstechniken in modernen CompilernGrundlagen15 Ressourcenmodellierung durch Reservierungstabellen Reservierungstabelle T v für jede Operationsart v: T v R {0,...,l}, wobei R die Menge aller Ressourcen ist. (r,t) T v bedeutet, dass die Ressource r genau t Takte nach dem Starten der Operation v benötigt wird. Zu einer Operationsart kann eine Menge von Reservierungstabellen v (R {0,...,l}) gehören, die eine Ausführung der Operation auf verschiedenen Ressourcen beschreiben. Globale Reservierungstabelle T G R {0,...,m}, wobei m die maximale Länge des Ablaufplans ist. Eine Operation der Art v kann zum Zeitpunkt p gestartet werden, falls: eine Reservierungstabelle T v v existiert und für alle (r,t) T v gilt: (r,p+t) T G. Zu jeder Operationsart v ist durch die Funktion delay(v) festgelegt, wie viele Takte nach dem Starten einer Operation der Art v eine datenabhängige Operation gestartet werden kann.

16 Optimierungstechniken in modernen CompilernGrundlagen16 Maschinenmodell für ressourcenbeschränkte Ablaufplanung Modellierung der Reservierungstabellen: R = {ALU 1, ALU 2, ALU 3, FPU, MMU}. ALU = {{(ALU 1,0)}, {(ALU 2,0)}, {(ALU 3,0)}}delay(ALU) = 1 MUL = {{(ALU 1,0), (ALU 1,1)}}delay(MUL) = 2 FPU = {{(FPU,0),(FPU,1),(FPU,2)}}delay(FPU) = 3 MMU = {{(MMU,0),(MMU,1)}}delay(MMU) = 2 Registerbank ALU Speicher FE/DE DE/EX EX/MEM Steuerwerk MMU ALU Speicher Schematischer Aufbau des zugehörigen VLIW Prozessors: FPUALU

17 Optimierungstechniken in modernen CompilernGrundlagen17 Ablaufplan Gegeben ist ein DAG G = (N, E, A, ord, label), dessen Knotenbeschriftung Operationsarten sind. A kann auch Datenabhängigkeiten modellieren, die z.B. durch eine abgeschlossene Registerallokation entstehen. Ein Ablaufplan : N legt für jede Operation einen Startzeitpunkt fest. Der Ablaufplan ist gültig, wenn: Für alle v N: (v) 0 und Aus (u,v) E A folgt: (v) (u) + delay(label(u)) Es darf keine Ressourcenkonflikte zwischen den Operationen geben. Länge eines Schedules : length( ) = max{ (v) + delay(label(v)) | v N} Finden eines Schedules minimaler Länge ist NP-vollständig. List-Scheduling ist eine Heuristik, die in der Praxis sehr gute Ergebnisse liefert.

18 Optimierungstechniken in modernen CompilernGrundlagen18 List-Scheduling-Algorithmus Eingabe: DAG (N, E, A, ord, label) und delay Ausgabe: Schedule maxDelay := max{delay(t) | t T }; i = 0; ready(0) = {u | v N: (v,u) (E A)}; ready(k) = für alle 1 k maxDelay; scheduled = ; while (scheduled N) do while ein Knoten von aus ready(0) ohne Ressourcenkonflikt zum Zeitpunkt i gestartet werden do Wähle einen Knoten v ready(0) der ohne Konflikt zum Zeitpunkt i starten kann (v) := i; scheduled := scheduled {v}; for each u (N – ready(0) –... – ready(maxDelay) - scheduled) do if((v,u) (E A) and w N: (w,u) (E A) w scheduled) then ready(delay(v)) := ready(delay(v)) {u} fi od i = i + 1; for k = 0 to maxDelay-1 do ready(k) := ready(k+1) od; ready(maxDelay) := od

19 Optimierungstechniken in modernen CompilernGrundlagen19 Beispiel Modellierung der Reservierungstabellen: R = {ALU 1, ALU 2, ALU 3, FPU, MMU}. ALU = {{(ALU 1,0)}, {(ALU 2,0)}, {(ALU 3,0)}}delay(ALU) = 1 MUL = {{(ALU 1,0), (ALU 1,1)}}delay(MUL) = 2 FPU = {{(FPU,0),(FPU,1),(FPU,2)}}delay(FPU) = 3 MMU = {{(MMU,0),(MMU,1)}}delay(MMU) = 2 ALU MUL ALU MUL ALU ALU 1 ALU 2 ALU 3 FPUMMU Globale Ressourcentabelle:Ready 0, 1,

20 Optimierungstechniken in modernen CompilernGrundlagen20 Globales Scheduling Verschieben von Operationen aus einem Basisblock in einen anderen. Neben Beachtung der Datenabhängigkeiten auch Beachtung der Steuerflussabhängigkeiten erforderlich: Alle Operationen, die im ursprünglichen Programm ausgeführt werden, müssen auch im optimierten Programm ausgeführt werden. Spekulativ ausgeführte Operationen im optimierten Programm, dürfen keine ungewollten Seiteneffekte verursachen.

21 Optimierungstechniken in modernen CompilernGrundlagen21 Wiederholung Definitionen 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. Block x postdominiert Block y (x pdom y) genau dann im Steuerflussgraphen, wenn jeder Pfad vom Block y zum Stoppknoten durch Block x führt. Block x und Block y sind genau dann steuerflussäquivalent, wenn x dom y und y pdom x. x z y x dom y und x dom z. z pdom x und z pdom y. x und z sind steuerflussäquivalent.

22 Optimierungstechniken in modernen CompilernGrundlagen22 Aufwärtsverschiebung von Operationen Eine Operation u wird aus einem Basisblock s in einen Basisblock d verschoben und d ist Steuerflussvorfahre von s. Voraussetzung: Es werden keine Datenabhängigkeiten verletzt. Die durch u definierte Variable ist in d nicht lebendig. Unterscheidung folgender Fälle: s und d sind steuerflussäquivalent: Verschiebung ist unproblematisch. d dom s und s nicht pdom d: u kann ausgeführt werden, obwohl es nicht ausgeführt werden sollte (spekulkative Ausführung). Nur zulässig, wenn u keine Seiteneffekte verursacht. Nur sinnvoll, wenn u in d "umsonst" ausgeführt werden kann. d nicht dom s und s pdom d: Auf allen Pfaden nach s muss an den Positionen, von denen aus d nicht mehr erreicht werden kann eine Kopie der Operation u eingefügt werden (Korrekturcode). Dabei ist zu beachten: –Die Operanden der Kopie von u müssen dieselben sein, wie im Block s. –Das Ergebnis von u darf keinen noch benötigten Wert überschreiben. –Das Ergebnis von u wird nicht überschrieben, bevor es den Block s erreicht. Einige Ausführungspfade im Programm können langsamer werden. Nur sinnvoll, wenn die optimierten Pfade häufiger ausgeführt werden als die verlangsamten Pfade.

23 Optimierungstechniken in modernen CompilernGrundlagen23 Warum ändern sich Datenabhängigkeiten? Code Motion kann Datenabhängigkeiten verändern: In dem Beispiel entsteht eine WAW-Abhängigkeit: x=1 kann nicht vor x=2 verschoben werden. Außerdem ändert sich der Lebendigkeitsbereich von x: x=1 kann nach der Transformation nicht in den Block mit x=2 verschoben werden.... x = 1 x = 2... x = 2... x = 1

24 Optimierungstechniken in modernen CompilernGrundlagen24 Beispiel load [R1] R6 nop beqz R6, B 3 load [R2] R7 nop store R7 [R3] load [R4] R8 nop add r8, r8 r8 store R8 [R5] load [R1] R6 || load [R4] R8 load [R2] R7 beqz R6, B 3 || add r8, r8 r8 store R7 [R3] store R8 [R5] Ursprünglicher SteuerflussgraphTransformierter Steuerflussgraph Architektureigenschaften: load hat Latenz von 2; jede andere Operation 1. Zwei Operationen können parallel ausgeführt werden.

25 Optimierungstechniken in modernen CompilernGrundlagen25 Abwärtsverschiebung von Operationen Eine Operation u wird aus einem Basisblock s in einen Basisblock d verschoben und d ist Steuerflussnachfahre von s. Voraussetzung: Es werden keine Datenabhängigkeiten verletzt. Unterscheidung folgender Fälle: s und d sind steuerflussäquivalent: Verschiebung ist unproblematisch. s nicht dom d und d pdom s: u kann ausgeführt werden, obwohl es nicht ausgeführt werden sollte (spekulkative Ausführung). Nur zulässig, wenn u keine Seiteneffekte verursacht. Alternativ: Kopien der Blöcke von s nach d erstellen und nur in die Kopie von d die Operation u verschieben. s dom d und d nicht pdom s: Auf allen Pfaden von s, muss an den Positionen, von denen aus d nicht mehr erreicht werden kann, eine Kopie der Operation u eingefügt werden (Korrekturcode). Dabei ist zu beachten: –Die Operanden der Kopie von u müssen dieselben sein, wie im Block s. –Das Ergebnis von u darf keinen noch benötigten Wert überschreiben. Einige Ausführungspfade im Programm können langsamer werden. Nur sinnvoll, wenn die optimierten Pfade häufiger ausgeführt werden als die verlangsamten Pfade.

26 Optimierungstechniken in modernen CompilernGrundlagen26 Beispiel load [R1] R6 || load [R4] R8 load [R2] R7 beqz R6, B 3 || add r8, r8 r8 store R7 [R3] store R8 [R5] load [R1] R6 || load [R4] R8 load [R2] R7 beqz R6, B 3 || add r8, r8 r8 store R7 [R3] || store R8 [R5] store R8 [R5] Anlegen einer Kopie und löschen des leeren Basisblocks.

27 Optimierungstechniken in modernen CompilernGrundlagen27 Zusammenfassung Codeverschiebung Verschiebung zwischen steuerflussäquivalenten Blöcken: Kein Korrekturcode und keine spekulative Ausführung von Operationen. Aufwärts-/Abwärtsverschiebung von s nach d, wobei s nicht pdom/dom d: Spekulative Ausführung von Operationen, die auf einigen Pfaden überflüssigerweise ausgeführt werden. Aufwärts-/Abwärtsverschiebung von s nach d, wobei s nicht dom/pdom d: Korrekturcode erforderlich. Ausführung auf einigen Pfaden kann verlangsamt werden. Aufwärts-/Abwärtsverschiebung von s nach d, wobei s nicht dom/pdom d und s nicht pdom/dom d: Kombination der letzten beiden Fälle mit allen Nachteilen.

28 Optimierungstechniken in modernen CompilernGrundlagen28 Region-Based-Scheduling Voraussetzung: Steuerflussgraph kann hierarchisch aus Regionen aufgebaut werden. Region-Based-Scheduling unterstützt: Aufwärtsverschiebung von Operationen aus s in einen steuerflussäquivalenten Basisblock d. Aufwärtsverschiebung von Operationen aus s um eine Verzweigung zu dominierenden Vorgängerblöcken d; d.h. d dom s und s nicht pdom d. In beiden Fällen ist kein Korrekturcode erforderlich.

29 Optimierungstechniken in modernen CompilernGrundlagen29 Region Eine Region in einem Steuerflussgraph (N,E) ist ein Subgraph (N',E') für den gilt: N' N E' E Es existiert ein Knoten h N' mit N' dom(h) Falls von einem Knoten m ein Knoten n N' erreicht werden kann, ohne dass auf diesem Pfad der Knoten h betreten wird, dann ist m N'. E' ist die Menge aller Kanten zwischen den Knoten in N', die nicht h als Ziel haben. Es ergibt sich eine Hierarchie von Regionen in einer Funktion: die gesamte Funktion ist eine Region, jede Schleife in der Funktion bildet eine eigene Region. Rücksprungkanten einer Schleife zu ihrem Kopf h werden ignoriert, wodurch der Steuerflussgraph azyklisch wird.

30 Optimierungstechniken in modernen CompilernGrundlagen30 Beispiel Region B1B1 B2B2 B3B3 B5B5 R2R2 R1R1 B7B7 B1B1 B2B2 B3B3 B5B5 B6B6 B4B4 B7B7 B1B1 R2R2 R3R3 B7B7

31 Optimierungstechniken in modernen CompilernGrundlagen31 Scheduling-Algorithmus Eingabe: Steuerflussgraph und Liste der Regionen Ausgabe: Schedule mit Zuordnung von Operationen zu Basisblöcken for each Region R, wobei innere Regionen zuerst abgearbeitet werden do Berechne Datenabhängigkeiten zwischen den Operationen for each Basisblock B in R in topologischer Sortierung do // Steuerflussäquivalente Blöcke CE = {x | x R und x ist steuerflussäquivalent mit B} // dominierte Nachfolger DS = {x | x R und b CE: b dom x und b CE: (b,x) E} CB = CE DS t = 0 while nicht alle Operationen aus B sind verplant do CI = Operationen in CB, deren Vorgänger bereits verplant wurden for each u CI in Prioritätsreihenfolge do if u hat keine Ressourcenkonflikte then (u) = (t,B); Aktualisiere Ressourcenverwendung Aktualisiere Datenabhängigkeiten fi od t = t + 1; od

32 Vorlesung, Wintersemester 2009/10M. Schölzel 32 Optimierungstechniken für DSPs und Mikrocontroller Registerallokation

33 Optimierungstechniken in modernen CompilernGrundlagen33 Basisblocklokale Registerallokation Ziel: Abbildung der temporären Variablen eines Zwischencodeprogramms auf eine beschränkte Anzahl von Prozessorregistern. Klassifizierung der Registerallokation: Lokal: Auf den Basisblock beschränkt. Global: Für Funktionen oder das gesamte Programm. Vorgehensweise bei der Registerallokation hängt stark von der Zielarchitektur ab: Register-/Register-Architektur oder Register-/Speicher-Architektur, 2-Adress- oder 3-Adress-Architektur, Universeller Registersatz oder Spezialregistersatz, Flache oder tiefe Registerhierarchie.

34 Optimierungstechniken in modernen CompilernGrundlagen34 Verwaltungsstrukturen für die Registerallokation V ist die Menge aller Variablen im 3-Adress-Code. Registerdeskriptor rd : {0,…,RegNum – 1} (V {w,r}) speichert für jedes Register die Menge der Variablen, deren Werte sich im Register befinden sowie deren Lese-/Schreibzustand. Speicherdeskriptor: sd: V speichert für jede im Speicher abgelegte Variable die Speicheradresse (absolut für globale und relativ für lokale Variablen). Belegungssituationen der Verwaltungsstrukturen: Für jede globale Variable a ist durch sd(a) immer ein Speicherplatz festgelegt. Bei Übersetzung einer Funktion f ist außerdem für jede lokale Variable a in f durch sd(a) eine relative Adresse festgelegt. Für eine temporäre Variabel existiert kein Eintrag in rd oder sd, nur ein Eintrag in rd oder nur ein Eintrag in sd oder ein Eintrag in rd und sd. Für eine Programmvariable existiert immer ein Eintrag in sd möglicherweise auch ein Eintrag in rd; dann befindet sich der aktuelle Wert der Variablen im Register.

35 Optimierungstechniken in modernen CompilernGrundlagen35 Hilfsfunktionen Hilfsfunktionen für eine Variable v: isLocal(v) = True gdw. der Speicherplatz für v im Stapel ist. addr(v) ist die Adresse des Speicherplatzes von v oder die relative Adresse, die während des Aufbaus der Symboltabelle festgelegt wurde. getNextFreeLocalAddress(): Liefert die nächste freie relative Adresse im Stapel getFreeReg(): liefert den Namen eines Registers, in das ein neuer Wert geschrieben werden kann. getVarInReg(v): Erzeugt den erforderlichen Zielcode, um den Wert der Variablen v in einem Register bereitzustellen. lockReg(r): Verhindert, dass der Inhalt des Registers r bei folgenden Registeranforderungen ausgelagert wird. unlockReg(r): Klar setRegDeskr(r,x): Danach gilt (x,w) = rd(r) und für alle i: 1 i RegNum und i r (x,w) rd(i) und (x,r) rd(i). delete(x,r): Danach gilt: (x,w) rd(r) und (x,r) rd(r). clear(): Löscht Einträge im Speicher- und Registerdeskriptor. saveRegs(): Sichert Register im Speicher, die aktualisierte Werte von Programmvariablen enthalten.

36 Optimierungstechniken in modernen CompilernGrundlagen36 Implementierung von getFreeReg Eingabe: keine Ausgabe: Name eines Registers, dessen Inhalt überschrieben werden kann Algorithmus getFreeReg: Falls ein i existiert mit 1 i RegNum und rd(i) = dann return i Falls ein i existiert mit 1 i RegNum und für alle (v,x) rd(i) gilt x = r, dann rd(i) :=, return i Wähle ein s mit 1 s RegNum und s ist nicht gesperrt Spill(s) return s Eingabe: Registernummer s Ausgabe: Zielcode zum Auslagern des Registerwertes Algorithmus Spill: for each (v,w) rd(s) do if sd(v) undefiniert then addr = getNextFreeLocalAddr() outCode("mov s,[bp-addr]") sd(v) := addr else if v ist global then outCode("mov s,[sd(v)]") else outCode("mov s,[bp-sd(v)]") fi od rd(s) :=

37 Optimierungstechniken in modernen CompilernGrundlagen37 Beispiel: getFreeReg Aufruf: getFreeReg() mit Registerdeskriptor: i rd(i) (t0,w), (a,r) (t2,w), (c,r) (t15,w), (p,r)15 … i rd(i) (t0,w), (t16,w) (t1,w), (a,w) (t2,w), (t18,w) (t15,w), (t31,w)15 … Rückgabewert: r1 Erzeugter Spillcode: mov r1,[bp-sd(t1)] mov r1,[sd(a)] Neuer Registerdeskriptor:Registerdeskriptor: Rückgabewert: r1 Erzeugter Spillcode: Keiner Neuer Registerdeskriptor:Registerdeskriptor: i rd(i) (t0,w), (a,r) (t2,w), (c,r) (t15,w), (p,r)15 … i rd(i) (t0,w), (t16,w) (t2,w), (t18,w) (t15,w), (t31,w)15 …

38 Optimierungstechniken in modernen CompilernGrundlagen38 Übersetzung binärer/unärer Anweisungen Eingabe: 3-Adress-Code-Anweisung x := y z Ausgabe: Zielcode Algorithmus: l := getVarInReg(y); lockReg(l); r := getVarInReg(z); lockReg(r); if isTemp(y) then Delete(y,l); if isTemp(z) then Delete(z,r); t := getFreeReg(x); unlock(l); unlock(r); asmmnem := Assembleropertion für outCode("asmmnem l,r,t"); setRegDeskr(t,x) Eingabe: 3-Adress-Code-Anweisung x := y Ausgabe: Zielcode Algorithmus: r := getVarInReg(y); lookReg(r); if isTemp(y) then Delete(y,r); t := getFreeReg(); unlook(r); asmmnem := Assembleropertion für outCode("asmmnem r,t"); setRegDeskr(t,x)

39 Optimierungstechniken in modernen CompilernGrundlagen39 Beispiel Übersetzung von t20 := t1 + t16; Aufruf von getVarInReg(t1) und getVarInReg(t16): Aufruf von getFreeReg() i rd(i) (t0,w), (a,w) (t1,w), (b,w) (t2,w), (c,w) (t15,w), (p,w)15 … Rückgabewert: r1 für t1 r0 für t16 Erzeugter Spillcode: mov r0,[bp-sd(t0)] mov r0,[sd(a)] mov [bp-sd(t16)],r0 Neuer Registerdeskriptor: i rd(i) (t16,r) (t1,w), (b,w) (t2,w), (c,w) (t15,w), (p,w)15 … Registerdeskriptor: locked i rd(i) (b,w) (t2,w), (c,w) (t15,w), (p,w)15 … locked Registerdeskriptor: Rückgabewert: r0 Erzeugter Spillcode: Keiner i rd(i) (t20,w) (b,w) (t2,w), (c,w) (t15,w), (p,w)15 … locked Zielcode: add r1,r0,r0 Neuer Registerdeskriptor:

40 Optimierungstechniken in modernen CompilernGrundlagen40 Übersetzung von Labels und Sprunganweisungen Eingabe: 3-Adress-Code-Anweisung label: Ausgabe: Zielcode Algorithmus: SaveRegs(); outCode("label:"); Clear(); Eingabe: 3-Adress-Code-Anweisung goto label Ausgabe: Zielcode Algorithmus: SaveRegs(); outCode("jmp label"); Eingabe: 3-Adress-Code-Anweisung if x then goto l Ausgabe: Zielcode Algorithmus: t := getVarInReg(x); Delete(x,t) SaveRegs(); outCode("BNZ t,l"); … a := t7 label: t8 := a … a := t7 goto label10 label9: … a := t7 if t8 then goto label20 b := t9 … Aktualisieren der Werte im Speicher. Einsprung von verschiedenen Position möglich; Belegung der Register unklar. Sprung zu einer Position an der der Registerdeskriptor gelöscht wird; Aktualisieren der Wert eim Speicher nötig. Hier wird die Registerallokation fortgesetzt. Sprung zu einer Position an der der Registerdeskriptor gelöscht wird; Aktualisieren der Wert eim Speicher nötig. Fortsetzung der Registeralloka- tion. Belegung der Register für jede Programmausführung fest. Kein Sichern erforderlich.

41 Optimierungstechniken in modernen CompilernGrundlagen41 Offensichtliche Verbesserungen Lade-/Speicheranweisungen an jedem Anfang/Ende eines Basisblocks sind erforderlich. Konseqnenz: Globale Planung der Register erforderlich. CISC-Architekturen werden nur schlecht unterstützt, da jede 3- Adress-Code-Anweisung nach einem festen Schema übersetzt wird. Konsequenz: Zielcodeauswahl flexibler gestalten. Anordnung der Zielcodeoperationen ist nur vom Quelltext und der Übersetzung des Syntaxbaums in 3-Adress-code abhängig Konsequenz: Bessere Anordnung der Operationen finden, um Verwendung von Prozessorregistern zu minimieren.

42 Optimierungstechniken in modernen CompilernGrundlagen42 Integrated Prepass Scheduling Ausführung der Ablaufplanung vor der Registerallokation. Ablaufplanung mittel List-Scheduling nach zwei Strategien: CSP: Ablaufplanung die bevorzugt Operationen plant, die keine Konflikte in der Pipeline verursachen. CSR: Ablaufplanung, die bevorzugt Operationen plant, die den Registerdruck verringern. Ablaufplanung protokolliert Anzahl der lebendigen Werte während der Planung mit. Schwellwert minRegs, gibt an, ab wann bevorzugt Operationen geplant werden, die den Registerdruck verringern: Solange mehr als minReg Register vorhanden sind, wird mit CSP geplant. Sobald Anzahl der freien Register unter den Schwellwert minRegs sinkt, wird CSR verwendet.

43 Optimierungstechniken in modernen CompilernGrundlagen43 Motivation Globale Registerallokation Problem der lokalen Registerallokation: Laden/Sichern der Werte von Programmvariablen am Anfang/Ende von jedem Basisblock. Konsequenz: Überflüssige Lade-/Speicheroperationen. Lösung: Globale Registerallokation für Programme in 3-Adress- Code-Form: Halten von Werten in Registern über Basisblockgrenzen hinaus unter Beachtung des Steuerflusses. Es muss abgesichert sein, dass der Wert einer Variablen, der an verschiedenen Programmpositionen definiert wird, sich immer im gleichen Register befindet. Modellierung durch ein Graphfärbungsproblem; Lösung durch Heuristik. Globale Registerallokation für Programme in SSA-Form: Modelliertes Graphfärbungsproblem ist optimal lösbar. Behandlung der -Funktionen erfordert zusätzliche Kopieroperationen.

44 Optimierungstechniken in modernen CompilernGrundlagen44 Modellierung der Globalen Registerallokation durch Graphfärbung Geeignet für Prozessorarchitekturen mit universellem Registersatz; K sei die Anzahl der Register. Grundlage ist die Information über die lebendigen Variablen an den Programmpositionen im Steuerflussgraphen. Sind zwei verschiedene Variablen u und v an derselben Position lebendig, dann können ihre Wert nicht im selben Register gehalten werden. Modellierung durch einen ungerichteten Graphen (Interferenzgraphen) I = (V,E) : V ist eine Knotenmenge, wobei jeder Knoten genau eine Variable im Zwischencode repräsentiert. E {{u,v} | u,v V und u v}, wobei {u,v} E gdw. es existiert eine Programmposition p, an der u und v gemeinsam lebendig sind. Eine Färbung I : V K des Interferenzgraphen (mit {u,v} E I (u) I (v)) entspricht einer Registerallokation, in der der Wert der Variablen v im Register I (v) gespeichert wird. Zu jedem ungerichteten Graphen existiert ein Programm, das diesen Graphen als Interferenzgraph besitzt.

45 Optimierungstechniken in modernen CompilernGrundlagen45 Beispiel Steuerflussgraph/Interferenzgraph d := 0 a := 1 c := 3 f := c d:= d+1 r := 2*d s := 3*c t := r+s e := t+5 d:= a+f u := c v := u+1 w := v+1 e := w c:= d+3 a := e*c z:= a+d () (d) (a,d) (a,d,c) (a,c,f,d) (c,d) (c,d,r) (d,s,r) (d,t) (d,e) (a,c,f,d) (c,d) (d,u) (d,v) (d,w) (d,e) (d,c,e) (d,c,a) (a,d) (z) ads fcr v u w t e z

46 Optimierungstechniken in modernen CompilernGrundlagen46 Färben des Interferenzgraphen Finden einer Reihenfolge v 1,…,v n, in der die Knoten (zusammen mit adjazenten Kanten) aus dem Interferenzgraphen entfernt werden. Einfügen der Knoten in der Reihenfolge v n,…,v 1 und dabei Zuordnung einer Farbe, die verschieden von den Farben der bis dahin wieder eingefügten Nachbarn ist. Heuristik zum Finden der Reihenfolge: Entferne als nächstes einen Knoten, der weniger als K Nachbarn hat. Dieser Knoten hat beim Einfügen dann auch weniger als K Nachbarn und kann damit sicher gefärbt werden. Es ergibt sich folgender Algorithmus graphCol: Eingabe: Interferenzgraph (V',E') Ausgabe: Färbung V := V'; E := E'; i := 1; while es gibt ein v V mit |{{w,u} | {w,u} E und w = v}| < K do v i := v; i := i+1; V := V – {v}; E := E – {{w,u} | {w,u} E und w = v}; od := if V then return ; while i > 1 do i := i-1; V := V {v i }; E := E {{v i,w} | {v i,w} E' und w V} (v i ) := k, wobei k {0,…,K-1} – { (w) | {v i,w} E} od return

47 Optimierungstechniken in modernen CompilernGrundlagen47 Beispiel 1 ad s fcr v u w t e I = (V, E), R = {0,1,2,3} z vuwtezsrcfa ads fcr v u w t e z d

48 Optimierungstechniken in modernen CompilernGrundlagen48 Spillen Was, wenn der Interferenzgraph nicht zum leeren Graphen reduziert werden kann? Falls im Algorithmus graphCol V und kein Knoten mit weniger als |K| Nachbarn existiert, dann Spillentscheidung treffen: Pessimistische Annahme: Färbung ist nicht möglich, weil die Nachbarn der verbleibenden Knoten mit mindestens K verschiedenen Farben gefärbt werden. Optimistische Annahme: Färbung ist trotzdem möglich, weil es Nachbarn der verbleibenden Knoten gibt, die mit derselben Farbe gefärbt werden: R = {0,1}

49 Optimierungstechniken in modernen CompilernGrundlagen49 Modifizierter Algorithmus bei optimistischer Strategie Erst, wenn beim Färben festgestellt wird, dass keine Farbe verfügbar ist, wird eine Spillentscheidung getroffen: Eingabe: Interferenzgraph (V',E') Ausgabe: Färbung V := V'; E := E'; i := 1; while V do Wähle einen Knoten v V mit |{{w,u} | {w,u} E und w = v}| ist minimal v i := v; i := i+1; V := V – {v}; E := E – {{w,u} | {w,u} E und w = v}; od := while i > 1 do i := i-1; V := V {v i }; E := E {{v i,w} | {v i,w} E' und w V} if {0,…,K-1} – { (w) | {v i,w} E} = then return ; (v i ) := k, wobei k {0,…,K-1} – { (w) | {v i,w} E} od return

50 Optimierungstechniken in modernen CompilernGrundlagen50 Beispiel optimistische vs. pessimistische Strategie ad s fcr v u w t e I = (V, E), R = {0,1,2} z vuwtezsr ad fc vuwtezsrcfda ad fc Pessimistische AnnahmeOptimistische Annahme Algorithmus graphCol bricht hier ab. Algorithmus graphCol bricht erst hier ab.

51 Optimierungstechniken in modernen CompilernGrundlagen51 Auswahl der Spillvariablen Falls ein Interferenzgraph nicht weiter reduziert werden kann, dann wird aus den verbleibenden Variablen v (Knoten) die ausgewählt, für die minimal ist. Dabei sind DefUse(v) alle Programmpositionen, an denen v verwendet/definiert wird. und deepth(p) die Schachtelungstiefe der innersten Schleife, die die Programmposition p enthält. Mindestend eine Variable zwischen einer Definition und Verwendung von v stirbt. Vor/nach allen Verwendungen/Definitionen von v wird Spillcode in den Zwischencode eingefügt. Jede Definition/Verwendung der gespillten Variablen kann einen neuen Namen erhalten. Dadurch entfallen Kanten im Interferenzgraphen. Interferenzgraph muss neu konstruiert werden. Es können mehrere solcher Iterationen erforderlich sein, bis eine Färbung des Interferenzgraphen gefunden wird.

52 Optimierungstechniken in modernen CompilernGrundlagen52 Beispiel Spillen d := 0 a := 1 c := 3 f := c d:= d+1 r := 2*d s := 3*c t := r+s e := t+5 d:= a+f u := c v := u+1 w := v+1 e := w c:= d+3 a := e*c z:= a+d Spillen von d () (d) (a,d) (a,d,c) (a,c,f,d) (c,d) (c,d,r) (d,s,r) (d,t) (d,e) (a,c,f,d) (c,d) (d,u) (d,v) (d,w) (d,e) (d,c,e) (d,c,a) (a,d) (z) d := := d a := 1 c := 3 f := c d d := := d d r := 2*d s := 3*c t := r+s e := t+5 d := := d u := c v := u+1 w := v+1 e := w d c:= d+3 a := e*c d z:= a+d () (d) () (a) (a,c) (c) (c,d) (c,r) (s,r) (t) (e) (a,c,f) (c,d) (c) (u) (v) (w) (e) (d,e) (c,e) (a,c) (a) (d) (z) (a,c) (a,c,f)

53 Optimierungstechniken in modernen CompilernGrundlagen53 Interferenzgraph nach Spillen d := := d a := 1 c := 3 f := c d d := := d d r := 2*d s := 3*c t := r+s e := t+5 d := := d u := c v := u+1 w := v+1 e := w d c:= d+3 a := e*c d z:= a+d () (d) () (a) (a,c) (c) (c,d) (c,r) (s,r) (t) (e) (a,c,f) (c,d) (c) (u) (v) (w) (e) (d,e) (c,e) (a,c) (a) (d) (z) (a,c) (a,c,f) ad s fcr v u w t e z

54 Optimierungstechniken in modernen CompilernGrundlagen54 Vollständiger Algorithmus Interferenzgraph konstruieren Interferenzgraph färben Spillen Fertig 3- Adress- Code 3- Adress- Code nicht erfolgreich erfolgreich

55 Optimierungstechniken in modernen CompilernGrundlagen55 Anwendung der Graphfärbungsmethode auf SSA-Code Vorgehen wie bisher: Lebendigkeit der SSA-Variablen wird berechnet. Für jede SSA-Variable wird ein Knoten im Interferenzgraphen erzeugt. Eine Kante existiert zwischen zwei Knoten (Variablen), falls diese gleichzeitig lebendig sind (gilt nicht für Programmpositionen der -Funktionen). Färbung liefert Registerzuordnung. Vorteil: Dieselbe Variable v kann auf verschiedene Register abgebildet werden, wenn z.B. eine Definition von v eine Teilmenge der Verwendungen von v dominiert und eine andere Definition eine dazu disjunkte Teilmenge von Verwendungen dominiert. Problem: Dieser Vorteil führt auch zu folgendem Problem: Unterschiedliche Färbung verschiedener SSA-Variablen zur selben Variablen möglich. Wie müssen dann -Funktionen behandelt werden, wenn sich der Wert einer Variablen in verschiedenen Registern befinden kann aber in einem bestimmten Register erwartet wird?

56 Optimierungstechniken in modernen CompilernGrundlagen56 Beispiel Vorteil Problem at bc def a def b def t 1 use t 1 … def c use a use b def t 2 use t 2 use c a t1t1 bc t2t2 def a def b def t use t … def c use a use b def t use t use c def a use a Interferenzgraph besitzt einen Knoten für a. def a 1 def a 2 a 3 = (a 1,a 2 ) use a 3 Interferenzgraph besitzt drei verschiedene Knoten für a, die unterschiedlich gefärbt werden können. (a) (a,b) (a,b,t) (a,b) (a,b,c) (b,c) (c) (c,t) (c) (a) (a,b) (a,b,t 1 ) (a,b) (a,b,c) (b,c) (c) (c,t 2 ) (c)

57 Optimierungstechniken in modernen CompilernGrundlagen57 Eliminierung -Funktion - Variante 1 Verschmelzen aller SSA-Variablen v 1,…,v n derselben Variablen zur selben Variablen v: Für jede Variable v: Es seien v 1,…,v n die SSA-Variablen der Variablen v. N' := {v | v i N}. E' := {{u,v} | {u j,v i } E}. Dadurch entsteht der Interferenzgraph (N',E') des nicht SSA-Programms aus dem Interferenzgraphen (N,E) des SSA-Programms. Problem wird damit auf das ursprüngliche Graphfärbungsproblem zurückgeführt. a t1t1 bc t2t2 at bc Verschmelzen

58 Optimierungstechniken in modernen CompilernGrundlagen58 Eliminierung -Funktion - Variante 2 Behandlung der -Funktionen in einem Block b als Kopieroperation: b habe die Steuerflussvorgänger u und v. Über u erreichen b die Definitionen a1 1,…,an 1 und über v die Definitionen a1 2,…an 2. a1 3,…an 3 seien die Definitionen dieser Variablen in b durch -Funktionen. a1 1,…,an 1, a1 2,…an 2, a1 3,…an 3 wurden Register zugeordnet. Falls b über u betreten wird, müssen die Werte von a1 1,…,an 1 in die Register kopiert werden, in denen die Werte von a1 3,…an 3 erwartet werden. Falls b über v betreten wird, müssen die Werte von a1 2,…,an 2 in die Register kopiert werden, in denen die Werte von a1 3,…an 3 erwartet werden. Einfügen der entsprechenden Kopieroperationen am Ende von u und v oder am Beginn von b. def a 1 def a 2 a 3 = (a 1,a 2 ) use a 3 a1a1 a2a2 a3a3 u:u:v:v: b:b: load a R0 jump b_left … load a R2 jump b_right … b_left: mov R0 R3 jump b_start b_right: mov R2 R3 b_start: … Steuerflussgraph Mögliche Färbung im Interferenzgraphen Zielcode u:u: v:v: b:b:

59 Optimierungstechniken in modernen CompilernGrundlagen59 Swap-Problem beim Kopieren Problem: Alle -Operationen müssen zeitgleich ausgeführt werden. Kopieroperationen generieren eine Permutation der Registerinhalte. Implementierungsvarianten: xchg -Operation verwenden, falls auf der Architektur vorhanden. Emulation der xchg -Operation durch xor -Operationen: Werte a und b in Registern x und y: x := x xor y y := y xor x == a x := x xor y == b Vertauschung unter Zuhilfenahme eines zusätzlichen Registers, falls vorhanden. def a 1 def b 1 def a 2 def b 2 a 3 = (a 1,a 2 ) b 3 = (b 1,b 2 ) u:u:v:v: b:b: a 1 und a 2 R0 b 1 und b 2 R1 a 3 R1 b 3 R0 Steuerflussgraph Mögliche Registerzuordnung b_left: b_right: mov R0 R1 mov R1 R0 b_start: … Falscher Zielcode b_left: b_right: xchg R0,R1 b_start: … Richtiger Zielcode

60 Optimierungstechniken in modernen CompilernGrundlagen60 Färbung des Interferenzgraphen eines SSA-Programms Erweiterung der Relation dom auf die Anweisungen an allen Programmpositionen (i,j) und (i',j') im Programm: (i,j) dom (i',j') gdw. (i i' und i dom i') oder (i = i' und j j'). Weitere Voraussetzung: Initialisierte Variablen. Es ergeben sich dann für SSA-Programme folgende Eigenschaften: Jede Verwendung einer Variablen v bei (i',j') wird von der Definition von v bei (i,j) = D v dominiert. Wenn zwei Variablen u und v an derselben Programmposition lebendig sind, dann gilt entweder D u dom D v oder D v dom D u. Wenn u und v an derselben Programmposition lebendig sind und D v dom D u, dann ist v bei D u lebendig. In einem Interferenzgraphen zu einem SSA-Programm gilt damit: Für jede Clique {v 1,…,v n } im Interferenzgraphen existiert eine Programmposition, an der alle Variablen v 1,…,v n lebendig sind.

61 Optimierungstechniken in modernen CompilernGrundlagen61 Finden einer optimalen Färbung des Graphen Idee: Eliminiere einen Knoten, für den gilt, dass alle seine Nachbarn eine Clique bilden. Beim Einfügen dieses Knotens sind dann alle Nachbarn verschieden gefärbt und die Anzahl der benötigten Farben ist durch die Größe der maximalen Clique bestimmt. Wieso kann so ein Knoten im Interferenzgraphen immer gefunden werden? Es sei (a,b) E und (b,c) E und (a,c) E. Wenn D a dom D b, dann D b dom D c. Wenn ein Knoten v aus dem Graphen entfernt wird, dann sind alle Variablen, deren Definitionen durch die Definition von v dominiert wird, bereits entfernt worden. Unter dieser Voraussetzung bilden alle mit Knoten v adjazenten Knoten eine Clique. Die Reihenfolge, in der die Knoten aus dem Interferenzgraphen zu entfernen sind, erhält man durch die Post-Order- Linearisierung des Dominatorbaums.

62 Optimierungstechniken in modernen CompilernGrundlagen62 Beispiel - Interferenzgraph d 0 := 0 a 0 := 1 c 0 := 3 d 1 := (d 0,d 4 ) a 1 := (a 0,a 2 ) c 1 := (c 0,c 2 ) f 0 := c 1 d 2 := d 1 +1 r 0 := 2*d 2 s 0 := 3*c 1 t 0 := r 0 +s 0 e 0 := t 0 +5 d 3 := a 1 +f 0 u 0 := c 1 v 0 := u 0 +1 w 0 := v 0 +1 e 1 := v 0 d 4 := (d 2,d 3 ) e 2 := (e 0,e 1 ) c 2 := d 4 +3 a 2 := e 2 *c 2 z 0 := a 2 +d 4 () (d 0 ) (a 0,d 0 ) (a 0,d 0,c 0 ) (a 1,c 1,d 1 ) (a 1,c 1,f 0,d 1 ) (d 1,c 1 ) (c 1,d 2 ) (c 1,d 2,r 0 ) (d 2,s 0,r 0 ) (d 2,t 0 ) (d 2,e 0 ) (c 1,a 1,f 0 ) (c 1,d 3 ) (d 3,u 0 ) (d 3,v 0 ) (d 3,w 0 ) (d 3,e 1 ) (d 4,e 2 ) (d 4,e 2,c 2 ) (d 4,c 2,a 2 ) (a 2,d 4 ) (z 0 ) a1a1 d1d1 f0f0 c1c1 u0u0 e1e1 z0z0 a0a0 d0d0 c0c0 r0r0 d2d2 s0s0 e0e0 t0t0 d3d3 v0v0 w0w0 a2a2 d4d4 c2c2 e2e2

63 Optimierungstechniken in modernen CompilernGrundlagen63 Beispiel - Dominatorbaum d 0 := 0 a 0 := 1 c 0 := 3 d 1 := (d 0,d 4 ) a 1 := (a 0,a 2 ) c 1 := (c 0,c 2 ) f 0 := c 1 d 2 := d 1 +1 r 0 := 2*d 2 s 0 := 3*c 1 t 0 := r 0 +s 0 e 0 := t 0 +5 d 3 := a 1 +f 0 u 0 := c 1 v 0 := u 0 +1 w 0 := v 0 +1 e 1 := v 0 d 4 := (d 2,d 3 ) e 2 := (e 0,e 1 ) c 2 := d 4 +3 a 2 := e 2 *c 2 z 0 := a 2 +d 4 (d 0 ) (a 0,d 0 ) (a 0,d 0,c 0 ) (a 1,c 1,f 0,d 1 ) (c 1,d 2 ) (c 1,d 2,r 0 ) (d 2,s 0,r 0 ) (d 2,t 0 ) (d 2,e 0 ) (c 1,d 3 ) (d 3,u 0 ) (d 3,v 0 ) (d 3,w 0 ) (d 3,e 1 ) (d 4,e 2,c 2 ) (d 4,c 2,a 2 ) (z 0 ) a0a0 d0d0 c0c0 a1a1 d1d1 c1c1 f0f0 r0r0 d2d2 s0s0 e0e0 t0t0 u0u0 d3d3 v0v0 e1e1 w0w0 e2e2 d4d4 c2c2 z0z0 a2a2

64 Optimierungstechniken in modernen CompilernGrundlagen64 Finden der optimalen Färbung Reihenfolge zum Entfernen der Knoten aus dem Interferenzgraphen ist die Postfixlinearisierung lrpost(t) des Dominatorbaums t, die durch einen Links-Rechts- Tiefendurchlauf in t berechnet wird. Offenbar wird ein Knoten immer dann entfernt, wenn alle Knoten, die durch ihn dominiert werden bereits entfernt wurden. Einfügen und Färben der Knoten in der Reihenfolge lrpost(t). Einfacher: Färben der Knoten in der Reihenfolge lrpost(t) = rlpre(t), wobei rlpre(t) Präfixlinearisierung nach einen Rechts-Links-Tiefendurchlauf ist.

65 Optimierungstechniken in modernen CompilernGrundlagen65 Beispiel - Färbung a1a1 d1d1 f0f0 c1c1 u0u0 e1e1 z0z0 a0a0 d0d0 c0c0 r0r0 d2d2 s0s0 e0e0 t0t0 d3d3 v0v0 w0w0 a2a2 d4d4 c2c2 a0a0 d0d0 c0c0 a1a1 d1d1 c1c1 f0f0 r0r0 d2d2 s0s0 e0e0 t0t0 u0u0 d3d3 v0v0 e1e1 w0w0 e2e2 d4d4 c2c2 z0z0 a2a2 e2e2

66 Optimierungstechniken in modernen CompilernGrundlagen66 Spillen in SSA-Code 1. Strategie: Spillen eines Knotens wie bisher mit Einfügen von Speicher- und Ladecode. Neukonstruktion des Interferenzgraphen wird erforderlich. Falls der Zwischencode nicht in SSA-Code umgeformt wird, kann die Eigenschaft der optimalen Färbbarkeit verloren gehen. 2. Strategie: Cliquen im Interferenzgraphen korrespondieren mit gleichzeitig lebendigen Variablen im SSA-Code Durch maximale Clique ist die Anzahl der benötigten Farben festgelegt. Spillen wird durchgeführt, bevor der Interferenzgraph aufgebaut wird. Finden der gleichzeitig lebendigen Variablen ist einfach. Damit ergibt sich folgender vollständiger Algorithmus: Interferenzgraph konstruieren Interferenzgraph färben SpillenFertig 3- Adress- Code

67 Vorlesung, Wintersemester 2009/10M. Schölzel 67 Optimierungstechniken für DSPs und Mikrocontroller Codeauswahl

68 Optimierungstechniken in modernen CompilernGrundlagen68 Instruktionsauswahl in Basisblöcken Problem (bereits bekannt): Einfache Zielcodeerzeugung nutzt feste Übrsetzungsschemata für jede Zwischencodeanweiung. Unzureichend für Architekturen mit komplexen Befehlssätzen und vielen Adressierungsmöglichkeiten. Lösung: Code-Selektion durch Berechnung einer Überdeckung Transformation des 3-Adress-Codes in einen gerichteten azyklischen Graphen (DAG). Zerlegung des DAG in Bäume. Berechnung einer Überdeckung der Bäume. Zu der berechneten Überdeckung wird eine Sequenz von Instruktionen der Zielarchitektur generiert.

69 Optimierungstechniken in modernen CompilernGrundlagen69 Zerlegung eines DAGs in Bäume Für jeden Baum der Zerlegung soll gelten: Nur der Wert der Wurzel darf mehrfach benutzt werden Vorgehen: Wähle einen Knoten n im DAG ohne Vorgänger: T = {n} Solange es in T einen Knoten m gibt, für den gilt: (m,n) E DAG und m T und n einziger Nachfolger von m oder (n,m) E und m T und m einziger Nachfolger von n: T := T {m} Berechne den Wert der Wurzel von T in eine neue Variable newV. Ersetze im DAG jede Verwendung v der Wurzel von T durch einen Knoten n v mit Beschriftung read newV. MiMi C4C4 * CbCb + ld MjMj * CaCa st C4C4 MnMn T1:T1:

70 Optimierungstechniken in modernen CompilernGrundlagen70 Zerlegung eines DAGs in Bäume Für jeden Baum der Zerlegung soll gelten: Nur der Wert der Wurzel darf mehrfach benutzt werden Vorgehen: Wähle einen Knoten n im DAG ohne Vorgänger: T = {n} Solange es in T einen Knoten m gibt, für den gilt: (m,n) E DAG und m T und n einziger Nachfolger von m oder (n,m) E und m T und m einziger Nachfolger von n: T := T {m} Berechne den Wert der Wurzel von T in eine neue Variable newV. Ersetze im DAG jede Verwendung v der Wurzel von T durch einen Knoten n v mit Beschriftung read newV. MiMi MnMn * CbCb + ld MjMj * CaCa st C4C4 MnMn T1:T1: MnMn MiMi MnMn * T2:T2: MmMm

71 Optimierungstechniken in modernen CompilernGrundlagen71 Zerlegung eines DAGs in Bäume Für jeden Baum der Zerlegung soll gelten: Nur der Wert der Wurzel darf mehrfach benutzt werden Vorgehen: Wähle einen Knoten n im DAG ohne Vorgänger: T = {n} Solange es in T einen Knoten m gibt, für den gilt: (m,n) E DAG und m T und n einziger Nachfolger von m oder (n,m) E und m T und m einziger Nachfolger von n: T := T {m} Berechne den Wert der Wurzel von T in eine neue Variable newV. Ersetze im DAG jede Verwendung v der Wurzel von T durch einen Knoten n v mit Beschriftung read newV. MmMm CbCb + ld MjMj * CaCa st C4C4 MnMn T1:T1: MnMn MiMi MnMn * T2:T2: MmMm MmMm

72 Optimierungstechniken in modernen CompilernGrundlagen72 Zerlegung eines DAGs in Bäume Für jeden Baum der Zerlegung soll gelten: Nur der Wert der Wurzel darf mehrfach benutzt werden Vorgehen: Wähle einen Knoten n im DAG ohne Vorgänger: T = {n} Solange es in T einen Knoten m gibt, für den gilt: (m,n) E DAG und m T und n einziger Nachfolger von m oder (n,m) E und m T und m einziger Nachfolger von n: T := T {m} Berechne den Wert der Wurzel von T in eine neue Variable newV. Ersetze im DAG jede Verwendung v der Wurzel von T durch einen Knoten n v mit Beschriftung read newV. MmMm CbCb + ld MjMj * CaCa st C4C4 MnMn T1:T1: MnMn MiMi MnMn * T2:T2: MmMm MmMm CaCa T3:T3:

73 Optimierungstechniken in modernen CompilernGrundlagen73 Prinzip Code-Selektion CbCb + ld + + MiMi CaCa MjMj R1R1 + + CaCa MjMj R1R1 + R2R2 R1R1 + R2R2 R1R1 Zielcode: mov Rz,y add rz,[x] mov r1,b add r1,[i] CyCy + MxMx RzRz RxRx ld RyRy RxRx + RyRy RxRx Zielcode: mov Ry,[Rx] Zielcode: add Rx,[Ry] mov r2,a add r2,[j] mov r1,[r1] add r1,[r2] Ersetzungsregeln:Zielcode: Ausdrucksbaum:

74 Optimierungstechniken in modernen CompilernGrundlagen74 Ersetzungsregeln S ist eine Menge von Speicherklassen: Im Folgenden oft S = {M, R, C}, wobei M – Speicher, R – Register, C – Konstante. Op ist die Menge der Operatoren mit denen Knoten im DAG beschriftet sein können. (V,l,r,,,z) ist eine Ersetzungsregel, wobei: V ist eine Variablenmenge, die in der Ersetzungsregel verwendet wird, l ist ein geordneter Baum (Muster genannt), : l S Op ist eine Beschriftung der Knoten des Baums, wobei die Blätter mit Speicherklassen beschriftet sind und die inneren Knoten mit Operatoren, : Leaves(l) V ist eine Beschriftung der Blätter in l, wobei Leaves(x) = { | x und i :.i x} r = (m,v) ist die Ersetzung für l, wobei m S und v V z ( V) * ist die Schablone für den zu erzeugenden Zielcode. Die Menge aller Ersetzungsregeln wird mit bezeichnet. Jeder Ersetzungsregel g können Kosten cost(g) zugeordnet sein.

75 Optimierungstechniken in modernen CompilernGrundlagen75 Wann passt ein Muster? In einem Ausdrucksbaum T mit Beschriftungsfunktion : T S Op passt ein Muster l aus der Ersetzungsregel g = (V,l,r,,,z) am Knoten n, falls: T/n = l und Für alle Knoten b T/n gilt: (b) = (b) CbCb + ld + + MiMi CaCa MjMj CyCy + MxMx RzRz

76 Optimierungstechniken in modernen CompilernGrundlagen76 Ersetzung eines Musters Falls das Muster l aus der Ersetzungsregel g = (V,l,r,,,z) an einem Knoten n passt, dann kann in T der Baum T/n durch den Knoten n ersetzt werden, dessen Beschriftung aus r hervorgeht, wodurch ein Baum T' entsteht, für den gilt: T' = T – (T/n) {n}, (T' – {n}): '( ) = ( ) und attr'( ) = attr( ) Die Variable (b) an jedem Blatt b in l wird an den Attributwert des Blattes b in T/n angepasst: val( (b)) = attr(b) (attr ist der Index in der Knotenbeschriftung). '(n) = m und attr'(n) = val(v), wobei r = (m,v) Der Zielcode wird erzeugt, indem z als Zielcode ausgegeben wird und jede Variable v in z durch val(v) ersetzt wird. CbCb + ld + + MiMi CaCa MjMj CyCy + MxMx RzRz Zielcodeschablone: mov Rz,y add rz,[x] Variablenanpassung: val(x) = i val(y) = b val(z) = getFreeReg() R0R0 Zielcode: mov R0,b add R0,[i]

77 Optimierungstechniken in modernen CompilernGrundlagen77 Greedy-Algorithmus Annahme: Es stehen genügend Prozessorregister zur Auswertung des Ausdrucks zur Verfügung. Unter Verwendung eines Greedy-Algorithmus kann ein gegebener Ausdrucksbaum durch wiederholte Ersetzung zu einem Knoten reduziert werden. Vorgehen: Suche ein Muster maximaler Größe (z.B. |l| ist maximal), das an einem Knoten n passt. Führe eine Ersetzung am Knoten n aus. Wiederhole diese Schritte solange, bis der Baum zu einem Knoten reduziert wurde. Leicht implementierbar, liefert aber nicht immer optimale Ergebnisse.

78 Optimierungstechniken in modernen CompilernGrundlagen78 Probleme Was, wenn mehrere Ersetzungsregeln passen? In welcher Reihenfolge sollen Ersetzungen vorgenommen werden? Effizienz des Zielcodes kann davon abhängen. Verhindern, dass Sackgassen entstehen: Für jeden einzelnen Knoten im Ausdrucksbaum, der mit einem Operator markiert ist, muss eine Ersetzungsregel existieren. Es gibt genug Prozessorregister, um jeden dieser Knoten zu übersetzen Verhindern, dass unendliche Ersetzungen entstehen: Ersetzungsregeln verkleinern den Baum Ersetzungsregeln, die einen Knoten in denselben Knoten überführen ersetzen die Beschriftung (Speicherort), wobei es eine Ordnung für die Ersetzung der Speicherorte gibt. Greedy-Algorithmus optimiert nicht die Kosten.

79 Optimierungstechniken in modernen CompilernGrundlagen79 Optimale Zielcodeerzeugung für Ausdrucksbäume Voraussetzung: Zielprozessor ist eine Load/Store- Architektur. Ershov Nummerierung: Jeder Knoten ist mit der Anzahl der Register beschriftet, die benötigt werden, um den Wert des Knotens zu berechnen, ohne einen Wert in den Speicher auszulagern. Berechnung durch: Jedes Blatt, das mit M beschriftet ist erhält eine 1 Jedes Blatt, das mit C oder R beschriftet ist, erhält eine 1 Jeder innere Knoten mit genau einem Sohn erhält die Nummer seines Sohns Jeder innere Knoten mit zwei Söhnen erhält die Nummer i, falls –beide Söhne verschiedene Nummer m und k haben und i = max(m,k) –beide Söhne die gleiche Nummer k haben und i = k+1

80 Optimierungstechniken in modernen CompilernGrundlagen80 Beispiel Ershov-Nummerierung t1 = a – b t2 = c + d t3 = e * t2 t4 = t1 + t3 - ab+ cd * e Zwischencodefragment Nummerierter Ausdrucksbaum

81 Optimierungstechniken in modernen CompilernGrundlagen81 Zielcodeerzeugung für Ausdrucksbäume Eingabe: Ausdrucksbaum mit Ershov-Nummerierung. Ausgabe: Optimale Zielcodesequenz, um die Wurzel in ein Register zu berechnen. Prinzip: genCode(n,b) generiert Code zur Berechnung des Werts des Knotens n mit Ershov-Nummer k in Register b+k-1 unter Verwendung der Register b,…b+k-1. Beginne mit genCode(n,0), wobei n die Wurzel des Ausdrucksbaums ist. Arbeitsweise von genCode(n,b): n ist innerer Knoten mit Ershov-Nummer k, dessen zwei Söhne Ershov-Nummer k-1 haben: Generiere Code für den linken Sohn von n zur Basis b+1; Ergebnis wird in Register b+k-1 berechnet. Generiere Code für den rechten Sohn zur Basis b; Ergebnis wird in Register b+k-2 berechnet. Generiere Code für n mit Beschriftung op: op R b+k-1,R b+k-2,R b+k-1 n ist innerer Knoten mit Ershov-Nummer k, dessen zwei Söhne Ershov-Nummern k und m < k haben: Generiere Code für den Sohn mit Ershov-Nummer k zur Basis b; Ergebnis wird in Register b+k-1 berechnet. Generiere Code für den Sohn mit Ershov-Nummer m zur Basis b; Ergebnis wird in Register b+m-1 berechnet. Generiere Code für n: op R b+k-1,R b+m-1,R b+k-1 oder op R b+m-1,R b+k-1,R b+k-1 n ist inneren Knoten mit Ershov-Nummer k, der genau einen Sohn (mit Ershov-Nummer k) hat: Generiere Code für den Sohn zur Basis b Generiere Code für n mit Beschriftung op: op R b+k-1,R b+k-1 n ist ein Blatt: Load R b,x oder Const R b,x

82 Optimierungstechniken in modernen CompilernGrundlagen82 Beispiel - ab+ cd * e b=0 b=1b=0 b=2 b=1 b=0 b=1b=0 Load R2,a Load R1,b Sub R2,R1,R2 Load R1,c Load R0,d Add R1,R0,R1 Load R0,e Mul R0,R1,R1 Add R2,R1,R2 Ausdrucksbaum Zielcodesequenz

83 Optimierungstechniken in modernen CompilernGrundlagen83 Einfügen von Spill-Code Eingabe: Ausdrucksbaum mit Ershov-Nummerierung. Ausgabe: Optimale Zielcodesequenz, um die Wurzel unter Verwendung von maximal r Registern in ein Zielregister zu berechnen. Prinzip: Arbeitsweise wie bisher, falls Ershov-Nummer eines Knotens n höchstens r ist. Sonst berechne die Ergebnisse der Söhne von n in den Speicher und das Ergebnis von n nach R r-1 Beginne mit genCode(n,0), wobei n die Wurzel des Ausdrucksbaums ist. Arbeitsweise von genCode(n,b) für Knoten mit Ershov-Nummer k > r: n ist innerer Knoten mit Ershov-Nummer k, dessen zwei Söhne Ershov-Nummern k und m < k haben: Generiere Code für den Sohn mit Ershov-Nummer k zur Basis b = 0; Ergebnis wird in Register r-1 berechnet. Generiere Zielcode Store R r-1,newT, wobei newT eine neue Variable ist. Generiere Code für den Sohn mit Ershov-Nummer m –Falls m r, dann generiere Code zur Basis b = r – m; Ergebnis wird in Register r-1 berechnet. –Falls r < m, dann generiere Code zur Basis b = 0; Ergebnis wird in Register r-1 berechnet. Generiere die Instruktion Load newT,R r-2 Generiere Code für n: op R r-2,R r-1,R r-1 oder op R r-1,R r-2,R r-1 n ist innerer Knoten mit Ershov-Nummer k, dessen zwei Söhne Ershov-Nummer k-1 haben: Vorgehen ist wie im vorigen Fall, wobei o.B.d.A. der linke Sohn als Knoten mit Ershov-Nummer m behandelt wird. n ist innerer Knoten mit Ershov-Nummer k, der genau einen Sohn (mit Ershov-Nummer k) hat: Generiere Code für den Sohn zur Basis b = 1; Ergebnis wird in Register r berechnet. Generiere Code für n mit Beschriftung op: op R r,R r

84 Optimierungstechniken in modernen CompilernGrundlagen84 Beispiel - ab+ cd * e b=0 b=1 b=0 b=1b=0 Load R1,a Load R0,b Sub R1,R0,R1 Load R1,c Load R0,d Add R1,R0,R1 Load R0,e Mul R0,R1,R1 Sub R1,R0,R1 Ausdrucksbaum Zielcodesequenz für r = 2 Store R1,newT Load newT,R0

85 Optimierungstechniken in modernen CompilernGrundlagen85 Finden einer kostenoptimalen Überdeckung durch dynamische Programmierung Gegeben ist ein Ausdrucksbaum T und ein Ersetzungssystem Bestimme alle Werte, die in den Speicher berechnet werden sollen und führe diese Berechnungen zuerst aus. Damit dürfen zur Berechnung jedes dieser Werte alle Register verwendet werden. 3 Phasen: Berechne Bottom-Up für jeden Knoten n von T die minimalen Kosten C n [i] die entstehen und die zugehörige Ersetzungsregel, wenn der Wert des Baums mit Wurzel n: in den Speicher berechnet wird (i = 0) in ein Register berechnet wird und i Register (1 i) für die Berechnung verfügbar sind. Tiefendurchlauf durch T, um festzustellen, die Werte welcher Knoten in den Speicher berechnet werden müssen. Alle Bäume traversieren (zuerst die, deren Ergebnis in den Speicher berechnet wird), um den zugehörigen Zielcode zu erzeugen.

86 Optimierungstechniken in modernen CompilernGrundlagen86 Details Phase 1 Eingabe: Knoten n C m, R m für jeden Nachfahren m von n Ausgabe: C n …Kosten für Knoten n M n …Verwendete Muster am Knoten n Algorithmus: for(i = 1; i maxRegs; i++) do C n [i] = ; for each r das an Knoten n passt do m 1,…,m z seien die Nachfahren von n, die mit den Blättern in r matchen und als Speicherklasse M haben. r 1,…,r k seien die Nachfahren von n, die mit den Blättern in r matchen und als Speicherklasse R haben. cost r = cost(r)// Kosten des Musters r selbst for(j = 1; j <= z; j++) do cost r = cost r + C m j [0]; od cost min = ; for each p 1,…,p k wobei p 1,…,p k eine Permutation der Blätter r 1,…,r k ist do cost reg = 0; for(j = 1; j <= k; j++) do cost reg = cost reg + C p j [i-(j-1)] od cost min = min(cost min,cost reg ); fi od if(C n [i] > costr + costmin) then C n [i] = cost r + cost min ; M n [i] = r; fi od

87 Optimierungstechniken in modernen CompilernGrundlagen87 Beispiel Phase 1 - MaMa MbMb + McMc MdMd * MeMe + RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] Passende Muster für Knoten 0 bis 5: MusterGesamtkostenVerfügbare Register keins0alle, Ziel Speicher 1: 2: 3: 4: 5: 6: C 0 = [0,1,1]C 1 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] C 2 = [0,1,1]

88 Optimierungstechniken in modernen CompilernGrundlagen88 Beispiel Phase 1 - MaMa MbMb + McMc MdMd * MeMe + C 0 = [0,1,1]C 1 = [0,1,1] RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] Passende Muster für Knoten 5 und 6: MusterGesamtkostenVerfügbare Register | | 1+1+2alle, Ziel Speicher alle, Ziel Speicher 1: 2: 3: 4: 5: 6: C 6 = [3,2,2] C 5 = [3,2,2] M 6 = [6,4,4] M 5 = [6,4,4] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1]

89 Optimierungstechniken in modernen CompilernGrundlagen89 Beispiel Phase 1 - MaMa MbMb + McMc MdMd * MeMe + RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] Passende Muster für Knoten 7: MusterGesamtkostenVerfügbare Register | ' | 2+1+4alle, Ziel Speicher alle, Ziel Speicher 6'0+2+2alle, Ziel Speicher 1: 2: 3: 4: 5: 6: C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1]

90 Optimierungstechniken in modernen CompilernGrundlagen90 Beispiel Phase 1 - MaMa MbMb + McMc MdMd * MeMe + RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] Passende Muster für Knoten 8: MusterGesamtkostenVerfügbare Register ' | | 3+2+2alle, Ziel Speicher alle, Ziel Speicher 6'3+3+2alle, Ziel Speicher 1: 2: 3: 4: 5: 6: C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1]

91 Optimierungstechniken in modernen CompilernGrundlagen91 Details Phase 2 Ziel: Finden der Unterbäume von T, deren Ergebnis in den Speicher berechnet werden soll. Vorgehen: Tiefendurchlauf durch den Baum T beginnend mit maxReg verfügbaren Registern durch cover(n,maxReg), n ist Wurzel. Bei Aufruf cover(n,r): Falls am Knoten r = 0, dann berechne den Wert von n in den Speicher und setze l = M n [0], sonst berechne den Wert von n in Register r-1 und setze l = M n [r]. Es seien m 1,…,m z die Blätter in l mit (m i ) = M und b 1,…,b z die Knoten in T, die mit m 1,…,m z matchen: Berechne cover(m i,0) für 1 i z. Es seien r 1,…,r k die Blätter in l mit (m i ) = R und p 1,…,p k die Reihenfolge, in der diese Blätter in Phase 1 ausgewertet wurden und die die minimalen Kosten lieferte: Berechne cover(p i,r-i+1) für 1 i k. Erzeuge Zielcode vor dem letzten Verlassen eines Knotens n, dessen Wert in den Speicher berechnet werden soll, durch Aufruf von genCode(n,0). M M T1T1 T2T2 M T3T3 Erzeugter Zielcode: genCode(T 1,0) genCode(T 2,0) genCode(T 3,0) Zielcode für T 4 T4T4 ld + RxRx + RyRy RxRx Zielcode: add Rx,[Ry] Muster 4: + C + = [7,8,2]cover(+,2) M + = [5,3,4]

92 Optimierungstechniken in modernen CompilernGrundlagen92 Beispiel Phase 2 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: - MaMa MbMb + McMc MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] Tiefendurchlauf: cover(8,2) cover(7,2) cover(6,1) cover(5,2) Kein Ergebnis wird in den Speicher berechnet! C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] cover(3,2) cover(0,1)

93 Optimierungstechniken in modernen CompilernGrundlagen93 Details Phase 3 Teilbäume in T, deren Ergebnis in Phase 2 in den Speicher geschrieben wurde, werden aus T gelöscht. Dadurch entsteht der Baum T'. Wiederholung des Tiefendurchlaufs aus Phase 2 in T' durch genCode(n,maxReg): Wähle am Knoten n Muster M[r] Es seien r 1,…,r k die Blätter in M[r] mit (m i ) = R und p 1,…,p k die Reihenfolge, in der diese Blätter in Phase 1 und 2 ausgewertet wurden und die die minimalen Kosten lieferte: Berechne genCode(p i,r-i+1) für 1 i k. Generiere Zielcode beim letzten Verlassen eines Knotens n entsprechend dem gewählten Muster M[r] und berechne das Ergebnis in Register maxReg-r. M M T1T1 T2T2 M T3T3 T4T4 M M T' M M T2T2 M T3T3 T4T4 M M T3T3 T4T4 Phase 2Phase 3

94 Optimierungstechniken in modernen CompilernGrundlagen94 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) cover(7,2) cover(6,1) cover(5,2) cover(3,2) genCode(3,0) cover(0,1) load [c],R0 - MaMa MbMb + McMc MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1]

95 Optimierungstechniken in modernen CompilernGrundlagen95 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) cover(7,2) cover(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) load [c],R0 - MaMa MbMb + R0R0 MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] add R0,[d],R0

96 Optimierungstechniken in modernen CompilernGrundlagen96 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) cover(7,2) ) genCode(7,0) cover(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) load [c],R0 - MaMa MbMb R0R0 * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] add R0,[d],R0 mul R0,[e],R0

97 Optimierungstechniken in modernen CompilernGrundlagen97 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) cover(7,2) ) genCode(7,0) cover(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) genCode(0,1) load [c],R0 - MaMa MbMb R0R C 6 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] add R0,[d],R0 mul R0,[e],R0 load [a],R1

98 Optimierungstechniken in modernen CompilernGrundlagen98 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) cover(7,2) ) genCode(7,0) cover(6,1) genCode(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) genCode(0,1) load [c],R0 - R1R1 MbMb R0R C 6 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 1 = [0,1,1] M 1 = [x,1,1] add R0,[d],R0 mul [e],R0,R0 load [a],R1 sub R1,[b],R1

99 Optimierungstechniken in modernen CompilernGrundlagen99 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) genCode(8,0) cover(7,2) ) genCode(7,0) cover(6,1) genCode(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) genCode(0,1) load [c],R0 R1R1 R0R C 7 = [4,3,3] C 8 = [7,7,6] M 7 = [6',4',4'] M 8 = [5,4,3] add R0,[d],R0 mul [e],R0,R0 load [a],R1 sub R1,[b],R1 add R1,R0,R0

100 Optimierungstechniken in modernen CompilernGrundlagen100 Beispiel Phase 3 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: Tiefendurchlauf: cover(8,2) genCode(8,0) cover(7,2) ) genCode(7,0) cover(6,1) genCode(6,1) cover(5,2) genCode(5,0) cover(3,2) genCode(3,0) cover(0,1) genCode(0,1) load [c],R0 add R0,[d],R0 mul [e],R0,R0 load [a],R1 sub R1,[b],R1 add R1,R0,R0 - MaMa MbMb + McMc MdMd * MeMe

101 Optimierungstechniken in modernen CompilernGrundlagen101 Beispiel mit unzureichender Registeranzahl - MaMa MbMb + McMc MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] ' C 8' = [7,7,6] M 8' = [5,4,3] 9 MusterGesamtkostenVerfügbare Register ' | | 7+6+2alle, Ziel Speicher alle, Ziel Speicher 6'7+6+2alle, Ziel Speicher RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: C 9 = [15,15,14] M 9 = [5,4,3]

102 Optimierungstechniken in modernen CompilernGrundlagen102 Beispiel mit unzureichender Registeranzahl - MaMa MbMb + McMc MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] 7 - 8' C 8' = [7,7,6] M 8' = [5,4,3] 9 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: C 9 = [15,15,14] M 9 = [5,4,3] Tiefendurchlauf: cover(9,2) cover(8',2) … cover(8,1)

103 Optimierungstechniken in modernen CompilernGrundlagen103 Beispiel mit unzureichender Registeranzahl - MaMa MbMb + McMc MdMd * MeMe C 6 = [3,2,2] C 5 = [3,2,2] C 7 = [4,3,3] C 8 = [7,7,6] M 6 = [6,4,4] M 5 = [6,4,4] M 7 = [6',4',4'] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1]C 2 = [0,1,1] C 3 = [0,1,1]C 4 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] M 3 = [x,1,1]M 4 = [x,1,1] 7 - 8' C 8' = [7,7,6] M 8' = [5,4,3] 9 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: C 9 = [15,15,14] M 9 = [5,4,3] Tiefendurchlauf: cover(9,2) cover(8',2) … cover(8,1) cover(7,0) cover(5,2) cover(3,2) load [c],R1 add R1,[d],R1 mul [e],R1,R1 store r1,[t1]

104 Optimierungstechniken in modernen CompilernGrundlagen104 Beispiel mit unzureichender Registeranzahl - MaMa MbMb M t C 6 = [3,2,2] C 8 = [7,7,6] M 6 = [6,4,4] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] - 8' C 8' = [7,7,6] M 8' = [5,4,3] 9 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: C 9 = [15,15,14] M 9 = [5,4,3] Tiefendurchlauf: cover(9,2) cover(8',2) … cover(8,1) cover(7,0) cover(5,2) cover(3,2) load [c],R1 add R1,[d],R1 mul [e],R1,R1 store r1,[t1] cover(6,1)

105 Optimierungstechniken in modernen CompilernGrundlagen105 Beispiel mit unzureichender Registeranzahl - MaMa MbMb M t C 6 = [3,2,2] C 8 = [7,7,6] M 6 = [6,4,4] M 8 = [5,4,3] C 0 = [0,1,1]C 1 = [0,1,1] M 0 = [x,1,1]M 1 = [x,1,1] - 8' C 8' = [7,7,6] M 8' = [5,4,3] 9 RyRy op RxRx RxRx MxMx RyRy load [x],Ry op Rx,Ry,Rx MyMy op RxRx RxRx op Rx,[y],Rx RxRx MyMy store Rx,[y] RyRy op RxRx MzMz op Rx,Ry,Rx store Rx,[z] MyMy op RxRx MzMz op Rx,[y],Rx store Rx,[z] 1: 2: 3: 4: 5: 6: C 9 = [15,15,14] M 9 = [5,4,3] Tiefendurchlauf: cover(9,2) cover(8',2) … cover(8,1) cover(7,0) cover(5,2) cover(3,2) load [c],R1 add R1,[d],R1 mul [e],R1,R1 store r1,[t1] cover(6,1) // Code für 8' load [a],R0 sub R0,[b],R0 add R0,[t1],R0 sub R0,R1,R1

106 Optimierungstechniken in modernen CompilernGrundlagen106 Zusammenfassung Für Ausdrucksbäume können die drei Phasen Code- Selektion, Registerallokation und Ablaufplanung kombiniert werden. Für Ausdrucksbäume und Load-/Store-Architekturen ist das Finden einer kostenminimalen Zielcodesequenz optimal lösbar. Für DAGs ist das Problem NP-vollständig. Finden einer Ausführungsreihenfolge der Operationen im DAG, die eine minimale Registeranzahl erfordert ist NP-vollständig (Pebble-Game).


Herunterladen ppt "Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für DSPs und Mikrocontroller."

Ähnliche Präsentationen


Google-Anzeigen