Code-Erzeugung Arne Kostulski ( arne@kostulski.net )
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3. Code-Repräsentation 4. Code-Erzeugung 5. Fazit und weiterführende Themen - zwei populäre Anwendungskontexte für den Begriff „Code-Erzeugung“
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3. Code-Repräsentation 4. Code-Erzeugung 5. Fazit und weiterführende Themen
Abgrenzung und Motivation Metaprogrammierung Templates: Generische Frames, Prototypen, Aspekte, Diagramme Zielsprache: Skripte, Stubs, Testcode, ORM-Klassen, GUI-Klassen Kompilation Optimierung nur optional von lesbar bis Nullen-Einsen-Folgen Motivation hier Kompilation Effizienz durch Automatisierung
Einordnung innerhalb des Kompilierungsprozesses Optimierung optional, parallel und/oder nach Zielcode-Erzeugung Analyse unabhängig, Synthese abhängig von Zielmaschine
Einordnung innerhalb des Kompilierungsprozesses Optimierung optional, parallel und/oder nach Zielcode-Erzeugung Analyse unabhängig, Synthese abhängig von Zielmaschine
Einordnung innerhalb des Kompilierungsprozesses hier schwerpunktmäßig… Ordnungsrahmen Zielgrößen Einflussgrößen: Zielmaschine, Praxis und Modellierung Code-Erzeugung vs. optimale Code-Erzeugung 1.
Einordnung innerhalb des Kompilierungsprozesses 3. 1. 2.
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen. Zielgrößen 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen * Zielgrößen * Entwurfsfaktoren * Kernaufgaben * Referenzmaschine 3. Code-Repräsentation 4. Code-Erzeugung 5. Fazit und weiterführende Themen - die Logik des Aufbaus erschließt sich aus den folgenden Folien
Zielgrößen Semantische Korrektheit Laufzeiteffizienz Virtuelle Speichernutzung Physische Speichernutzung Kompilierzeit Heuristiken für komplexe Optimierungsprobleme konträr Korrektheit Übereinstimmung der Programmfunktion von Zwischencode zu Zielcode absolut und höchste Prio LZ-Effizienz: Fähigkeiten der Zielmaschine ausnutzen Virtuelle + physische Speichernutzung: Kapazitäten der Zielmaschine Kompilierzeit: insbesondere beim Debugging, gegenläufig: während obere Ziele auch synergistisch sein können, zu Kzeit konträr Komplexität der Problemstellung häufig NP-schwer Korrektheit als NB, Laufzeit+Kompilierzeit als Zielwerte
Speicherhierarchie
Speicherhierarchie
Registerverwendung Aufgaben Ziel Architekturparameter Registeranzahl Kernaufgabe Registerverwendung Aufgaben Registerzuteilung Registerauswahl Ziel Totale Lade- und Speicherzeiten minimieren Registertypen erwähnen Registerzuteilung: Welche Variablen in Register ablegen? Registerauswahl: In welches Register Variable ablegen?
Instruktionsselektion Architekturparameter Befehlssatz Kernaufgabe Instruktionsselektion Aufgabe Auswahl von Instruktionskombination Ziel Minimiere totale Instruktionskosten! Beispiel Zwischencode: a := a + 1 Zielcode: LD R, a ADD R, R, #1 ST a, R 1 2 LD R, a INC a ST a, R 1 - Alternativen bei Beibehaltung der Programmsemantik - man kann LD, ADD und ST bestimmte Operationskosten zuordnen (je nach Zielmaschine), wenn INC geringer, dann vorteilhafter ∑ 4 ∑ 3
Instruktionsanordnung Architekturparameter Anzahl der Prozessorkerne Kernaufgabe Instruktionsanordnung Aufgabe Befehlsfolgen reorganisieren Ziele Code-Vereinfachung Bessere Registerausnutzung Parallelisierung unabhängiger Befehle - Parallelisierung: Heute Nachmittag
Kernaufgaben und ISA Instruction Set Architecture (ISA) Complex Instruction Set Computer (CISC) Reduced Set Computer (RISC) Very Long Instruction Word (VLIW) Architekturparameter Registeranzahl Befehlssatz Anzahl der Prozessorkerne Kernaufgaben Registerzuteilung Instruktionsselektion Instruktionsanordnung
Abstrakte Zielmaschine - Zielsprache Notation für Operationen: Bsp.: MUL R3, R1, R2 OP <destination>, <source>, <source>, mit <destination> = Ri | { Kleinbuchstabe } <source> = Ri | „#“ { Ziffer } OP = ADD | SUB | MUL | DIV Ausnahmen: LD R, x ST x, R - Drei-Adress-Operationen - Zielregister und Operandenregister nicht unbedingt disjunkt
Abstrakte Zielmaschine Definition Zielsprache Universalregister: R1, …,Rn Administrative Register separat Adressierungen Repräsentant für viele Klassen von Zielmaschinen n wird für Beispiele gesetzt, am Schluss Generierungsmechanismen für allgemein n Register - Administrativ = Stackpointer hier unberücksichtigt
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3. Code-Repräsentation * Basisblöcke * Flussgraphen * DAG 4. Code-Erzeugung 5. Fazit und weiterführende Themen
Code-Repräsentation: Basisblöcke Strukturierung des Zwischencodes Kontrollfluss Variablenverwendung Basisblock Minimale Sequenz von Instruktionen Blockanfänge definiert durch: Die erste Instruktion des Zwischencodes. Jede direkte Nachfolge-Instruktion eines Sprunges. Jede Zielinstruktion eines (bedingen oder unbedingten) Sprungbefehls.
Code-Repräsentation: Basisblöcke Beispiel a) Die erste Instruktion des Zwischencodes.
Code-Repräsentation: Basisblöcke Beispiel a) Die erste Instruktion des Zwischencodes.
Code-Repräsentation: Basisblöcke Beispiel b) Jede direkte Nachfolge-Instruktion eines Sprunges.
Code-Repräsentation: Basisblöcke Beispiel b) Jede direkte Nachfolge-Instruktion eines Sprunges.
Code-Repräsentation: Basisblöcke Beispiel c) Jede Zielinstruktion eines (bedingen oder unbedingten) Sprungbefehls.
Code-Repräsentation: Basisblöcke Beispiel c) Jede Zielinstruktion eines (bedingen oder unbedingten) Sprungbefehls.
Code-Repräsentation: Basisblöcke Ergebnis
Code-Repräsentation: Flussgraphen gerichteter, zyklischer Graph G=(V,E) mit Knotenmenge V: Menge aller Basisblöcke (Block als Blackbox) Kantenmenge E: e(Bi, Bj) Є E genau dann, wenn: Bj folgt direkt auf Bi in der Programmfolge und Bi ohne unbedingten Sprung. Bj ist Ziel eines Sprungbefehls.
Code-Repräsentation: Flussgraphen Beispiel: a) Bj folgt direkt auf Bi in der Programmfolge und Bi enthält keinen unbedingten Sprung.
Code-Repräsentation: Flussgraphen Beispiel: b) Bj ist Ziel eines Sprungbefehls.
Code-Repräsentation: Blockoptimierung mit DAG Innendarstellung eines Blocks gerichteter, azyklischer Graph (DAG) Ziele Registerausnutzung verbessern Code-Vereinfachung Toten Code eliminieren Elemente Operation Label Registerausnutzung durch neue Instruktionsanordnung verbessern Initiale Variable
Code-Repräsentation: Blockoptimierung mit DAG DAG-Konstruktion a := b * c c := a + d b := a * b Erzeuge - Blatt / Blätter Operationssymbol Kanten Label suche nach b und c in Blätter und Labels, neue als Blätter erzeuge OP mit Kindknoten LR
Code-Repräsentation: Blockoptimierung mit DAG DAG-Konstruktion a := b * c c := a + d b := a * b Erzeuge - Blatt / Blätter Operationssymbol Kanten Label Fall 4 nachträglichFüge a als Label an OP an.
Code-Repräsentation: Blockoptimierung mit DAG DAG-Konstruktion a := b * c c := a + d b := a * b Erzeuge - Blatt / Blätter Operationssymbol Kanten Label Was nutzt mir ein DAG? – folgt!
Code-Repräsentation: Blockoptimierung mit DAG Code-Vereinfachung a := b * c c := a + d b := a * b d := a + d , d a := b * c c := a + d b := a * b d := c Erzeuge - Blätter Operationssymbol Kanten Label Fall 4 nachträglichFüge a als Label an OP an.
Code-Repräsentation: Blockoptimierung mit DAG Lebendigkeit von Variablen Eine Variable x ist lebendig in Anweisung Ai (1 ≤ i < n), wenn: x in Anweisung A1 ein Wert zugewiesen wird, x in Anweisung An als Operand benutzt bzw. gelesen wird, x zwischen A1 und An kein Wert zugewiesen wird. x hat bei Ai die nächste Verwendung in An, wenn An der erste Lesezugriff auf x nach A1 ist. Eine am Basisblockende lebendige Variable heißt live on exit. Einfach: - Ich brauche den Wert von x noch, wenn er noch gelesen wird, ohne vorher überschrieben zu werden. - Wurzel = Keine Operanden für Instruktionen, nur Schreibzugriff
Code-Repräsentation: Blockoptimierung mit DAG Toter Code Instruktionen mit nicht benötigten Ergebnissen Beispiel c, d nicht live on exit Eliminierung: Streiche (iterativ) Wurzelknoten ohne lebendige Variablen Einfach: - Ich brauche den Wert von x noch, wenn er noch gelesen wird, ohne vorher überschrieben zu werden. Wurzel = Keine Operanden für Instruktionen, nur Schreibzugriff
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3. Code-Repräsentation 4. Code-Erzeugung * Elementare Code-Erzeugung * Globale Registerzuteilung * Optimale Code-Erzeugung * Peephole-Optimierung 5. Fazit und weiterführende Themen
Elementare Code-Erzeugung Register-Deskriptor Zuordnungen: Register Variablennamen Adressen-Deskriptor Zuordnungen: Variablen Speicheradresse Annahmen für Zielmaschine LD und ST explizit keine globalen Register Variablen mit nächster Verwendung am Blockanfang zu laden live on exit-Variablen zu speichern - explizit: wenn - Liste von Einträgen - Adresse sowohl Register als auch Hauptspeicher
Elementare Code-Erzeugung Hilfsfunktion getReg getReg(B) mit Drei-Adress-Befehl B Für a := b OP c Lade nötige Variablen Führe den Befehl OP Ra, Rb, Rc aus. Fallunterscheidungen für Code-Erzeugung LD R, a ST a, R a := b OP c a := b
Elementare Code-Erzeugung Beispiel: a := b * c Register-Deskriptor Adress-Deskriptor ¬live on exit live on exit Zwischencode Zielcode Regel R1 R2 R3 a b c d
Elementare Code-Erzeugung Register- Deskriptor Adress-Deskriptor ¬lebendig Blockende lebendig Blockende Zwischencode Zielcode Regel R1 R2 R3 a b c d 1. a := b * c 2. LD R1, b 1 b,R1 3. LD R2, c c, R2 a := b OP c (mit den Registern Ra, Rb, Rc) 4.1 Falls b bzw. c nicht im Register, führe LD Rb, b bzw. LD Rc, c aus. […] LD R, a 1.1 Der Inhalt von R im Register-Deskriptor ist durch a zu ersetzen 1.2 Füge im Adress-Deskriptor R zu a hinzu.
Elementare Code-Erzeugung Register- Deskriptor Adress-Deskriptor ¬lebendig Blockende lebendig Blockende Zwischencode Zielcode Regel R1 R2 R3 a b c d 1. a := b * c 2. LD R1, b 1 b,R1 3. LD R2, c c, R2 4. MUL R2, R1, R2 3 c, a 4. a := b OP c (mit den Registern Ra, Rb, Rc) 4.1 Falls b bzw. c nicht im Register, führe LD Rb, b bzw. LD Rc, c aus. 4.2 Der Inhalt von Ra im Register-Deskriptor ist durch a zu ersetzen. 4.3 Der Inhalt von a im Adress-Deskriptor ist durch Ra zu ersetzen. 4.4 Entferne Ra von allen anderen Variablen im Adress-Deskriptor.
Elementare Code-Erzeugung Register- Deskriptor Adress-Deskriptor ¬lebendig Blockende lebendig Blockende Zwischencode Zielcode Regel R1 R2 R3 a b c d 4. MUL R2, R1, R2 3 b,R1 c, R2 5. EXIT 6. ST a, R2 a, R2 b, R1 ST a, R Der Inhalt von a im Adress-Deskriptor ist durch R zu ersetzen.
Globale Register Problem Lösung Viele Hauptspeicherzugriffe ST a, R1 LD R1, a Lösung if … then goto …
Globale Register Beispiel Betrachte Variable b Lesezugriff LD R, a
Globale Register Beispiel Lösung Registerzuteilung? Betrachte Variable b Lesezugriff LD R, a Schreibzugriff live on exit Lösung Registerinhalte überdauern Basisblöcke Registerzuteilung? Bewertung:
Graphfärbung Problem Lebensspanne für ideelle Register Register voll Registerauswahl? Lebensspanne für ideelle Register Hier: Graph 2-fach färbbar Kollisionen unvermeidbar Zwischenspeicherung
Instruktionsselektion Idee: Alternative Instruktionskombinationen gleiche Semantik unterschiedliche Kosten
Optimale Code-Erzeugung Ziel Code mit minimaler Registeranzahl Berücksichtigt Registerzuteilung Registerauswahl Instruktionsanordnung Basis Directed Acyclic Graph (DAG) Procedere: Annotiere Ershov-Nummern an Graph-Knoten Führe Generierungsalgorithmus aus
Optimale Code-Erzeugung Ershov-Nummer für Knoten v: val(v) =
Optimale Code-Erzeugung Ershov-Nummer für Knoten v: val(v) = 1 1 1
Optimale Code-Erzeugung Ershov-Nummer für Knoten v: val(v) = 2 1 1 1
Optimale Code-Erzeugung Ershov-Nummer für Knoten v: val(v) = 3 2 2 2 1 1 1
Optimale Code-Erzeugung gencode-Algorithmus gencode(v, b) if v hat Kindknoten vl, vr mit label(vl) = label(vr) then 1.1 k := label(vl) 1.2 Rb+k := gencode(vr, b+1) 1.3 Rb+k-1 := gencode(vl, b) 1.4 print(name(v), Rb+k, Rb+k-1, Rb+k) else if v hat Kindknoten vl, vr mit label(vl) < label(vr) then 2.1 k := label(vr) 2.2 Rb+k-1 := gencode(vr, b) 2.3 m := label(vl) 2.4 Rb+m-1 := gencode(vl, b) 2.5 print(name(v), Rb+k-1, Rb+m-1, Rb+k-1) else if v hat Kindknoten vl, vr mit label(vl) > label(vr) then 3.1 k := label(vl) 3.2 Rb+k-1 := gencode(vl, b) 3.3 m := label(vr) 3.4 Rb+m-1 := gencode(vr, b) 3.5 print(name(v), Rb+k-1, Rb+k-1, Rb+m-1) else // v ist Blattknoten 4.1 print(„LD“, Rb, name(v)) Beispiel f Regel Code v b vl vr k R1 R2 R3 a) k := label(v1) v1 1 v2 v3 3 b) R3:= gencode(v1, 1) 1.1 k := label(v2) 2 1.2 R3 := gencode(v3, 2) 3.1 k := label(v4) v4 v5 3.2 R3 := gencode(v4, 2) 1.1 k := label(v6) v6 v7 1.2 R3 := gencode(v7, 3) - 4.1 print(„LD“, R3, c) c0 1.3 R2 := gencode(v6, 2) print(„LD“, R2, b) b0 1.4 print(„MUL“, R3, R2, R3) a 3.3 m := label(v5) // m := 1 3.4 R2 := gencode(v5, 2) 4.1 print(„LD“, R2, d) d0 3.5 print(„ADD“, R3, R2, R3) d 1.3 R2 := gencode(v2, 1) 2.1 2.2 R2 := gencode(v4, 2)
Optimale Code-Erzeugung gencode Regel Code v b vl vr k R1 R2 R3 3 2 2 LD R3, c LD R2, b 2 1 - laufe größere Teilbäume entlang 1 1
Optimale Code-Erzeugung 3 2 2 MUL“, R3, R2, R3 2 1 1 1
Optimale Code-Erzeugung 3 3 2 2 2 2 2 2 1 1 1 1 1 1
Peephole Optimization Problem Komplexität Idee Lokale Suche im “Gucklock” Beispiel Viele Hauptspeicherzugriffe ST a, R1 LD R1, a … c := a * b d := a + d e := c + d a := b * c ST a, R1 LD R1, a
1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3 1. Abgrenzung, Motivation, Einordnung 2. Grundlagen 3. Code-Repräsentation * Basisblöcke * Flussgraphen 4. Code-Erzeugung 5. Fazit und weiterführende Themen
Fazit Gelöst Ansätze Offen Elementare Codeerzeugung Erzeugung mit minimaler Registeranzahl Ansätze Registerverwendung Instruktionsselektion Instruktionsanordnung Offen Erweiterte Heuristiken Parallelisierung Just-In-Time-Compilation
Diskussion
BACKUP Code-Repräsentation DAG-Konstruktion Für jeden Drei-Adress-Befehl der Form a := b OP c eines Basisblocks B: Suche nach Vorkommen von b und c in Blättern, Knoten und Labels des Graphs (dies entspricht einer vorigen Benutzung der Variablen). Erzeuge für nicht existierende Variablen neue Blätter mit ihrem Variablennamen und dem Index 0 als Kennzeichnung der Erstverwendung. Erzeuge für OP einen Knoten mit dem Operationssymbol und setze b als linken, c als rechten Kindknoten für OP. Füge a als Label an OP an.
Elementare Code-Erzeugung BACKUP Elementare Code-Erzeugung Eintragung in die Deskriptoren LD R, a 1.1 Der Inhalt von R im Register-Deskriptor ist durch a zu ersetzen. 1.2 Füge im Adress-Deskriptor R zu a hinzu. ST a, R Der Inhalt von a im Adress-Deskriptor ist durch R zu ersetzen. a := b OP c (mit den Registern Ra, Rb, Rc) 3.1 Falls b bzw. c nicht im Register, führe LD Rb, b bzw. LD Rc, c aus. 3.2 Der Inhalt von Ra im Register-Deskriptor ist durch a zu ersetzen. 3.3 Der Inhalt von a im Adress-Deskriptor ist durch Ra zu ersetzen. 3.4 Entferne Ra von allen anderen Variablen im Adress-Deskriptor. a := b (mit den Registern Ra, Rb) 4.1 Falls b nicht im Register, führe LD Rb, b aus (siehe 1.). 4.2 Der Inhalt von Rb im Register-Deskriptor ist durch a zu ergänzen. 4.3 Der Inhalt von a im Adress-Deskriptor ist durch Rb zu ersetzen.
Elementare Code-Erzeugung BACKUP Elementare Code-Erzeugung Register-Deskriptor Adress-Deskriptor ¬lebendig Blockende lebendig Blockende Zwischencode Zielcode Regel R1 R2 R3 a b c d 1. a := b * c 2. LD R1, b 1 b,R1 3. LD R2, c c,R2 4. MUL R2, R1, R2 3 5. c := a + d 6. LD R3, d 7. ADD R3, R2, R3 8. EXIT 9. ST c, R1 c,d c,R1 10. ST d, R3 d,R3
Elementare Code-Erzeugung BACKUP Elementare Code-Erzeugung Register-Deskriptor Adress-Deskriptor ¬lebendig Blockende lebendig Blockende Zwischencode Zielcode Regel R1 R2 R3 a b c d 10. ST d, R3 c,d c,R1 d,R3 11. MUL R1, R2, R1 3 b,R1 12. EXIT 13. ST c, R1 14. 15.
Optimale Codeerzeugung BACKUP Optimale Codeerzeugung Funktionsaufruf: a) k := label(vroot) b) Rk := gencode(vroot, 1) Funktionsdeklaration: gencode(v, b) 1. if v hat Kindknoten vl, vr mit label(vl) = label(vr) then 1.1. k := label(vl) 1.2. Rb+k := gencode(vr, b+1) 1.3. Rb+k-1 := gencode(vl, b) 1.4. print(name(v), Rb+k, Rb+k-1, Rb+k) 2. else if v hat Kindknoten vl, vr mit label(vl) < label(vr) then 2.1. k := label(vr) 2.2. Rb+k-1 := gencode(vr, b) 2.3. m := label(vl) 2.4. Rb+m-1 := gencode(vl, b) 2.5. print(name(v), Rb+k-1, Rb+m-1, Rb+k-1) 3. else if v hat Kindknoten vl, vr mit label(vl) > label(vr) then 3.1. k := label(vl) 3.2. Rb+k-1 := gencode(vl, b) 3.3. m := label(vr) 3.4. Rb+m-1 := gencode(vr, b) 3.5. print(name(v), Rb+k-1, Rb+k-1, Rb+m-1) 4. else // v ist Blattknoten 4.1. print(„LD“, Rb, name(v))
Optimale Codeerzeugung BACKUP Optimale Codeerzeugung Regel Code v b vl vr k R1 R2 R3 a) k := label(v1) v1 1 v2 v3 3 b) R3:= gencode(v1, 1) 1.1 k := label(v2) 2 1.2 R3 := gencode(v3, 2) 3.1 k := label(v4) v4 v5 3.2 R3 := gencode(v4, 2) 1.1 k := label(v6) v6 v7 1.2 R3 := gencode(v7, 3) - 4.1 print(„LD“, R3, c) c0 1.3 R2 := gencode(v6, 2) print(„LD“, R2, b) b0 1.4 print(„MUL“, R3, R2, R3) a 3.3 m := label(v5) // m := 1 3.4 R2 := gencode(v5, 2) 4.1 print(„LD“, R2, d) d0 3.5 print(„ADD“, R3, R2, R3) d 1.3 R2 := gencode(v2, 1) 2.1 2.2 R2 := gencode(v4, 2)
Optimale Codeerzeugung BACKUP Optimale Codeerzeugung … siehe Schritte 6-12 mit b=1 . 2.3 m := label(v6) // m := 1 v2 1 v6 v4 2 b0 a d 2.4 R1 := gencode(v6, 1) 4.1 print(„LD“, R1, b) - 2.5 print(„MUL“, R2, R1, R2) c 1.4 print(„ADD“, R3, R2, R3) v1 v3 e