Compilerbau/Kapitel 5: Codeerzeugung
Aus Halloserv Wiki
Inhaltsverzeichnis |
Übersicht
Vorbereitung: Transformation des Codes
Konstantenpropagierung
Zuerst werden triviale Ausdrücke symbolisch ausgewertet.
Bei einer Variablenbindung der Form
let x = c in e
werden die Terme der Form
reduziert.
let x = Vector() in e let
= x in e'
Danach wird der nutzlose (weil z.B. nicht ausgeführte) Code entfernt.
Beispielsweise ist let x = h in e nutzlos, falls h ein trivialer Ausdruck oder ein Vektor
mit
ist.
Instruktionsauswahl
Abbildung trivialer Ausdrücke, Header und wichtiger Ausdrücke (Serious) auf Assembler-Befehle.
- Serious: Jeder Term entspricht einer Folge von Assembler-Befehlen
- trivialer Ausdruck: zwei verschiedene Verfahren (greedy und optimal)
-
-viele Register
- wir ignorieren vorerst das Retten von Registern über Funktionsaufrufe
m rt reg-arg0: erzeugt Spielraum bei der Registerallokation, falls in der Funktion arg0 gebraucht wird. framepointer sind eigentlich unnötig, da sp-fp konstant ist.
Konkret für MIPS-Assembler:
- RISE, LOAD/STORE
- 32 GPRs (General Purpose Registers)
- Sonderregister: PC, ..., HI, LO
- Koprozesor: FPU
siehe Instruktionsauswahl durch Patternmatching
Lebendigkeitsanalyse
siehe Lebendigkeitsanalyse
Registerzuteilung
siehe Registerallokation
Instruktionsauswahl durch Patternmatching
Idee:
- beschreibe Instruktionen durch Fragmente von trivialen Ausdrücken
- jede Instruktion / jedes Fragment ist mit Kosten assoziiert, beispielsweise die Anzahl der Maschinenzyklen für die Ausführung
- bestimme dann die Abdeckung eines trivialen Ausdrucks aus Fragmenten mit minimalen Kosten.
2 grundliegende Algorithmen:
- greedy
- top-down-Patternmatching der Fragmente auf den Eingabeausdruck
- Pattern dürfen überlappen
- Pattern aufsteigend nach Kosten geordnet
einfach (durch Ocaml-Patternmatching, findet aber nicht die Abdeckung mit minimalen Kosten
- Appell: "optimal cover" (2 aufeinandertreffende Fragmente können nicht durch ein Fragment mit geringeren Kosten ersetzt werden
- dynamische Programmierung
- verwende Baumgrammatik mit Attributen (
Verallgemeinerung von kontextfreien Grammatiken)
- Menge von nichtterminalen Symbolen können Attribute mit sich führen
- Produktionen der Form
, wobei Blätter auch Nichtterminale sein können.
- verwende Baumgrammatik mit Attributen (
<div> ::= VAR(id)
| ADD (<div>,<div>)
| CONST(id)
ADD(VAR "x", CONST "1")
Auf der rechten Seite der Grammatikregel ist auch ADD(τ, CONST(1))
ADD(VAR(id),CONST(1)) erlaubt, mit τ als Nichtterminal und
VAR(id)
Jede Regel hat außerdem Kosten mit sich assoziiert.
Nichtterminale können folgende Attribute besitzen:
- Liste der Instruktionen
- simulierte Kosten
- Wert von Konstanten
- Registernamen
Aufgabenstellung
Vorliegender Term/Ausdruck/Baum soll auf sein Startsymbol reduziert werden
Parsen des Terms !
Verfahren:
- Bottom-Up Durchlauf durch den Baum
- lege an jedem Knoten die Liste der matchenden Nichtterminale ab
- für jedes Nichtterminal ist nur das mit minimalen Kosten interessant Autoren-Anmerkung(Firefox): Vielleicht sollte das eigentlich "für jede Liste ist nur das NT mit den geringsten Kosten..." bedeuten. In beiden Quellen allerdings identisch so formuliert
- Matchen (am Knoten)
- Knotennamen müssen gleich sein
- falls rechte Seite
suche
in den Kinderknoten
- falls erfolgreich:
- berechne Kosten
- verkette Codesequenzen
- hänge attribuiertes Nichtterminal an den Knoten des Subjektterms an. Autoren-Anmerkung(Firefox): Was ist ein Subjektterm ?
Lebendigkeitsanalyse
siehe Appel: Modern Compiler Implementation in ML, Kapitel 10
Zustand des übersetzten Programs
- Folge von Instruktionen über Pseudoregister
- Annahme: Maschine mit unendlich vielen Registern
- Reale Maschine: endlich viele Register
Gesucht: Abbildung Pseudoregister
reale Register, sodass 2 unterschiedliche Pseudoregister nur dann auf ein reales Register abgebildet werden, wenn sie nicht gleichzeitig benutzt werden.
- Lebendigkeit formuliert diese Eigenschaft
- Analyse der Lebendigkeit ist ein Beispiel für statische und semantische Analyse.
Die Analyse wird auf einem Kontrollflußgraphen (KFG) durchgeführt:
|
|
|
|
Kontrollflußgraphen
Definition
Ein Kontrollflußgraph ist ein markierter gerichteter Graph. Die Markierungen sind entry, exit, return c, a <rel_op> b, a: = e. Jede Instruktion liefert einen Knoten.
Kanten
| Markierung | eingehende Kanten | ausgehende Kanten |
|---|---|---|
| entry | 0 | 1 |
| exit | N | 0 |
| return | N | 0 |
| a <rel_op> b | N | 2 |
| a:=e | N | 1 |
Über die eingehende Kanten werden mit pred(n) die Nachfolgerknoten erreicht; über die ausgehenden Kanten erhält man mit succ(n) die Vorgängerknoten.
Verhalten bei Konstruktion
- use[n]: Menge der Pseudoregister die von Knoten n verwendet werden.
- def[n]: Pseudoregister die von Knoten n definiert (und überschrieben) werden.
| Instruktion | use[n] | def[n] |
|---|---|---|
| return c | {c} |
|
| a <rel_op> b | {a,b} |
|
| a:=e | Regs(e) | {a} |
| entry,exit | |
|
Lebendigkeit
Definition
Ein Pseudoregister r ist auf einer Kante des Kontrollflußgraphen lebendig falls es einen Pfad zu einem n mit
gibt auf dem kein Knoten n' mit
liegt.
-
, falls r lebendig auf einer eingehenden Kante zu n ist
-
, falls r auf einer ausgehenden Kante lebendig ist
Daraus ergeben sich die Datenflussgleichungen für die Lebendigkeit:
![live\mbox{-}in[n]=Use[n] \cup (live\mbox{-}out[n] \setminus Def[n])](/wiki/upload/math/9/1/2/912342e2f6e9442b0e5e9d1294c4caef.png)
![live\mbox{-}out[n]=\bigcup_{n' \in Succ(n)} live\mbox{-}in[n']](/wiki/upload/math/7/0/b/70bb94e1eca45cd6a56634bfe09f3452.png)
Die Lösung der Gleichungen erhalten wir durch Fixpunktiteration
Für die Rückwärtsanalyse müssen wir einfach die Zeilen 8 und 9 vertauschen.
Beispiel
| 1 | 2 | 3 | |||||
|---|---|---|---|---|---|---|---|
| in | out | in | out | in | out | ... | |
| 1 | | | | a | | a | ... |
| 2 | a | | a | c,b | a,c | c,b | ... |
| 3 | c,b | | c,b | b | c,b | b | ... |
| 4 | b | | b | a | b | a | ... |
| 5 | a | a | a | c,a | a,c | c,a | ... |
| 6 | c | | c | | c | | ... |
Rückwärtsanalyse: Information wird gegen den Kontrollflußgraph propagiert.
| 1 | 2 | ||||
|---|---|---|---|---|---|
| 1 | a | a,c | c | a,c | ... |
| 2 | a,c | c,b | a,c | b,c | ... |
| 3 | c,b | b,c | b,c | b,c | ... |
| 4 | b,c | a,c | b,c | a,c | ... |
| 5 | a,c | c | a,c | a,c | ... |
| 6 | c | | c | | ... |
Komplexität
Grundoperationen
Mengenrepräsentation
N: = Größe des Programms (Anzahl an Instruktionen), W: = Wortbreite
| Operation | Bitvektor | geordnete Liste (ohne Wiederholungen) |
|---|---|---|
| Platzbedarf | | N |
| Kopieren | O(N) | O(N) |
| xor: O(N) | O(N) |
| and/neg: O(N) | O(N) |
| Vorteilhaft bei nichtbesetzten Mengen | Vorteilhaft bei sparsam besetzten Mengen ( Elemente)
|
Anzahl an Variablen
- erste Schleife: O(N2)
- innere Schleife: O(N2)
- repeat Schleife: O(NN)
-mal.
Die Datenflussgleichungen haben eine kleinste Lösung (wegen der Monotonie). Jede Lösung kann durch eine Variable, die nirgends definiert wird vergrößert werden.
Es existiert keine eindeutige Lösung, aber aus der Monotonie kann eine eindeutige, kleinste Lösung gefolgert werden.
Für jede tatsächliche Lösung L1/L0 der Datenflussgleichungen gilt folgende Invariante der repeat-Schleife:
(informeller) Beweis:
- bei Betreten der Schleife: ok
-
-
Der Algorithmus findet also die kleinste Lösung, da beim Verlassen der repeat-Schleife die Datenflussgleichungen erfüllt sind.
Registerallokation
Interferenzgraph
Der Interferenzgraph (IG) ist ein ungerichteter Graph mit
- Knoten : = Menge der Pseudoregister
- Kantenmenge E, sodass für
gilt
- falls m keine MOVE-Instruktion ist, dann
- falls m ist t: = r gilt (MOVE-Instruktion), dann
- falls m keine MOVE-Instruktion ist, dann
Bemerkung: Weitere Constraints können durch den Interferenzgraph ausgedrückt werden:
- falls
eine Instruktion ist, deren Ergebnis nicht im Register z erzeugt werden kann, dann
- verwende Pseudoregister dem z direkt zugeordnet ist
-
- Caller-Save-Register werden als Def[Call-Instruktionen] betrachtet
Interferenz zwischen den Caller-Save-Registern und den lebendigen Registern
Lebendige Register
Caller-Save-Register oder Speicher
Zurückführung auf Graphfärbung des Interferenzgraphen.
Farbe
Maschinenregister
Färbung ist Abbildung von Knoten(IG) auf Menge der Farben, sodass benachbarte Knoten unterschiedliche Farben erhalten
k Maschinenregister
k-Färbung gesucht.
Probleme:
- k-Färbung zu finden ist NP-Vollständig.
- falls eine k-Färbung nicht möglich ist müssen Pseudoregister im Speicher abgelegt werden. (Spilling)
Sei G = (V,E) ungerichtet und
mit Grad(V) < k. Falls
eine k-Färbung F' besitzt, dann exisitiert ein F mit: F ist k-Färbung von G.
Seien
und f eine Farbe.

, also ist
eine k-Färbung von G.
Algorithmus
Build
- Erstelle den Interferenzgraphen.
- Erzeuge einen Stack
Simplify
- wähle Knoten v mit Grad(v) < k
- entferne v und push v (alle Kanten zu v werden ebenfalls entfernt und reduzieren dadurch den Grad anderer Knoten)
- wiederhole solange wie möglich. Danach gibt es nur noch Knoten v mit
(oder gar keine)
Spill
- wähle Knoten v mit
- markiere v als "potential spill"
- entferne v und push v (alle Kanten zu v werden ebenfalls entfernt und reduzieren dadurch den Grad anderer Knoten)
- zurück zu Simplify und solange wiederholen bis der Interferenzgraph leer ist.
Select
- let v = pop
- falls v durch Simplify entfernt wurde (also keine Markierung trägt) wähle eine Farbe für v
- falls v durch Spill entfernt wurde (also mit "potential spill" markiert ist) und färbbar ist , färbe v
- ansonsten wird v als "actual spill" markiert und eine Speicherzelle dafür alloziiert. Außerdem wird der Code geändert:
- STORE nach jeder Definition von v
- LOAD vor jeder Benutzung von v
- wiederhole bis der Stack leer ist.
End
Entweder ist der Stack nun leer und kein "actual spill" ist aufgetreten, oder der Code des Programms wurde geändert und der Algorithmus muss nochmal von Anfang an ausgeführt werden.
Vorgefärbte Register
- Pseudoregister, die von vornherein bestimmten Registern zugeordnet sind (Argumentregister, Multiplikationsergebnis, etc.)
- jedes vorgefärbte Register interferiert mit jedem anderen
- dürfen nicht gespillt werden (
Lebensbereiche klein halten mit MOVE) und werden von Simplify zuletzt gewählt damit Select ihnen zuerst die gewünschten Farben zuordnen kann.
Behandlung von MOVE / Coalescing (Verschmelzen)
- Die Instruktion r:=s kann eliminiert werden falls
- die zugehörigen Kanten im Interferenzgraphen werden verschmolzen
Der Grad erhöht sich
Die Färbbarkeit wird beeinträchtigt.
![]() |
Also: Konservative Strategie
Verschmelze nur Register mit insgesamt nicht weniger als k Nachbarn mit
damit die k-Färbbarkeit erhalten bleibt.
Denn:(hinreichendes Kriterium)
Nach Entfernen der Kanten vom Grad < k (Simplify) verbleiben höchstens Nachbarn mit
, also hat der verschmolzene Knoten einen Grad < k und kann somit von Simplify entfernt werden.
Registerallokation mit Verschmelzen
Build
- Erstelle den Interferenzgraphen.
- Erzeuge einen Stack
-
markiere v und s als "move-related".
Simplify
- wähle unmarkierten Knoten v mit Grad(v) < k (berücksichtige obrige Bemerkungen zur Verschmelzung)
- entferne v und push v (alle Kanten zu v werden ebenfalls entfernt und reduzieren dadurch den Grad anderer Knoten)
Coalescing
- wähle markierten Knoten und verschmelze ihn mit "MOVE-Partner" (falls der Gesamtgrad < k ist )
- entferne MOVE
- entferne Markierung, falls der verschmolzene Knoten nicht mehr an MOVE beteiligt ist
- gehe zu Simplify und wiederhole
Freeze
- wähle markierten Knoten v mit Grad(v) < k
- MOVE bleibt erhalten, Markierung wird entfernt
- gehe zu Simplify und wiederhole
Spill
Select
- wie im normalen Algorithmus (Select)
Registerallokation für Ausdrücke (Baumstruktur)
t1 braucht m Register
t2 braucht n Register
if max(m,n+1) > max(m+1,n)
then t1 zuerst
else t2 zuerst
) in e
let
= x in e'
VII
Elemente)





