Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Optimierungstechniken in modernen Compilern

Ähnliche Präsentationen


Präsentation zum Thema: "Optimierungstechniken in modernen Compilern"—  Präsentation transkript:

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 my mf 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 := y" : TranslateStore(iri); break; case "x : 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 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} := 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 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 + 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}; 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) := 

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 1 2 Globale Ressourcentabelle: Ready ALU ALU1 ALU2 ALU3 FPU MMU 3 1 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 = 2 x = 1 x = 1 ... ...

24 Beispiel load [R1]  R6 nop beqz R6, B3 load [R2]  R7 nop
Architektureigenschaften: load hat Latenz von 2; jede andere Operation 1. Zwei Operationen können parallel ausgeführt werden. 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 B3 B4 B3 R1 R3 B5 B5 B6 R2 R2 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;

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)]") outCode("mov s,[bp-sd(v)]") fi od rd(s) := 

37 Beispiel: getFreeReg Aufruf: getFreeReg() mit Registerdeskriptor:
Erzeugter Spillcode: Keiner Neuer Registerdeskriptor: i rd(i) i rd(i) (t0,w), (a,r) (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 rd(i) i rd(i) (t0,w), (t16,w) (t0,w), (t16,w) 1 (t1,w), (a,w) 1 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 rd(i) locked i rd(i) locked (t0,w), (a,w) (t16,r) 1 1 (t1,w), (b,w) 1 (t1,w), (b,w) 1 Rückgabewert: r1 für t1 r0 für t16 2 (t2,w), (c,w) 2 (t2,w), (c,w) 15 (t15,w), (p,w) 15 (t15,w), (p,w) Registerdeskriptor: Erzeugter Spillcode: Keiner Neuer Registerdeskriptor: i rd(i) locked i rd(i) locked Rückgabewert: r0 1 (t20,w) 1 (b,w) 1 1 (b,w) 2 (t2,w), (c,w) Zielcode: add r1,r0,r0 2 (t2,w), (c,w) 15 (t15,w), (p,w) 15 (t15,w), (p,w)

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  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 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,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} return 

47 Beispiel 1 u u v t v t w e w e a d s a d s f c r z f c r z w v u t e 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 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 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} return 

50 Beispiel optimistische vs. pessimistische Strategie
Pessimistische Annahme Optimistische Annahme u v t w e a d s 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

