1 / 106

Optimierungstechniken in modernen Compilern

Optimierungstechniken in modernen Compilern. Optimierungstechniken für DSPs und Mikrocontroller. 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

ifama
Télécharger la présentation

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 Optimierungstechniken für DSPs und Mikrocontroller

  2. 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-Speicher Y-Speicher ax ay af mx mf my ALU ALU +/- ar mr

  3. 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. 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. Optimierungstechniken für DSPs und Mikrocontroller Ablaufplanung

  6. 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. 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. 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. Konstruktion eines DAGs zu einem Basisblock • Eingabe: Basisblock als Folge von 3-Adress-Code-Anweisungen ir0,…,irn • 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(iri) case "x := y  z": TranslateBinStmt(iri); break; case "x :=  y : TranslateUnaStmt(iri); break; case "x := y" : TranslateCopy(iri); break; case "@x := y" : TranslateStore(iri); break; case "x := @y" : TranslateLoad(iri); 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. Übersetzung von Kopieranweisungen 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)} 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)}

  11. Übersetzung binärer und unärer Operationen 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)} 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)}

  12. Übersetzung von Speicherzugriffen TranslateLoad(x := @y) 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} TranslateStore(@x := 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}

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

  14. 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. Ressourcenmodellierung durch Reservierungstabellen • Reservierungstabelle Tv für jede Operationsart v: • Tv R  {0,...,l}, wobei R die Menge aller Ressourcen ist. • (r,t)  Tv 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 TG  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 Tv  v existiert und • für alle (r,t)  Tv gilt: (r,p+t)  TG. • 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. Maschinenmodell für ressourcenbeschränkte Ablaufplanung • Modellierung der Reservierungstabellen: • R = {ALU1, ALU2, ALU3, FPU, MMU}. • ALU = {{(ALU1,0)}, {(ALU2,0)}, {(ALU3,0)}} delay(ALU) = 1 • MUL = {{(ALU1,0), (ALU1,1)}} delay(MUL) = 2 • FPU = {{(FPU,0),(FPU,1),(FPU,2)}} delay(FPU) = 3 • MMU = {{(MMU,0),(MMU,1)}} delay(MMU) = 2 Schematischer Aufbau des zugehörigen VLIW Prozessors: Speicher Steuerwerk FE/DE Registerbank DE/EX ALU ALU ALU FPU MMU Speicher EX/MEM

  17. 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. 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}; foreach 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 od i = i + 1; for k = 0 to maxDelay-1 do ready(k) := ready(k+1) od; ready(maxDelay) :=  od

  19. Beispiel • Modellierung der Reservierungstabellen: • R = {ALU1, ALU2, ALU3, FPU, MMU}. • ALU = {{(ALU1,0)}, {(ALU2,0)}, {(ALU3,0)}} delay(ALU) = 1 • MUL = {{(ALU1,0), (ALU1,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 0 1 2 Globale Ressourcentabelle: Ready ALU ALU1 ALU2 ALU3 FPU MMU 3 1 0 2 0, 1, 2  1 ALU 3 3 4 4 4 5 5 MUL  5 5

  20. 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. 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 x dom y und x dom z. z pdom x und z pdom y. y x und z sind steuerflussäquivalent. z

  22. 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. 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 = 2 ... x = 1 x = 2 x = 1 ... ...

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

  25. 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. Beispiel load [R1]  R6 || load [R4]  R8 load [R2]  R7 beqz R6, B3 || add r8, r8  r8 store R7  [R3] store R8  [R5] Anlegen einer Kopie und löschen des leeren Basisblocks. load [R1]  R6 || load [R4]  R8 load [R2]  R7 beqz R6, B3 || add r8, r8  r8 store R8  [R5] store R7  [R3] || store R8  [R5]

  27. 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. 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. 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. Beispiel Region B1 B1 B1 B2 B2 R1 R3 B4 B3 B3 B5 B5 R2 R2 B6 B7 B7 B7

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

  32. Optimierungstechniken für DSPs und Mikrocontroller Registerallokation

  33. 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. 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. 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. 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 fi od rd(s) := 

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

  38. Ü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. Beispiel • Übersetzung von t20 := t1 + t16; Aufruf von getVarInReg(t1) und getVarInReg(t16): • Aufruf von getFreeReg() Registerdeskriptor: Erzeugter Spillcode: mov r0,[bp-sd(t0)] mov r0,[sd(a)] mov [bp-sd(t16)],r0 Neuer Registerdeskriptor: i i rd(i) locked rd(i) locked 0 0 (t16,r) 1 0 (t0,w), (a,w) 1 (t1,w), (b,w) 1 (t1,w), (b,w) 0 1 Rückgabewert: r1 für t1 r0 für t16 0 2 (t2,w), (c,w) 0 2 (t2,w), (c,w) … … 0 15 (t15,w), (p,w) 0 15 (t15,w), (p,w) Neuer Registerdeskriptor: Erzeugter Spillcode: Keiner Registerdeskriptor: i i rd(i) locked rd(i) locked Rückgabewert: r0  0 1 0 (t20,w) 0 1 (b,w) 1 (b,w) 1 0 Zielcode: add r1,r0,r0 2 (t2,w), (c,w) 0 2 (t2,w), (c,w) 0 … … 15 (t15,w), (p,w) 0 15 (t15,w), (p,w) 0

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

  41. 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. 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. 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. 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  Kdes 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. Beispiel Steuerflussgraph/Interferenzgraph () (d) (a,d) (a,d,c) d := 0 a := 1 c := 3 u v t (a,d,c) (a,c,f,d) w e f := c (c,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:= 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 a d s f c r z (d,e) (d,c,e) (d,c,a) c:= d+3 a := e*c (a,d) (z) z:= a+d

  46. Färben des Interferenzgraphen • Finden einer Reihenfolge v1,…,vn, in der die Knoten (zusammen mit adjazenten Kanten) aus dem Interferenzgraphen entfernt werden. • Einfügen der Knoten in der Reihenfolge vn,…,v1 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 vi := 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  {vi}; E := E  {{vi,w} | {vi,w}  E' und w  V} (vi) := k, wobei k  {0,…,K-1} – {(w) | {vi,w}  E} od return 

  47. Beispiel 1 u u v t v t w e w e s a d a d s f c r z f c r z I = (V, E), R = {0,1,2,3} w v u t e z s r c f a d

  48. Spillen • Was, wenn der Interferenzgraph nicht zum leeren Graphen reduziert werden kann? • Falls im Algorithmus graphColV   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. 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 vi := 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  {vi}; E := E  {{vi,w} | {vi,w}  E' und w  V} if {0,…,K-1} – {(w) | {vi,w}  E} =  then return ; (vi) := k, wobei k  {0,…,K-1} – {(w) | {vi,w}  E} od return 

  50. Beispiel optimistische vs. pessimistische Strategie Pessimistische Annahme Optimistische Annahme u v t w e s a d a a d d f c r z f f c c I = (V, E), R = {0,1,2} Algorithmus graphCol bricht erst hier ab. Algorithmus graphCol bricht hier ab. w v u t e z s r c f d a w v u t e z s r

More Related