Upload
jutte-landsberger
View
104
Download
0
Embed Size (px)
Citation preview
1
Vorlesung, Wintersemester 2009/10 M. Schölzel
Optimierungstechniken in modernen Compilern
Optimierungstechniken für DSPs und Mikrocontroller
2Optimierungstechniken in modernen Compilern Grundlagen
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
3Optimierungstechniken in modernen Compilern Grundlagen
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.
4Optimierungstechniken in modernen Compilern Grundlagen
Problem der Reihenfolge
RA vor SC: RA erzeugt zusätzliche Abhängigkeiten durch die genutzten Register.
SC vor RA: Hoher Registerdruck kann entstehen. Evtl. erforderlicher Spill-Code muss erneut geplant werden:
Ablaufplanung besteht deshalb aus Prepass und Postpass. CS vor SC:
Schlecht parallelisierbarer Code kann entstehen. SC vor CS:
Nicht alle durch die Architektur vorgegebenen Einschränkungen können bei der Parallelisierung beachtet werden. Konsequenz: Code ist schlecht parallelisiert; Ressourcen bleiben ungenutzt.
RA vor CS: Durch Registerzuordnung können gewisse Zieloperationen nicht mehr
genutzt werden, weil sich die Werte in den falschen Registern befinden.
CS vor RA: Einschränkung der Registerallokation, weil Ergebnisse in ganz
bestimmte Register geschrieben werden. Konsequenz kann hoher Registerdruck sein.
5
Vorlesung, Wintersemester 2009/10 M. Schölzel
Optimierungstechniken für DSPs und Mikrocontroller
Ablaufplanung
6Optimierungstechniken in modernen Compilern Grundlagen
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
7Optimierungstechniken in modernen Compilern Grundlagen
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.
8Optimierungstechniken in modernen Compilern Grundlagen
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.
9Optimierungstechniken in modernen Compilern Grundlagen
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 berechnetfor 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; endodFü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
10Optimierungstechniken in modernen Compilern Grundlagen
Übersetzung von Kopieranweisungen
TranslateCopy(x := y) if findVar(y) = 0 then Erzeuge Knoten n mit label(n) = read y // passiert nur, wenn y Programmvariable N := N {n} S := S {(y,n,R)} fi l := findVar(y) S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,l,W)}
TranslateConst(x := k) if findLabel(const k,0,0) = 0 then Erzeuge Knoten n mit label(n) = const k N := N {n} fi n := findLabel(const k,0,0) S := S {(x,n,W)}
11Optimierungstechniken in modernen Compilern Grundlagen
Übersetzung binärer und unärer Operationen
TranslateBinStmt(x := y z) l := findVar(y) r := findVar(z) if n N mit label(n) = und (l,n) E und (r,n) E und not (ord(r,n) < ord(l,n)) then m := n else Erzeuge einen Knoten m mit Beschriftung N := N {m} E := E {(l,m),(r,m)} ord((l,m)) := 0; ord((r,m)) := 0, falls kommutativ, sonst ord((r,m)) := 1 fi S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,m,W)}
TranslateUnaStmt(x := y) l := findVar(y) // immer erfolgreich if findLabel(, l) then m := findLabel(, l) else Erzeuge neuen Knoten m mit label(m) = N := N {m} E := E {(l,m)} fi S := S – {(x,n,k) | n N und k {R,W}) S := S {(x,m,W)}
12Optimierungstechniken in modernen Compilern Grundlagen
Übersetzung von Speicherzugriffen
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}
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}
13Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
Beispiel: a[i] = b[i] + a[j]
t0 := it1 := 4t2 := t1 * t0t3 := &bt4 := t3 + t2t5 := @t4t6 := jt7 := 4t8 := t7 * t6t9 := &at10 := t9 + t8t11 := @t10t12 := t5 + t11t13 := it14 := 4t15 := t14 * t13t16 := &at17 := t16 + t15@t17 := t12
1read i
2const 4
3
(t0,1,W)
S
(t1,2,W)(t2,3,W)
*4
const &b(t3,4,W)
5+
(t4,5,W)
6 load
(t5,6,W)
7read j
(i,1,R)
(t6,7,W)(j,7,R)
(t7,2,W)
8*
(t8,8,W)
9const &a
(t9,9,W)
10+
(t10,10,W)11 load
(t11,11,W)
12+ (t12,12,W)
(t13,1,W)(t14,2,W)(t15,3,W)(t16,9,W)
13+
(t17,13,W)14 store
14Optimierungstechniken in modernen Compilern Grundlagen
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:0, falls { |( , ) }
( )max{ ( ) ( ) |( , ) }, sonst.
w w v Easap v
asapw delay w w v E
ì Î = Æïïï= íï + Îïïî
max{ ( ) | }, falls { |( , ) }( )
min{ ( ) ( ) |( , ) }, sonst.
asap v v V w v w Ealap v
alapw delay v v w E
ì Î Î = Æïïï= íï - Îïïî
15Optimierungstechniken in modernen Compilern Grundlagen
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.
16Optimierungstechniken in modernen Compilern Grundlagen
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
Registerbank
ALU Speicher
FE/DE
DE/EX
EX/MEM
Ste
ue
rwe
rk
MMUALU
Speicher
Schematischer Aufbau des zugehörigen VLIW Prozessors:
FPUALU
17Optimierungstechniken in modernen Compilern Grundlagen
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.
18Optimierungstechniken in modernen Compilern Grundlagen
List-Scheduling-AlgorithmusEingabe: DAG (N, E, A, ord, label) und delayAusgabe: Schedule
maxDelay := max{delay(t) | t };i = 0;ready(0) = {u | vN: (v,u) (EA)};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) (EA) and w N: (w,u) (EA) 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
19Optimierungstechniken in modernen Compilern Grundlagen
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
ALU
MUL
ALU
0 1 2
3
4
5
ALU1 ALU2 ALU3 FPU MMU
1 0 2
1
3
4
5
Globale Ressourcentabelle: Ready
0, 1, 2345
5
20Optimierungstechniken in modernen Compilern Grundlagen
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.
21Optimierungstechniken in modernen Compilern Grundlagen
Wiederholung Definitionen
Block x dominiert Block y (x dom y) genau dann im Steuerflussgraphen, wenn jeder Pfad vom Startknoten zum Block y durch Block x führt.
Block x postdominiert Block y (x pdom y) genau dann im Steuerflussgraphen, wenn jeder Pfad vom Block y zum Stoppknoten durch Block x führt.
Block x und Block y sind genau dann steuerflussäquivalent, wenn x dom y und y pdom x.
x
z
y
x dom y und x dom z.
z pdom x und z pdom y.
x und z sind steuerflussäquivalent.
22Optimierungstechniken in modernen Compilern Grundlagen
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.
23Optimierungstechniken in modernen Compilern Grundlagen
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 = 1x = 2
...x = 2
...
x = 1
24Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
load [R1] R6nopbeqz R6, B3
load [R2] R7nopstore R7 [R3]
load [R4] R8nopadd r8, r8 r8store R8 [R5]
load [R1] R6 || load [R4] R8load [R2] R7beqz R6, B3 || add r8, r8 r8
store R7 [R3]
store R8 [R5]
Ursprünglicher Steuerflussgraph Transformierter Steuerflussgraph
Architektureigenschaften:load hat Latenz von 2; jede andere Operation 1.Zwei Operationen können parallel ausgeführt werden.
25Optimierungstechniken in modernen Compilern Grundlagen
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.
26Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
load [R1] R6 || load [R4] R8load [R2] R7beqz R6, B3 || add r8, r8 r8
store R7 [R3]
store R8 [R5]
load [R1] R6 || load [R4] R8load [R2] R7beqz R6, B3 || add r8, r8 r8
store R7 [R3] || store R8 [R5]store R8 [R5]
Anlegen einer Kopie und löschen des leeren Basisblocks.
27Optimierungstechniken in modernen Compilern Grundlagen
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.
28Optimierungstechniken in modernen Compilern Grundlagen
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.
29Optimierungstechniken in modernen Compilern Grundlagen
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.
30Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Region
B1
B2
B3
B5
R2
R1
B7
B1
B2
B3
B5
B6
B4
B7
B1
R2
R3
B7
31Optimierungstechniken in modernen Compilern Grundlagen
Scheduling-AlgorithmusEingabe: Steuerflussgraph und Liste der RegionenAusgabe: 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 bCE: (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 odod
32
Vorlesung, Wintersemester 2009/10 M. Schölzel
Optimierungstechniken für DSPs und Mikrocontroller
Registerallokation
33Optimierungstechniken in modernen Compilern Grundlagen
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.
34Optimierungstechniken in modernen Compilern Grundlagen
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.
35Optimierungstechniken in modernen Compilern Grundlagen
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.
36Optimierungstechniken in modernen Compilern Grundlagen
Implementierung von getFreeReg
Eingabe: keineAusgabe: Name eines Registers, dessen Inhalt überschrieben werden kannAlgorithmus getFreeReg:Falls ein i existiert mit 1 i RegNum und rd(i) = dann return iFalls ein i existiert mit 1 i RegNum und für alle (v,x) rd(i) gilt x = r, dann rd(i) := , return iWähle ein s mit 1 s RegNum und s ist nicht gesperrtSpill(s)return s
Eingabe: Registernummer sAusgabe: Zielcode zum Auslagern des RegisterwertesAlgorithmus 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 fiodrd(s) :=
37Optimierungstechniken in modernen Compilern Grundlagen
Beispiel: getFreeReg
Aufruf: getFreeReg() mit Registerdeskriptor:
Aufruf: getFreeReg() mit Registerdeskriptor:
i rd(i)
(t0,w), (a,r)
(t2,w), (c,r)
0
1
2
(t15,w), (p,r)15…
i rd(i)
(t0,w), (t16,w)
(t1,w), (a,w)
(t2,w), (t18,w)
0
1
2
(t15,w), (t31,w)15…
Rückgabewert:
r1
Erzeugter Spillcode:mov r1,[bp-sd(t1)]mov r1,[sd(a)]
Neuer Registerdeskriptor:
Registerdeskriptor:
Rückgabewert:
r1
Erzeugter Spillcode:Keiner
Neuer Registerdeskriptor:
Registerdeskriptor: i rd(i)
(t0,w), (a,r)
(t2,w), (c,r)
0
1
2
(t15,w), (p,r)15…
i rd(i)
(t0,w), (t16,w)
(t2,w), (t18,w)
0
1
2
(t15,w), (t31,w)15…
38Optimierungstechniken in modernen Compilern Grundlagen
Übersetzung binärer/unärer Anweisungen
Eingabe: 3-Adress-Code-Anweisung x := y zAusgabe: ZielcodeAlgorithmus:
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 := yAusgabe: ZielcodeAlgorithmus:
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)
39Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
Übersetzung von t20 := t1 + t16; Aufruf von getVarInReg(t1) und getVarInReg(t16):
Aufruf von getFreeReg()
i rd(i)
(t0,w), (a,w)
(t1,w), (b,w)
(t2,w), (c,w)
0
1
2
(t15,w), (p,w)15…
Rückgabewert:
r1 für t1r0 für t16
Erzeugter Spillcode:mov r0,[bp-sd(t0)]mov r0,[sd(a)]mov [bp-sd(t16)],r0
Neuer Registerdeskriptor:i rd(i)
(t16,r)
(t1,w), (b,w)
(t2,w), (c,w)
0
1
2
(t15,w), (p,w)15…
Registerdeskriptor:
locked locked
0
0
0
0
1
1
0
0
i rd(i)
(b,w)
(t2,w), (c,w)
0
1
2
(t15,w), (p,w)15…
locked
1
1
0
0
Registerdeskriptor:
Rückgabewert:
r0
Erzeugter Spillcode:Keiner
i rd(i)
(t20,w)
(b,w)
(t2,w), (c,w)
0
1
2
(t15,w), (p,w)15…
locked
0
0
0
0
Zielcode:add
r1,r0,r0
Neuer Registerdeskriptor:
40Optimierungstechniken in modernen Compilern Grundlagen
Übersetzung von Labels und Sprunganweisungen
Eingabe: 3-Adress-Code-Anweisung label:Ausgabe: ZielcodeAlgorithmus:
SaveRegs();outCode("label:");Clear();
Eingabe: 3-Adress-Code-Anweisung goto labelAusgabe: ZielcodeAlgorithmus:
SaveRegs();outCode("jmp label");
Eingabe: 3-Adress-Code-Anweisung if x then goto lAusgabe: ZielcodeAlgorithmus:
t := getVarInReg(x);Delete(x,t)SaveRegs();outCode("BNZ t,l");
… a := t7label: t8 := a …
… a := t7 goto label10label9: …
… a := t7 if t8 then goto label20 b := t9 …
Aktualisieren der Werte im Speicher.
Einsprung von verschiedenen Position möglich; Belegung der
Register unklar.
Sprung zu einer Position an der der Registerdeskriptor
gelöscht wird; Aktualisieren der Wert eim Speicher
nötig.
Hier wird die Registerallokation
fortgesetzt.
Sprung zu einer Position an der der Registerdeskriptor
gelöscht wird; Aktualisieren der Wert eim Speicher
nötig.
Fortsetzung der Registeralloka-tion.
Belegung der Register für jede Programmausführung
fest. Kein Sichern erforderlich.
41Optimierungstechniken in modernen Compilern Grundlagen
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.
42Optimierungstechniken in modernen Compilern Grundlagen
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.
43Optimierungstechniken in modernen Compilern Grundlagen
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.
44Optimierungstechniken in modernen Compilern Grundlagen
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.
45Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Steuerflussgraph/Interferenzgraph
d := 0a := 1c := 3
f := c
d:= d+1r := 2*ds := 3*ct := r+se := t+5
d:= a+fu := cv := u+1w := v+1e := w
c:= d+3a := e*c
z:= a+d
()(d)(a,d)(a,d,c)
(a,d,c)(a,c,f,d)
(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,e)(d,c,e)(d,c,a)(a,d)(z)
a d s
f c r
vu
w
t
e
z
46Optimierungstechniken in modernen Compilern Grundlagen
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}odreturn
47Optimierungstechniken in modernen Compilern Grundlagen
Beispiel 1
a d s
f c r
vu
w
t
e
I = (V, E), R = {0,1,2,3}
z
v uw t e z s r c f a
a d s
f c r
vu
w
t
e
z
d
48Optimierungstechniken in modernen Compilern Grundlagen
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}
49Optimierungstechniken in modernen Compilern Grundlagen
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}odreturn
50Optimierungstechniken in modernen Compilern Grundlagen
Beispiel optimistische vs. pessimistische Strategie
a d s
f c r
vu
w
t
e
I = (V, E), R = {0,1,2}
z
v uw t e z s r
a d
f c
v uw t e z s r c f d a
a d
f c
Pessimistische Annahme Optimistische Annahme
Algorithmus graphCol bricht hier ab. Algorithmus graphCol bricht erst hier ab.
51Optimierungstechniken in modernen Compilern Grundlagen
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.
( )
( )
( ), wobei ( ) 10
( )deepth p
p DefUse v
spillCost vspillCost v
degv Î
= å
52Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Spillen
d := 0a := 1c := 3
f := c
d:= d+1r := 2*ds := 3*ct := r+se := t+5
d:= a+fu := cv := u+1w := v+1e := w
c:= d+3a := e*c
z:= a+d
Spillen von d()(d)(a,d)(a,d,c)
(a,d,c)(a,c,f,d)
(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,e)(d,c,e)(d,c,a)(a,d)(z)
d := 0@&d := da := 1c := 3
f := c
d := @&dd := d+1@&d := dd := @&dr := 2*ds := 3*ct := r+se := t+5
d := a+f@&d := du := cv := u+1w := v+1e := w
d := @&dc:= d+3a := e*c
d := @&dz:= a+d
()(d)()(a)(a,c)
(c)(c,d)(c,d)(c,d)(c,d)(c,r)(s,r)(t)(e)
(a,c,f)(c,d)(c)(u)(v)(w)(e)
(e)(d,e)(c,e)(a,c)
(a)(d)(z)
(a,c)(a,c,f)
53Optimierungstechniken in modernen Compilern Grundlagen
Interferenzgraph nach Spillen
d := 0@&d := da := 1c := 3
f := c
d := @&dd := d+1@&d := dd := @&dr := 2*ds := 3*ct := r+se := t+5
d := a+f@&d := du := cv := u+1w := v+1e := w
d := @&dc:= d+3a := e*c
d := @&dz:= a+d
()(d)()(a)(a,c)
(c)(c,d)(c,d)(c,d)(c,d)(c,r)(s,r)(t)(e)
(a,c,f)(c,d)(c)(u)(v)(w)(e)
(e)(d,e)(c,e)(a,c)
(a)(d)(z)
(a,c)(a,c,f)
a d s
f c r
vu
w
t
e
z
54Optimierungstechniken in modernen Compilern Grundlagen
Vollständiger Algorithmus
Interferenzgraphkonstruieren
Interferenzgraphfärben
Spillen
Fertig
3-Adress-Code
3-Adress-Code
nicht erfolgreich
erfolgreich
55Optimierungstechniken in modernen Compilern Grundlagen
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?
56Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
Vorteil Problem
a t
b c
def adef bdef t1
use t1
…def cuse ause bdef t2
use t2
use c
a t1
b c
t2
def adef bdef tuse t…def cuse ause bdef tuse tuse c
def a def a
use a
Interferenzgraph besitzt einen Knoten für a.
def a1 def a2
a3 = (a1,a2)use a3
Interferenzgraph besitzt drei verschiedene Knoten für a, die unterschiedlich gefärbt werden können.
(a)(a,b)(a,b,t)(a,b)
(a,b,c)(b,c)(c)(c,t)(c)
(a)(a,b)(a,b,t1)(a,b)
(a,b,c)(b,c)(c)(c,t2)(c)
57Optimierungstechniken in modernen Compilern Grundlagen
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
b c
t2
a t
b c
Verschmelzen
58Optimierungstechniken in modernen Compilern Grundlagen
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.
def a1 def a2
a3 = (a1,a2)use a3
a1
a2 a3
u: v:
b:
load a R0 jump b_left … load a R2 jump b_right …b_left: mov R0 R3 jump b_startb_right: mov R2 R3b_start: …
SteuerflussgraphMögliche Färbung im Interferenzgraphen Zielcode
u:
v:
b:
59Optimierungstechniken in modernen Compilern Grundlagen
Swap-Problem beim Kopieren Problem:
Alle -Operationen müssen zeitgleich ausgeführt werden. Kopieroperationen generieren eine Permutation der Registerinhalte.
Implementierungsvarianten: xchg-Operation verwenden, falls auf der Architektur vorhanden. Emulation der xchg-Operation durch xor-Operationen:
• Werte a und b in Registern x und y:• x := x xor y • y := y xor x == a • x := x xor y == b
Vertauschung unter Zuhilfenahme eines zusätzlichen Registers, falls vorhanden.
def a1
def b1
def a2
def b2
a3 = (a1,a2)b3 = (b1,b2)
u: v:
b:
a1 und a2 R0
b1 und b2 R1a3 R1b3 R0
SteuerflussgraphMögliche
Registerzuordnung
b_left: b_right: mov R0 R1 mov R1 R0b_start: …
Falscher Zielcode
b_left: b_right: xchg R0,R1b_start: …
Richtiger Zielcode
60Optimierungstechniken in modernen Compilern Grundlagen
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.
61Optimierungstechniken in modernen Compilern Grundlagen
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.
62Optimierungstechniken in modernen Compilern Grundlagen
Beispiel - Interferenzgraph
d0 := 0a0 := 1c0 := 3
d1 := (d0,d4)a1 := (a0,a2)c1 := (c0,c2)f0 := c1
d2:= d1+1r0 := 2*d2
s0 := 3*c1
t0 := r0+s0
e0 := t0+5
d3:= a1+f0
u0 := c1
v0 := u0+1w0 := v0+1e1 := v0
d4 := (d2,d3)e2 := (e0,e1)c2:= d4+3a2 := e2*c2
z0:= a2+d4
()(d0)(a0,d0)(a0,d0,c
0)
(a1,c1,d1)(a1,c1,f0,d
1)
(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)
(d4,e2)(d4,e2,c2)(d4,c2,a2)
(a2,d4)(z0)
a1 d1
f0 c1
u0 e1 z0
a0 d0
c0
r0d2
s0
e0t0d3
v0 w0
a2 d4
c2
e2
63Optimierungstechniken in modernen Compilern Grundlagen
Beispiel - Dominatorbaum
d0 := 0a0 := 1c0 := 3
d1 := (d0,d4)a1 := (a0,a2)c1 := (c0,c2)f0 := c1
d2:= d1+1r0 := 2*d2
s0 := 3*c1
t0 := r0+s0
e0 := t0+5
d3:= a1+f0
u0 := c1
v0 := u0+1w0 := v0+1e1 := v0
d4 := (d2,d3)e2 := (e0,e1)c2:= d4+3a2 := e2*c2
z0:= a2+d4
(d0)(a0,d0)(a0,d0,c0)
(a1,c1,f0,d1)
(c1,d2)(c1,d2,r0)(d2,s0,r0)(d2,t0)(d2,e0)
(c1,d3)(d3,u0)(d3,v0)(d3,w0)(d3,e1)
(d4,e2,c2)(d4,c2,a2)
(z0)
a0
d0
c0
a1
d1
c1
f0
r0
d2
s0
e0
t0
u0
d3
v0
e1
w0
e2
d4
c2
z0
a2
64Optimierungstechniken in modernen Compilern Grundlagen
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.
65Optimierungstechniken in modernen Compilern Grundlagen
Beispiel - Färbung
a1 d1
f0 c1
u0 e1 z0
a0 d0
c0
r0d2
s0
e0t0d3
v0 w0
a2 d4
c2
a0
d0
c0
a1
d1
c1
f0
r0
d2
s0
e0
t0
u0
d3
v0
e1
w0
e2
d4
c2
z0
a2
e2
66Optimierungstechniken in modernen Compilern Grundlagen
Spillen in SSA-Code
1. Strategie: Spillen eines Knotens wie bisher mit Einfügen von Speicher- und
Ladecode. Neukonstruktion des Interferenzgraphen wird erforderlich. Falls der Zwischencode nicht in SSA-Code umgeformt wird, kann die
Eigenschaft der optimalen Färbbarkeit verloren gehen. 2. Strategie:
Cliquen im Interferenzgraphen korrespondieren mit gleichzeitig lebendigen Variablen im SSA-Code
Durch maximale Clique ist die Anzahl der benötigten Farben festgelegt. Spillen wird durchgeführt, bevor der Interferenzgraph aufgebaut wird. Finden der gleichzeitig lebendigen Variablen ist einfach. Damit ergibt sich folgender vollständiger Algorithmus:
Interferenzgraphkonstruieren
Interferenzgraphfärben
Spillen Fertig3-
Adress-Code
67
Vorlesung, Wintersemester 2009/10 M. Schölzel
Optimierungstechniken für DSPs und Mikrocontroller
Codeauswahl
68Optimierungstechniken in modernen Compilern Grundlagen
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.
69Optimierungstechniken in modernen Compilern Grundlagen
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.
Mi C4
* Cb
+
ld
Mj
* Ca
+
ld
+
+
st
C4
Mn
T1:
70Optimierungstechniken in modernen Compilern Grundlagen
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.
Mi Mn
* Cb
+
ld
Mj
* Ca
+
ld
+
+
st
C4
Mn
T1:
Mn
Mi Mn
*
T2:
Mm
71Optimierungstechniken in modernen Compilern Grundlagen
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.
MmCb
+
ld
Mj
* Ca
+
ld
+
+
st
C4
Mn
T1:
Mn
Mi Mn
*
T2:
Mm
Mm
72Optimierungstechniken in modernen Compilern Grundlagen
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.
MmCb
+
ld
Mj
*Ca
+
ld
+
+
st
C4
Mn
T1:
Mn
Mi Mn
*
T2:
Mm
Mm Ca
T3:
73Optimierungstechniken in modernen Compilern Grundlagen
Prinzip Code-Selektion
Cb
+
ld
+
ld
+
Mi CaMj
R1
ld
+
ld
+
CaMj
R1
ld ld
+
R2
R1 ld
+
R2
R1
Zielcode:mov Rz,yadd rz,[x]
mov r1,badd r1,[i]
Cy
+
Mx
Rz
Rx
ld
Ry
Rx ld
+
Ry
Rx
Zielcode:mov Ry,[Rx]
Zielcode:add Rx,[Ry]
mov r2,aadd r2,[j]
mov r1,[r1]
add r1,[r2]
Ersetzungsregeln: Zielcode:
Ausdrucksbaum:
74Optimierungstechniken in modernen Compilern Grundlagen
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.
75Optimierungstechniken in modernen Compilern Grundlagen
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)
Cb
+
ld
+
ld
+
Mi CaMj
Cy
+
Mx
Rz
76Optimierungstechniken in modernen Compilern Grundlagen
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.
Cb
+
ld
+
ld
+
Mi CaMjCy
+
Mx
Rz
Zielcodeschablone:mov Rz,yadd rz,[x]
Variablenanpassung:val(x) = ival(y) = bval(z) = getFreeReg()
R0
Zielcode:mov R0,badd R0,[i]
77Optimierungstechniken in modernen Compilern Grundlagen
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.
78Optimierungstechniken in modernen Compilern Grundlagen
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.
79Optimierungstechniken in modernen Compilern Grundlagen
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
80Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Ershov-Nummerierung
t1 = a – bt2 = c + dt3 = e * t2t4 = t1 + t3
-
a b +
c d
*
e
+
1 1 1
1 1
2
2 2
3
Zwischencodefragment Nummerierter Ausdrucksbaum
81Optimierungstechniken in modernen Compilern Grundlagen
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
82Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
-
a b +
c d
*
e
+
1 1 1
1 1
2
2 2
3b=0
b=1 b=0
b=2 b=1 b=0
b=0
b=1 b=0
Load R2,a
Load R1,b
Sub R2,R1,R2
Load R1,c
Load R0,d
Add R1,R0,R1
Load R0,e
Mul R0,R1,R1
Add R2,R1,R2
Ausdrucksbaum Zielcodesequenz
83Optimierungstechniken in modernen Compilern Grundlagen
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
84Optimierungstechniken in modernen Compilern Grundlagen
Beispiel
-
a b +
c d
*
e
+
1 1 1
1 1
2
2 2
3b=0
b=0 b=0
b=1 b=0 b=0
b=0
b=1 b=0
Load R1,a
Load R0,b
Sub R1,R0,R1
Load R1,c
Load R0,d
Add R1,R0,R1
Load R0,e
Mul R0,R1,R1
Sub R1,R0,R1
Ausdrucksbaum Zielcodesequenz für r = 2
Store R1,newT
Load newT,R0
85Optimierungstechniken in modernen Compilern Grundlagen
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.
86Optimierungstechniken in modernen Compilern Grundlagen
Details Phase 1Eingabe: Knoten n Cm, Rm für jeden Nachfahren m von nAusgabe: Cn…Kosten für Knoten n Mn …Verwendete Muster am Knoten nAlgorithmus: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 od if(Cn[i] > costr + costmin) then Cn[i] = costr + costmin; Mn[i] = r; fi odod
87Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 1
-
Ma Mb +
Mc Md
*
Me
+
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
Passende Muster für Knoten 0 bis 5:
0 1
3 4
25
6 7
8
Muster Gesamtkosten Verfügbare Register
1 1 1
1 1 2
keins 0 alle, Ziel Speicher
1:
2:
3:
4:
5:
6:
C0 = [0,1,1] C1 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
C2 = [0,1,1]
88Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 1
-
Ma Mb +
Mc Md
*
Me
+
C0 = [0,1,1] C1 = [0,1,1]
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
Passende Muster für Knoten 5 und 6:
0 1
3 4
25
6 7
8
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 alle, Ziel Speicher
1:
2:
3:
4:
5:
6:
C6 = [3,2,2]
C5 = [3,2,2]
M6 = [6,4,4]
M5 = [6,4,4]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
89Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 1
-
Ma Mb +
Mc Md
*
Me
+
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
Passende Muster für Knoten 7:
0 1
3 4
25
6 7
8
Muster Gesamtkosten Verfügbare Register
3 2+1+1 | 1+2+1 2
4 3+1+1 1
4' 0+2+1 1
5 1+2+4 | 2+1+4 alle, Ziel Speicher
6 3+1+2 alle, Ziel Speicher
6' 0+2+2 alle, Ziel Speicher
1:
2:
3:
4:
5:
6:
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
90Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 1
-
Ma Mb +
Mc Md
*
Me
+
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
Passende Muster für Knoten 8:
0 1
3 4
25
6 7
8
Muster Gesamtkosten Verfügbare Register
4 4+2+1 1
4' 3+3+1 1
3 3+2+1 | 2+3+1 2
5 2+3+2 | 3+2+2 alle, Ziel Speicher
6 4+2+2 alle, Ziel Speicher
6' 3+3+2 alle, Ziel Speicher
1:
2:
3:
4:
5:
6:
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
91Optimierungstechniken in modernen Compilern Grundlagen
Details Phase 2
Ziel: Finden der Unterbäume von T, deren Ergebnis in den Speicher berechnet werden soll. Vorgehen:
Tiefendurchlauf durch den Baum T beginnend mit maxReg verfügbaren Registern durch cover(n,maxReg), n ist Wurzel.
Bei Aufruf cover(n,r): Falls am Knoten r = 0, dann
• berechne den Wert von n in den Speicher und setze l = 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).
M
M
T1
T2 M
T3
Erzeugter Zielcode:genCode(T1,0)genCode(T2,0)genCode(T3,0)Zielcode für T4T4
ld
+
Rx ld
+
Ry
Rx
Zielcode:add Rx,[Ry]
Muster 4:
+
C+ = [7,8,2] cover(+,2)
M+ = [5,3,4]
92Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 2
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
Tiefendurchlauf:cover(8,2)cover(7,2)
cover(6,1)
cover(5,2)
Kein Ergebnis wird in den Speicher berechnet!
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
cover(3,2)
cover(0,1)
93Optimierungstechniken in modernen Compilern Grundlagen
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.
M
M
T1
T2 M
T3
T4
M
M
T'M
M
T2 M
T3
T4
M
M
T3
T4
Phase 2 Phase 3
94Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:cover(8,2)cover(7,2)
cover(6,1)
cover(5,2)cover(3,2) genCode(3,0)
cover(0,1)
load [c],R0
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
95Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:cover(8,2)cover(7,2)
cover(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1)
load [c],R0
-
Ma Mb +
R0 Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
add R0,[d],R0
96Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:cover(8,2)cover(7,2) ) genCode(7,0)
cover(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1)
load [c],R0
-
Ma Mb R0
*
Me
+
0 1 25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
add R0,[d],R0
mul R0,[e],R0
97Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:cover(8,2)cover(7,2) ) genCode(7,0)
cover(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1) genCode(0,1)
load [c],R0
-
Ma Mb
R0
+
0 1
6 7
8
C6 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
add R0,[d],R0
mul R0,[e],R0
load [a],R1
98Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:cover(8,2)cover(7,2) ) genCode(7,0)
cover(6,1) genCode(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1) genCode(0,1)
load [c],R0
-
R1 Mb
R0
+
0 1
6 7
8
C6 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C1 = [0,1,1]
M1 = [x,1,1]
add R0,[d],R0
mul [e],R0,R0
load [a],R1
sub R1,[b],R1
99Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:
cover(8,2) genCode(8,0)cover(7,2) ) genCode(7,0)
cover(6,1) genCode(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1) genCode(0,1)
load [c],R0
R1 R0
+
6 7
8
C7 = [4,3,3]
C8 = [7,7,6]
M7 = [6',4',4']
M8 = [5,4,3]
add R0,[d],R0
mul [e],R0,R0
load [a],R1
sub R1,[b],R1
add R1,R0,R0
100Optimierungstechniken in modernen Compilern Grundlagen
Beispiel Phase 3
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
Tiefendurchlauf:
cover(8,2) genCode(8,0)cover(7,2) ) genCode(7,0)
cover(6,1) genCode(6,1)
cover(5,2) genCode(5,0)cover(3,2) genCode(3,0)
cover(0,1) genCode(0,1)
load [c],R0
add R0,[d],R0
mul [e],R0,R0
load [a],R1
sub R1,[b],R1
add R1,R0,R0
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
101Optimierungstechniken in modernen Compilern Grundlagen
Beispiel mit unzureichender Registeranzahl
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
7
-
+8'
C8' = [7,7,6]M8' = [5,4,3]
9
Muster Gesamtkosten Verfügbare Register
4 7+7+1 1
4' 7+7+1 1
3 7+6+1 | 7+6+1 2
5 7+6+2 | 7+6+2 alle, Ziel Speicher
6 7+6+2 alle, Ziel Speicher
6' 7+6+2 alle, Ziel Speicher
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
C9 = [15,15,14]
M9 = [5,4,3]
102Optimierungstechniken in modernen Compilern Grundlagen
Beispiel mit unzureichender Registeranzahl
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
7
-
8'
C8' = [7,7,6]M8' = [5,4,3]
9
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
C9 = [15,15,14]
M9 = [5,4,3]
Tiefendurchlauf:cover(9,2)cover(8',2)
…cover(8,1)
103Optimierungstechniken in modernen Compilern Grundlagen
Beispiel mit unzureichender Registeranzahl
-
Ma Mb +
Mc Md
*
Me
+
0 1
3 4
25
6 7
8
C6 = [3,2,2]
C5 = [3,2,2]
C7 = [4,3,3]
C8 = [7,7,6]
M6 = [6,4,4]
M5 = [6,4,4]
M7 = [6',4',4']
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1] C2 = [0,1,1]
C3 = [0,1,1] C4 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
M3 = [x,1,1] M4 = [x,1,1]
7
-
8'
C8' = [7,7,6]M8' = [5,4,3]
9
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
C9 = [15,15,14]
M9 = [5,4,3]Tiefendurchlauf:cover(9,2)cover(8',2)
…cover(8,1)cover(7,0)cover(5,2)cover(3,2)
load [c],R1
add R1,[d],R1
mul [e],R1,R1store r1,[t1]
104Optimierungstechniken in modernen Compilern Grundlagen
Beispiel mit unzureichender Registeranzahl
-
Ma Mb
Mt1
+
0 1
6 7
8
C6 = [3,2,2]
C8 = [7,7,6]
M6 = [6,4,4]
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
-
8'
C8' = [7,7,6]M8' = [5,4,3]
9
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
C9 = [15,15,14]
M9 = [5,4,3]Tiefendurchlauf:cover(9,2)cover(8',2)
…cover(8,1)cover(7,0)cover(5,2)cover(3,2)
load [c],R1
add R1,[d],R1
mul [e],R1,R1store r1,[t1]
cover(6,1)
105Optimierungstechniken in modernen Compilern Grundlagen
Beispiel mit unzureichender Registeranzahl
-
Ma Mb
Mt1
+
0 1
6 7
8
C6 = [3,2,2]
C8 = [7,7,6]
M6 = [6,4,4]
M8 = [5,4,3]
C0 = [0,1,1] C1 = [0,1,1]
M0 = [x,1,1] M1 = [x,1,1]
-
8'
C8' = [7,7,6]M8' = [5,4,3]
9
Ry
op
Rx
Rx
Mx Ry load [x],Ry
op Rx,Ry,Rx
My
op
Rx
Rx op Rx,[y],Rx
Rx My store Rx,[y]
Ry
op
Rx
Mzop Rx,Ry,Rxstore Rx,[z]
My
op
Rx
Mz op Rx,[y],Rxstore Rx,[z]
1:
2:
3:
4:
5:
6:
C9 = [15,15,14]
M9 = [5,4,3]Tiefendurchlauf:cover(9,2)cover(8',2)
…cover(8,1)cover(7,0)cover(5,2)cover(3,2)
load [c],R1
add R1,[d],R1
mul [e],R1,R1store r1,[t1]
cover(6,1)
// Code für 8'load [a],R0
sub R0,[b],R0
add R0,[t1],R0
sub R0,R1,R1
106Optimierungstechniken in modernen Compilern Grundlagen
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).