51 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 Beispiel Spillen Spillen von d () (d) (a) (a,c) d := 0 @&d := d a := 1
(a,d) (a,d,c) d := 0 a := 1 c := 3 (a,c) (a,c,f) (a,d,c) (a,c,f,d) f := c f := c (c) (c,d) (c,r) (s,r) (t) (e) d d := d+1 @&d := d r := 2*d s := 3*c t := r+s e := t+5 (a,c,f) (c,d) (c) (u) (v) (w) (e) d := a+f @&d := d u := c v := u+1 w := v+1 e := w (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 (e) (d,e) (c,e) (a,c) (d,e) (d,c,e) (d,c,a) d c:= d+3 a := e*c c:= d+3 a := e*c (a) (d) (z) (a,d) (z) d z:= a+d z:= a+d

53 Interferenzgraph nach Spillen
() (d) (a) (a,c) d := 0 @&d := d a := 1 c := 3 (a,c) (a,c,f) f := c u (c) (c,d) (c,r) (s,r) (t) (e) v t d d := d+1 @&d := d r := 2*d s := 3*c t := r+s e := t+5 (a,c,f) (c,d) (c) (u) (v) (w) (e) d := a+f @&d := d u := c v := u+1 w := v+1 e := w w e a d s (e) (d,e) (c,e) (a,c) d c:= d+3 a := e*c f c r z (a) (d) (z) d z:= a+d

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

55 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 Beispiel Vorteil Problem a t b c a t1 b c t2
def a def b def t use t def c use a use b use c (a) (a,b) (a,b,t) (a,b,c) (b,c) (c) (c,t) def a def a a t use a b c Interferenzgraph besitzt einen Knoten für a. def a def b def t1 use t1 def c use a use b def t2 use t2 use c (a) (a,b) (a,b,t1) (a,b,c) (b,c) (c) (c,t2) a t1 def a1 def a2 b c a3 = (a1,a2) use a3 Interferenzgraph besitzt drei verschiedene Knoten für a, die unterschiedlich gefärbt werden können. t2

57 Eliminierung -Funktion - Variante 1
Verschmelzen aller SSA-Variablen v1,…,vn derselben Variablen zur selben Variablen v: Für jede Variable v: Es seien v1,…,vn die SSA-Variablen der Variablen v. N' := {v | vi  N}. E' := {{u,v} | {uj,vi}  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 t1 a t Verschmelzen b c b c t2

58 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 a11,…,an1 und über v die Definitionen a12,…an2. a13,…an3 seien die Definitionen dieser Variablen in b durch -Funktionen. a11,…,an1, a12,…an2, a13,…an3 wurden Register zugeordnet. Falls b über u betreten wird, müssen die Werte von a11,…,an1 in die Register kopiert werden, in denen die Werte von a13,…an3 erwartet werden. Falls b über v betreten wird, müssen die Werte von a12,…,an2 in die Register kopiert werden, in denen die Werte von a13,…an3 erwartet werden. Einfügen der entsprechenden Kopieroperationen am Ende von u und v oder am Beginn von 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: u: u: def a1 v: def a2 a1 v: b: a3 = (a1,a2) use a3 a2 a3 b: Mögliche Färbung im Interferenzgraphen Steuerflussgraph Zielcode

59 Swap-Problem beim Kopieren
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. a1 und a2  R0 def a1 def b1 def a2 def b2 b1 und b2  R1 u: v: b_left: b_right: mov R0  R1 mov R1  R0 b_start: b_left: b_right: xchg R0,R1 b_start: a3  R1 b3  R0 b: a3 = (a1,a2) b3 = (b1,b2) Mögliche Registerzuordnung Falscher Zielcode Richtiger Zielcode Steuerflussgraph

60 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) = Dv dominiert. Wenn zwei Variablen u und v an derselben Programmposition lebendig sind, dann gilt entweder Du dom Dv oder Dv dom Du. Wenn u und v an derselben Programmposition lebendig sind und Dv dom Du, dann ist v bei Du lebendig. In einem Interferenzgraphen zu einem SSA-Programm gilt damit: Für jede Clique {v1,…,vn} im Interferenzgraphen existiert eine Programmposition, an der alle Variablen v1,…,vn lebendig sind.

61 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 Da dom Db, dann Db dom Dc. 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 Beispiel - Interferenzgraph
() (d0) (a0,d0) (a0,d0,c0) d0 := 0 a0 := 1 c0 := 3 a0 d0 a2 d4 e2 d1 := (d0,d4) a1 := (a0,a2) c1 := (c0,c2) f0 := c1 c0 c2 (a1,c1,d1) (a1,c1,f0,d1) a1 d1 s0 (d1,c1) (c1,d2) (c1,d2,r0) (d2,s0,r0) (d2,t0) (d2,e0) (c1,a1,f0) (c1,d3) (d3,u0) (d3,v0) (d3,w0) (d3,e1) d2:= d1+1 r0 := 2*d2 s0 := 3*c1 t0 := r0+s0 e0 := t0+5 d3:= a1+f0 u0 := c1 v0 := u0+1 w0 := v0+1 e1 := v0 f0 c1 d2 r0 d3 t0 e0 d4 := (d2,d3) e2 := (e0,e1) c2:= d4+3 a2 := e2*c2 (d4,e2) (d4,e2,c2) (d4,c2,a2) u0 v0 w0 e1 z0 z0:= a2+d4 (a2,d4) (z0)

63 Beispiel - Dominatorbaum
c0 := 3 (d0) (a0,d0) (a0,d0,c0) a0 c0 d1 := (d0,d4) a1 := (a0,a2) c1 := (c0,c2) f0 := c1 d1 a1 (a1,c1,f0,d1) c1 d2:= d1+1 r0 := 2*d2 s0 := 3*c1 t0 := r0+s0 e0 := t0+5 d3:= a1+f0 u0 := c1 v0 := u0+1 w0 := v0+1 e1 := v0 (c1,d2) (c1,d2,r0) (d2,s0,r0) (d2,t0) (d2,e0) (c1,d3) (d3,u0) (d3,v0) (d3,w0) (d3,e1) f0 d2 d3 d4 r0 u0 e2 d4 := (d2,d3) e2 := (e0,e1) c2:= d4+3 a2 := e2*c2 s0 v0 c2 (d4,e2,c2) (d4,c2,a2) t0 w0 a2 e0 e1 z0 z0:= a2+d4 (z0)

64 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 Beispiel - Färbung d0 a0 d0 e2 a0 a2 d4 c0 c0 c2 d1 a1 a1 d1 s0 c1 f0
t0 e0 s0 v0 c2 t0 w0 a2 u0 v0 w0 e1 z0 e0 e1 z0

66 Spillen in SSA-Code 1. Strategie: 2. 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: 3- Adress- Code Interferenzgraph konstruieren Interferenzgraph färben Spillen Fertig

67 Optimierungstechniken für DSPs und Mikrocontroller
Codeauswahl

68 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 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)EDAG 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 nv mit Beschriftung read newV. Mj Mi C4 * Cb * Ca + + + T1: ld ld C4 Mn + st

70 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)EDAG 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 nv mit Beschriftung read newV. Mj Mi Mn Mn * Cb * Ca + + + T1: ld ld C4 Mn + T2: Mi Mn * st Mm

71 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)EDAG 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 nv mit Beschriftung read newV. Mj Mn Mm Mm Cb * Ca + + + T1: ld ld C4 Mn + T2: Mi Mn * st Mm

72 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)EDAG 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 nv mit Beschriftung read newV. T3: Mj Mn Mm Mm Ca Ca Cb * + + + T1: ld ld C4 Mn + T2: Mi Mn * st Mm

73 Prinzip Code-Selektion
Ausdrucksbaum: Mi Cb Mj Ca Mj Ca R1 R2 R2 + + R1 + ld ld R1 ld ld ld ld ld + + R1 + + Ersetzungsregeln: Zielcode: mov r1,b add r1,[i] Mx Cy Rx Ry Rz Ry + ld Rx ld Rx mov r2,a add r2,[j] + mov r1,[r1] Zielcode: mov Rz,y add rz,[x] Zielcode: mov Ry,[Rx] Zielcode: add Rx,[Ry] add r1,[r2]

74 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 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) Mi Cb Mj Ca Mx Cy + + Rz + ld ld +

76 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. Mi Cb Mj Ca Zielcodeschablone: mov Rz,y add rz,[x] Mx Cy Rz + R0 + + Variablenanpassung: val(x) = i val(y) = b val(z) = getFreeReg() Zielcode: mov R0,b add R0,[i] ld ld +

77 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 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 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 Beispiel Ershov-Nummerierung
Zwischencodefragment Nummerierter Ausdrucksbaum 1 1 t1 = a – b t2 = c + d t3 = e * t2 t4 = t1 + t3 c d 1 1 1 a b e + 2 - * 2 2 + 3

81 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 Rb+k-1,Rb+k-2,Rb+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 Rb+k-1,Rb+m-1,Rb+k-1 oder op Rb+m-1,Rb+k-1,Rb+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 Rb+k-1,Rb+k-1 n ist ein Blatt: Load Rb,x oder Const Rb,x

82 Beispiel Ausdrucksbaum Zielcodesequenz Load R2,a Load R1,b
Sub R2,R1,R2 1 1 1 a b e b=0 + Load R1,c b=1 2 b=2 b=0 Load R0,d b=1 b=0 - * Add R1,R0,R1 2 2 Load R0,e + Mul R0,R1,R1 b=0 3 Add R2,R1,R2

83 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 Rr-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 Rr-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,Rr-2 Generiere Code für n: op Rr-2,Rr-1,Rr-1 oder op Rr-1,Rr-2,Rr-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 Rr,Rr

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

85 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 Cn[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 Details Phase 1 Eingabe: Knoten n Cm, Rm für jeden Nachfahren m von n
Ausgabe: Cn…Kosten für Knoten n Mn …Verwendete Muster am Knoten n Algorithmus: for(i = 1; i  maxRegs; i++) do Cn[i] = ; for each r   das an Knoten n passt do m1,…,mz seien die Nachfahren von n, die mit den Blättern in r matchen und als Speicherklasse M haben. r1,…,rk seien die Nachfahren von n, die mit den Blättern in r matchen und als Speicherklasse R haben. costr = cost(r) // Kosten des Musters r selbst for(j = 1; j <= z; j++) do costr = costr + Cmj[0]; od costmin = ; for each p1,…,pk wobei p1,…,pk eine Permutation der Blätter r1,…,rk ist do costreg = 0; for(j = 1; j <= k; j++) do costreg = costreg + Cpj[i-(j-1)] od costmin = min(costmin,costreg); fi if(Cn[i] > costr + costmin) then Cn[i] = costr + costmin; Mn[i] = r; fi

87 Beispiel Phase 1 1: 2: 3: 4: Passende Muster für Knoten 0 bis 5: 5: 6:
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 Rx Ry 3: - * Rx op Rx,Ry,Rx 6 7 op + 8 Rx My 4: Rx op Rx,[y],Rx Passende Muster für Knoten 0 bis 5: op Muster Gesamtkosten Verfügbare Register 1 2 keins alle, Ziel Speicher Rx Ry 5: op Rx,Ry,Rx store Rx,[z] Mz op Rx My 6: Mz op Rx,[y],Rx store Rx,[z] op

88 Beispiel Phase 1 1: 2: 3: 4: Passende Muster für Knoten 5 und 6: 5: 6:
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op + 8 Rx My 4: Rx op Rx,[y],Rx Passende Muster für Knoten 5 und 6: op Muster Gesamtkosten Verfügbare Register 4 1+1 1 3 1+1+1 | 1+1+1 2 5 1+1+2 | 1+1+2 alle, Ziel Speicher 6 0+1+2 Rx Ry 5: op Rx,Ry,Rx store Rx,[z] Mz op Rx My 6: Mz op Rx,[y],Rx store Rx,[z] op

89 Beispiel Phase 1 1: 2: 3: 4: Passende Muster für Knoten 7: 5: 6:
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 Rx My 4: Rx op Rx,[y],Rx Passende Muster für Knoten 7: op Muster Gesamtkosten Verfügbare Register 3 2+1+1 | 1+2+1 2 4 3+1+1 1 4' 0+2+1 5 1+2+4 | 2+1+4 alle, Ziel Speicher 6 3+1+2 6' 0+2+2 Rx Ry 5: op Rx,Ry,Rx store Rx,[z] Mz op Rx My 6: Mz op Rx,[y],Rx store Rx,[z] op

90 Beispiel Phase 1 1: 2: 3: 4: Passende Muster für Knoten 8: 5: 6:
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Passende Muster für Knoten 8: Muster Gesamtkosten Verfügbare Register 4 4+2+1 1 4' 3+3+1 3 3+2+1 | 2+3+1 2 5 2+3+2 | 3+2+2 alle, Ziel Speicher 6 4+2+2 6' 3+3+2 Rx Ry 5: op Rx,Ry,Rx store Rx,[z] Mz op Rx My 6: Mz op Rx,[y],Rx store Rx,[z] op

91 Details Phase 2 Erzeugter Zielcode: genCode(T1,0) genCode(T2,0)
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 = Mn[0], sonst berechne den Wert von n in Register r-1 und setze l = Mn[r]. Es seien m1,…,mz die Blätter in l mit (mi) = M und b1,…,bz die Knoten in T, die mit m1,…,mz matchen: Berechne cover(mi,0) für 1  i  z. Es seien r1,…,rk die Blätter in l mit (mi) = R und p1,…,pk die Reihenfolge, in der diese Blätter in Phase 1 ausgewertet wurden und die die minimalen Kosten lieferte: Berechne cover(pi,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). T1 Muster 4: Erzeugter Zielcode: genCode(T1,0) genCode(T2,0) genCode(T3,0) Zielcode für T4 Ry M T3 + T2 Rx ld Rx M ld M + + T4 Zielcode: add Rx,[Ry] C+ = [7,8,2] cover(+,2) M+ = [5,3,4]

92 Beispiel Phase 2 1: 2: 3: 4: Tiefendurchlauf: 5: 6:
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: Rx Ry cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz cover(7,2) op cover(5,2) cover(3,2) Rx My cover(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1) op Kein Ergebnis wird in den Speicher berechnet!

93 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 r1,…,rk die Blätter in M[r] mit (mi) = R und p1,…,pk die Reihenfolge, in der diese Blätter in Phase 1 und 2 ausgewertet wurden und die die minimalen Kosten lieferte: Berechne genCode(pi,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. T1 M T3 M T3 T3 M T2 T2 M M M M M M M T' T4 T4 T4 Phase 2 Phase 3

94 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6: cover(8,2)
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz cover(7,2) op cover(5,2) cover(3,2)  genCode(3,0) Rx My cover(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1) op

95 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6: cover(8,2)
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: R0 Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz cover(7,2) op cover(5,2)  genCode(5,0) cover(3,2)  genCode(3,0) Rx My cover(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1) op

96 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6: cover(8,2)
Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me R0 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz mul R0,[e],R0 cover(7,2) )  genCode(7,0) op cover(5,2)  genCode(5,0) cover(3,2)  genCode(3,0) Rx My cover(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1) op

97 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6: cover(8,2)
Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] C0 = [0,1,1] C1 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb 1 Rx Ry 3: C6 = [3,2,2] - R0 Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz mul R0,[e],R0 cover(7,2) )  genCode(7,0) op load [a],R1 cover(5,2)  genCode(5,0) cover(3,2)  genCode(3,0) Rx My cover(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1)  genCode(0,1) op

98 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6: cover(8,2)
Mx Ry load [x],Ry M1 = [x,1,1] C1 = [0,1,1] 2: Rx My store Rx,[y] R1 Mb 1 Rx Ry 3: C6 = [3,2,2] - R0 Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2) 5: op Rx,Ry,Rx store Rx,[z] Mz mul [e],R0,R0 cover(7,2) )  genCode(7,0) op load [a],R1 cover(5,2)  genCode(5,0) sub R1,[b],R1 cover(3,2)  genCode(3,0) Rx My cover(6,1)  genCode(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1)  genCode(0,1) op

99 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6:
Mx Ry load [x],Ry 2: Rx My store Rx,[y] Rx Ry 3: R1 R0 Rx op Rx,Ry,Rx 6 7 op C7 = [4,3,3] M7 = [6',4',4'] + 8 C8 = [7,7,6] Rx My 4: M8 = [5,4,3] Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2)  genCode(8,0) 5: op Rx,Ry,Rx store Rx,[z] Mz mul [e],R0,R0 cover(7,2) )  genCode(7,0) op load [a],R1 cover(5,2)  genCode(5,0) sub R1,[b],R1 cover(3,2)  genCode(3,0) Rx My add R1,R0,R0 cover(6,1)  genCode(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1)  genCode(0,1) op

100 Beispiel Phase 3 1: 2: 3: 4: Tiefendurchlauf: 5: 6:
Mc Md Mx Ry load [x],Ry 3 4 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 Rx Ry 3: - * Rx op Rx,Ry,Rx 6 7 op + Rx My 4: Rx op Rx,[y],Rx op Tiefendurchlauf: load [c],R0 Rx Ry add R0,[d],R0 cover(8,2)  genCode(8,0) 5: op Rx,Ry,Rx store Rx,[z] Mz mul [e],R0,R0 cover(7,2) )  genCode(7,0) op load [a],R1 cover(5,2)  genCode(5,0) sub R1,[b],R1 cover(3,2)  genCode(3,0) Rx My add R1,R0,R0 cover(6,1)  genCode(6,1) 6: Mz op Rx,[y],Rx store Rx,[z] cover(0,1)  genCode(0,1) op

101 Beispiel mit unzureichender Registeranzahl
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 7 M6 = [6,4,4] op C7 = [4,3,3] 8' M7 = [6',4',4'] 8 + + C8 = [7,7,6] Rx My C8' = [7,7,6] 4: M8 = [5,4,3] M8' = [5,4,3] Rx op Rx,[y],Rx - op C9 = [15,15,14] 9 M9 = [5,4,3] Rx Ry Muster Gesamtkosten Verfügbare Register 4 7+7+1 1 4' 3 7+6+1 | 7+6+1 2 5 7+6+2 | 7+6+2 alle, Ziel Speicher 6 7+6+2 6' 5: op Rx,Ry,Rx store Rx,[z] Mz op Rx My 6: Mz op Rx,[y],Rx store Rx,[z] op

102 Beispiel mit unzureichender Registeranzahl
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 7 M6 = [6,4,4] op C7 = [4,3,3] 8' M7 = [6',4',4'] 8 + C8 = [7,7,6] Rx My C8' = [7,7,6] 4: M8 = [5,4,3] M8' = [5,4,3] Rx op Rx,[y],Rx - op C9 = [15,15,14] 9 M9 = [5,4,3] Tiefendurchlauf: Rx Ry 5: op Rx,Ry,Rx store Rx,[z] Mz cover(9,2) op cover(8',2) Rx My cover(8,1) 6: Mz op Rx,[y],Rx store Rx,[z] op

103 Beispiel mit unzureichender Registeranzahl
M3 = [x,1,1] M4 = [x,1,1] C3 = [0,1,1] C4 = [0,1,1] 1: Mc Md Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] 3 4 C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb Me + 5 1 2 C5 = [3,2,2] M5 = [6,4,4] Rx Ry 3: C6 = [3,2,2] - * Rx op Rx,Ry,Rx 6 7 7 M6 = [6,4,4] op C7 = [4,3,3] 8' M7 = [6',4',4'] 8 + C8 = [7,7,6] Rx My C8' = [7,7,6] 4: M8 = [5,4,3] M8' = [5,4,3] Rx op Rx,[y],Rx - op Tiefendurchlauf: C9 = [15,15,14] 9 M9 = [5,4,3] cover(9,2) Rx Ry cover(8',2) 5: op Rx,Ry,Rx store Rx,[z] Mz load [c],R1 op add R1,[d],R1 cover(8,1) mul [e],R1,R1 store r1,[t1] cover(7,0) Rx My cover(5,2) 6: Mz op Rx,[y],Rx store Rx,[z] cover(3,2) op

104 Beispiel mit unzureichender Registeranzahl
1: Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] C0 = [0,1,1] C1 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb 1 Rx Ry 3: C6 = [3,2,2] - Mt1 Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op 8' 8 + C8 = [7,7,6] Rx My C8' = [7,7,6] 4: M8 = [5,4,3] M8' = [5,4,3] Rx op Rx,[y],Rx - op Tiefendurchlauf: C9 = [15,15,14] 9 M9 = [5,4,3] cover(9,2) Rx Ry cover(8',2) 5: op Rx,Ry,Rx store Rx,[z] Mz load [c],R1 op add R1,[d],R1 cover(8,1) mul [e],R1,R1 store r1,[t1] cover(7,0) Rx My cover(5,2) 6: Mz op Rx,[y],Rx store Rx,[z] cover(3,2) op cover(6,1)

105 Beispiel mit unzureichender Registeranzahl
1: Mx Ry load [x],Ry M0 = [x,1,1] M1 = [x,1,1] C0 = [0,1,1] C1 = [0,1,1] 2: Rx My store Rx,[y] Ma Mb 1 Rx Ry 3: C6 = [3,2,2] - Mt1 Rx op Rx,Ry,Rx 6 7 M6 = [6,4,4] op 8' 8 + C8 = [7,7,6] Rx My C8' = [7,7,6] 4: M8 = [5,4,3] M8' = [5,4,3] Rx op Rx,[y],Rx - op Tiefendurchlauf: C9 = [15,15,14] 9 M9 = [5,4,3] cover(9,2) Rx Ry cover(8',2) load [c],R1 5: op Rx,Ry,Rx store Rx,[z] Mz add R1,[d],R1 op cover(8,1) mul [e],R1,R1 store r1,[t1] cover(7,0) Rx My // Code für 8' cover(5,2) 6: load [a],R0 Mz op Rx,[y],Rx store Rx,[z] cover(3,2) op sub R0,[b],R0 cover(6,1) add R0,[t1],R0 sub R0,R1,R1

106 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 "Optimierungstechniken in modernen Compilern"

Ähnliche Präsentationen


Google-Anzeigen