◦ ◦◦◦ ◦◦◦◦ ◦ ◦ ◦◦◦ ◦◦◦◦ ◦ ◦◦ TECHNISCHE UNIVERSITÄT MÜNCHEN FAKULTÄT FÜR INFORMATIK Compilerbau I Michael Petter, Axel Simon SoSe 2010 1 / 406 Organisatorisches Master oder Bachelor ab 6. Semester mit 5 ECTS Voraussetzungen Informatik 1 & 2 Theoretische Informatik Technische Informatik Grundlegende Algorithmen Aufbauende Vorlesungen Virtuelle Maschinen Programmoptimierung Programmiersprachen Praktikum Compilerbau Hauptseminare Material: Aufzeichnung der Vorlesungen über TTT die Folien selbst Literaturliste online (→ Bibliothek) Tools zur Visualisierung der Virtuellen Maschinen Tools, um Komponenten eines Compilers zu generieren 2 / 406 Organisatorisches Zeiten: Vorlesung: Mi. 14:15-15:45 Uhr Übung: wird per Onlineabstimmung geklärt Abschlussprüfung Klausur, Termin über TUM-online Erfolgreiches Lösen der Aufgaben wird als Bonus von 0.3 angerechnet. 3 / 406 Vorläufiger Inhalt Grundlagen Reguläre Ausdrücke und Automaten Von der Spezifizierung mit mehreren Regulären Ausdrücken zur Implementation mit Automaten Reduzierte kontextfreie Grammatiken und Kellerautomaten Bottom-Up Syntaxanalyse Attributsysteme Typüberprüfung Codegenerierung für Stackmaschinen Registerverteilung Einfache Optimierung 4 / 406 Themengebiet: Einführung 5 / 406 Interpreter Programm Interpreter Ausgabe Eingabe Vorteil: Keine Vorberechnung auf dem Programmtext erforderlich ⇒ keine/geringe Startup-Zeit Nachteil: Während der Ausführung werden die Programm-Bestandteile immer wieder analysiert ⇒ längere Laufzeit 6 / 406 Prinzip eines Übersetzers: Programm Übersetzer Code Maschine Ausgabe Code Eingabe Zwei Phasen: 1 Übersetzung des Programm-Texts in ein Maschinen-Programm; 2 Ausführung des Maschinen-Programms auf der Eingabe. 7 / 406 Übersetzer Eine Vorberechnung auf dem Programm gestattet u.a. eine geschickte(re) Verwaltung der Variablen; Erkennung und Umsetzung globaler Optimierungsmöglichkeiten. Nachteil Die Übersetzung selbst dauert einige Zeit Vorteil Die Ausführung des Programme wird effizienter ⇒ lohnt sich bei aufwendigen Programmen und solchen, die mehrmals laufen. 8 / 406 Übersetzer allgemeiner Aufbau eines Übersetzers: Analyse Interndarstellung Synthese Compiler Compiler Programmtext Code 9 / 406 Übersetzer allgemeiner Aufbau eines Übersetzers: Analyse Interndarstellung Synthese Compiler Compiler Programmtext Code 9 / 406 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Analyse Programmtext (annotierter) Syntaxbaum 9 / 406 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Programmtext Analyse Scanner lexikalische Analyse: Aufteilung in Sinneinheiten Token-Strom (annotierter) Syntaxbaum 9 / 406 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Programmtext Analyse Scanner lexikalische Analyse: Aufteilung in Sinneinheiten Token-Strom Parser syntaktische Analyse: Erkennen der hierarchischen Struktur Syntaxbaum (annotierter) Syntaxbaum 9 / 406 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Programmtext Analyse Scanner lexikalische Analyse: Aufteilung in Sinneinheiten Token-Strom Parser syntaktische Analyse: Erkennen der hierarchischen Struktur Syntaxbaum Type Checker... semantische Analyse: Überprüfung/Ermittlung semantischer Eigenschaften (annotierter) Syntaxbaum 9 / 406 Themengebiet: Lexikalische Analyse 10 / 406 Die lexikalische Analyse Programmtext Scanner Token-Strom 11 / 406 Die lexikalische Analyse xyz + 42 Scanner xyz + 42 11 / 406 Die lexikalische Analyse xyz + 42 Scanner xyz + 42 Ein Token ist eine Folge von Zeichen, die zusammen eine Einheit bilden. Tokens werden in Klassen zusammen gefasst. Zum Beispiel: → Namen (Identifier) wie → Konstanten wie 42, 3.14, ”abc”, ... → Operatoren wie +, ... → reservierte Worte wie xyz, pi, ... if, int, ... 11 / 406 Die lexikalische Analyse I xyz + 42 Scanner O C xyz + 42 Ein Token ist eine Folge von Zeichen, die zusammen eine Einheit bilden. Tokens werden in Klassen zusammen gefasst. Zum Beispiel: → Namen (Identifier) wie → Konstanten wie 42, 3.14, ”abc”, ... → Operatoren wie +, ... → reservierte Worte wie xyz, pi, ... if, int, ... 11 / 406 Die lexikalische Analyse Sind Tokens erst einmal klassifiziert, kann man die Teilwörter vorverarbeiten: Wegwerfen irrelevanter Teile wie Leerzeichen, Kommentare,... Aussondern von Pragmas, d.h. Direktiven an den Compiler, die nicht Teil des Programms sind, wie include-Anweisungen; Ersetzen der Token bestimmter Klassen durch ihre Bedeutung / Interndarstellung, etwa bei: → Konstanten; → Namen: die typischerweise zentral in einer Symbol-Tabelle verwaltet, evt. mit reservierten Worten verglichen (soweit nicht vom Scanner bereits vorgenommen) und gegebenenfalls durch einen Index ersetzt werden. ⇒ Sieber 12 / 406 Die lexikalische Analyse Diskussion: Scanner und Sieber werden i.a. in einer Komponente zusammen gefasst, indem man dem Scanner nach Erkennen eines Tokens gestattet, eine Aktion auszuführen Scanner werden i.a. nicht von Hand programmiert, sondern aus einer Spezifikation generiert: Spezifikation Generator Scanner 13 / 406 Die lexikalische Analyse - Generierung: Vorteile Produktivität Die Komponente lässt sich schneller herstellen Korrektheit Die Komponente realisiert (beweisbar) die Spezifikation. Effizienz Der Generator kann die erzeugte Programmkomponente mit den effizientesten Algorithmen ausstatten. 14 / 406 Die lexikalische Analyse - Generierung: Vorteile Produktivität Die Komponente lässt sich schneller herstellen Korrektheit Die Komponente realisiert (beweisbar) die Spezifikation. Effizienz Der Generator kann die erzeugte Programmkomponente mit den effizientesten Algorithmen ausstatten. Einschränkungen Spezifizieren ist auch Programmieren — nur eventuell einfacher Generierung statt Implementierung lohnt sich nur für Routine-Aufgaben ... und ist nur für Probleme möglich, die sehr gut verstanden sind 14 / 406 Die lexikalische Analyse - Generierung: ... in unserem Fall: Spezifikation Generator Scanner 15 / 406 Die lexikalische Analyse - Generierung: ... in unserem Fall: 0 0 | [1-9][0-9]* Generator [1−9] [0−9] Spezifikation von Token-Klassen: Reguläre Ausdrücke; Generierte Implementierung: Endliche Automaten + X 15 / 406 Lexikalische Analyse Kapitel 1: Grundlagen: Reguläre Ausdrücke 16 / 406 Reguläre Ausdrücke Grundlagen Programmtext benutzt ein endliches Alphabet Eingabe-Zeichen, z.B. ASCII Σ von Die Menge der Textabschnitte einer Token-Klasse ist i.a. regulär. Reguläre Sprachen kann man mithilfe regulärer Ausdrücke spezifizieren. 17 / 406 Reguläre Ausdrücke Grundlagen Programmtext benutzt ein endliches Alphabet Eingabe-Zeichen, z.B. ASCII Σ von Die Menge der Textabschnitte einer Token-Klasse ist i.a. regulär. Reguläre Sprachen kann man mithilfe regulärer Ausdrücke spezifizieren. Die Menge EΣ der (nicht-leeren) regulären Ausdrücke ist die kleinste Menge E mit: ∈E ( neues Symbol nicht aus Σ); a∈E für alle a ∈ Σ; (e1 | e2 ), (e1 · e2 ), e1 ∗ ∈ E sofern e1 , e2 ∈ E. 17 / 406 Stephen Kleene, Madison Wisconsin, 1909-1994 18 / 406 Reguläre Ausdrücke ... im Beispiel: ((a · b∗ )·a) (a | b) ((a · b)·(a · b)) 19 / 406 Reguläre Ausdrücke ... im Beispiel: ((a · b∗ )·a) (a | b) ((a · b)·(a · b)) Achtung: Wir unterscheiden zwischen Zeichen a, 0, $,... und Meta-Zeichen (, |, ),... Um (hässliche) Klammern zu sparen, benutzen wir Operator-Präzedenzen: ∗ >·>| und lassen “·” weg 19 / 406 Reguläre Ausdrücke ... im Beispiel: ((a · b∗ )·a) (a | b) ((a · b)·(a · b)) Achtung: Wir unterscheiden zwischen Zeichen a, 0, $,... und Meta-Zeichen (, |, ),... Um (hässliche) Klammern zu sparen, benutzen wir Operator-Präzedenzen: ∗ >·>| und lassen “·” weg Reale Spezifikations-Sprachen bieten zusätzliche Konstrukte: e? e+ ≡ ( | e) ≡ (e · e∗ ) und verzichten auf “” 19 / 406 Reguläre Ausdrücke Spezifikationen benötigen eine Semantik ...im Beispiel: Spezifikation abab a|b ab∗ a Semantik {abab} {a, b} {abn a | n ≥ 0} Für e ∈ EΣ definieren wir die spezifizierte Sprache [[e]] ⊆ Σ∗ induktiv durch: [[]] = {} [[a]] = {a} [[e∗ ]] = ([[e]])∗ [[e1 |e2 ]] = [[e1 ]] ∪ [[e2 ]] [[e1 ·e2 ]] = [[e1 ]] · [[e2 ]] 20 / 406 Beachte: Die Operatoren (_)∗ , ∪, · sind die entsprechenden Operationen auf Wort-Mengen: (L)∗ L1 · L2 = = {w1 . . . wk | k ≥ 0, wi ∈ L} {w1 w2 | w1 ∈ L1 , w2 ∈ L2 } 21 / 406 Beachte: Die Operatoren (_)∗ , ∪, · sind die entsprechenden Operationen auf Wort-Mengen: (L)∗ L1 · L2 = = {w1 . . . wk | k ≥ 0, wi ∈ L} {w1 w2 | w1 ∈ L1 , w2 ∈ L2 } Reguläre Ausdrücke stellen wir intern als markierte geordnete Bäume dar: * (ab|)∗ | . a b Innere Knoten: Operator-Anwendungen; Blätter: einzelne Zeichen oder . 21 / 406 Reguläre Ausdrücke Beispiel: Identifier in Java: le = [a-zA-Z_\$] di = [0-9] Id = {le} ({le} | {di})* 22 / 406 Reguläre Ausdrücke Beispiel: Identifier in Java: le = [a-zA-Z_\$] di = [0-9] Id = {le} ({le} | {di})* Float = {di}* (\.{di}|{di}\.) {di}*((e|E)(\+|\-)?{di}+)? 22 / 406 Reguläre Ausdrücke Beispiel: Identifier in Java: le = [a-zA-Z_\$] di = [0-9] Id = {le} ({le} | {di})* Float = {di}* (\.{di}|{di}\.) {di}*((e|E)(\+|\-)?{di}+)? Bemerkungen: “le” und “di” sind Zeichenklassen. Definierte Namen werden in “{”, “}” eingeschlossen. Zeichen werden von Meta-Zeichen durch “\” unterschieden. 22 / 406 Lexikalische Analyse Kapitel 2: Grundlagen: Endliche Automaten 23 / 406 Endliche Automaten im Beispiel: a b 24 / 406 Endliche Automaten im Beispiel: a b Knoten: Zustände; Kanten: Übergänge; Beschriftungen: konsumierter Input 24 / 406 Michael O. Rabin, Stanford University Dana S. Scott, Carnegy Mellon University, Pittsburgh 25 / 406 Endliche Automaten Definition Ein nicht-deterministischer endlicher Automat (NFA) ist ein Tupel A = (Q, Σ, δ, I, F) wobei: Q Σ I⊆Q F⊆Q δ eine endliche Menge von Zuständen; ein endliches Eingabe-Alphabet; die Menge der Anfangszustände; die Menge der Endzustände und die Menge der Übergänge (die Übergangs-Relation) ist. 26 / 406 Endliche Automaten Definition Ein nicht-deterministischer endlicher Automat (NFA) ist ein Tupel A = (Q, Σ, δ, I, F) wobei: Q Σ I⊆Q F⊆Q δ eine endliche Menge von Zuständen; ein endliches Eingabe-Alphabet; die Menge der Anfangszustände; die Menge der Endzustände und die Menge der Übergänge (die Übergangs-Relation) ist. Für NFAs gilt: Definition Ist δ : Q × Σ → Q eine Funktion und #I = 1, heißt A deterministisch (DFA). 26 / 406 Endliche Automaten Berechnungen sind Pfade im Graphen. akzeptierende Berechnungen führen von I nach F. Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden Pfades ... a b 27 / 406 Endliche Automaten Berechnungen sind Pfade im Graphen. akzeptierende Berechnungen führen von I nach F. Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden Pfades ... a b 27 / 406 Endliche Automaten Dazu definieren wir den transitiven Abschluss δ ∗ von δ als kleinste Menge δ 0 mit: (p, , p) ∈ δ 0 (p, xw, q) ∈ δ 0 und sofern (p, x, p1 ) ∈ δ und (p1 , w, q) ∈ δ 0 . δ ∗ beschreibt für je zwei Zustände, mit welchen Wörtern man vom einen zum andern kommt Die Menge aller akzeptierten Worte, d.h. die von A akzeptierte Sprache können wir kurz beschreiben als: L(A) = {w ∈ Σ∗ | ∃ i ∈ I, f ∈ F : (i, w, f ) ∈ δ ∗ } 28 / 406 Berry-Sethi Verfahren – Umwandlung von Regex in NFA Satz: Für jeden regulären Ausdruck e kann (in linearer Zeit) ein NFA konstruiert werden, der die Sprache [[e]] akzeptiert. Idee: Der Automat verfolgt (konzepionell mithilfe einer Marke “•”), im Syntaxbaum des regulären Ausdrucks, wohin man in e mit der Eingabe w gelangen kann. 29 / 406 Berry-Sethi Verfahren ... im Beispiel: . ∗ (a|b) a(a|b) . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 406 Berry-Sethi Verfahren Beachte: Gelesen wird nur an den Blättern. Die Navigation im Baum erfolgt ohne Lesen, d.h. mit -Übergängen. Für eine formale Konstruktion müssen wir die Automatenzustände bezeichnen. Dazu benutzen wir (hier) einfach den Teilausdruck des Knotens im Syntaxbaum Leider gibt es eventuell mehrere gleiche Teilausdrücke ==⇒ Wir numerieren die Blätter durch ... 31 / 406 Berry-Sethi Verfahren ... im Beispiel: . . * | a | a b a b 32 / 406 Berry-Sethi Verfahren ... im Beispiel: . . * | a 0 a b 1 | 2 a 3 b 4 32 / 406 Berry-Sethi Verfahren ... im Beispiel: . . * | 0 a 2 1 b | a 3 a 4 b 32 / 406 Berry-Sethi Verfahren Die Konstruktion: Zustände: Anfangszustand: Endzustand: Übergangsrelation: •r, r• r Knoten von e; •e; e•; Für Blätter r ≡ i x benötigen wir: (•r, x, r•). Die übrigen Übergänge sind: r r1 | r2 r1 · r2 Übergänge (•r, , •r1 ) (•r, , •r2 ) (r1 •, , r•) (r2 •, , r•) (•r, , •r1 ) (r1 •, , •r2 ) (r2 •, , r•) r r1∗ r1 ? Übergänge (•r, , r•) (•r, , •r1 ) (r1 •, , •r1 ) (r1 •, , r•) (•r, , r•) (•r, , •r1 ) (r1 •, , r•) 33 / 406 Berry-Sethi Verfahren Diskussion: Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren Der Automat ist i.a. nichtdeterministisch 34 / 406 Berry-Sethi Verfahren Diskussion: Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren Der Automat ist i.a. nichtdeterministisch ⇒ Strategie: Vermeide die Generierung der -Übergänge Benötigte Knoten-Eigenschaften: empty kann der Teilausdruck r einlesen? first die Menge der Lesezustände unterhalb von r, die potentiell als erste bei einem Abstieg erreicht werden. next die Menge der Lesezustände rechts von r, die potentiell beim Wiederaufstieg von r erreicht werden. last die Menge der Lesezustände unterhalb von r, die beim Abstieg potentiell als letzte erreicht werden. Idee: Generiere diese Attribute für die Knoten über DFS! 34 / 406 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: . . * | 0 a 2 1 b | a 3 a 4 b 35 / 406 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: . . * f | f 0 2 f a 1 b a | f 3 f a 4 b 35 / 406 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: . . * f f | f 0 2 f a 1 b f a | f 3 f a 4 b 35 / 406 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: . t f * . f f | f 0 2 f a 1 b f a | f 3 f a 4 b 35 / 406 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: f . t f * . f f | f 0 2 f a 1 b f a | f 3 f a 4 b 35 / 406 Berry-Sethi Verfahren: 1. Schritt Implementierung: DFS post-order Traversierung Für Blätter r ≡ i x ist empty[r] = (x ≡ ). Andernfalls: empty[r1 | r2 ] = empty[r1 · r2 ] = empty[r1∗ ] = empty[r1 ?] = empty[r1 ] ∨ empty[r2 ] empty[r1 ] ∧ empty[r2 ] t t 36 / 406 Berry-Sethi Verfahren: 2. Schritt Die Menge der ersten Lesezustände: Die Menge der Lesezustände, die von •r aus (d.h. beim Absteigen in den Teilbaum r) mit aufeinanderfolgenden -Transitionen erreicht werden können: first[r] = {i in r | (•r, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: f . t f * . f f | f 0 2 f a 1 b f a | f 3 f a 4 b 37 / 406 Berry-Sethi Verfahren: 2. Schritt Die Menge der ersten Lesezustände: Die Menge der Lesezustände, die von •r aus (d.h. beim Absteigen in den Teilbaum r) mit aufeinanderfolgenden -Transitionen erreicht werden können: first[r] = {i in r | (•r, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: f . t f * 2 f f | 0 f 0 a 1 2a f 1 b . f | 3 f 3 a 4 f 4 b 37 / 406 Berry-Sethi Verfahren: 2. Schritt Die Menge der ersten Lesezustände: Die Menge der Lesezustände, die von •r aus (d.h. beim Absteigen in den Teilbaum r) mit aufeinanderfolgenden -Transitionen erreicht werden können: first[r] = {i in r | (•r, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: f . t f * 2 f 01 f | 0 f 0 a 1 2a f 1 b . 34 f | 3 f 3 a 4 f 4 b 37 / 406 Berry-Sethi Verfahren: 2. Schritt Die Menge der ersten Lesezustände: Die Menge der Lesezustände, die von •r aus (d.h. beim Absteigen in den Teilbaum r) mit aufeinanderfolgenden -Transitionen erreicht werden können: first[r] = {i in r | (•r, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: f 01 t . * 2 f 01 f | 0 f 0 a 2 f 1 2a f 1 b . 34 f | 3 f 3 a 4 f 4 b 37 / 406 Berry-Sethi Verfahren: 2. Schritt Die Menge der ersten Lesezustände: Die Menge der Lesezustände, die von •r aus (d.h. beim Absteigen in den Teilbaum r) mit aufeinanderfolgenden -Transitionen erreicht werden können: first[r] = {i in r | (•r, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f 01 t . * 2 f 01 f | 0 f 0 a 2 f 1 2a f 1 b . 34 f | 3 f 3 a 4 f 4 b 37 / 406 Berry-Sethi Verfahren: 2. Schritt Implementierung: DFS post-order Traversierung Für Blätter r ≡ i x ist first[r] = {i | x 6≡ }. Andernfalls: first[r1 | r2 ] first[r1 · r2 ] first[r1∗ ] first[r1 ?] first[r 1 ] ∪ first[r2 ] first[r1 ] ∪ first[r2 ] = first[r1 ] = first[r1 ] = first[r1 ] = falls empty[r1 ] = t falls empty[r1 ] = f 38 / 406 Berry-Sethi Verfahren: 3. Schritt Die Menge nächster Lesezustände: Die Menge der Lesezustände in einem Teilbaum rechts neben r•, die möglicherweise als nächstes erreicht werden. next[r] = {i | (r•, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f . 01 t * | 0 a . 2 f 01 f 0 f 2 f 2 1 f 1 b a 34 f | 3 f 3 a 4 f 4 b 39 / 406 Berry-Sethi Verfahren: 3. Schritt Die Menge nächster Lesezustände: Die Menge der Lesezustände in einem Teilbaum rechts neben r•, die möglicherweise als nächstes erreicht werden. next[r] = {i | (r•, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 t * | 0 a . 2 f 01 f 0 f 2 f ∅ 2 1 f 1 b a 34 f ∅ | 3 ∅ f 3 a 4 f ∅ 4 b 39 / 406 Berry-Sethi Verfahren: 3. Schritt Die Menge nächster Lesezustände: Die Menge der Lesezustände in einem Teilbaum rechts neben r•, die möglicherweise als nächstes erreicht werden. next[r] = {i | (r•, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 2 t * | 0 a . 2 34 f 01 f 0 f 2 f ∅ 2 1 f 1 b a 34 f ∅ | 3 ∅ f 3 a 4 f ∅ 4 b 39 / 406 Berry-Sethi Verfahren: 3. Schritt Die Menge nächster Lesezustände: Die Menge der Lesezustände in einem Teilbaum rechts neben r•, die möglicherweise als nächstes erreicht werden. next[r] = {i | (r•, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 2 t * | 0 a . 2 34 f 0 1 2 01 f 0 f 2 f ∅ 2 1 f 1 b a 34 f ∅ | 3 ∅ f 3 a 4 f ∅ 4 b 39 / 406 Berry-Sethi Verfahren: 3. Schritt Die Menge nächster Lesezustände: Die Menge der Lesezustände in einem Teilbaum rechts neben r•, die möglicherweise als nächstes erreicht werden. next[r] = {i | (r•, , • i x ) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 2 t * 2 34 f 0 1 2 01 f | 0 012 f 0 a 2 1 a f 012 1 b 2 f ∅ . 34 f ∅ | 3 ∅ f 3 a 4 f ∅ 4 b 39 / 406 Berry-Sethi Verfahren: 3. Schritt Implementierung: DFS pre-order Traversierung Für die Wurzel haben wir: next[e] = ∅ Ansonsten machen wir eine Fallunterscheidung über den Kontext: r r1 | r2 Regeln next[r1 ] next[r2 ] r1 · r2 next[r1 ] r1∗ r1 ? next[r2 ] next[r1 ] next[r1 ] = next[r] = next[r] first[r2 ] ∪ next[r] = first[r2 ] = next[r] = first[r1 ] ∪ next[r] = next[r] falls empty[r2 ] = t falls empty[r2 ] = f 40 / 406 Berry-Sethi Verfahren: 4. Schritt Die Menge letzter Lesezustände: Die Menge der Lesezustände, die möglicherweise als letzte beim Wiederaufstieg aus dem Teilbaum unter r erreicht werden: last[r] = {i in r | ( i x •, , r•) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 2 t * 2 34 f 0 1 2 01 f | 0 012 f 0 a 2 1 a f 012 1 b 2 f ∅ . 34 f ∅ | 3 ∅ f 3 a 4 f ∅ 4 b 41 / 406 Berry-Sethi Verfahren: 4. Schritt Die Menge letzter Lesezustände: Die Menge der Lesezustände, die möglicherweise als letzte beim Wiederaufstieg aus dem Teilbaum unter r erreicht werden: last[r] = {i in r | ( i x •, , r•) ∈ δ ∗ , x 6= } ... im Beispiel: 012 f ∅ . 01 2 t * 22 34 f 0 1 2 01 f 0 0 012 f 0 a | 2 11 a f 012 1 b 2 f ∅ . 34 f ∅ 33 ∅ f 3 a | 44 f ∅ 4 b 41 / 406 Berry-Sethi Verfahren: 4. Schritt Die Menge letzter Lesezustände: Die Menge der Lesezustände, die möglicherweise als letzte beim Wiederaufstieg aus dem Teilbaum unter r erreicht werden: last[r] = {i in r | ( i x •, , r•) ∈ δ ∗ , x 6= } ... im Beispiel: 012 34 f ∅ . 01 01 2 t * 2 2 34 f 0 1 2 01 f 01 0 0 012 f 0 a | 2 11 a f 012 1 b 2 34 f ∅ . 34 34 f ∅ 3 3 ∅ f 3 a | 44 f ∅ 4 b 41 / 406 Berry-Sethi Verfahren: 4. Schritt Implementierung: DFS post-order Traversierung Für Blätter r ≡ i x ist last[r] = {i | x 6≡ }. Andernfalls: last[r1 | r2 ] last[r1 · r2 ] last[r1∗ ] last[r1 ?] last[r1 ] ∪ last[r2 ] last[r1 ] ∪ last[r2 ] = last[r2 ] = last[r1 ] = last[r1 ] = falls falls empty[r2 ] = t empty[r2 ] = f 42 / 406 Berry-Sethi Verfahren: Integration Konstruktion: Erstelle Automat aus Attributen des Syntaxbaums: Zustände: {•e} ∪ {i• | i Blatt} Startzustand: •e Endzustände: last[e] {•e} ∪ last[e] falls empty[e] = f . sonst. Übergänge: (•e, a, i•) falls i ∈ first[e] und i mit a beschriftet ist. (i•, a, i0 •) falls i0 ∈ next[i] und i0 mit a beschriftet ist. Den resultierenden Automaten bezeichnen wir mit Ae . 43 / 406 Berry-Sethi Verfahren ... im Beispiel: a 0 a a b 3 a b a 2 a b a 1 4 b Bemerkung: Die Konstruktion heißt Berry-Sethi- oder auch Glushkov-Konstruktion. Sie wird in XML zur Definition von Content Models benutzt Das Ergebnis ist vielleicht nicht, was wir erwartet haben ... 44 / 406 Gerard Berry, Esterel Technologies Ravi Sethi, Research VR, Lucent Technologies 45 / 406 Der erwartete Automat: a, b a a, b Bemerkung: in einen Zustand eingehende Kanten haben hier nicht unbedingt die gleiche Beschriftung Dafür ist die Berry-Sethi-Konstruktion direkter In Wirklichkeit benötigen wir aber deterministische Automaten ⇒ Teilmengen-Konstruktion 46 / 406 Teilmengenkonstruktion a ... im Beispiel: 0 a a b 3 a b a 2 a b a 1 4 b 47 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b 3 a b a 2 a b a 1 4 b 02 a 47 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b 3 a b a 2 a b a 1 4 b 02 a a b 1 b 47 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b 3 a b a 2 a b a 1 4 b a 02 a 023 a a b 1 b 47 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b 3 a b a 2 a b a 1 4 b a a 02 023 a b a a b 1 b b 14 b 47 / 406 Teilmengenkonstruktion Satz: Zu jedem nichtdeterministischen Automaten A = (Q, Σ, δ, I, F) kann ein deterministischer Automat P(A) konstruiert werden mit L(A) = L(P(A)) 48 / 406 Teilmengenkonstruktion Satz: Zu jedem nichtdeterministischen Automaten A = (Q, Σ, δ, I, F) kann ein deterministischer Automat P(A) konstruiert werden mit L(A) = L(P(A)) Konstruktion: Zustände: Teilmengen von Q; Anfangszustände: {I}; Endzustände: {Q0 ⊆ Q | Q0 ∩ F 6= ∅}; Übergangsfunktion: δP (Q0 , a) = {q ∈ Q | ∃ p ∈ Q0 : (p, a, q) ∈ δ}. 48 / 406 Teilmengenkonstruktion Achtung: Leider gibt es exponentiell viele Teilmengen von Q Um nur nützliche Teilmengen zu betrachten, starten wir mit der Menge QP = {I} und fügen weitere Zustände nur nach Bedarf hinzu ... d.h., wenn sie von einem Zustand in QP aus erreichbar sind Trotz dieser Optimierung kann der Ergebnisautomat riesig sein ... was aber in der Praxis (so gut wie) nie auftritt 49 / 406 Teilmengenkonstruktion Achtung: Leider gibt es exponentiell viele Teilmengen von Q Um nur nützliche Teilmengen zu betrachten, starten wir mit der Menge QP = {I} und fügen weitere Zustände nur nach Bedarf hinzu ... d.h., wenn sie von einem Zustand in QP aus erreichbar sind Trotz dieser Optimierung kann der Ergebnisautomat riesig sein ... was aber in der Praxis (so gut wie) nie auftritt In Tools wie grep wird deshalb zu der DFA zu einem regulären Ausdruck nicht aufgebaut ! Stattdessen werden während der Abbarbeitung der Eingabe genau die Mengen konstruiert, die für die Eingabe notwendig sind ... 49 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b a b a b 3 a b a 2 a b a 1 4 b 02 023 1 14 50 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b a b a b 3 a b a 2 a b a 1 4 b 02 023 1 14 a 50 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b a b a b 3 a b a 2 a b a 1 4 b 02 023 a b 1 14 50 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b a b a b 3 a b a 2 a b a 1 4 b 02 023 a b a 1 14 50 / 406 Teilmengenkonstruktion a 0 a ... im Beispiel: a b a b a b 3 a b a 2 a b a 1 4 b 02 023 a b a 1 14 50 / 406 Bemerkungen: Bei einem Eingabewort der Länge Mengen konstruiert n werden maximal O(n) Ist eine Menge bzw. eine Kante des DFA einmal konstuiert, heben wir sie in einer Hash-Tabelle auf. Bevor wir einen neuen Übergang konstruieren, sehen wir erst nach, ob wir diesen nicht schon haben 51 / 406 Bemerkungen: Bei einem Eingabewort der Länge Mengen konstruiert n werden maximal O(n) Ist eine Menge bzw. eine Kante des DFA einmal konstuiert, heben wir sie in einer Hash-Tabelle auf. Bevor wir einen neuen Übergang konstruieren, sehen wir erst nach, ob wir diesen nicht schon haben Zusammenfassend finden wir: Satz: Zu jedem regulären Ausdruck e kann ein deterministischer Automat A = P(Ae ) konstruiert werden mit L(A) = [[e]] 51 / 406 Lexikalische Analyse Kapitel 3: Design eines Scanners 52 / 406 Design eines Scanners Eingabe (vereinfacht): eine Menge von Regeln: { action1 } { action2 } e1 e2 ... ek { actionk } 53 / 406 Design eines Scanners Eingabe (vereinfacht): eine Menge von Regeln: { action1 } { action2 } e1 e2 ... ek Ausgabe: { actionk } ein Programm, das ... von der Eingabe ein maximales Präfix w e1 | . . . | ek erfüllt; ... das minimale i ermittelt, so dass ... für liest, das w ∈ [[ei ]]; w actioni ausführt. 53 / 406 Implementierung: Idee: Konstruiere den DFA P(Ae ) = (Q, Σ, δ, {q0 }, F) zu dem Ausdruck e = (e1 | . . . | ek ); Definiere die Mengen: F1 F2 Fk = = ... = {q ∈ F | q ∩ last[e1 ] 6= ∅} {q ∈ (F\F1 ) | q ∩ last[e2 ] 6= ∅} {q ∈ (F\(F1 ∪ . . . ∪ Fk−1 )) | q ∩ last[ek ] 6= ∅} Für Eingabe w gilt: δ ∗ (q0 , w) ∈ Fi genau dann wenn der Scanner für w actioni ausführen soll 54 / 406 Implementierung: Idee (Fortsetzung): Der Scanner verwaltet zwei Zeiger hA, Bi und die zugehörigen Zustände hqA , qB i... Der Zeiger A merkt sich die letzte Position in der Eingabe, nach der ein Zustand qA ∈ F erreicht wurde; Der Zeiger B verfolgt die aktuelle Position. s t d o u t . w r i t e l n A ( " H a l l o " ) ; B 55 / 406 Implementierung: Idee (Fortsetzung): Der Scanner verwaltet zwei Zeiger hA, Bi und die zugehörigen Zustände hqA , qB i... Der Zeiger A merkt sich die letzte Position in der Eingabe, nach der ein Zustand qA ∈ F erreicht wurde; Der Zeiger B verfolgt die aktuelle Position. w r i t e l n ( " H a l l o " ) ; A B ⊥ ⊥ 55 / 406 Implementierung: Idee (Fortsetzung): Ist der aktuelle Zustand qB = ∅ , geben wir Eingabe bis zur Postion A aus und setzen: B := A; A := ⊥; qB := q0 ; qA := ⊥ w r i t e l n A 4 ( " H a l l o " ) ; B 4 56 / 406 Implementierung: Idee (Fortsetzung): Ist der aktuelle Zustand qB = ∅ , geben wir Eingabe bis zur Postion A aus und setzen: B qB := A; := q0 ; w r i A qA t e l n A 4 := ⊥; := ⊥ ( " H a l l o " ) ; B ∅ 56 / 406 Implementierung: Idee (Fortsetzung): Ist der aktuelle Zustand qB = ∅ , geben wir Eingabe bis zur Postion A aus und setzen: B qB := A; := q0 ; A qA := ⊥; := ⊥ ( " H a l w r i t e l n l o " ) ; A B ⊥ q0 56 / 406 Erweiterung: Zustände Gelegentlich ist es nützlich, verschiedene Scanner-Zustände zu unterscheiden. In unterschiedlichen Zuständen sollen verschiedene Tokenklassen erkannt werden können. In Abhängigkeit der gelesenen Tokens kann der Scanner-Zustand geändert werden Beispiel: Kommentare Innerhalb eines Kommentars werden Identifier, Konstanten, Kommentare, ... nicht erkannt 57 / 406 Eingabe (verallgemeinert): hstatei { eine Menge von Regeln: { action1 { action2 yybegin(state1 ); } yybegin(state2 ); } { actionk yybegin(statek ); } yybegin (statei ); setzt den Zustand auf e1 e2 ... ek } Der Aufruf statei . Der Startzustand ist (z.B. bei JFlex) YYINITIAL. ... im Beispiel: hYYINITIALi hCOMMENTi 00 /∗00 { ∗ /00 . | \n } 00 { yybegin(COMMENT); } { yybegin(YYINITIAL); } { } 58 / 406 Bemerkungen: “.” matcht alle Zeichen ungleich “\n”. Für jeden Zustand generieren wir den entsprechenden Scanner. Die Methode yybegin (STATE); verschiedenen Scannern um. schaltet zwischen den Kommentare könnte man auch direkt mithilfe einer geeigneten Token-Klasse implementieren. Deren Beschreibung ist aber ungleich komplizierter Scanner-Zustände sind insbesondere nützlich bei der Implementierung von Präprozessoren, die in einen Text eingestreute Spezifikationen expandieren sollen. 59 / 406 Themengebiet: Syntaktische Analyse 60 / 406 Die syntaktische Analyse Token-Strom Parser Syntaxbaum Die syntaktische Analyse versucht, Tokens zu größeren Programmeinheiten zusammen zu fassen. 61 / 406 Die syntaktische Analyse Token-Strom Parser Syntaxbaum Die syntaktische Analyse versucht, Tokens zu größeren Programmeinheiten zusammen zu fassen. Solche Einheiten können sein: → Ausdrücke; → Statements; → bedingte Verzweigungen; → Schleifen; ... 61 / 406 Diskussion: Auch Parser werden i.a. nicht von Hand programmiert, sondern aus einer Spezifikation generiert: Spezifikation Generator Parser 62 / 406 Diskussion: Auch Parser werden i.a. nicht von Hand programmiert, sondern aus einer Spezifikation generiert: E→E{op}E Generator Spezifikation der hierarchischen Struktur: kontextfreie Grammatiken Generierte Implementierung: Kellerautomaten + X 62 / 406 Syntaktische Analyse Kapitel 1: Grundlagen: Kontextfreie Grammatiken 63 / 406 Grundlagen: Kontextfreie Grammatiken Programme einer Programmiersprache können unbeschränkt viele Tokens enthalten, aber nur endlich viele Token-Klassen Als endliches Terminal-Alphabet T wählen wir darum die Menge der Token-Klassen. Die Schachtelung von Programm-Konstrukten lässt sich elegant mit Hilfe von kontextfreien Grammatiken beschreiben ... 64 / 406 Grundlagen: Kontextfreie Grammatiken Programme einer Programmiersprache können unbeschränkt viele Tokens enthalten, aber nur endlich viele Token-Klassen Als endliches Terminal-Alphabet T wählen wir darum die Menge der Token-Klassen. Die Schachtelung von Programm-Konstrukten lässt sich elegant mit Hilfe von kontextfreien Grammatiken beschreiben ... Definition: Eine kontextfreie Grammatik (CFG) ist ein 4-Tupel G = (N, T, P, S) mit: N die Menge der Nichtterminale, T die Menge der Terminale, P die Menge der Produktionen oder Regeln, und S∈N das Startsymbol 64 / 406 Noam Chomsky, MIT John Backus, IBM (Erfinder von Fortran) 65 / 406 Konventionen Die Regeln kontextfreier Grammatiken sind von der Form: A→α mit A ∈ N , α ∈ (N ∪ T)∗ 66 / 406 Konventionen Die Regeln kontextfreier Grammatiken sind von der Form: A→α mit A ∈ N , α ∈ (N ∪ T)∗ ... im Beispiel: S S Spezifizierte Sprache: → → aSb {an bn | n ≥ 0} 66 / 406 Konventionen Die Regeln kontextfreier Grammatiken sind von der Form: A→α mit A ∈ N , α ∈ (N ∪ T)∗ ... im Beispiel: S S Spezifizierte Sprache: → → aSb {an bn | n ≥ 0} Konventionen: In Beispielen ist die Spezifikation der Nichtterminale und Terminale i.a. implizit: Nichtterminale sind: A, B, C, ..., hexpi, hstmti, ...; Terminale sind: a, b, c, ..., int, name, ...; 66 / 406 ... weitere Beispiele: S → hstmti hstmti → hifi | hwhilei | hrexpi; hifi → if ( hrexpi ) hstmti else hstmti hwhilei → while ( hrexpi ) hstmti hrexpi → int | hlexpi | hlexpi = hrexpi hlexpi → name | ... | ... 67 / 406 ... weitere Beispiele: S → hstmti hstmti → hifi | hwhilei | hrexpi; hifi → if ( hrexpi ) hstmti else hstmti hwhilei → while ( hrexpi ) hstmti hrexpi → int | hlexpi | hlexpi = hrexpi hlexpi → name | ... | ... Weitere Konventionen: Für jedes Nichtterminal sammeln wir die rechten Regelseiten und listen sie gemeinsam auf Die j-te Regel für A bezeichnen (j ≥ 0). können wir durch das Paar (A, j) 67 / 406 Weitere Grammatiken: E E T F → → → → E+E 0 | E+T 0 | T∗F 0 | (E )0 | E∗E 1 | ( E ) 2 T1 F1 name 1 | int 2 | name 3 | int 4 Die beiden Grammatiken beschreiben die gleiche Sprache 68 / 406 Weitere Grammatiken: E E T F → → → → E+E 0 | E+T 0 | T∗F 0 | (E )0 | E∗E 1 | ( E ) 2 T1 F1 name 1 | int 2 | name 3 | int 4 Die beiden Grammatiken beschreiben die gleiche Sprache 68 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → E+T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → E+T T+T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → E+T T+T T∗F+T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → E+T T+T T∗F+T T ∗ int + T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T name ∗ int + F 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T name ∗ int + F name ∗ int + int 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T name ∗ int + F name ∗ int + int Definition Eine Ableitung → ist eine Relation auf Wörtern über N ∪ T, mit α → α0 gdw. α = α1 A α2 ∧ α0 = α1 β α2 für ein A → β ∈ P 69 / 406 Ableitung Grammatiken sind Wortersetzungssysteme. Die Regeln geben die möglichen Ersetzungsschritte an. Eine Folge solcher Ersetzungsschritte α0 → . . . → αm heißt Ableitung. ... im Beispiel: E → → → → → → → → E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T name ∗ int + F name ∗ int + int Definition Eine Ableitung → ist eine Relation auf Wörtern über N ∪ T, mit α → α0 gdw. α = α1 A α2 ∧ α0 = α1 β α2 für ein A → β ∈ P Den reflexiven und transitiven Abschluss von → schreiben wir: →∗ 69 / 406 Ableitung Bemerkungen: Die Relation → hängt von der Grammatik ab In jedem Schritt einer Ableitung können wir: ∗ eine Stelle auswählen, wo wir ersetzen wollen, sowie ∗ eine Regel, wie wir ersetzen wollen. Die von G spezifizierte Sprache ist: L(G) = {w ∈ T ∗ | S →∗ w} 70 / 406 Ableitung Bemerkungen: Die Relation → hängt von der Grammatik ab In jedem Schritt einer Ableitung können wir: ∗ eine Stelle auswählen, wo wir ersetzen wollen, sowie ∗ eine Regel, wie wir ersetzen wollen. Die von G spezifizierte Sprache ist: L(G) = {w ∈ T ∗ | S →∗ w} Achtung: Die Reihenfolge, in der disjunkte Teile abgeleitet werden, ist unerheblich 70 / 406 Ableitungsbaum Ableitungen eines Symbols stellt man als Ableitungsbaum dar: E 0 ... im Beispiel: E →0 →1 →0 →2 →1 →1 →1 →2 E+T T+T T∗F+T T ∗ int + T F ∗ int + T name ∗ int + T name ∗ int + F name ∗ int + int E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name Ein Ableitungsbaum für A ∈ N: innere Knoten: Regel-Anwendungen Wurzel: Regel-Anwendung für A Blätter: Terminale oder Die Nachfolger von (B, i) entsprechen der rechten Seite der Regel 71 / 406 Spezielle Ableitungen Beachte: Neben beliebigen Ableitungen betrachtet man solche, bei denen stets das linkeste (bzw. rechteste) Vorkommen eines Nichtterminals ersetzt wird. Diese heißen Links- (bzw. Rechts-) Ableitungen und werden durch Index L bzw. R gekennzeichnet. Links-(bzw. Rechts-) Ableitungen entsprechen einem links-rechts (bzw. rechts-links) preorder-DFS-Durchlauf durch den Ableitungsbaum Reverse Rechts-Ableitungen entsprechen einem links-rechts postorder-DFS-Durchlauf durch den Ableitungsbaum 72 / 406 Spezielle Ableitungen ... im Beispiel: E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name 73 / 406 Spezielle Ableitungen ... im Beispiel: E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name Links-Ableitung: (E, 0) (E, 1) (T, 0) (T, 1) (F, 1) (F, 2) (T, 1) (F, 2) 73 / 406 Spezielle Ableitungen ... im Beispiel: E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name Links-Ableitung: Rechts-Ableitung: (E, 0) (E, 1) (T, 0) (T, 1) (F, 1) (F, 2) (T, 1) (F, 2) (E, 0) (T, 1) (F, 2) (E, 1) (T, 0) (F, 2) (T, 1) (F, 1) 73 / 406 Spezielle Ableitungen ... im Beispiel: E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name Links-Ableitung: (E, 0) (E, 1) (T, 0) (T, 1) (F, 1) (F, 2) (T, 1) (F, 2) Rechts-Ableitung: (E, 0) (T, 1) (F, 2) (E, 1) (T, 0) (F, 2) (T, 1) (F, 1) Reverse Rechts-Ableitung: (F, 1) (T, 1) (F, 2) (T, 0) (E, 1) (F, 2) (T, 1) (E, 0) 73 / 406 Eindeutige Grammatiken Die Konkatenation der Blätter des Ableitungsbaums t wir auch mit yield(t) . ... im Beispiel: bezeichnen E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name liefert die Konkatenation: name ∗ int + int . 74 / 406 Eindeutige Grammatiken Definition: Die Grammatik G heißt eindeutig, falls es zu jedem w ∈ T ∗ maximal einen Ableitungsbaum t von S gibt mit yield(t) = w. ... in unserem Beispiel: E E T F → → → → E+E 0 | E+T 0 | T∗F 0 | (E )0 | E∗E 1 | ( E ) 2 T1 F1 name 1 | int 2 | name 3 | int 4 Die zweite ist eindeutig, die erste nicht 75 / 406 Fazit: Ein Ableitungsbaum repräsentiert eine mögliche hierarchische Struktur eines Worts. Bei Programmiersprachen sind wir nur an Grammatiken interessiert, bei denen die Struktur stets eindeutig ist Ableitungsbäume stehen in eins-zu-eins-Korrespondenz mit Links-Ableitungen wie auch (reversen) Rechts-Ableitungen. Links-Ableitungen entsprechen einem Topdown-Aufbau des Ableitungsbaums. Reverse Rechts-Ableitungen entsprechen einem Bottom-up-Aufbau des Ableitungsbaums. 76 / 406 Syntaktische Analyse Kapitel 2: Grundlagen: Kellerautomaten 77 / 406 Grundlagen: Kellerautomaten Durch kontextfreie Grammatiken spezifizierte Sprachen können durch Kellerautomaten (Pushdown Automata) akzeptiert werden: Der Keller wird z.B. benötigt, um korrekte Klammerung zu überprüfen 78 / 406 Friedrich L. Bauer, TUM Klaus Samelson, TUM 1957: Patent auf die Verwendung von Stapelspeicher zur Übersetzung von Programmiersprachen 79 / 406 Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 0 1 11 12 a a b b 11 11 2 2 80 / 406 Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 0 1 11 12 a a b b 11 11 2 2 Konventionen: Wir unterscheiden nicht zwischen Kellersymbolen und Zuständen Das rechteste / oberste Kellersymbol repräsentiert den Zustand Jeder Übergang liest / modifiziert einen oberen Abschnitt des Kellers 80 / 406 Kellerautomaten Definition: Formal definieren wir deshalb einen Kellerautomaten (PDA) als ein Tupel: M = (Q, T, δ, q0 , F) mit: Q eine endliche Menge von Zuständen; T das Eingabe-Alphabet; q0 ∈ Q der Anfangszustand; F⊆Q die Menge der Endzustände und δ ⊆ Q+ × (T ∪ {}) × Q∗ eine endliche Menge von Übergängen 81 / 406 Kellerautomaten Definition: Formal definieren wir deshalb einen Kellerautomaten (PDA) als ein Tupel: M = (Q, T, δ, q0 , F) mit: Q eine endliche Menge von Zuständen; T das Eingabe-Alphabet; q0 ∈ Q der Anfangszustand; F⊆Q die Menge der Endzustände und δ ⊆ Q+ × (T ∪ {}) × Q∗ eine endliche Menge von Übergängen Mithilfe der Übergänge definieren wir Berechnungen von Kellerautomaten Der jeweilige Berechnungszustand (die aktuelle Konfiguration) ist ein Paar: (γ, w) ∈ Q∗ × T ∗ bestehend aus dem Kellerinhalt und dem noch zu lesenden Input. 81 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 0 1 11 12 a a b b 11 11 2 2 82 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , 0 1 11 12 a a b b 11 11 2 2 a a a b b b) 82 / 406 ... im Beispiel: 0 1 11 12 Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) ` a a b b 11 11 2 2 (1 1 , a a b b b) 82 / 406 ... im Beispiel: 0 1 11 12 Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) ` ` a a b b 11 11 2 2 (1 1 , a a b b b) (1 1 1 , a b b b) 82 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) 0 1 11 12 a a b b 11 11 2 2 ` (1 1 , a a b b b) ` (1 1 1 , a b b b) ` (1 1 1 1 , b b b) 82 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) 0 1 11 12 ` (1 1 , ` (1 1 1 , ` (1 1 1 1 , ` (1 1 2 , a a b b 11 11 2 2 a a b b b) a b b b) b b b) b b) 82 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) 0 1 11 12 ` (1 1 , ` (1 1 1 , ` (1 1 1 1 , ` (1 1 2 , ` (1 2 , a a b b 11 11 2 2 a a b b b) a b b b) b b b) b b) b) 82 / 406 ... im Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 (0 , a a a b b b) 0 1 11 12 ` (1 1 , ` (1 1 1 , ` (1 1 1 1 , ` (1 1 2 , ` (1 2 , ` (2 , a a b b 11 11 2 2 a a b b b) a b b b) b b b) b b) b) ) 82 / 406 Ein Berechnungsschritt wird durch die Relation beschrieben, wobei 2 ` ⊆ (Q∗ × T ∗ ) (α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ 83 / 406 Ein Berechnungsschritt wird durch die Relation beschrieben, wobei 2 ` ⊆ (Q∗ × T ∗ ) (α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ Bemerkungen: Die Relation ` hängt natürlich vom Kellerautomaten M ab Die reflexive und transitive Hülle von ` bezeichnen wir mit `∗ Dann ist die von M akzeptierte Sprache: L(M) = {w ∈ T ∗ | ∃ f ∈ F : (q0 , w) `∗ (f , )} 83 / 406 Ein Berechnungsschritt wird durch die Relation beschrieben, wobei 2 ` ⊆ (Q∗ × T ∗ ) (α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ Bemerkungen: Die Relation ` hängt natürlich vom Kellerautomaten M ab Die reflexive und transitive Hülle von ` bezeichnen wir mit `∗ Dann ist die von M akzeptierte Sprache: L(M) = {w ∈ T ∗ | ∃ f ∈ F : (q0 , w) `∗ (f , )} Wir akzeptieren also mit Endzustand und leerem Keller 83 / 406 Deterministischer Kellerautomat Definition: Der Kellerautomat M heißt deterministisch, falls jede Konfiguration maximal eine Nachfolge-Konfiguration hat. Das ist genau dann der Fall wenn für verschiedene Übergänge (γ1 , x, γ2 ) , (γ10 , x0 , γ20 ) ∈ δ gilt: Ist γ1 ein Suffix von γ10 , dann muss x 6= x0 ∧ x 6= 6= x0 sein. 84 / 406 Deterministischer Kellerautomat Definition: Der Kellerautomat M heißt deterministisch, falls jede Konfiguration maximal eine Nachfolge-Konfiguration hat. Das ist genau dann der Fall wenn für verschiedene Übergänge (γ1 , x, γ2 ) , (γ10 , x0 , γ20 ) ∈ δ gilt: Ist γ1 ein Suffix von γ10 , dann muss x 6= x0 ∧ x 6= 6= x0 sein. ... im Beispiel: 0 1 11 12 a a b b 11 11 2 2 ... ist das natürlich der Fall 84 / 406 Kellerautomaten Satz: Zu jeder kontextfreien Grammatik G = (N, T, P, S) kann ein Kellerautomat M konstruiert werden mit L(G) = L(M) . Der Satz ist für uns so wichtig, dass wir zwei Konstruktionen für Automaten angeben, motiviert durch die beiden speziellen Ableitungsmöglichkeiten: MGL zur Bildung von Linksableitungen MGR zur Bildung von reversen Rechtsableitungen 85 / 406 Marcel-Paul Schützenberger (1920-1996), Paris Anthony G. Öttinger, Präsident der ACM 1966-68 1961: On context free languages and pushdownautomata 86 / 406 Syntaktische Analyse Kapitel 3: Top-down Parsing 87 / 406 Item-Kellerautomat Konstruktion: Item-Kellerautomat MGL Rekonstruiere eine Linksableitung. Expandiere Nichtterminale mithilfe einer Regel. Verifiziere sukzessive, dass die gewählte Regel mit der Eingabe übereinstimmt. ==⇒ Die Zustände sind jetzt Items (= Regeln mit Punkt): [A →α • β] , A → αβ ∈ P Der Punkt gibt an, wieweit die Regel bereits abgearbeitet wurde 88 / 406 Item-Kellerautomat Unser Beispiel: S → AB A → a 0 Wir fügen eine Regel: S → S B → b hinzu Dann konstruieren wir: Anfangszustand: Endzustand: [S0 → • S] [S → • A B] [A → • a] [S → • A B] [A → a •] [S → A • B] [B → • b] [S → A • B] [B → b •] [S0 → • S] [S → A B •] a b [S0 → • S] [S0 → S •] [S0 → • S] [S → • A B] [S → • A B] [A → • a] [A → a •] [S → A • B] [S → A • B] [B → • b] [B → b •] [S → A B •] [S0 → S •] 89 / 406 Item-Kellerautomat Der Item-Kellerautomat MGL Expansionen: Shifts: Reduce: hat drei Arten von Übergängen: ([A → α • B β], , [A → α • B β] [B → • γ]) für A → α B β, B → γ ∈ P ([A → α • a β], a, [A → α a • β]) für A → α a β ∈ P ([A → α • B β] [B → γ•], , [A → α B • β]) für A → α B β, B → γ ∈ P Items der Form: [A → α •] heißen auch vollständig Der Item-Kellerautomat schiebt den Punkt einmal um den Ableitungsbaum herum ... 90 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 91 / 406 Item-Kellerautomat Diskussion: Die Expansionen einer Berechnung bilden eine Linksableitung Leider muss man bei den Expansionen nichtdeterministisch zwischen verschiedenen Regeln auswählen Zur Korrektheit der Konstruktion zeigt man, dass für jedes Item [A → α • B β] gilt: ([A → α • B β], w) `∗ ([A → α B • β], ) gdw. B →∗ w LL-Parsing basiert auf dem Item-Kellerautomaten und versucht, die Expansionen deterministisch zu machen ... 92 / 406 Philip M. Lewis, SUNY Richard E. Stearns, SUNY 1968: Syntax-Directed Transduction 93 / 406 Item-Kellerautomat Beispiel: S→ | aSb Die Übergänge des zugehörigen Item-Kellerautomat: 0 1 2 3 4 5 6 7 8 9 [S0 → • S] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → a S b•] [S → a S • b] [S0 → • S] [S → •] [S0 → • S] [S → a S b•] a b [S0 → • S] [S → •] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → • a S b] [S → a S • b] [S → a S • b] [S → a S b•] [S0 → S•] [S0 → S•] Konflikte gibt es zwischen den Übergängen (0, 1) bzw. zwischen (3, 4). 94 / 406 Topdown Parsing Problem: Konflikte zwischen Übergängen verhindern die Implementierung des Item-Kellerautomaten als deterministischer Kellerautomat. 95 / 406 Topdown Parsing Problem: Konflikte zwischen Übergängen verhindern die Implementierung des Item-Kellerautomaten als deterministischer Kellerautomat. Idee 1: GLL Parsing Bei jedem Konflikt wird virtuell der Stack komplett kopiert und parallel weiter geführt. 95 / 406 Topdown Parsing Problem: Konflikte zwischen Übergängen verhindern die Implementierung des Item-Kellerautomaten als deterministischer Kellerautomat. Idee 1: GLL Parsing Bei jedem Konflikt wird virtuell der Stack komplett kopiert und parallel weiter geführt. Idee 2: Recursive Descent & Backtracking Tiefensuche nach einer passenden Ableitung. 95 / 406 Topdown Parsing Problem: Konflikte zwischen Übergängen verhindern die Implementierung des Item-Kellerautomaten als deterministischer Kellerautomat. Idee 1: GLL Parsing Bei jedem Konflikt wird virtuell der Stack komplett kopiert und parallel weiter geführt. Idee 2: Recursive Descent & Backtracking Tiefensuche nach einer passenden Ableitung. Idee 3: Recursive Descent & Lookahead Konflikte werden durch Betrachten des/der nächsten Zeichens gelöst. 95 / 406 Struktur des LL(1)-Parsers: δ Ausgabe M Der Parser sieht ein Fenster der Länge 1 der Eingabe; er realisiert im Wesentlichen den Item-Kellerautomaten; die Tabelle M[q, w] enthält die jeweils zuwählende Regel 96 / 406 Topdown Parsing Idee: Benutze den Item-Kellerautomaten. Benutze das nächste Zeichen, um die Regeln für die Expansionen zu bestimmen Eine Grammatik heißt LL(1) , falls dies immer eindeutig möglich ist. 97 / 406 Topdown Parsing Idee: Benutze den Item-Kellerautomaten. Benutze das nächste Zeichen, um die Regeln für die Expansionen zu bestimmen Eine Grammatik heißt LL(1) , falls dies immer eindeutig möglich ist. Definition: Eine reduzierte Grammatik heißt dann LL(1), falls für je zwei verschiedene Regeln A → α , A → α0 ∈ P und jede Ableitung S →∗L u A β mit u ∈ T ∗ gilt: First1 (α β) ∩ First1 (α0 β) = ∅ 97 / 406 Topdown Parsing Beispiel 1: S → E → if ( E ) S else S | while ( E ) S | E; id ist LL(1), da First1 (E) = {id} 98 / 406 Topdown Parsing Beispiel 1: S → E → if ( E ) S else S | while ( E ) S | E; id ist LL(1), da First1 (E) = {id} Beispiel 2: S → E → if ( E ) S else S | if ( E ) S | while ( E ) S | E; id ... ist nicht LL(k) für jedes k > 0. 98 / 406 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T ∗ definieren wir: First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb 99 / 406 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T ∗ definieren wir: First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb die Präfixe der Länge 1 99 / 406 Vorausschau-Mengen Rechenregeln: First1 (_) ist verträglich mit Vereinigung und Konkatenation: First1 (∅) First1 (L1 ∪ L2 ) First1 (L1 · L2 ) = = = := ∅ First1 (L1 ) ∪ First1 (L2 ) First1 (First1 (L1 ) · First1 (L2 )) First1 (L1 ) First1 (L2 ) 1 − Konkatenation 100 / 406 Vorausschau-Mengen Rechenregeln: First1 (_) ist verträglich mit Vereinigung und Konkatenation: First1 (∅) First1 (L1 ∪ L2 ) First1 (L1 · L2 ) = = = := ∅ First1 (L1 ) ∪ First1 (L2 ) First1 (First1 (L1 ) · First1 (L2 )) First1 (L1 ) First1 (L2 ) 1 − Konkatenation Beobachtung: Seien L1 , L2 ⊆ T ∪ {} mit L1 6= ∅ 6= L2 . Dann ist: L1 falls 6∈ L1 L1 L2 = (L1 \{}) ∪ L2 sonst Sind alle Regeln von G produktiv, sind alle Mengen First1 (A) nichtleer. 100 / 406 Vorausschau-Mengen Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: First1 (α) = First1 ({w ∈ T ∗ | α →∗ w}) Idee: Behandle separat: F Sei empty(X) = true gdw. X →∗ . Sj F (X1 . . . Xm ) = i=1 F (Xi ) falls empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) 101 / 406 Vorausschau-Mengen Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: First1 (α) = First1 ({w ∈ T ∗ | α →∗ w}) Idee: Behandle separat: F Sei empty(X) = true gdw. X →∗ . Sj F (X1 . . . Xm ) = i=1 F (Xi ) falls empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) Definiere die -freien First1 -Mengen als Ungleichungssystem F (a) = {a} falls F (A) ⊇ F (Xj ) falls a∈T A → X1 . . . Xm ∈ P, empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) 101 / 406 Vorausschau-Mengen im Beispiel... E T F wobei → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | name 1 | int 2 empty(E) = empty(T) = empty(F) = false . 102 / 406 Vorausschau-Mengen im Beispiel... E T F wobei → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | name 1 | int 2 empty(E) = empty(T) = empty(F) = false . ... erhalten wir: F (S0 ) ⊇ F (E) F (E) ⊇ F (T) F (T) ⊇ F (F) F (E) ⊇ F (E) F (T) ⊇ F (T) F (F) ⊇ { ( , name, int} 102 / 406 Schnelle Berechnung von Vorausschau-Mengen Beobachtung: Die Form der Ungleichungen dieser Ungleichungssysteme ist: x w y bzw. x w d für Variablen x, y und d ∈ D. Solche Systeme heißen reine Vereinigungs-Probleme Diese Probleme können mit linearem Aufwand gelöst werden D = 2{a,b,c} im Beispiel: c x0 x1 x2 x3 ⊇ {a} ⊇ {b} ⊇ {c} ⊇ {c} x1 ⊇ x0 x2 ⊇ x1 x3 ⊇ x2 x1 ⊇ x3 a 0 b 1 3 x3 ⊇ x3 2 c 103 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. 104 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert 104 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert Hat eine SZK keine eingehenden Kanten, erhält man ihren Wert, indem man die kleinste obere Schranke aller Werte in der SZK berechnet 104 / 406 Schnelle Berechnung von Vorausschau-Mengen a b c 3 a 0 1 2 Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert Hat eine SZK keine eingehenden Kanten, erhält man ihren Wert, indem man die kleinste obere Schranke aller Werte in der SZK berechnet Gibt es eingehende Kanten, muss man zusätzlich die Werte an deren Startknoten hinzufügen 104 / 406 Schnelle Berechnung von Vorausschau-Mengen ... für unsere Beispiel-Grammatik: First1 : (, int, name S’ E T F 105 / 406 Item-Kellerautomat als LL(1)-Parser zurück zum Beispiel: S → | aSb Die Übergänge des zugehörigen Item-Kellerautomat: 0 1 2 3 4 5 6 7 8 9 [S0 → • S] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → a S b•] [S → a S • b] [S0 → • S] [S → •] [S0 → • S] [S → a S b•] a b [S0 → • S] [S → •] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → • a S b] [S → a S • b] [S → a S • b] [S → a S b•] [S0 → S•] [S0 → S•] Konflikte gibt es zwischen den Übergängen (0, 1) bzw. zwischen (3, 4). 106 / 406 Item-Kellerautomat als LL(1)-Parser Ist G eine LL(1)-Grammatik, können wir eine Vorausschau-Tabelle mit Items und Nichtterminalen indizieren: Wir setzen M[B, w] = i genau dann wenn (B, i) die Regel S B → γ ist und: w ∈ First1 (γ) {First1 (β) | S0 →∗L u B β } . i0 S 0 ... im Beispiel: S → | aSb S 0 a 1 β0 i1 A1 b 0 in An i B β1 β γ w ∈ First1 ( ) 107 / 406 Item-Kellerautomat als LL(1)-Parser i0 S 0 β0 i1 A1 in An i B β1 β γ w ∈ First1 ( Ungleichungssystem für Follow1 (B) = Follow1 (S) ⊇ {} Follow1 (B) ⊇ F (Xj ) falls Follow1 (B) ⊇ Follow1 (A) falls S ) {First1 (β) | S0 →∗L u B β } A → α B X1 . . . Xm ∈ P, empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) A → α B X1 . . . Xm ∈ P, empty(X1 ) ∧ . . . ∧ empty(Xm ) 108 / 406 Item-Kellerautomat als LL(1)-Parser Im Beispiel: S→ | aSb Die Übergänge des zugehörigen Item-Kellerautomat: 0 1 2 3 4 5 6 7 8 9 [S0 → • S] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → a S b•] [S → a S • b] [S0 → • S] [S → •] [S0 → • S] [S → a S b•] a b [S0 → • S] [S → •] [S0 → • S] [S → • a S b] [S → a • S b] [S → a • S b] [S → •] [S → a • S b] [S → • a S b] [S → a S • b] [S → a S • b] [S → a S b•] [S0 → S•] [S0 → S•] Vorausschautabelle: S 0 a 1 b 0 109 / 406 Topdown-Parsing Diskussion Einer praktischen Implementation von LL(1)-Parsern über recursive Descent steht nichts im Wege Jedoch kann nur eine Teilmenge der deterministisch kontextfreien Sprachen auf diesem Weg gelesen werden. 110 / 406 Topdown-Parsing Diskussion Einer praktischen Implementation von LL(1)-Parsern über recursive Descent steht nichts im Wege Jedoch kann nur eine Teilmenge der deterministisch kontextfreien Sprachen auf diesem Weg gelesen werden. Ausweg: Schritt von LL(1) auf LL(k) Die Größe der auftretenden Mengen steigt mit k rapide Leider reichen auch LL(k) Parser nicht aus, um alle deterministisch kontextfreien Sprachen zu erkennen. In praktischen Systemen wird darum meist nur der Fall k = 1 implementiert ... 110 / 406 Syntaktische Analyse Kapitel 4: Schnelle Berechnung von Vorausschau-Mengen 111 / 406 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T ∗ definieren wir: First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb 112 / 406 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T ∗ definieren wir: First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb die Präfixe der Länge 1 112 / 406 Vorausschau-Mengen Rechenregeln: First1 (_) ist verträglich mit Vereinigung und Konkatenation: First1 (∅) First1 (L1 ∪ L2 ) First1 (L1 · L2 ) = = = := ∅ First1 (L1 ) ∪ First1 (L2 ) First1 (First1 (L1 ) · First1 (L2 )) First1 (L1 ) First1 (L2 ) 1 − Konkatenation 113 / 406 Vorausschau-Mengen Rechenregeln: First1 (_) ist verträglich mit Vereinigung und Konkatenation: First1 (∅) First1 (L1 ∪ L2 ) First1 (L1 · L2 ) = = = := ∅ First1 (L1 ) ∪ First1 (L2 ) First1 (First1 (L1 ) · First1 (L2 )) First1 (L1 ) First1 (L2 ) 1 − Konkatenation Beobachtung: Seien L1 , L2 ⊆ T ∪ {} mit L1 6= ∅ 6= L2 . Dann ist: L1 falls 6∈ L1 L1 L2 = (L1 \{}) ∪ L2 sonst Sind alle Regeln von G produktiv, sind alle Mengen First1 (A) nichtleer. 113 / 406 Vorausschau-Mengen Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: First1 (α) = First1 ({w ∈ T ∗ | α →∗ w}) Idee: Behandle separat: F Sei empty(X) = true gdw. X →∗ . Sj F (X1 . . . Xm ) = i=1 F (Xi ) falls empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) 114 / 406 Vorausschau-Mengen Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: First1 (α) = First1 ({w ∈ T ∗ | α →∗ w}) Idee: Behandle separat: F Sei empty(X) = true gdw. X →∗ . Sj F (X1 . . . Xm ) = i=1 F (Xi ) falls empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) Definiere die -freien First1 -Mengen als Ungleichungssystem F (a) = {a} falls F (A) ⊇ F (Xj ) falls a∈T A → X1 . . . Xm ∈ P, empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) 114 / 406 Vorausschau-Mengen im Beispiel... E T F wobei → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | name 1 | int 2 empty(E) = empty(T) = empty(F) = false . 115 / 406 Vorausschau-Mengen im Beispiel... E T F wobei → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | name 1 | int 2 empty(E) = empty(T) = empty(F) = false . ... erhalten wir: F (S0 ) ⊇ F (E) F (E) ⊇ F (T) F (T) ⊇ F (F) F (E) ⊇ F (E) F (T) ⊇ F (T) F (F) ⊇ { ( , name, int} 115 / 406 Schnelle Berechnung von Vorausschau-Mengen Beobachtung: Die Form der Ungleichungen dieser Ungleichungssysteme ist: x w y bzw. x w d für Variablen x, y und d ∈ D. Solche Systeme heißen reine Vereinigungs-Probleme Diese Probleme können mit linearem Aufwand gelöst werden D = 2{a,b,c} im Beispiel: c x0 x1 x2 x3 ⊇ {a} ⊇ {b} ⊇ {c} ⊇ {c} x1 ⊇ x0 x2 ⊇ x1 x3 ⊇ x2 x1 ⊇ x3 a 0 b 1 3 x3 ⊇ x3 2 c 116 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. 117 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert 117 / 406 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert Hat eine SZK keine eingehenden Kanten, erhält man ihren Wert, indem man die kleinste obere Schranke aller Werte in der SZK berechnet 117 / 406 Schnelle Berechnung von Vorausschau-Mengen a b c 3 a 0 1 2 Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. Innerhalb einer starken Zusammenhangskomponente (→ Tarjan) haben alle Variablen den gleichen Wert Hat eine SZK keine eingehenden Kanten, erhält man ihren Wert, indem man die kleinste obere Schranke aller Werte in der SZK berechnet Gibt es eingehende Kanten, muss man zusätzlich die Werte an deren Startknoten hinzufügen 117 / 406 Schnelle Berechnung von Vorausschau-Mengen ... für unsere Beispiel-Grammatik: First1 : (, int, name S’ E T F 118 / 406 Syntaktische Analyse Kapitel 5: Bottom-up Analyse 119 / 406 Donald E. Knuth, Stanford 120 / 406 Bottom-up Analyse Achtung: Viele Grammatiken sind nicht LL(k) ! Eine Grund dafür ist: Definition Die Grammatik G heißt links-rekursiv, falls A →+ A β für ein A ∈ N , β ∈ (T ∪ N)∗ 121 / 406 Bottom-up Analyse Achtung: Viele Grammatiken sind nicht LL(k) ! Eine Grund dafür ist: Definition Die Grammatik G heißt links-rekursiv, falls A →+ A β für ein A ∈ N , β ∈ (T ∪ N)∗ Beispiel: E T F → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | name 1 | int 2 ... ist links-rekursiv 121 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: Sei A → A β | α ∈ P und A erreichbar von S Annahme: G ist LL(k) 122 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: S Sei A → A β | α ∈ P und A erreichbar von S A n A Annahme: G ist LL(k) A Firstk ( β n β γ ) 122 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: S Sei A → A β | α ∈ P und A erreichbar von S A n A Annahme: G ist LL(k) A β n β γ α Firstk ( ) 122 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: S Sei A → A β | α ∈ P und A erreichbar von S A n A Annahme: G ist LL(k) A A β n β γ β α Firstk ( ) 122 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: Sei A → A β | α ∈ P und A erreichbar von S Annahme: G ist LL(k) ⇒Firstk (α β n γ) ∩ Firstk (α β n+1 γ) = ∅ 122 / 406 Bottom-up Analyse Satz: Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht LL(k) für jedes k. Beweis: Sei A → A β | α ∈ P und A erreichbar von S Annahme: G ist LL(k) ⇒Firstk (α β n γ) ∩ Firstk (α β n+1 γ) = ∅ Fall 1: β →∗ — Widerspruch !!! Fall 2: β →∗ w 6= ==⇒ Firstk (α β k γ) ∩ Firstk (α β k+1 γ) 6= ∅ 122 / 406 Shift-Reduce-Parser Idee: Wir verzögern die Entscheidung ob wir reduzieren bis wir wissen, ob die Eingabe einer rechten Seite entspricht! Konstruktion: Shift-Reduce-Parser MGR Die Eingabe wird sukzessive auf den Keller geschoben. Liegt oben auf dem Keller eine vollständige rechte Seite (ein Handle) vor, wird dieses durch die zugehörige linke Seite ersetzt (reduziert) 123 / 406 Shift-Reduce-Parser Beispiel: S A B → → → AB a b Der Kellerautomat: Zustände: Anfangszustand: Endzustand: q0 a A b AB q0 S q0 , f , a, b, A, B, S; q0 f a q0 a A b Ab B S f 124 / 406 Shift-Reduce-Parser Konstruktion: Allgemein konstruieren wir einen Automaten MGR = (Q, T, δ, q0 , F) mit: Q = T ∪ N ∪ {q0 , f } (q0 , f neu); F = {f }; Übergänge: δ = {(q, x, q x) | q ∈ Q, x ∈ T} ∪ // {(q α, , q A) | q ∈ Q, A → α ∈ P} ∪ // {(q0 S, , f )} // Shift-Übergänge Reduce-Übergänge Abschluss 125 / 406 Shift-Reduce-Parser Konstruktion: Allgemein konstruieren wir einen Automaten MGR = (Q, T, δ, q0 , F) mit: Q = T ∪ N ∪ {q0 , f } (q0 , f neu); F = {f }; Übergänge: δ = {(q, x, q x) | q ∈ Q, x ∈ T} ∪ // {(q α, , q A) | q ∈ Q, A → α ∈ P} ∪ // {(q0 S, , f )} // Shift-Übergänge Reduce-Übergänge Abschluss Beispiel-Berechnung: (q0 , a b) ` ` ` (q0 a , b) ` (q0 A b , ) ` (q0 S, ) ` (q0 A, b) (q0 A B , ) (f , ) 125 / 406 Shift-Reduce-Parser Offenbar gilt: Die Folge der Reduktionen entspricht einer reversen Rechtsableitung für die Eingabe Zur Korrektheit zeigt man, dass gilt: (, w) `∗ (A, ) gdw. Der Shift-Reduce-Kellerautomat MGR nicht-deterministisch A →∗ w ist i.a. auch Um ein deterministisches Parse-Verfahren zu erhalten, muss man die Reduktionsstellen identifizieren ==⇒ LR-Parsing 126 / 406 Bottom-up Analyse Idee: Wir rekonstruieren reverse Rechtsableitungen! Dazu versuchen wir, für den Shift-Reduce-Parser MGR Reduktionsstellen zu identifizieren ... die Betrachte eine Berechnung dieses Kellerautomaten: (q0 α γ, v) ` (q0 α B, v) `∗ (q0 S, ) α γ nennen wir zuverlässiges Präfix für das vollständige Item [B → γ•] . 127 / 406 Bottom-up Analyse Dann ist αγ zuverlässig für [B → γ•] gdw. S →∗R α B v A0 i0 α1 α2 αm A1 i1 A2 i2 B i γ ... wobei α = α1 . . . αm 128 / 406 Bottom-up Analyse Dann ist αγ zuverlässig für [B → γ•] gdw. S →∗R α B v A0 i0 α1 α2 αm A1 i1 A2 i2 B i γ ... wobei α = α1 . . . αm Umgekehrt können wir zu jedem möglichen Wort α0 die Menge aller möglicherweise später passenden Regeln ermitteln ... 128 / 406 Bottom-up Analyse Das Item [B → γ • β] α0 = α γ : heißt gültig für α0 gdw. S →∗R α B v mit A0 i0 α1 A1 i1 α2 A2 i2 αm B i γ β ... wobei α = α1 . . . αm :-) 129 / 406 Charakteristischer Automat Beobachtung: Die Menge der zuverlässigen Präfixe aus (N ∪ T)∗ für (vollständige) Items kann mithilfe eines endlichen Automaten, aus dem Kellerinhalt des Shift-Reduce-Parsers berechnet werden: Zustände: Items Anfangszustand: [S0 → • S] Endzustände: {[B → γ•] | B → γ ∈ P} Übergänge: (1) (2) ([A → α • X β],X,[A → α X • β]), ([A → α • B β],, [B → • γ]), X ∈ (N ∪ T), A → α X β ∈ P; A → α B β , B → γ ∈ P; Den Automaten c(G) nennen wir charakteristischen Automaten für G. 130 / 406 Charakteristischer Automat im Beispiel: E T F S’ E E E+T E T T T∗F T F F (E) F int E E T T F → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | int 2 S’ E E E +T E T T T ∗F T F F ( E) F int ( int + ∗ E E E+ T T T∗ F F (E ) T F ) E E+T T T∗F F (E) 131 / 406 Charakteristischer Automat im Beispiel: E T F S’ E E E+T E T T T∗F T F F (E) F int E E T T F → E+T 0 | T 1 → T ∗F 0 | F 1 → ( E ) 0 | int 2 S’ E E E +T E T T T ∗F T F F ( E) F int ( int + ∗ E E E+ T T T∗ F F (E ) T F ) E E+T T T∗F F (E) 131 / 406 Kanonischer LR(0)-Automat Den kanonischen LR(0)-Automaten LR(G) erhalten wir aus c(G) , indem wir: 1 nach jedem lesenden Übergang beliebig viele -Übergänge einschieben 2 die Teilmengenkonstruktion anwenden. + 1 int E ... im Beispiel: int 0 + int F 3 F 5 ( T 2 9 int 4 ( T T 6 F E ( * 11 ( ) 8 * 7 F 10 132 / 406 Kanonischer LR(0)-Automat Dazu konstruieren wir: q0 = {[S0 → • E], {[E → • E + T], {[E → • T], {[T → • T ∗ F]} {[T → • F], {[F → • ( E ) ], {[F → • int]} q1 = δ(q0 , E) = {[S0 → E•], {[E → E • + T]} q2 = δ(q0 , T) = {[E → T•], {[T → T • ∗ F]} q3 = δ(q0 , F) = {[T → F•]} q4 = δ(q0 , int) = {[F → int•]} 133 / 406 Kanonischer LR(0)-Automat q5 q6 = = δ(q0 , ( ) δ(q1 , +) = = {[F → ( • E ) ], {[E → • E + T], {[E → • T], {[T → • T ∗ F], {[T → • F], {[F → • ( E ) ], {[F → • int]} {[E → E + • T], {[T → • T ∗ F], {[T → • F], {[F → • ( E ) ], {[F → • int]} q7 = δ(q2 , ∗) = {[T → T ∗ • F], {[F → • ( E ) ], {[F → • int]} q8 = δ(q5 , E) = {[F → ( E • ) ]} {[E → E • + T]} q9 = δ(q6 , T) = {[E → E + T•], {[T → T • ∗ F]} q10 = δ(q7 , F) = {[T → T ∗ F•]} q11 = δ(q8 , ) ) = {[F → ( E ) •]} 134 / 406 Kanonischer LR(0)-Automat Beachte: Der kanonische LR(0)-Automat kann auch direkt aus der Grammatik konstruiert werden. Man benötigt die Hilfsfunktion: δ∗ (-Abschluss) δ∗ (q) = q ∪ {[B → • γ] | ∃ [A → α • B0 β 0 ] ∈ q , ∃ β ∈ (N ∪ T)∗ : B0 →∗ B β} Dann definiert man: Zustände: Mengen von Items; Anfangszustand δ∗ {[S0 → • S]} Endzustände: {q | ∃ A → α ∈ P : [A → α •] ∈ q} Übergänge: δ(q, X) = δ∗ {[A → α X • β] | [A → α • X β] ∈ q} 135 / 406 LR(0)-Parser Idee zu einem Parser: Der Parser verwaltet ein zuverlässiges Präfix α = X1 . . . Xm auf dem Keller und benutzt LR(G) , um Reduktionsstellen zu entdecken. Er kann mit einer Regel A → γ reduzieren, falls [A → γ •] für α gültig ist Optimierung: Damit der Automat nicht immer wieder neu über den Kellerinhalt laufen muss, kellern wir anstelle der Xi jeweils die Zustände! Reduktion mit A → γ führt zum Poppen der obersten |γ| Zustände und Fortsetzung mit dem Zustand auf dem Keller unter Eingabe A. Achtung: Dieser Parser ist nur dann deterministisch, wenn jeder Endzustand des kanonischen LR(0)-Automaten keine Konflikte enthält. 136 / 406 LR(0)-Parser ... im Beispiel: q1 = {[S0 → E •], {[E → E • + T]} q2 = {[E → T •], {[T → T • ∗ F]} q9 = {[E → E + T •], {[T → T • ∗ F]} q3 = {[T → F •]} q10 = {[T → T ∗ F •]} q4 = {[F → int •]} q11 = {[F → ( E ) •]} Die Endzustände q1 , q2 , q9 enthalten mehr als ein gültiges Item ⇒ nicht deterministisch! 137 / 406 LR(0)-Parser Die Konstruktion des LR(0)-Parsers: Zustände: Q ∪ {f } (f neu) Anfangszustand: q0 Endzustand: f Übergänge: Shift: Reduce: Finish: wobei (p, a, p q) (p q1 . . . qm , , p q) falls falls (q0 p, , f ) falls q = δ(p, a) 6= ∅ [A → X1 . . . Xm •] ∈ qm , q = δ(p, A) [S0 → S•] ∈ p LR(G) = (Q, T, δ, q0 , F) . 138 / 406 LR(0)-Parser Zur Korrektheit: Man zeigt: Die akzeptierenden Berechnungen des LR(0)-Parsers stehen in eins-zu-eins Beziehung zu denen des Shift-Reduce-Parsers MGR . Wir folgern: ==⇒ Die akzeptierte Sprache ist genau L(G) ==⇒ Die Folge der Reduktionen einer akzeptierenden Berechnung für ein Wort w ∈ T liefert eine reverse Rechts-Ableitung von G für w 139 / 406 LR(0)-Parser Achtung: Leider ist der LR(0)-Parser im allgemeinen nicht-deterministisch Wir identifizieren zwei Gründe: Reduce-Reduce-Konflikt: [A → γ •] , [A0 → γ 0 •] ∈ q mit Shift-Reduce-Konflikt: [A → γ •] , [A0 → α • a β] ∈ q A 6= A0 ∨ γ 6= γ 0 mit a∈T für einen Zustand q ∈ Q . Solche Zustände nennen wir LR(0)-ungeeignet. 140 / 406 LR(k)-Grammatik Idee: Benutze k-Vorausschau, um Konflikte zu lösen. Definition: Die reduzierte kontextfreie Grammatik G heißt LR(k)-Grammatik, falls für Firstk (w) = Firstk (x) aus: S →∗R α A w → α β w folgt: α = α0 ∧ A = A0 ∧ w0 = x S →∗R α0 A0 w0 → α β x 141 / 406 LR(k)-Grammatik im Beispiel: (1) S→A | B A→aAb | 0 B→aBbb | 1 ... ist nicht LL(k) für jedes k — aber LR(0) : Sei S →∗R α X w → α β w . Formen: Dann ist α β A , B , an a A b , an a B b b , an 0 , an 1 von einer der (n ≥ 0) 142 / 406 LR(k)-Grammatik im Beispiel: (1) S→A | B A→aAb | 0 B→aBbb | 1 ... ist nicht LL(k) für jedes k — aber LR(0) : Sei S →∗R α X w → α β w . Formen: Dann ist α β A , B , an a A b , an a B b b , an 0 , an 1 (2) S→aAc von einer der (n ≥ 0) A→Abb | b ... ist ebenfalls LR(0) : Sei S →∗R α X w → α β w . Formen: Dann ist α β von einer der ab , aAbb , aAc 142 / 406 LR(k)-Grammatik im Beispiel: (3) S→aAc LR(1) : A→bbA | b Für S →∗R α X w → α β w von einer der Formen: ... ist nicht LR(0), aber mit {y} = Firstk (w) ist αβy a b2n b c , a b2n b b A c , a A c 143 / 406 LR(k)-Grammatik im Beispiel: (3) S→aAc LR(1) : A→bbA | b Für S →∗R α X w → α β w von einer der Formen: ... ist nicht LR(0), aber mit {y} = Firstk (w) ist αβy a b2n b c , a b2n b b A c , a A c (4) S→aAc k ≥ 0: A→bAb | b ... ist nicht LR(k) für jedes Betrachte einfach die Rechtsableitungen: S →∗R a bn A bn c → a bn b bn c 143 / 406 LR(1)-Items Sei k = 1. Idee: Wir statten Items mit 1-Vorausschau aus Ein LR(1)-Item ist dann ein Paar: [B → α • β, x] , x ∈ Follow1 (B) = [ {First1 (ν) | S →∗ µ B ν} Dieses Item ist gültig für γ α falls: S →∗R γ B w mit {x} = First1 (w) 144 / 406 LR(1)-Items S i0 γ0 A1 i1 γ1 A m im γm B i α β ... wobei γ0 . . . γm = γ 145 / 406 LR(1)-Items S i0 γ0 A1 i1 γ1 A m im γm B i α β ... wobei γ0 . . . γm = γ Die Menge der gültigen LR(1)-Items für zuverlässige Präfixe berechnen wir wieder mithilfe eines endlichen Automaten 145 / 406 Der charakteristische LR(1)-Automat Der Automat c(G, 1) : Zustände: LR(1)-Items Anfangszustand: [S0 → • S, ] Endzustände: {[B → γ•, x] | B → γ ∈ P, x ∈ Follow1 (B)} Übergänge: (1) ([A → α • X β, x],X,[A → α X • β, x]), X ∈ (N ∪ T) (2) ([A → α • B β, x],, [B → • γ, x0 ]), A → α B β , B → γ ∈ P, x0 ∈ First1 (β) {x}; 146 / 406 Der charakteristische LR(1)-Automat Der Automat c(G, 1) : Zustände: LR(1)-Items Anfangszustand: [S0 → • S, ] Endzustände: {[B → γ•, x] | B → γ ∈ P, x ∈ Follow1 (B)} Übergänge: (1) ([A → α • X β, x],X,[A → α X • β, x]), X ∈ (N ∪ T) (2) ([A → α • B β, x],, [B → • γ, x0 ]), A → α B β , B → γ ∈ P, x0 ∈ First1 (β) {x}; Dieser Automat arbeitet wie c(G) — verwaltet aber zusätzlich ein 1-Präfix aus dem Follow1 der linken Seiten. 146 / 406 Der kanonische LR(1)-Automat Den kanonischen LR(1)-Automaten LR(G, 1) erhält man aus c(G, 1) , indem man nach jedem Übergang beliebig viele liest und dann den Automaten deterministisch macht ... 147 / 406 Der kanonische LR(1)-Automat Den kanonischen LR(1)-Automaten LR(G, 1) erhält man aus c(G, 1) , indem man nach jedem Übergang beliebig viele liest und dann den Automaten deterministisch macht ... Man kann ihn aber auch direkt aus der Grammatik konstruieren Wie bei LR(0) benötigt man eine Hilfsfunktion: δ∗ (q) = q ∪ {[B → • γ, x] | ∃ [A → α • B0 β 0 , x0 ] ∈ q , β ∈ (N ∪ T)∗ : B0 →∗ B β ∧ x ∈ First1 (β β 0 ) {x0 }} 147 / 406 Der kanonische LR(1)-Automat Den kanonischen LR(1)-Automaten LR(G, 1) erhält man aus c(G, 1) , indem man nach jedem Übergang beliebig viele liest und dann den Automaten deterministisch macht ... Man kann ihn aber auch direkt aus der Grammatik konstruieren Wie bei LR(0) benötigt man eine Hilfsfunktion: δ∗ (q) = q ∪ {[B → • γ, x] | ∃ [A → α • B0 β 0 , x0 ] ∈ q , β ∈ (N ∪ T)∗ : B0 →∗ B β ∧ x ∈ First1 (β β 0 ) {x0 }} Dann definiert man: Zustände: Mengen von LR(1)-Items; Anfangszustand: δ∗ {[S0 → • S, ]} Endzustände: {q | ∃ A → α ∈ P : [A → α •, x] ∈ q} Übergänge: δ(q, X) = δ∗ {[A → α X • β, x] | [A → α•X β, x] ∈ q} 147 / 406 Der kanonische LR(1)-Automat im Beispiel: q0 q1 q2 ], {[S0 → • E ], {[E → • E + T {[E → • T ], {[T → • T ∗ F ], {[T → • F ], {[F → • ( E ) ], {[F → • int ]} = = = δ(q0 , E) δ(q0 , T) = = 0 {[S → E • {[E → E • + T {[E → T • {[T → T • ∗ F ], ]} q3 = δ(q0 , F) q4 = δ(q0 , int) q5 = δ(q0 , ( ) = {[T → F • ]} {[F → int • = {[F → ( • E ) {[E → • E + T {[E → • T {[T → • T ∗ F {[T → • F {[F → • ( E ) {[F → • int ]} ], ], ], ] ], ] ]} ], ]} 148 / 406 Der kanonische LR(1)-Automat im Beispiel: q0 q1 q2 {[S0 → • E, {}], {[E → • E + T, {, +}], {[E → • T, {, +}], {[T → • T ∗ F, {, +, ∗}], {[T → • F, {, +, ∗}], {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} = = = δ(q0 , E) δ(q0 , T) = = 0 {[S → E • {[E → E • + T {[E → T • {[T → T • ∗ F ], ]} q3 = δ(q0 , F) q4 = δ(q0 , int) q5 = δ(q0 , ( ) = {[T → F • ]} {[F → int • = {[F → ( • E ) {[E → • E + T {[E → • T {[T → • T ∗ F {[T → • F {[F → • ( E ) {[F → • int ]} ], ], ], ] ], ] ]} ], ]} 148 / 406 Der kanonische LR(1)-Automat im Beispiel: q0 q1 q2 {[S0 → • E, {}], {[E → • E + T, {, +}], {[E → • T, {, +}], {[T → • T ∗ F, {, +, ∗}], {[T → • F, {, +, ∗}], {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} = = = δ(q0 , E) δ(q0 , T) = = 0 {[S → E • , {}], {[E → E • + T, {, +}]} q3 = δ(q0 , F) q4 = δ(q0 , int) q5 = δ(q0 , ( ) = {[T → F • , {, +, ∗}]} {[F → int • , {, +, ∗}]} = {[F → ( • E ) {[E → • E + T {[E → • T {[T → • T ∗ F {[T → • F {[F → • ( E ) {[F → • int ], ], ], ] ], ] ]} {[E → T • , {, +}], {[T → T • ∗ F, {, +, ∗}]} 148 / 406 Der kanonische LR(1)-Automat im Beispiel: q0 {[S0 → • E, {}], {[E → • E + T, {, +}], {[E → • T, {, +}], {[T → • T ∗ F, {, +, ∗}], {[T → • F, {, +, ∗}], {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} = q1 = δ(q0 , E) = {[S0 → E • , {}], {[E → E • + T, {, +}]} q2 = δ(q0 , T) = {[E → T • , {, +}], {[T → T • ∗ F, {, +, ∗}]} q3 = δ(q0 , F) q4 = δ(q0 , int) q5 = δ(q0 , ( ) = {[T → F • , {, +, ∗}]} {[F → int • , {, +, ∗}]} = {[F → ( • E ) , {, +, ∗}], {[E → • E + T, { ) , +}], {[E → • T, { ) , +}], {[T → • T ∗ F, { ) , +, ∗}] {[T → • F, { ) , +, ∗}], {[F → • ( E ) , { ) , +, ∗}] {[F → • int, { ) , +, ∗}]} 148 / 406 Der kanonische LR(1)-Automat im Beispiel: q05 q6 = = δ(q5 , ( ) δ(q1 , +) = = {[F → ( • E ) {[E → • E + T {[E → • T {[T → • T ∗ F {[T → • F {[F → • ( E ) {[F → • int {[E → E + • T {[T → • T ∗ F {[T → • F {[F → • ( E ) {[F → • int ], q7 = δ(q2 , ∗) = ], ], {[T → T ∗ • F {[F → • ( E ) {[F → • int ], ], ]} ], ], q8 = δ(q5 , E) = {[F → ( E • ) {[E → E • + T ]} ]} q9 = δ(q6 , T) = {[E → E + T • {[T → T • ∗ F ], ], ]} ], ]} ], ], q10 = δ(q7 , F) = {[T → T ∗ F • ]} q11 = δ(q8 , ) ) = {[F → ( E ) • ]} ], ]} 149 / 406 Der kanonische LR(1)-Automat im Beispiel: q05 q6 = = δ(q5 , ( ) δ(q1 , +) = = {[F → ( • E ) , { ) , +, ∗}], q7 {[E → • E + T, { ) , +}], {[E → • T, { ) , +}], {[T → • T ∗ F, { ) , +, ∗}], {[T → • F, { ) , +, ∗}], q8 {[F → • ( E ) , { ) , +, ∗}], {[F → • int, { ) , +, ∗}]} q9 {[E → E + • T ], {[T → • T ∗ F ], {[T → • F ], q10 {[F → • ( E ) ], {[F → • int ]} q11 = δ(q2 , ∗) = {[T → T ∗ • F {[F → • ( E ) {[F → • int ], ], ]} = δ(q5 , E) = {[F → ( E • ) {[E → E • + T ]} ]} = δ(q6 , T) = {[E → E + T • {[T → T • ∗ F ], ]} = δ(q7 , F) = {[T → T ∗ F • ]} = δ(q8 , ) ) = {[F → ( E ) • ]} 149 / 406 Der kanonische LR(1)-Automat im Beispiel: q05 q6 = = δ(q5 , ( ) δ(q1 , +) = = {[F → ( • E ) , { ) , +, ∗}], q7 {[E → • E + T, { ) , +}], {[E → • T, { ) , +}], {[T → • T ∗ F, { ) , +, ∗}], {[T → • F, { ) , +, ∗}], q8 {[F → • ( E ) , { ) , +, ∗}], {[F → • int, { ) , +, ∗}]} q9 {[E → E + • T, {, +}], {[T → • T ∗ F, {, +, ∗}], {[T → • F, {, +, ∗}], q10 {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} q11 = δ(q2 , ∗) = {[T → T ∗ • F {[F → • ( E ) {[F → • int ], ], ]} = δ(q5 , E) = {[F → ( E • ) {[E → E • + T ]} ]} = δ(q6 , T) = {[E → E + T • {[T → T • ∗ F ], ]} = δ(q7 , F) = {[T → T ∗ F • ]} = δ(q8 , ) ) = {[F → ( E ) • ]} 149 / 406 Der kanonische LR(1)-Automat im Beispiel: q05 q6 = = δ(q5 , ( ) δ(q1 , +) = = {[F → ( • E ) , { ) , +, ∗}], q7 {[E → • E + T, { ) , +}], {[E → • T, { ) , +}], {[T → • T ∗ F, { ) , +, ∗}], {[T → • F, { ) , +, ∗}], q8 {[F → • ( E ) , { ) , +, ∗}], {[F → • int, { ) , +, ∗}]} q9 {[E → E + • T, {, +}], {[T → • T ∗ F, {, +, ∗}], {[T → • F, {, +, ∗}], q10 {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} q11 = δ(q2 , ∗) = {[T → T ∗ • F, {, +, ∗}], {[F → • ( E ) , {, +, ∗}], {[F → • int, {, +, ∗}]} = δ(q5 , E) = {[F → ( E • ) , {, +, ∗}]} {[E → E • + T, { ) , +}]} = δ(q6 , T) = {[E → E + T • , {, +}], {[T → T • ∗ F, {, +, ∗}]} = δ(q7 , F) = {[T → T ∗ F • , {, +, ∗}]} = δ(q8 , ) ) = {[F → ( E ) • , {, +, ∗}]} 149 / 406 Der kanonische LR(1)-Automat im Beispiel: q02 = δ(q05 , T) = {[E → T•, { ) , +}], q07 {[T → T • ∗ F, { ) , +, ∗}]} q03 = δ(q05 , F) = {[F → F•, { ) , +, ∗}]} q04 = δ(q05 , int) = {[F → int•, { ) , +, ∗}]} q06 = δ(q8 , +) = {[E → E + • T, { ) , +}], q09 {[T → • T ∗ F, { ) , +, ∗}], {[T → • F, { ) , +, ∗}], {[F → • ( E ) , { ) , +, ∗}], q010 {[F → • int, { ) , +, ∗}]} q011 q08 = δ(q9 , ∗) = {[T → T ∗ • F, { ) , +, ∗}], {[F → • ( E ) , { ), +, ∗}], {[F → • int, { ) , +, ∗}]} = δ(q05 , E) = {[F → ( E • ) , { ) , +, ∗}]} {[E → E • + T, { ) , +}]} = δ(q06 , T) = {[E → E + T•, { ) , +}], {[T → T • ∗ F, { ) , +, ∗}]} = δ(q07 , F) = {[T → T ∗ F•, { ) , +, ∗}]} = δ(q08 , ) ) = {[F → ( E ) •, { ) , +, ∗}]} 150 / 406 Der kanonische LR(1)-Automat im Beispiel: + 1 int E int 0 + int F 3 F 5 T 2 9 int 4 ( T T 6 F E ( * 11 ( ) 8 * 7 F 10 151 / 406 Der kanonische LR(1)-Automat im Beispiel: + 1 int E int 0 F ( T + 4 int 3 F 5 T( 2 ( T 6 F 4’ ( int 3’ E F( 5’ T 2’ 9 T 6’ int int + int * 11 F ) ( * * 11’ (8 E 9’ ) 8’ * 7 F 10 7’ F 10’ 151 / 406 Der kanonische LR(1)-Automat Diskussion: Im Beispiel hat sich die Anzahl der Zustände fast verdoppelt ... und es kann noch schlimmer kommen Die Konflikte in den Zuständen q1 , q2 , q9 sind nun aufgelöst ! z.B. haben wir für: q9 = {[E → E + T•, {, +}], {[T → T • ∗ F, {, +, ∗}]} mit: {, +} ∩ (First1 (∗ F) {, +, ∗}) = {, +} ∩ {∗} = ∅ 152 / 406 Der LR(1)-Parser: action Ausgabe goto Die goto-Tabelle kodiert die Zustandsübergänge: goto[q, X] = δ(q, X) ∈ Q Die action-Tabelle beschreibt für jeden Zustand q und möglichen Look-ahead w die erforderliche Aktion. 153 / 406 Der LR(1)-Parser: Mögliche Aktionen sind: shift // Shift-Operation reduce (A → γ) // Reduktion mit Ausgabe error // Fehler ... im Beispiel: E T F → → → E+T 0 | T 1 T ∗F 0 | F 1 ( E ) 0 | int 1 action q1 q2 q02 q3 q03 q4 q04 q9 q09 q10 q010 q11 q011 S0 , 0 E, 1 int ( ) + E, 1 T, 1 T, 1 F, 1 F, 1 E, 0 E, 0 T, 0 T, 0 F, 0 F, 0 T, 1 T, 1 F, 1 F, 1 E, 0 E, 0 T, 0 T, 0 F, 0 F, 0 ∗ s s s T, 1 T, 1 F, 1 F, 1 s s T, 0 T, 0 F, 0 F, 0 154 / 406 Der kanonische LR(1)-Automat Allgemein: Wir identifizieren zwei Konflikte: Reduce-Reduce-Konflikt: [A → γ •, x] , [A0 → γ 0 •, x] ∈ q mit A 6= A0 ∨ γ 6= γ 0 Shift-Reduce-Konflikt: [A → γ •, x] , [A0 → α • a β, y] ∈ q mit a ∈ T und x ∈ {a} . für einen Zustand q ∈ Q . Solche Zustände nennen wir jetzt LR(1)-ungeeignet 155 / 406 Der kanonische LR(1)-Automat Allgemein: Wir identifizieren zwei Konflikte: Reduce-Reduce-Konflikt: [A → γ •, x] , [A0 → γ 0 •, x] ∈ q mit A 6= A0 ∨ γ 6= γ 0 Shift-Reduce-Konflikt: [A → γ •, x] , [A0 → α • a β, y] ∈ q mit a ∈ T und x ∈ {a} k Firstk (β) k {y} . für einen Zustand q ∈ Q . Solche Zustände nennen wir jetzt LR(k)-ungeeignet 155 / 406 Spezielle LR(k)-Teilklassen Satz: Eine reduzierte kontextfreie Grammatik G ist genau dann LR(k) wenn der kanonische LR(k)-Automat LR(G, k) keine LR(k)-ungeeigneten Zustände besitzt. 156 / 406 Spezielle LR(k)-Teilklassen Satz: Eine reduzierte kontextfreie Grammatik G ist genau dann LR(k) wenn der kanonische LR(k)-Automat LR(G, k) keine LR(k)-ungeeigneten Zustände besitzt. Diskussion: Unser Beispiel ist offenbar LR(1) Im Allgemeinen hat der kanonische LR(k)-Automat sehr viel mehr Zustände als LR(G) = LR(G, 0) Man betrachtet darum i.a. Teilklassen von LR(k)-Grammatiken, bei denen man nur LR(G) benutzt ... 156 / 406 Spezielle LR(k)-Teilklassen Satz: Eine reduzierte kontextfreie Grammatik G ist genau dann LR(k) wenn der kanonische LR(k)-Automat LR(G, k) keine LR(k)-ungeeigneten Zustände besitzt. Diskussion: Unser Beispiel ist offenbar LR(1) Im Allgemeinen hat der kanonische LR(k)-Automat sehr viel mehr Zustände als LR(G) = LR(G, 0) Man betrachtet darum i.a. Teilklassen von LR(k)-Grammatiken, bei denen man nur LR(G) benutzt ... Zur Konflikt-Auflösung ordnet man den Items in den Zuständen Vorausschau-Mengen zu: 1 2 Die Zuordnung ist unabhängig vom Zustand Die Zuordnung hängt vom Zustand ab ==⇒ Simple LR(k) ==⇒ LALR(k) 156 / 406 Simple-LR(k) Idee 1: Benutze Followk -Mengen zur Konflikt-Lösung Reduce-Reduce-Konflikt: Falls für [A → γ •] , [A0 → γ 0 •] ∈ q mit A 6= A0 ∨ γ 6= γ 0 , Followk (A) ∩ Followk (A0 ) 6= ∅ Shift-Reduce-Konflikt: Falls für [A → γ •] , [A0 → α • a β] ∈ q mit a∈T, Followk (A) ∩ ({a} Firstk (β) Followk (A0 )) 6= ∅ für einen Zustand q ∈ Q. Dann nennen wir den Zustand q SLR(k)-ungeeignet 157 / 406 Simple-LR(k) Definition: Die reduzierte Grammatik G nennen wir SLR(k) (simple LR(k)) falls der kanonische LR(0)-Automat LR(G) keine SLR(k)-ungeeigneten Zustände enthält. 158 / 406 Simple-LR(k) Definition: Die reduzierte Grammatik G nennen wir SLR(k) (simple LR(k)) falls der kanonische LR(0)-Automat LR(G) keine SLR(k)-ungeeigneten Zustände enthält. ... im Beispiel: Bei unserer Beispiel-Grammatik treten Konflikte möglicherweise in den Zuständen q1 , q2 , q9 auf: q1 = {[S0 → E•], {[E → E • + T]} Follow1 (S0 ) ∩ {+} {. . .} = {} ∩ {+} = ∅ 158 / 406 Simple-LR(k) Definition: Die reduzierte Grammatik G nennen wir SLR(k) (simple LR(k)) falls der kanonische LR(0)-Automat LR(G) keine SLR(k)-ungeeigneten Zustände enthält. ... im Beispiel: Bei unserer Beispiel-Grammatik treten Konflikte möglicherweise in den Zuständen q1 , q2 , q9 auf: q1 = {[S0 → E•], {[E → E • + T]} Follow1 (S0 ) ∩ {+} {. . .} q2 = {[E → T•], {[T → T • ∗ F]} Follow1 (E) ∩ {∗} {. . .} = = q9 = {[E → E + T•], {[T → T • ∗ F]} Follow1 (E) ∩ {∗} {. . .} = {, +, ) } ∩ {∗} = ∅ = {} ∩ {+} = ∅ {, +, ) } ∩ {∗} ∅ 158 / 406 Lookahead-LR(k) Idee 2: Berechne Followk -Mengen für jedes Item Idee 2: – abhängig vom Zustand q Für [A → α • β] ∈ q definieren wir: Λk (q, [A → α • β]) = // {Firstk (w) | S0 →∗R γ A w ∧ δ(q0 , γ α) = q} ⊆ Followk (A) 159 / 406 Lookahead-LR(k) Idee 2: Berechne Followk -Mengen für jedes Item Idee 2: – abhängig vom Zustand q Für [A → α • β] ∈ q definieren wir: Λk (q, [A → α • β]) = // {Firstk (w) | S0 →∗R γ A w ∧ δ(q0 , γ α) = q} ⊆ Followk (A) Reduce-Reduce-Konflikt: [A → γ •] , [A0 → γ 0 •] ∈ q mit A 6= A0 ∨ γ 6= γ 0 wobei: Λk (q, [A → γ •]) ∩ Λk (q, [A0 → γ 0 •]) 6= ∅ 159 / 406 Lookahead-LR(k) Idee 2: Berechne Followk -Mengen für jedes Item Idee 2: – abhängig vom Zustand q Für [A → α • β] ∈ q definieren wir: Λk (q, [A → α • β]) = // {Firstk (w) | S0 →∗R γ A w ∧ δ(q0 , γ α) = q} ⊆ Followk (A) Reduce-Reduce-Konflikt: [A → γ •] , [A0 → γ 0 •] ∈ q mit A 6= A0 ∨ γ 6= γ 0 wobei: Λk (q, [A → γ •]) ∩ Λk (q, [A0 → γ 0 •]) 6= ∅ Shift-Reduce-Konflikt: [A → γ •] , [A0 → α • a β] ∈ q mit a∈T wobei: Λk (q, [A → γ •]) ∩ ({a} Firstk (β) Λk (q, [A0 → α • a β])) 6= ∅ Solche Zustände nennen wir jetzt LALR(k)-ungeeignet 159 / 406 Lookahead-LR(k) Definition: Die reduzierte Grammatik G nennen wir LALR(k), falls der kanonische LR(0)-Automat LR(G) keine LALR(k)-ungeeigneten Zustände enthält Bevor wir Beispiele betrachten, überlegen wir erst, wie die Mengen Λk (q, [A → α • β]) berechnet werden können 160 / 406 Lookahead-LR(k) Definition: Die reduzierte Grammatik G nennen wir LALR(k), falls der kanonische LR(0)-Automat LR(G) keine LALR(k)-ungeeigneten Zustände enthält Bevor wir Beispiele betrachten, überlegen wir erst, wie die Mengen Λk (q, [A → α • β]) berechnet werden können Idee: Stelle ein Ungleichungssystem für Λk auf ! Λk (q0 , [S0 → • S]) Λk (q, [A → α X • β]) Λk (q, [A → • γ]) ⊇ ⊇ ⊇ {} Λk (p, [A → α • X β]) Firstk (β) Λk (q, [B → α • A β]) falls falls δ(p, X) = q [B → α • A β] ∈ q 160 / 406 Lookahead-LR(k) im Beispiel: S → A → B → AbB | B a | bB A Der kanonische LR(0)-Automat hat dann die folgenden Zustände: q0 q1 = = δ(q0 , S) = {[S0 → • S], {[S → • A b B], {[A → • a], {[A → • b B], {[S → • B], {[B → • A]} q2 = δ(q0 , a) = {[A → a•]} q3 = δ(q0 , b) = {[A → b • B], {[B → • A], {[A → • a], {[A → • b B]} {[S0 → S•]} q4 = δ(q0 , B) = {[S → B•]} 161 / 406 Lookahead-LR(k) im Beispiel: q5 = δ(q0 , A) = {[S → A • b B], {[B → A•]} q6 = δ(q3 , A) = {[B → A•]} q7 = δ(q3 , B) = {[A → b B•]} Shift-Reduce-Konflikt: q8 = δ(q5 , b) = {[S → A b • B], {[B → • A], {[A → • a], {[A → • b B]} q9 = δ(q8 , B) = {[S → A b B•]} q5 = {[S → A • b B], {[B → A•]} Auflösung mit SLR(1) schlägt fehl: Follow1 (B) ∩ {b} {. . .} = {, b} ∩ {b} = 6 ∅ 162 / 406 Lookahead-LR(k) im Beispiel: 2 1 Konfliktzustand: q5 = {[S → A • b B], {[B → A•]} a S b 0 B b A a a A 7 b A 4 B 3 6 5 b 8 9 B Ausschnitt des Ungleichungssystems für Λ1 : Λ1 (q5 , [B → A•]) ⊇ Λ1 (q0 , [B → • A]) Λ1 (q0 , [B → • A]) Λ1 (q0 , [S → • B]) Λ1 (q0 , [S0 → • S]) ⊇ ⊇ ⊇ Λ1 (q0 , [S → • B]) Λ1 (q0 , [S0 → • S]) {} Auflösung mit LALR(1): Λ1 (q5 , [B → A•]) ∩ First1 (b) {. . .} = {} ∩ {b} {. . .} = ∅ 163 / 406 Spezielle Bottom-up Verfahren mit LR(G) Diskussion: Das Beispiel ist folglich nicht SLR(1), aber LALR(1) Das Beispiel ist nicht so an den Haaren herbei gezogen, wie es scheint ... Umbenennung: A⇒L B⇒R S L R a⇒id b⇒∗ / = liefert: → L=R | R → id | ∗ R → L ... d.h. ein Fragment der Grammatik für C-Ausdrücke 164 / 406 LALR(1) Für k = 1 lassen sich die Mengen Λk (q, [A → α • β]) wieder effizient berechnen Das verbesserte Ungleichungssystem für Λ1 : Λ1 (q0 , [S0 → • S]) ⊇ Λ1 (q, [A → α X • β]) ⊇ Λ1 (q, [A → • γ]) ⊇ Λ1 (q, [A → • γ]) ⊇ ==⇒ {} Λ1 (p, [A → α • X β]) F (Xj ) falls δ(p, X) = q falls [B → α • A X1 . . . Xm ] ∈ q und empty(X1 ) ∧ . . . ∧ empty(Xj−1 ) Λ1 (q, [B → α • A X1 . . . Xm ]) falls [B → α • A X1 . . . Xm ] ∈ q und empty(X1 ) ∧ . . . ∧ empty(Xm ) wieder ein reines Vereinigungsproblem ! 165 / 406 Syntaktische Analyse Kapitel 6: Zusammenfassung 166 / 406 Parsing Verfahren determinististische Sprachen = LR(1) = ... = LR(k) LALR(k) SLR(k) LR(0) reguläre Sprachen LL(1) LL(k) 167 / 406 Parsing Verfahren determinististische Sprachen = LR(1) = ... = LR(k) LALR(k) SLR(k) LR(0) reguläre Sprachen LL(1) LL(k) Diskussion: Alle kontextfreien Sprachen, die sich mit einem deterministischen Kellerautomaten parsen lassen, können durch eine LR(1)-Grammatik beschrieben werden. Durch LR(0)-Grammatiken lassen sich alle präfixfreien deterministisch kontextfreien Sprachen beschreiben Die Sprachklassen zu LL(k)-Grammatiken bilden dagegen eine Hierarchie innerhalb der deterministisch kontextfreien Sprachen. 168 / 406 Lexikalische und syntaktische Analyse: Prinzip von Spezifikation und Realisierung: 0 0 | [1-9][0-9]* Generator [1−9] [0−9] E→E{op}E Generator 169 / 406 Lexikalische und syntaktische Analyse: Vom Regulären Ausdruck zum Endlichen Automaten 012 34 f ∅ 2 34 f ∅ * 0 0 012 f 0 1 2 a 3 3 ∅ f 3 b a 0 a 34 34 f ∅ 34 f 11 f 012 a . 2 2 0 1 2 01 f 01 | a . 01 01 2 t | a 44 f ∅ 4 a 2 a b 3 a b b a 1 b 4 b Vom Endlichen Automaten zum Scanner 02 023 a w r i t e l n ( " H a l l o " ) ; b a 1 A 4 B ∅ 14 170 / 406 Lexikalische und syntaktische Analyse: Berechnung von Vorausschau-Mengen: F (S0 ) ⊇ F (E) ⊇ F (T) ⊇ F (E) F (T) F (F) F (E) ⊇ F (E) F (T) ⊇ F (T) F (F) ⊇ { ( , name, int} (, int, name a b c 3 a 0 S’ E T F 1 2 Vom Item-Kellerautomat zum LL(1)-Parser: S 0 i0 S 0 β0 i1 A 1 A 0 B 0 a b in A n i B β1 β δ Ausgabe γ M w ∈ First1 ( ) 171 / 406 Lexikalische und syntaktische Analyse: Vom charakteristischen zum kanonischen Automaten: S’ E E E+T E T T T∗F T F F (E) E E T T F S’ E E E +T E T T T ∗F T F F ( E) F int ( F int int + E E+ T T T∗ F T E+T F T T∗F int F (E ) ) F 3 int ( E ( T * 11 ( 5 T (E) 9 F F ( E + 4 F 0 T 6 int E int ∗ + 1 E 2 ) 8 * 7 F 10 Vom Shift-Reduce-Parser zum LR(1)-Parser: S i0 + 1 γ0 A1 i1 γ1 int 0 A m im ( γm B i α T β + 4 int F 3 F 5 T( 2 ( T 6 int E F 4’ ( int 3’ E F( 5’ T 2’ 9 T 6’ int int + * 11 F ) ( * * 11’ (8 E 9’ int action ) 8’ * 7 F 10 7’ F 10’ Ausgabe goto 172 / 406 Themengebiet: Die semantische Analyse 173 / 406 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn 174 / 406 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn der Compiler kann einige dieser sinnlosen Programme erkennen diese Programme werden als fehlerhaft zurückgewiesen Sprachdefinition definiert was fehlerhaft bedeutet 174 / 406 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn der Compiler kann einige dieser sinnlosen Programme erkennen diese Programme werden als fehlerhaft zurückgewiesen Sprachdefinition definiert was fehlerhaft bedeutet semantische Analysen sind hierzu erforderlich, die Bezeichner eindeutig machen; die Typen von Variablen ermitteln; 174 / 406 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn der Compiler kann einige dieser sinnlosen Programme erkennen diese Programme werden als fehlerhaft zurückgewiesen Sprachdefinition definiert was fehlerhaft bedeutet semantische Analysen sind hierzu erforderlich, die Bezeichner eindeutig machen; die Typen von Variablen ermitteln; semantische Analysen dienen auch dazu Möglichkeiten zur Programm-Optimierung zu finden; über möglicherweise fehlerhafte Programme zu warnen 174 / 406 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn der Compiler kann einige dieser sinnlosen Programme erkennen diese Programme werden als fehlerhaft zurückgewiesen Sprachdefinition definiert was fehlerhaft bedeutet semantische Analysen sind hierzu erforderlich, die Bezeichner eindeutig machen; die Typen von Variablen ermitteln; semantische Analysen dienen auch dazu Möglichkeiten zur Programm-Optimierung zu finden; über möglicherweise fehlerhafte Programme zu warnen ; semantische Analysen attributieren den Syntaxbaum 174 / 406 Die semantische Analyse Kapitel 1: Attributierte Grammatiken 175 / 406 Attributierte Grammatiken viele Berechnungen der semantischen Analyse als auch der Code-Generierung arbeiten auf dem Syntaxbaum. was an einem Knoten lokal berechnet wird, hängt nur von dem Typ des Knotens ab eine lokale Berechnung greift auf bereits berechnete Informationen von Nachbarknoten zu und berechnen daraus neue Informationen für den lokalen Knoten und andere Nachbarknoten damit die Eingabe-Werte eines Knotens bei der Berechnung bereits vorliegen, müssen die Knoten des Syntaxbaums in einer bestimmten Reihenfolge durchlaufen werden 176 / 406 Attributierte Grammatiken viele Berechnungen der semantischen Analyse als auch der Code-Generierung arbeiten auf dem Syntaxbaum. was an einem Knoten lokal berechnet wird, hängt nur von dem Typ des Knotens ab eine lokale Berechnung greift auf bereits berechnete Informationen von Nachbarknoten zu und berechnen daraus neue Informationen für den lokalen Knoten und andere Nachbarknoten damit die Eingabe-Werte eines Knotens bei der Berechnung bereits vorliegen, müssen die Knoten des Syntaxbaums in einer bestimmten Reihenfolge durchlaufen werden Definition Eine attributierte Grammatik ist eine CFG erweitert um: Attribute für jedes Nichtterminal; lokale Attribut-Gleichungen. 176 / 406 Beispiel: Berechnung des Prädikats empty[r] Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b): . . * | 0 a 2 1 b | a 3 a 4 b 177 / 406 Beispiel: Berechnung des Prädikats empty[r] Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b): . . * f | f 0 2 f a 1 b a | f 3 f a 4 b 177 / 406 Beispiel: Berechnung des Prädikats empty[r] Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b): . . * f f | f 0 2 f a 1 b f a | f 3 f a 4 b 177 / 406 Beispiel: Berechnung des Prädikats empty[r] Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b): . t f * . f f | f 0 2 f a 1 b f a | f 3 f a 4 b 177 / 406 Beispiel: Berechnung des Prädikats empty[r] Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b): f . t f * . f f | f 0 2 f a 1 b f a | f 3 f a 4 b ; Werte von empty[r] werden von unten nach oben berechnet. 177 / 406 Idee zur Implementierung für jeden Knoten führen wir ein Attribut empty ein. die Attribute werden in einer depth-first post-order Traversierung berechnet: an einem Blatt lässt sich der Wert des Attributs unmittelbar ermitteln das Attribut an einem inneren Knoten hängt nur von den Attributen der Nachfolger ab das empty ist ein synthetisches Attribut 178 / 406 Idee zur Implementierung für jeden Knoten führen wir ein Attribut empty ein. die Attribute werden in einer depth-first post-order Traversierung berechnet: an einem Blatt lässt sich der Wert des Attributs unmittelbar ermitteln das Attribut an einem inneren Knoten hängt nur von den Attributen der Nachfolger ab das empty ist ein synthetisches Attribut Im Allgemeinen: Definition Ein Attribut ist synthetisch: Wert wird von den Blättern zur Wurzel propagiert inherit: Wert wird von der Wurzel zu den Blättern propagiert 178 / 406 Berechnungsregeln für empty Wie das Attribut lokal zu berechnen ist, ergibt sich aus dem Typ des Knotens: für Blätter: r ≡ andernfalls: i x definieren wir empty[r1 | r2 ] = empty[r1 · r2 ] = empty[r1∗ ] = empty[r1 ?] = empty[r] = (x ≡ ). empty[r1 ] ∨ empty[r2 ] empty[r1 ] ∧ empty[r2 ] t t 179 / 406 Spezifizierung von allgemeinen Attributsystemen Das empty Attributs ist rein synthetisch, daher kann es durch einfache strukturelle Induktion definiert werden. wir benötigen einen einfachen und flexiblen Mechanismus, mit dem wir über allgemeine Attribute an einem Knoten und seinen Nachfolgern reden können. der Einfachheit halber benutzen wir fortlaufende Indizes: empty[0] : empty[i] : das Attribut des aktuellen Knotens das Attribut des i-ten Sohns (i > 0) 180 / 406 Spezifizierung von allgemeinen Attributsystemen Das empty Attributs ist rein synthetisch, daher kann es durch einfache strukturelle Induktion definiert werden. wir benötigen einen einfachen und flexiblen Mechanismus, mit dem wir über allgemeine Attribute an einem Knoten und seinen Nachfolgern reden können. der Einfachheit halber benutzen wir fortlaufende Indizes: empty[0] : empty[i] : das Attribut des aktuellen Knotens das Attribut des i-ten Sohns (i > 0) ... im Beispiel: x | · ∗ ? : : : : : empty[0] empty[0] empty[0] empty[0] empty[0] := := := := := (x ≡ ) empty[1] ∨ empty[2] empty[1] ∧ empty[2] t t 180 / 406 Beobachtungen: Die lokalen Berechnungen der Attributwerte müssen von einem globalen Algorithmus zusammen gesetzt werden Dazu benötigen wir: 1 2 eine Besuchsreihenfolge der Knoten des Baums; eine lokale Berechnungsreihenfolgen Die Auswertungsstrategie muss mit den Attribut-Abhängigkeiten kompatibel sein 181 / 406 Beobachtungen: Die lokalen Berechnungen der Attributwerte müssen von einem globalen Algorithmus zusammen gesetzt werden Dazu benötigen wir: 1 2 eine Besuchsreihenfolge der Knoten des Baums; eine lokale Berechnungsreihenfolgen Die Auswertungsstrategie muss mit den Attribut-Abhängigkeiten kompatibel sein Wir geben Abhängigkeiten als gerichtete Kanten an: empty empty | empty ; Pfeil zeigt in die Richtung des Informationsflusses 181 / 406 Beobachtung: Zur Ermittlung einer Auswertungsstrategie reicht es nicht, sich die lokalen Attribut-Abhängigkeiten anzusehen. Es kommt auch darauf an, wie sie sich global zu einem Abhängigkeitsgraphen zusammen setzen. Im Beispiel sind die Abhängigkeiten stets von den Attributen der Söhne zu den Attributen des Vaters gerichtet. ; post-order depth-first Traversierung möglich Die Variablen-Abhängigkeiten können aber auch komplizierter sein. 182 / 406 Simultane Berechnung von mehreren Attributen Berechne empty, first, next von regulären Ausdrücken: x empty[0] := (x ≡ ) first[0] := {x | x 6= } // (keine Gleichung für next ) : root: : f e empty[0] := first[0] := next[0] := next[1] := x empty[1] first[1] ∅ next[0] f e f e root n n n 183 / 406 RE Auswertung: Regeln für Alternative | f f empty[1] ∨ empty[2] first[1] ∪ first[2] next[0] next[0] empty[0] := first[0] := next[1] := next[2] := : e | e n f n e n 184 / 406 RE Auswertung: Regeln für Konkatenation · empty[0] := empty[1] ∧ empty[2] first[0] := first[1] ∪ (empty[1] ? first[2] : ∅) next[1] := first[2] ∪ (empty[2] ? next[0]: ∅) next[2] := next[0] : f f e . e n f n e n 185 / 406 RE Auswertung: Regeln für Kleene-Stern und ‘?’ f e f e ∗ : empty[0] := t first[0] := first[1] next[1] := first[1] ∪ next[0] ? : empty[0] := t first[0] := first[1] next[1] := next[0] * n f e n f e ? n n 186 / 406 Problem bei allgemeinen Attributen Eine Auswertungsstrategie kann es nur dann geben, wenn die Variablen-Abhängigkeiten in jedem attributierten Baum azyklisch sind Es ist DEXPTIME-vollständig, herauszufinden, ob keine zyklischen Variablenabhängigkeiten vorkommen können [Jazayeri, Odgen, Rounds, 1975] Beispiel: Grammatik S → a | b; Attributenabhängigkeiten: h h h i a j i k S j j k h i b j k 187 / 406 Berechnung der Abhängigkeiten Betrachte Abhängigkeiten in jedem möglichen Baum: h h S j i a j k h h S j i b j k Idee: Berechne Abhänigkeitsgraphen für jedes Symbol T ∪ N. Initialisiere S(s) = ∅ für s ∈ N und setze S(s) = {G} wobei G der Abhängigkeitsgraph von s ∈ T ist. Für jede Regel eines Nicht-Terminals N mit RHS s1 . . . sp , erweitere S(N) durch Graphen die entstehen, wenn die Kanten von S(s1 ), . . . S(sp ) in die Kinderattribute eingesetzt werden. Enthält keiner der berechneten Graphen einen Zyklus, so kann jeder Ableitungsbaum berechnet werden. 188 / 406 Stark azyklische Attributierung Idee: Berechne einen Abhängigkeitsgraphen für jedes Symbol. Beobachtung: Die Relation → wird zur partiellen Ordnung. Wir starten mit der trivialen Ordung v = → Die aktuelle Ordnung setzen wir an den Sohn-Knoten in die lokalen Abhängigkeitsgraphen ein. Ergibt sich ein Kreis, geben wir auf. Andernfalls fügen wir alle Beziehungen a v b hinzu, für die es jetzt einen Pfad von a[0] nach b[0] gibt. Lässt sich v nicht mehr vergrößern, hören wir auf. Im Beispiel: h h i S j j k 189 / 406 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 190 / 406 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 1 Der Benutzer soll die Strategie spezifizieren 190 / 406 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 1 Der Benutzer soll die Strategie spezifizieren 2 Berechne eine Strategie anhand der Abhängigkeiten Berechne eine linear Ordnung aus der partielle Ordnung → bzw. v. Werte die Attribute anhand dieser linearen Ordnung aus. Die lokalen Abhängigkeitsgraphen zusammen mit der linearen Ordnung erlauben die Berechnung einer Strategie. n f e 190 / 406 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 1 Der Benutzer soll die Strategie spezifizieren 2 Berechne eine Strategie anhand der Abhängigkeiten Berechne eine linear Ordnung aus der partielle Ordnung → bzw. v. Werte die Attribute anhand dieser linearen Ordnung aus. Die lokalen Abhängigkeitsgraphen zusammen mit der linearen Ordnung erlauben die Berechnung einer Strategie. n f e 3 Betrachte fixe Strategie und erlaube nur entsprechende Attribute Frage: Wie berechnet man eine sinnvolle Linearisierung? 190 / 406 Linearisierung der Abhängigkeitsordnung Mögliche automatische Strategien: 191 / 406 Linearisierung der Abhängigkeitsordnung Mögliche automatische Strategien: 1 Bedarfsgetriebene Auswertung Beginne mit der Berechnung eines Attributs. Sind die Argument-Attribute noch nicht berechnet, berechne rekursiv deren Werte Besuche die Knoten des Baum nach Bedarf 191 / 406 Linearisierung der Abhängigkeitsordnung Mögliche automatische Strategien: 1 Bedarfsgetriebene Auswertung Beginne mit der Berechnung eines Attributs. Sind die Argument-Attribute noch nicht berechnet, berechne rekursiv deren Werte Besuche die Knoten des Baum nach Bedarf 2 Auswertung in Pässen: Minimiere die Anzahl der Besuche an jedem Knoten. Organisiere die Auswertung in Durchläufen durch den Baum. Berechne für jeden Durchlauf eine Besuchsstrategie für die Knoten zusammen mit einer lokalen Strategie für jeden Knoten-Typ Betrachte Beispiel für bedarfsgetriebene Auswertung 191 / 406 Beispiel bedarfsgetriebene Auswertung Berechne next der Blätter von ((a|b)∗ a(a|b)): | : next[1] := next[0] next[2] := next[0] · : next[1] := first[2] ∪ (empty[2] ? next[0]: ∅) next[2] := next[0] . . * | a 0 a b 1 | 2 a 3 b 4 192 / 406 Beispiel bedarfsgetriebene Auswertung Berechne next der Blätter von ((a|b)∗ a(a|b)): | : next[1] := next[0] next[2] := next[0] · : next[1] := first[2] ∪ (empty[2] ? next[0]: ∅) next[2] := next[0] . n . * | a 0 a b 1 n | 2 a n 3 n b n 4 192 / 406 Beispiel bedarfsgetriebene Auswertung Berechne next der Blätter von ((a|b)∗ a(a|b)): | : next[1] := next[0] next[2] := next[0] · : next[1] := first[2] ∪ (empty[2] ? next[0]: ∅) next[2] := next[0] . n . * | a 0 a b n e f 2 e f 1 n a n 3 | n e f b n 4 192 / 406 Beispiel bedarfsgetriebene Auswertung Berechne next der Blätter von ((a|b)∗ a(a|b)): | : next[1] := next[0] next[2] := next[0] · : next[1] := first[2] ∪ (empty[2] ? next[0]: ∅) next[2] := next[0] . n . * | a 0 a b n e f 2 e f 1 n a n 3 | n e f b n 4 192 / 406 Bedarfsgetriebene Auswertung Diskussion: Die Reihenfolge hängt i.a. vom zu attributierenden Baum ab. Der Algorithmus muss sich merken, welche Attribute er bereits berechnete Der Algorithmus besucht manche Knoten unnötig oft. Der Algorithmus ist nicht-lokal 193 / 406 Bedarfsgetriebene Auswertung Diskussion: Die Reihenfolge hängt i.a. vom zu attributierenden Baum ab. Der Algorithmus muss sich merken, welche Attribute er bereits berechnete Der Algorithmus besucht manche Knoten unnötig oft. Der Algorithmus ist nicht-lokal Ansatz nur im Prinzip günstig: Berechnung der Berechnungsstrategie aufwendig Berechnung der Attribute oft billiger Reihenfolge der Attributberechnung oft dem Programmierer klar 193 / 406 Diskussion Annahme: Jedes Attribut ist entweder inherit oder synthetisch. Dann gilt: ein Attribut eines Knotens in einer stark azyklische Attributierungen lässt sich stets in einem Durchlauf berechnen man braucht folglich für stark azyklische Attributierungen maximal so viele Pässe, wie es Attribute gibt hat man einen Baum-Durchlauf zur Berechnung eines Attributes, kann man überprüfen, ob er geeignet ist, gleichzeitig weitere Attribute auszuwerten ; Optimierungsproblem 194 / 406 Diskussion Annahme: Jedes Attribut ist entweder inherit oder synthetisch. Dann gilt: ein Attribut eines Knotens in einer stark azyklische Attributierungen lässt sich stets in einem Durchlauf berechnen man braucht folglich für stark azyklische Attributierungen maximal so viele Pässe, wie es Attribute gibt hat man einen Baum-Durchlauf zur Berechnung eines Attributes, kann man überprüfen, ob er geeignet ist, gleichzeitig weitere Attribute auszuwerten ; Optimierungsproblem ... im Beispiel: empty und first lassen sich gemeinsam berechnen. next muss in einem weiteren Pass berechnet werden ; lasse den Benutzer festlegen, wie Attribute ausgewertet werden 194 / 406 Implementierung der lokalen Auswertung: Pre-Order vs. Post-Order Betrachte Beispiel: Nummerierung der Blätter eines Baums: . . * | a 0 a b 1 | 2 a 3 b 4 195 / 406 Praktische Implementierung der Nummerierung Idee: Führe Hilfsattribute pre und post ein mit pre reichen wir einen Zählerstand nach unten (inherites Attribut) mit post reichen wir einen Zählerstand wieder nach oben (synthetisches Attribut) Root: pre[0] pre[1] post[0] := 0 := pre[0] := post[1] Node: pre[1] pre[2] post[0] := pre[0] := post[1] := post[2] Leaf: post[0] := pre[0] + 1 196 / 406 Die lokalen Attribut-Abhängigkeiten: pre pre post post pre pre post post die Attributierung ist offenbar stark azyklisch 197 / 406 Die lokalen Attribut-Abhängigkeiten: pre pre post post pre pre post post die Attributierung ist offenbar stark azyklisch ein innerer Knoten berechnet inherite Attribute bevor in einen Kindknoten abgestiegen wird (pre-order traversal) synthetische Attribute nach der Rückkehr von einem Kindknoten (post-order traversal) 197 / 406 Die lokalen Attribut-Abhängigkeiten: pre pre post post pre pre post post die Attributierung ist offenbar stark azyklisch ein innerer Knoten berechnet inherite Attribute bevor in einen Kindknoten abgestiegen wird (pre-order traversal) synthetische Attribute nach der Rückkehr von einem Kindknoten (post-order traversal) man kann alle Attribute in einer depth-first Traversierung von links nach rechts auswerten (mit pre- und post-order Berechnung) So etwas nennen wir L-Attributierung. 197 / 406 L-Attributierung Definition Ein Knoten mit Attributen A ist L-attributiert wenn jedes Attribut ai [n] nur von aj [m] abhängt wobei m < n und ai , aj ∈ A. 198 / 406 L-Attributierung Definition Ein Knoten mit Attributen A ist L-attributiert wenn jedes Attribut ai [n] nur von aj [m] abhängt wobei m < n und ai , aj ∈ A. Idee: partitioniere alle Attribute A = A1 ∪ . . . ∪ An so dass für alle Attribute in Ai jeder Knoten L-attributiert ist für jede Attributmenge Ai führe eine depth-first Traversierung durch ; Bestimme Attribute mit Hinblick auf wenige Traversierungen 198 / 406 Praktische Benutzung Symboltabellen, Typ-Überprüfung / Inferenz und (einfache) Codegenerierung können durch Attributierung berechnet werden In diesen Anwendungen werden stets Syntaxbäume annotiert. Die Knotenbeschriftungen entsprechen den Regeln einer kontextfreien Grammatik Knoten können in Typen eingeteilt werden — entsprechend den Nichtterminalen auf der linken Seite Unterschiedliche Nichtterminale benötigen evt. unterschiedliche Mengen von Attributen. 199 / 406 Praktische Implementierung In objekt-orientierten Sprachen benutze visitor pattern: Klasse mit Methode für jedes Nicht-Terminal der Grammatik public abstract class Regex { public abstract Regex accept(RegexVisitor v); } Durch Überschreiben der folgenden Methoden wird eine Attribut-spezifische Auswertung implementiert public interface RegexVisitor { public abstract boolean preLeft(OrEx orEx); public abstract boolean preRight(OrEx orEx); public abstract Regex post(OrEx orEx); ... public abstract Regex visit(Const const1); } Vordefiniert ist die Traversierung des Ableitungsbaumes public class OrEx extends Regex { public Regex accept(RegexVisitor v) { ... return v.post(this); } } 200 / 406 Ausblick In der Übung: implementiere visitor pattern für C Grammatik prüfe, ob jeder Bezeichner deklariert wurde 201 / 406 Die semantische Analyse Kapitel 2: Symboltabellen 202 / 406 Symboltabellen Betrachte folgenden Java Kode: void foo() { int A; void bar() { double A; A = 0.5; write(A); } A = 2; bar(); write(A); } innerhalb des Rumpfs von bar wird die Definition von A durch die lokale Definition verdeckt für die Code-Erzeugung benötigen wir für jede Benutzung eines Bezeichners die zugehörige Definitionsstelle statische Bindung bedeutet, dass die Definition eines Namens A an allen Programmpunkten innerhalb des gesamten Blocks gültig ist. sichtbar ist sie aber nur in denjenigen Teilbereichen, in denen keine weitere Definition von A gültig ist 203 / 406 Gültigkeitsbereiche von Bezeichnern void foo() { int A; void bar() { double A; A = 0.5; write(A); } A = 2; bar(); write(A); Gültigkeitsbereich von int A } 204 / 406 Gültigkeitsbereiche von Bezeichnern void foo() { int A; void bar() { double A; A = 0.5; write(A); Gültigkeitsbereich von double A } A = 2; bar(); write(A); } 204 / 406 Gültigkeitsbereiche von Bezeichnern void foo() { int A; void bar() { double A; A = 0.5; write(A); Gültigkeitsbereich von double A } A = 2; bar(); write(A); } Verwaltungs von Bezeichnern kann kompliziert werden... 204 / 406 Sichtbarkeitsregeln in objektorientierten Prog.spr. 1 2 3 4 5 6 7 8 9 10 public class Foo { protected int x = 17; protected int y = 5; private int z = 42; public int b() { return 1; } } class Bar extends Foo { protected double y = 0.5; public int b(int a) { return a; } } Beobachtung: 205 / 406 Sichtbarkeitsregeln in objektorientierten Prog.spr. 1 2 3 4 5 6 7 8 9 10 public class Foo { protected int x = 17; protected int y = 5; private int z = 42; public int b() { return 1; } } class Bar extends Foo { protected double y = 0.5; public int b(int a) { return a; } } Beobachtung: private Members sind nur innerhalb der aktuellen Klasse gültig protected Members sind innerhalb der Klasse, in den Unterklassen sowie innerhalb des gesamten package als Foo::x gültig Methoden b gleichen Namens sind stets verschieden, wenn ihre Argument-Typen verschieden sind ; overloading 205 / 406 Dynamische Auflösung von Funktionen public class Foo { protcted int foo() { return 1; } } class Bar extends Foo { protected int foo() { return 2; } public int test(boolean b) { Foo x = (b) ? new Foo() : new Bar(); return x.foo(); } } Beobachtungen: 206 / 406 Dynamische Auflösung von Funktionen public class Foo { protcted int foo() { return 1; } } class Bar extends Foo { protected int foo() { return 2; } public int test(boolean b) { Foo x = (b) ? new Foo() : new Bar(); return x.foo(); } } Beobachtungen: der Typ von x ist Foo oder Bar, je nach Wert von b x.foo() ruft entweder foo in Zeile 2 oder Zeile 5 auf 206 / 406 Dynamische Auflösung von Funktionen public class Foo { protcted int foo() { return 1; } } class Bar extends Foo { protected int foo() { return 2; } public int test(boolean b) { Foo x = (b) ? new Foo() : new Bar(); return x.foo(); } } Beobachtungen: der Typ von x ist Foo oder Bar, je nach Wert von b x.foo() ruft entweder foo in Zeile 2 oder Zeile 5 auf Entscheidung wird zur Laufzeit gefällt (hat nichts mit Namensauflösung zu tun) 206 / 406 Überprüfung von Bezeichnern Aufgabe: Finde zu jeder Benutzung eines Bezeichners die zugehörige Deklaration Idee: 1 ersetze Bezeichner durch eindeutige Nummern 2 finde zu jedem Benutzung eines Bezeichners die Deklaration 207 / 406 Überprüfung von Bezeichnern Aufgabe: Finde zu jeder Benutzung eines Bezeichners die zugehörige Deklaration Idee: 1 ersetze Bezeichner durch eindeutige Nummern das Vergleichen von Nummern ist schneller das Ersetzen von gleichen Namen durch eine Nummer spart Speicher 2 finde zu jedem Benutzung eines Bezeichners die Deklaration 207 / 406 Überprüfung von Bezeichnern Aufgabe: Finde zu jeder Benutzung eines Bezeichners die zugehörige Deklaration Idee: 1 ersetze Bezeichner durch eindeutige Nummern das Vergleichen von Nummern ist schneller das Ersetzen von gleichen Namen durch eine Nummer spart Speicher 2 finde zu jedem Benutzung eines Bezeichners die Deklaration für jede Deklaration eines Bezeichners muss zur Laufzeit des Programs Speicher reserviert werden bei Programmiersprachen ohne explizite Deklaration wird implizit eine Deklaration bei der ersten Benutzung eines Bezeichners erzeugt 207 / 406 (1) Ersetze Bezeichner durch eindeutige Namen Idee für Algorithmus: Eingabe: Folge von Strings Ausgabe: 1 Folge von Nummern 2 Tabelle, die zu Nummern die Strings auflistet Wende diesen Algorithmus im Scanner auf jeden Bezeichner an. 208 / 406 Beispiel für die Anwendung des Algorithmus Eingabe: das das Ausgabe: 0 schwein schwein 1 ist dem dem 1 schwein menschen 2 3 4 0 1 2 3 4 5 6 das schwein ist dem was menschen wurst 0 1 3 ist 5 was wurst 2 6 und 209 / 406 Spezifikation der Implementierung Idee: benutze eine partielle Abbildung: S : String→int verwalte einen Zähler int count = 0; für die Anzahl der bereits gefundenen Wörter Damit definieren wir eine Funktion int getIndex(String w): int getIndex(String w) { if (S (w) ≡ undefined) { S = S ⊕ {w 7→ count}; return count++; else return S (w); } 210 / 406 Datenstrukturen für partielle Abbildungen Ideen: Liste von Paaren (w, i) ∈ String × int : O(1) O(n) ; zu teuer 211 / 406 Datenstrukturen für partielle Abbildungen Ideen: Liste von Paaren (w, i) ∈ String × int : O(1) O(n) ; zu teuer balancierte Bäume : O(log(n)) O(log(n)) ; zu teuer 211 / 406 Datenstrukturen für partielle Abbildungen Ideen: Liste von Paaren (w, i) ∈ String × int : O(1) O(n) ; zu teuer balancierte Bäume : O(log(n)) O(log(n)) Hash Tables : O(1) O(1) ; zu teuer zumindest im Mittel 211 / 406 Datenstrukturen für partielle Abbildungen Ideen: Liste von Paaren (w, i) ∈ String × int : O(1) O(n) ; zu teuer balancierte Bäume : O(log(n)) O(log(n)) Hash Tables : O(1) O(1) ; zu teuer zumindest im Mittel Caveat: In alten Compilern war der Scanner das langsamste Glied in der Kette. Heute der Zeitverbrauch des Scanners vernachlässigbar. 211 / 406 Implementierung mit Hash-Tabellen lege ein Feld M von hinreichender Größe m an wähle eine Hash-Funktion H : String → [0, m − 1] mit den Eigenschaften: H(w) ist leicht zu berechnen H streut die vorkommenden Wörter gleichmäßig über [0, m − 1] Mögliche Wahlen: = (x0 + xr−1 ) % m Pr−1 = ( i=0 xi · pi ) % m = (x0 + p · (x1 + p · (. . . + p · xr−1 · · · ))) % m für eine Primzahl p (z.B. 31) Das Argument-Wert-Paar (w, i) legen wir dann in M[H(w)] ab 212 / 406 Berechnung einer Hash-Tabelle am Beispiel Mit m=7 und H0 erhalten wir: 0 1 2 3 4 5 6 schwein 1 menschen 5 was 4 ist 2 wurst 6 das 0 dem 3 Um den Wert des Worts w zu finden, müssen wir w mit allen Worten x vergleichen, für die H(w) = H(x) 213 / 406 Überprüfung von Bezeichnern: (2) Symboltabellen Überprüfe die korrekte Benutzung von Bezeichnern: Durchmustere den Syntaxbaum in einer geeigneten Reihenfolge, die jede Definition vor ihren Benutzungen besucht die jeweils aktuell sichtbare Definition zuletzt besucht für jeden Bezeichner verwaltet man einen Keller der gültigen Definitionen trifft man bei der Durchmusterung auf eine Deklaration eines Bezeichners, schiebt man sie auf den Keller verlässt man den Gültigkeitsbereich, muss man sie wieder vom Keller werfen trifft man bei der Durchmusterung auf eine Benutzung, schlägt man die letzte Deklaration auf dem Keller nach findet man keine Deklaration, haben wir einen Fehler gefunden 214 / 406 Beispiel: Keller von Symboltabellen { int a, b; b = 5; if (b>3) { int a, c; a = 3; c = a + 1; b = c; } else { int c; c = a + 1; b = c; } b = a + b; Abbildung von Namen auf Zahlen: 0 1 2 a b c } 215 / 406 Der zugehörige Syntaxbaum d Deklaration b Basis-Block a Zuweisung b b d b b d 0 int a b 1 int if = b > 1 5 b b b 1 3 d b d a 2 int a d 0 int 2 int = = 1 1 2 2 216 / 406 Der zugehörige Syntaxbaum d Deklaration b Basis-Block a Zuweisung b b d b b d 0 int a b 1 int if = b > 1 5 b b b 1 3 d b d a 2 int a d 0 int 2 int = = 1 1 2 2 216 / 406 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen 217 / 406 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen Benutzt man eine Listen-Implementierung der Symboltabelle und verwaltet Marker für Blöcke, kann man auf das Entfernen von Deklarationen am Ende eines Blockes verzichten a b vor if-Anweisung 217 / 406 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen Benutzt man eine Listen-Implementierung der Symboltabelle und verwaltet Marker für Blöcke, kann man auf das Entfernen von Deklarationen am Ende eines Blockes verzichten a b a c a b vor if-Anweisung then-Zweig 217 / 406 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen Benutzt man eine Listen-Implementierung der Symboltabelle und verwaltet Marker für Blöcke, kann man auf das Entfernen von Deklarationen am Ende eines Blockes verzichten a b a c a b c a b vor if-Anweisung then-Zweig else-Zweig 217 / 406 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen Benutzt man eine Listen-Implementierung der Symboltabelle und verwaltet Marker für Blöcke, kann man auf das Entfernen von Deklarationen am Ende eines Blockes verzichten a b a c a b c a b vor if-Anweisung then-Zweig else-Zweig Anstelle erst die Namen durch Nummern zu ersetzen und dann die Zuordnung von Benutzungen zu Definitionen vorzunehmen, kann man auch gleich eindeutige Nummern vergeben Anstatt ein eindeutige Nummern zu vergeben wird in der Praxis auch gerne ein Zeiger zum Deklarationsknoten gespeichert 217 / 406 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. 218 / 406 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. weise jedem Block eine eindeutige Nummer zu speichere für jede Deklaration auch die Nummer des Blocks, zu der sie gehört 218 / 406 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. weise jedem Block eine eindeutige Nummer zu speichere für jede Deklaration auch die Nummer des Blocks, zu der sie gehört gibt es eine weitere Deklaration des gleichen Namens mit derselben Blocknummer, muss ein Fehler gemeldet werden 218 / 406 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. weise jedem Block eine eindeutige Nummer zu speichere für jede Deklaration auch die Nummer des Blocks, zu der sie gehört gibt es eine weitere Deklaration des gleichen Namens mit derselben Blocknummer, muss ein Fehler gemeldet werden Wie zuvor können eindeutige Block Nummern auch durch Zeiger auf die Blöcke implementiert werden. 218 / 406 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. weise jedem Block eine eindeutige Nummer zu speichere für jede Deklaration auch die Nummer des Blocks, zu der sie gehört gibt es eine weitere Deklaration des gleichen Namens mit derselben Blocknummer, muss ein Fehler gemeldet werden Wie zuvor können eindeutige Block Nummern auch durch Zeiger auf die Blöcke implementiert werden. Um rekursive Datenstrukturen zu ermöglichen erlauben Sprachen Vorwärtsdeklarationen: struct list0 { int info; struct list1* next; } struct list1 { int info; struct list0* next; } 218 / 406 Deklaration von Funktionsnamen Auf gleiche Weise müssen auch Funktionsnamen verwaltet werden. bei einer rekursiven Funktion muss der Name vor der Durchmusterung des Rumpfes in die Tabelle eingetragen werden int fac(int i) { return i*fac(i-1); } 219 / 406 Deklaration von Funktionsnamen Auf gleiche Weise müssen auch Funktionsnamen verwaltet werden. bei einer rekursiven Funktion muss der Name vor der Durchmusterung des Rumpfes in die Tabelle eingetragen werden int fac(int i) { return i*fac(i-1); } bei wechselseitig rekursiven Funktionsdefinitionen gilt dies für alle Funktionsnamen. Beispiel in ML und C: fun | | and | | odd 0 = false odd 1 = true odd x = even (x-1) even 0 = true even 1 = false even x = odd (x-1) int even(int x); int odd(int x) { return (x==0 ? 0 : (x==1 ? 1 : odd(x-1))); } int even(int x) { return (x==0 ? 1 : (x==1 ? 0 : even(x-1))); } 219 / 406 Überladung von Namen Ähnliche Abhängigkeiten gelten für objekt-orientierte Sprachen: bei objekt-orientierter Sprache mit Vererbung zwischen Klassen sollte die übergeordnete Klasse vor der Unterklasse besucht werden 220 / 406 Überladung von Namen Ähnliche Abhängigkeiten gelten für objekt-orientierte Sprachen: bei objekt-orientierter Sprache mit Vererbung zwischen Klassen sollte die übergeordnete Klasse vor der Unterklasse besucht werden bei Überladung muss simultan die Signatur verglichen werden eventuell muss eine Typüberprüfung vorgenommen werden 220 / 406 Überladung von Namen Ähnliche Abhängigkeiten gelten für objekt-orientierte Sprachen: bei objekt-orientierter Sprache mit Vererbung zwischen Klassen sollte die übergeordnete Klasse vor der Unterklasse besucht werden bei Überladung muss simultan die Signatur verglichen werden eventuell muss eine Typüberprüfung vorgenommen werden Sobald Namen von Bezeichnern aufgelöst sind, kann die semantische Analyse fortsetzen mit der Typprüfung (oder Typinferenz, siehe Vorlesung „Programmiersprachen”). 220 / 406 Mehrere Sorten von Bezeichnern Einige Programmiersprachen unterscheiden verschiedene Bezeichner: C: Variablennamen und Typnamen Java: Klassen, Methoden, Felder Haskell: Typnamen, Variablen, Operatoren mit Prioritäten 221 / 406 Mehrere Sorten von Bezeichnern Einige Programmiersprachen unterscheiden verschiedene Bezeichner: C: Variablennamen und Typnamen Java: Klassen, Methoden, Felder Haskell: Typnamen, Variablen, Operatoren mit Prioritäten In einigen Fällen verändern Deklarationen in der Sprache die Bedeutung eines Bezeichners: Der Parser informiert den Scanner über eine neue Deklaration Der Scanner generiert verschiedene Token für einen Bezeichner Der Parser generiert einen Syntaxbaum, der von den bisherigen Deklarationen abhängt 221 / 406 Mehrere Sorten von Bezeichnern Einige Programmiersprachen unterscheiden verschiedene Bezeichner: C: Variablennamen und Typnamen Java: Klassen, Methoden, Felder Haskell: Typnamen, Variablen, Operatoren mit Prioritäten In einigen Fällen verändern Deklarationen in der Sprache die Bedeutung eines Bezeichners: Der Parser informiert den Scanner über eine neue Deklaration Der Scanner generiert verschiedene Token für einen Bezeichner Der Parser generiert einen Syntaxbaum, der von den bisherigen Deklarationen abhängt Interaktion zwischen Parser und Scanner: problematisch! 221 / 406 Fixity-Deklaration in Haskell Haskell erlaubt beliebige binäre Operatoren aus (?!^&|=+-_*/)+ . In Haskell Standard-Bibliothek: infixr infixl infixl infix 8 7 6 4 ^ *,/ +,==,/= Grammatik ist generisch: Exp0 → Exp0 LOp0 Exp1 | Exp1 ROp0 Exp0 | Exp1 Op0 Exp1 | Exp1 .. . Exp9 Exp → | | | → | Exp9 LOp9 Exp Exp ROp9 Exp9 Exp Op9 Exp Exp ident | num ( Exp0 ) 222 / 406 Fixity-Deklaration in Haskell Haskell erlaubt beliebige binäre Operatoren aus (?!^&|=+-_*/)+ . In Haskell Standard-Bibliothek: infixr infixl infixl infix 8 7 6 4 ^ *,/ +,==,/= Grammatik ist generisch: Exp0 → Exp0 LOp0 Exp1 | Exp1 ROp0 Exp0 | Exp1 Op0 Exp1 | Exp1 .. . Exp9 Exp → | | | → | Exp9 LOp9 Exp Exp ROp9 Exp9 Exp Op9 Exp Exp ident | num ( Exp0 ) Parser trägt Deklarationen in Tabelle ein Scanner erzeugt: Operator - wird zum Token LOp6 . Operator * wird zum Token LOp7 . Operator == wird zum Token Op4 . etc. Parser erkennt 3-4*5-6 als (3-(4*5))-6 222 / 406 Fixity-Deklarationen in Haskell: Beobachtungen Nicht ohne Probleme: Scanner hat einen Zustand, der vom Parser bestimmt wird ; nicht mehr kontextfrei, braucht globale Datenstruktur 223 / 406 Fixity-Deklarationen in Haskell: Beobachtungen Nicht ohne Probleme: Scanner hat einen Zustand, der vom Parser bestimmt wird ; nicht mehr kontextfrei, braucht globale Datenstruktur ein Stück Kode kann mehrere Semantiken haben syntaktische Korrektheit hängt evtl. von importierten Modulen ab 223 / 406 Fixity-Deklarationen in Haskell: Beobachtungen Nicht ohne Probleme: Scanner hat einen Zustand, der vom Parser bestimmt wird ; nicht mehr kontextfrei, braucht globale Datenstruktur ein Stück Kode kann mehrere Semantiken haben syntaktische Korrektheit hängt evtl. von importierten Modulen ab Fehlermeldungen des Parsers schwer verständlich 223 / 406 Fixity-Deklarationen in Haskell: Beobachtungen Nicht ohne Probleme: Scanner hat einen Zustand, der vom Parser bestimmt wird ; nicht mehr kontextfrei, braucht globale Datenstruktur ein Stück Kode kann mehrere Semantiken haben syntaktische Korrektheit hängt evtl. von importierten Modulen ab Fehlermeldungen des Parsers schwer verständlich Im GHC Haskell Compiler werden alle Operatoren als LOp0 gelesen und der AST anschliessend transformiert. 223 / 406 Typbezeichner und Variablen in C Die C Grammatik unterscheidet zwischen Terminal typedef-name und identifier. Betrachte folgende Liste von Deklarationen: typedef struct { int x,y } point_t; point_t origin; Relevante C Grammatik: declaration → declaration-specifier → | declarator → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · 224 / 406 Typbezeichner und Variablen in C Die C Grammatik unterscheidet zwischen Terminal typedef-name und identifier. Betrachte folgende Liste von Deklarationen: typedef struct { int x,y } point_t; point_t origin; Relevante C Grammatik: declaration → declaration-specifier → | declarator → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Problem: Parser trägt point_t in Typen-Tabelle ein wenn die declaration Regel reduziert wird 224 / 406 Typbezeichner und Variablen in C Die C Grammatik unterscheidet zwischen Terminal typedef-name und identifier. Betrachte folgende Liste von Deklarationen: typedef struct { int x,y } point_t; point_t origin; Relevante C Grammatik: declaration → declaration-specifier → | declarator → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Problem: Parser trägt point_t in Typen-Tabelle ein wenn die declaration Regel reduziert wird Parserzustand hat mindestens ein Lookahead-Token 224 / 406 Typbezeichner und Variablen in C Die C Grammatik unterscheidet zwischen Terminal typedef-name und identifier. Betrachte folgende Liste von Deklarationen: typedef struct { int x,y } point_t; point_t origin; Relevante C Grammatik: declaration → declaration-specifier → | declarator → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Problem: Parser trägt point_t in Typen-Tabelle ein wenn die declaration Regel reduziert wird Parserzustand hat mindestens ein Lookahead-Token Scanner hat point_t in zweiter Zeile also schon als identifier gelesen 224 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: 225 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: versuche, Lookahead-Token im Parser zu ändern 225 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: versuche, Lookahead-Token im Parser zu ändern füge folgende Regel zur Grammatik hinzu: typedef-name → identifier 225 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: versuche, Lookahead-Token im Parser zu ändern füge folgende Regel zur Grammatik hinzu: typedef-name → identifier registriere den Typnamen früher 225 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: versuche, Lookahead-Token im Parser zu ändern füge folgende Regel zur Grammatik hinzu: typedef-name → identifier registriere den Typnamen früher separiere Regel für typedef Produktion 225 / 406 Typbezeichner und Variablen in C: Lösungen Relevante C Grammatik: declaration declaration-specifier declarator → → | → (declaration-specifier)+ declarator ; static | volatile · · · typedef void | char | char · · · typedef-name identifier | · · · Lösung schwierig: versuche, Lookahead-Token im Parser zu ändern füge folgende Regel zur Grammatik hinzu: typedef-name → identifier registriere den Typnamen früher separiere Regel für typedef Produktion rufe alternative declarator Produktion auf, die identifier als Typenamen registriert 225 / 406 Die semantische Analyse Kapitel 3: Typ-Überprüfung 226 / 406 Ziel der Typ-Überprüfung In modernen (imperativen / objektorientierten / funktionalen) Programmiersprachen besitzen Variablen und Funktionen einen Typ, z.B. int, void*, struct { int x; int y; }. Typen sind nützlich für: die Speicherverwaltung; die Vermeidung von Laufzeit-Fehlern In imperativen / objektorientierten Programmiersprachen muss der Typ bei der Deklaration spezifiziert und vom Compiler die typ-korrekte Verwendung überprüft werden. 227 / 406 Typ-Ausdrücke Typen werden durch Typ-Ausdrücke beschrieben. Die Menge T der Typausdrücke enthält: 1 Basis-Typen: int, char, float, void, ... 2 Typkonstruktoren, die auf Typen angewendet werden Beispiele für Typkonstruktoren: Verbunde: struct { t1 a1 ; . . . tk ak ; } Zeiger: t * Felder: t [] in C kann/muss zusätzlich eine Größe spezifiziert werden die Variable muss zwischen t und [n] stehen Funktionen: t (t1 , . . . , tk ) in C muss die Variable zwischen t und (t1 , . . . , tk ) stehen. in ML würde man diesen Typ anders herum schreiben: t1 ∗ . . . ∗ tk → t wir benutzen (t1 , . . . , tk ) als Tupel-Typen 228 / 406 Typ-Namen Ein Typ-Name ist ein Synonym für einen Typ-Ausdruck. In C kann man diese mit Hilfe von typedef einführen. Typ-Namen sind nützlich als Abkürzung: typedef struct { int x; int y; } point_t; zur Konstruktion rekursiver Typen: Erlaubt in C: Lesbarer: struct list { int info; struct list* next; } typedef struct list list_t; struct list { int info; list_t* next; } list_t* head; struct list* head; 229 / 406 Typ-Prüfung Aufgabe: Gegeben: eine Menge von Typ-Deklarationen Γ = {t1 x1 ; . . . tm xm ; } Überprüfe: Kann ein Ausdruck e mit dem Typ t versehen werden? Beispiel: struct list { int info; struct list* next; }; int f(struct list* l) { return 1; }; struct { struct list* c;}* b; int* a[11]; Betrachte den Ausdruck: *a[f(b->c)]+2; 230 / 406 Typ-Prüfung am Syntax-Baum Prüfe Ausdruck *a[f(b->c)]+2: + 2 ∗ [ ] a ( ) f . ∗ Idee: c b traversiere den Syntaxbaum bottom-up für Bezeichner schlagen wir in Γ den richtigen Typ nach Konstanten wie 2 oder 0.5 sehen wir den Typ direkt an die Typen für die inneren Knoten erschießen wir mit Hilfe von Typ-Regeln 231 / 406 Typ-Systeme Formal betrachten wir Aussagen der Form: Γ `e : t // (In der Typ-Umgebung Γ hat e den Typ t) Axiome: Const: Γ ` c : tc Var: Γ ` x : Γ(x) (tc (x Typ der Konstante c) Variable) Regeln: Ref: Γ `e : t Γ ` &e : t∗ Deref: Γ ` e : t∗ Γ ` ∗e : t 232 / 406 Typ-System für C-ähnliche Sprachen Weitere Regeln für diverse Typausdrücke: Array: Array: Struct: App: Op: Cast: Γ ` e1 : t ∗ Γ ` e2 : int Γ ` e1 [e2 ] : t Γ ` e1 : t [ ] Γ ` e2 : int Γ ` e1 [e2 ] : t Γ ` e : struct {t1 a1 ; . . . tm am ; } Γ ` e.ai : ti Γ ` e : t (t1 , . . . , tm ) Γ ` e1 : t1 . . . Γ ` em : tm Γ ` e(e1 , . . . , em ) : t Γ ` e1 : int Γ ` e2 : int Γ ` e1 + e2 : int Γ ` e : t1 t1 in t2 konvertierbar Γ ` (t2 ) e : t2 233 / 406 Beispiel: Typ-Prüfung Ausdruck *a[f(b->c)]+2 und Γ = { struct list { int info; struct list* next; }; int f(struct list* l); struct { struct list* c;}* b; int* a[11]; }: + 2 ∗ [ ] a ( ) f . ∗ c b 234 / 406 Beispiel: Typ-Prüfung Ausdruck *a[f(b->c)]+2: + int int ∗ int ∗ [ ] 2 ∗ int [ ] a int (struct list ∗) int ( ) f int . struct {struct list ∗ c;} ∗ struct {struct list ∗ c;} ∗ b struct list ∗ c 235 / 406 Gleichheit von Typen Zusammenfassung Typprüfung: Welche Regel an einem Knoten angewendet werden muss, ergibt sich aus den Typen für die bereits bearbeiteten Kinderknoten Dazu muss die Gleichheit von Typen festgestellt werden. Typgleichheit in C: struct A {} und struct B {} werden als verschieden betrachtet ; der Compiler kann die Felder von A und B nach Bedarf umordnen (in C nicht erlaubt) um einen Verband A um weitere Felder zu erweitern, muss er eingebettet werden: typedef struct B { struct A a; int field_of_B; } extension_of_A; Nach typedef int C; haben C und int den gleichen Typ. 236 / 406 Strukturelle Typ-Gleichheit Alternative Interpretation von Gleichheit (gilt nicht in C): Semantisch können wir zwei rekursive Typen t1 , t2 als gleich betrachten, falls sie die gleiche Menge von Pfaden zulassen. Beispiel: struct list { int info; struct list* next; } struct list1 { int info; struct { int info; struct list1* next; }* next; } Sei struct list* l oder struct list1* l. Beide erlauben l->info l->next->info jedoch hat l jeweils einen anderen Typen in C. 237 / 406 Algorithmus zum Test auf Semantische Gleichheit Idee: Verwalte Äquivalenz-Anfragen für je zwei Typausdrücke Sind die beiden Ausdrücke syntaktisch gleich, ist alles gut Andernfalls reduziere die Äquivalenz-Anfrage zwischen Äquivalenz-Anfragen zwischen (hoffentlich) einfacheren anderen Typausdrücken Nehmen wir an, rekursive Typen würden mit Hilfe von Typ-Gleichungen der Form: A=t eingeführt (verzichte auf ein Γ). Dann definieren wir folgende Regeln: 238 / 406 Regeln für Wohlgetyptheit t t s∗ t∗ A t A= s s struct {s1 a1 ; ... sm am ; } s1 t1 t s t struct {t1 a1 ; ... tm am ; } sm tm 239 / 406 Beispiel: = struct {int info; A ∗ next; } = struct {int info; struct {int info; B ∗ next; } ∗ next; } Wir fragen uns etwa, ob gilt: A B struct {int info; A ∗ next; } = B Dazu konstruieren wir: 240 / 406 Beweis Beispiel: A B = = struct {int info; A ∗ next; } struct {int info; struct {int info; B ∗ next; } ∗ next; } struct{int info; A ∗ next; } struct{int info; A ∗ next; } int B struct{int info; . . . ∗ next; } A ∗ ... ∗ int A struct{int info; A ∗ next; } int int struct{int info; B ∗ next; } struct{int info; B ∗ next; } A∗ B∗ A B struct{int info; A ∗ next; } B 241 / 406 Implementierung Stoßen wir bei der Konstruktion des Beweisbaums auf eine Äquivalenz-Anfrage, auf die keine Regel anwendbar ist, sind die Typen ungleich Die Konstruktion des Beweisbaums kann dazu führen, dass die gleiche Äquivalenz-Anfrage ein weiteres Mal auftritt Taucht eine Äquivalenz-Anfrage ein weiteres Mal auf, sind die Typen der Anfrage per Definition gleich Terminierung? 242 / 406 Implementierung Stoßen wir bei der Konstruktion des Beweisbaums auf eine Äquivalenz-Anfrage, auf die keine Regel anwendbar ist, sind die Typen ungleich Die Konstruktion des Beweisbaums kann dazu führen, dass die gleiche Äquivalenz-Anfrage ein weiteres Mal auftritt Taucht eine Äquivalenz-Anfrage ein weiteres Mal auf, sind die Typen der Anfrage per Definition gleich Terminierung? die Menge D aller deklarierten Typen ist endlich es gibt höchstens |D|2 viele Äquivalenzanfragen wiederholte Anfragen sind automatisch erfüllt ; Terminierung ist gesichert 242 / 406 Überladung und Koersion Manche Operatoren wie z.B. + sind überladen: + besitzt mehrere mögliche Typen Zum Beispiel: int +(int,int), float +(float, float) aber auch float* +(float*, int), int* +(int, int*) je nach Typ hat der Operator + ein unterschiedliche Implementation welche Implementierung ausgewählt wird, entscheiden die Argument-Typen 243 / 406 Überladung und Koersion Manche Operatoren wie z.B. + sind überladen: + besitzt mehrere mögliche Typen Zum Beispiel: int +(int,int), float +(float, float) aber auch float* +(float*, int), int* +(int, int*) je nach Typ hat der Operator + ein unterschiedliche Implementation welche Implementierung ausgewählt wird, entscheiden die Argument-Typen Koersion: Erlaube auch die Anwendung von + auf int und float. anstatt + für alle Argument-Kombinationen zu definieren, werden die Typen der Argumente konvertiert Konvertierung kann Code erzeugen (z.B. Umwandlung von int nach float) Konvertiert wird in der Regel auf die Supertypen, d.h. 5+0.5 hat Typ float (da float ≥ int) 243 / 406 Koersion von Integer-Typen in C: Promotion C enthält spezielle Koersionsregeln für Integer: Promotion unsigned char unsigned short ≤ ≤ int ≤ unsigned int signed char signed short . . . wobei eine Konvertierung über alle Zwischentypen gehen muss. 244 / 406 Koersion von Integer-Typen in C: Promotion C enthält spezielle Koersionsregeln für Integer: Promotion unsigned char unsigned short ≤ ≤ int ≤ unsigned int signed char signed short . . . wobei eine Konvertierung über alle Zwischentypen gehen muss. Subtile Fehler möglich! Berechne Zeichenverteilung in char* str: char* str = "..."; int dist[256]; memset(dist, 0, sizeof(dist)); while (*str) { dist[(unsigned) *str]++; str++; }; Beachte: unsigned bedeutet unsigned int. 244 / 406 Teiltypen Auf den arithmetischen Basistypen char, int, long, etc. gibt es i.a. eine reichhaltige Teiltypen-Beziehungen. Dabei bedeutet t1 ≤ t2 , dass die Menge der Werte vom Typ t1 1 2 3 eine Teilmenge der Werte vom Typ t2 sind; in einen Wert vom Typ t2 konvertiert werden können; die Anforderungen an Werte vom Typ t2 erfüllen. 245 / 406 Teiltypen Auf den arithmetischen Basistypen char, int, long, etc. gibt es i.a. eine reichhaltige Teiltypen-Beziehungen. Dabei bedeutet t1 ≤ t2 , dass die Menge der Werte vom Typ t1 1 2 3 eine Teilmenge der Werte vom Typ t2 sind; in einen Wert vom Typ t2 konvertiert werden können; die Anforderungen an Werte vom Typ t2 erfüllen. Erweitere Teiltypen-Beziehungen der Basistypen auf komplexe Typen 245 / 406 Beispiel: Teiltypen Betrachte: string extractInfo( struct { string info; } x) { return x.info; } Wir möchten dass extractInfo für alle Argument-Strukturen funktioniert, die eine Komponente string info besitzen Wann t1 ≤ t2 gelten soll, beschreiben wir durch Regeln Die Idee ist vergleichbar zur Anwendbarkeit auf Unterklassen (aber allgemeiner) 246 / 406 Regeln für Wohlgetyptheit von Teiltypen t t s∗ t∗ A t A= s s t s t struct {s1 a1 ; ... sm am ; } struct {tj1 aj1 ; ... tjk ajk ; } sj1 tj1 sjk tjk 247 / 406 Regeln und Beispiel für Teiltypen s0 (s1 , . . . , sm ) s0 t0 t1 s1 t0 (t1 , . . . , tm ) tm sm Beispiele: struct {int a; int b; } int (int) int (float) struct {float a; } float (float) float (int) 248 / 406 Regeln und Beispiel für Teiltypen s0 (s1 , . . . , sm ) s0 t0 t1 s1 t0 (t1 , . . . , tm ) tm sm Beispiele: struct {int a; int b; } int (int) int (float) struct {float a; } float (float) float (int) Achtung: Bei Argumenten dreht sich die Anordnung der Typen gerade um 248 / 406 Regeln und Beispiel für Teiltypen s0 (s1 , . . . , sm ) s0 t0 t0 (t1 , . . . , tm ) t1 s1 tm sm Beispiele: struct {int a; int b; } int (int) int (float) ≤ 6≤ ≤ struct {float a; } float (float) float (int) Achtung: Bei Argumenten dreht sich die Anordnung der Typen gerade um Im Unterschied zum Überschreiben in OO Sprachen wo gilt: Kovarianz der Argumente Kontravarianz der Rückgabewerte 248 / 406 Regeln und Beispiel für Teiltypen s0 (s1 , . . . , sm ) s0 t0 t0 (t1 , . . . , tm ) t1 s1 tm sm Beispiele: struct {int a; int b; } int (int) int (float) ≤ 6≤ ≤ struct {float a; } float (float) float (int) Achtung: Bei Argumenten dreht sich die Anordnung der Typen gerade um Im Unterschied zum Überschreiben in OO Sprachen wo gilt: Kovarianz der Argumente Kontravarianz der Rückgabewerte Diese Regeln können wir direkt benutzen, um auch für rekursive Typen die Teiltyp-Relation zu entscheiden 248 / 406 Teiltypen: Anwendung der Regeln (I) Prüfe ob S1 ≤ R1 : R1 S1 R2 S2 = = = = struct {int a; struct {int a; struct {int a; struct {int a; R1 (R1 ) f ; } int b; S1 (S1 ) f ; } R2 (S2 ) f ; } int b; S2 (R2 ) f ; } S1 R1 a int int f S1 (S1 ) R1 (R1 ) S1 R1 R1 S1 249 / 406 Teiltypen: Anwendung der Regeln (II) Prüfe ob S2 ≤ S1 : R1 S1 R2 S2 = = = = struct {int a; struct {int a; struct {int a; struct {int a; R1 (R1 ) f ; } int b; S1 (S1 ) f ; } R2 (S2 ) f ; } int b; S2 (R2 ) f ; } S2 S1 a, b int int f S2 (R2 ) S1 (S1 ) S2 S1 S1 R2 a int int f S1 (S1 ) R2 (S2 ) S1 R2 S2 S1 250 / 406 Teiltypen: Anwendung der Regeln (III) Prüfe ob S2 ≤ R1 : R1 S1 R2 S2 = = = = struct {int a; struct {int a; struct {int a; struct {int a; R1 (R1 ) f ; } int b; S1 (S1 ) f ; } R2 (S2 ) f ; } int b; S2 (R2 ) f ; } S 2 R1 a int int f S2 (R2 ) R1 (R1 ) S 2 R1 R1 R2 a int int f R1 (R1 ) R2 (S2 ) R1 R2 S2 R1 251 / 406 Diskussion Um die Beweisbäume nicht in den Himmel wachsen zu lassen, werden Zwischenknoten oft ausgelassen Strukturelle Teiltypen sind sehr mächtig und deshalb nicht ganz leicht zu durchschauen. Java verallgemeinert Strukturen zu Objekten / Klassen. Teiltyp-Beziehungen zwischen Klassen müssen explizit deklariert werden Durch Vererbung wird sichergestellt, dass Unterklassen über die (sichtbaren) Komponenten der Oberklasse verfügen Überdecken einer Komponente mit einer anderen gleichen Namens ist möglich — aber nur, wenn diese keine Methode ist 252 / 406 Themengebiet: Die Synthesephase 253 / 406 Codegenerierung: Überblick Vom AST des Programs erzeugen wir induktiv Instruktionen: für jedes Nicht-Terminal Symbol in der Grammatik gibt es eine Regel zur Generierung von Machineninstruktionen der Code ist nur ein weiteres Attribut im Syntaxbaum die Codegenerierung benutzt die schon berechneten Attribute 254 / 406 Codegenerierung: Überblick Vom AST des Programs erzeugen wir induktiv Instruktionen: für jedes Nicht-Terminal Symbol in der Grammatik gibt es eine Regel zur Generierung von Machineninstruktionen der Code ist nur ein weiteres Attribut im Syntaxbaum die Codegenerierung benutzt die schon berechneten Attribute Um die Codeerzeugung zu spezifizieren, benötigt man die Semantik der Ausgangssprache (C Standard) die Semantik der Maschineninstruktionen 254 / 406 Codegenerierung: Überblick Vom AST des Programs erzeugen wir induktiv Instruktionen: für jedes Nicht-Terminal Symbol in der Grammatik gibt es eine Regel zur Generierung von Machineninstruktionen der Code ist nur ein weiteres Attribut im Syntaxbaum die Codegenerierung benutzt die schon berechneten Attribute Um die Codeerzeugung zu spezifizieren, benötigt man die Semantik der Ausgangssprache (C Standard) die Semantik der Maschineninstruktionen ; Wir definieren zunächst die Machineninstruktionen 254 / 406 Die Synthesephase Kapitel 1: Die C-Maschine 255 / 406 Die C-Maschine (CMA) Wir erzeugen Code für die C-Maschine. Die C-Maschine ist eine virtuelle Machine. es existiert kein Prozessor, der die Instruktionen der CMA ausführen kann aber es steht ein Interpreter zur Verfügung es seht auch eine Visualisierungsumgebung zur Verfügung die CMA kennt keine double, float, char, short oder long Typen die CMA hat kennt keine Instruktionen um mit dem Betriebssystem zu kommunizieren (Eingabe/Ausgabe) 256 / 406 Die C-Maschine (CMA) Wir erzeugen Code für die C-Maschine. Die C-Maschine ist eine virtuelle Machine. es existiert kein Prozessor, der die Instruktionen der CMA ausführen kann aber es steht ein Interpreter zur Verfügung es seht auch eine Visualisierungsumgebung zur Verfügung die CMA kennt keine double, float, char, short oder long Typen die CMA hat kennt keine Instruktionen um mit dem Betriebssystem zu kommunizieren (Eingabe/Ausgabe) Die CMA ist realistischer als es auf den ersten Blick erscheint: die Einschränkungen können mit wenig Aufwand aufgehoben werden die Java Virtual Machine (JVM) ist sehr ähnlich zur CMA JVM Code kann mittlerweile von einigen eingebetteten Prozessoren direkt ausgeführt werden ein Interpreter für die CMA läuft auf jeder Platform 256 / 406 Virtuelle Maschinen Ein virtuelle Maschine definiert sich wie folgt: Jede virtuelle Maschine stellt einen Satz von Instruktionen zur Verfügung. Instruktionen werden auf der virtuellen Hardware ausgeführt. Die virtuelle Hardware ist eine Menge von Datenstrukturen, auf die die Instruktionen zugreifen ... und die vom Laufzeitsystem verwaltet werden. Für die CMa benötigen wir: C 0 1 PC S 0 SP 257 / 406 Komponenten der CMA Zur Implementierung der CMA verwenden wir folgende Strukturen: S ist der (Daten-)Speicher, auf dem nach dem LIFO-Prinzip neue Zellen allokiert werden können ; Keller/Stack. SP (= b Stack Pointer) ist ein Register, das die Adresse der obersten belegten Zelle enthält. Vereinfachung: Alle Daten passen jeweils in eine Zelle von S. 258 / 406 Komponenten der CMA Zur Implementierung der CMA verwenden wir folgende Strukturen: S ist der (Daten-)Speicher, auf dem nach dem LIFO-Prinzip neue Zellen allokiert werden können ; Keller/Stack. SP (= b Stack Pointer) ist ein Register, das die Adresse der obersten belegten Zelle enthält. Vereinfachung: Alle Daten passen jeweils in eine Zelle von S. C ist der Code-Speicher, der das Programm enthält. Jede Zelle des Felds C kann exakt einen virtuellen Befehl aufnehmen. Der Code-Speicher kann nur gelesen werden. PC (= b Program Counter) ist ein Register, das die Adresse des nächsten auszuführenden Befehls enthält. Vor Programmausführung enthält der PC die Adresse 0 ; C[0] enthält den ersten auszuführenden Befehl. 258 / 406 Die Ausführung von Programmen Die Maschine lädt die Instruktion aus C[PC] in ein Instruktions-Register IR und führt sie aus. Vor der Ausführung eines Befehls wird der PC um 1 erhöht. while (true) { IR = C[PC]; PC++; execute (IR); } Der PC muss vor der Ausführung der Instruktion erhöht werden, da diese möglicherweise den PC überschreibt Die Schleife (der Maschinen-Zyklus) wird durch Ausführung der Instruktion halt verlassen, die die Kontrolle an das Betriebssystem zurückgibt. Die weiteren Instruktionen führen wir nach Bedarf ein 259 / 406 Die Synthesephase Kapitel 2: Ausdrucksauswertung 260 / 406 Einfache Ausdrücke und Wertzuweisungen Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus Das heißt: erzeuge eine Instruktionsfolge, die den Wert des Ausdrucks ermittelt und dann oben auf dem Keller ablegt 261 / 406 Einfache Ausdrücke und Wertzuweisungen Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus Das heißt: erzeuge eine Instruktionsfolge, die den Wert des Ausdrucks ermittelt und dann oben auf dem Keller ablegt Idee: berechne erst die Werte für die Teilausdrücke; merke diese Zwischenergebnisse oben auf dem Keller; wende dann den Operator an 261 / 406 Einfache Ausdrücke und Wertzuweisungen Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus Das heißt: erzeuge eine Instruktionsfolge, die den Wert des Ausdrucks ermittelt und dann oben auf dem Keller ablegt Idee: berechne erst die Werte für die Teilausdrücke; merke diese Zwischenergebnisse oben auf dem Keller; wende dann den Operator an ; ähnlich wie postfix Notation des Taschenrechners aus der ersten Übung 261 / 406 Generelles Prinzip die Argumente für Instruktionen werden oben auf dem Keller erwartet; die Ausführung einer Instruktion konsumiert ihre Argumente; möglicherweise berechnete Ergebnisse werden oben auf dem Keller wieder abgelegt. loadc q q SP++; S[SP] = q; Die Instruktion loadc q benötigt keine Argumente, legt dafür aber als Wert die Konstante q oben auf dem Stack ab. 262 / 406 Binäre Operatoren Operatoren mit zwei Argumenten werden wie folgt angewandt: 3 8 mul 24 SP--; S[SP] = S[SP] ∗ S[SP+1]; 263 / 406 Binäre Operatoren Operatoren mit zwei Argumenten werden wie folgt angewandt: 3 8 mul 24 SP--; S[SP] = S[SP] ∗ S[SP+1]; mul erwartet zwei Argumente oben auf dem Stack, konsumiert sie und legt sein Ergebnis oben auf dem Stack ab. 263 / 406 Binäre Operatoren Operatoren mit zwei Argumenten werden wie folgt angewandt: 3 8 mul 24 SP--; S[SP] = S[SP] ∗ S[SP+1]; mul erwartet zwei Argumente oben auf dem Stack, konsumiert sie und legt sein Ergebnis oben auf dem Stack ab. analog arbeiten auch die übrigen binären arithmetischen und logischen Instruktionen add, sub, div, mod, and, or und xor, wie auch die Vergleiche eq, neq, le, leq, gr und geq. 263 / 406 Vergleiche und unäre Operatoren Beispiel: Der Operator 7 3 leq leq 1 Einstellige Operatoren wie neg und not konsumieren dagegen ein Argument und erzeugen einen Wert: 8 neg -8 S[SP] = – S[SP]; 264 / 406 Zusammensetzung von Instruktionen Beispiel: Erzeuge Code für 1 + 7: loadc 1 loadc 7 add Ausführung dieses Codes: loadc 1 1 loadc 7 7 1 add 8 265 / 406 Ausdrücke mit Variablen Variablen haben eine Speicherzellen in S: z: y: x: 266 / 406 Ausdrücke mit Variablen Variablen haben eine Speicherzellen in S: z: y: x: Die Zuordnung von Adressen zu Variablen kann während der Erstellung der Symboltabelle erfolgen. Die Adresse wird an dem Deklarationsknoten der Variable gespeichert. 266 / 406 Ausdrücke mit Variablen Variablen haben eine Speicherzellen in S: z: y: x: Die Zuordnung von Adressen zu Variablen kann während der Erstellung der Symboltabelle erfolgen. Die Adresse wird an dem Deklarationsknoten der Variable gespeichert. Jede Benutzung einer Variable hat als Attribut einen Zeiger auf den Deklarationsknoten der Variable. 266 / 406 Ausdrücke mit Variablen Variablen haben eine Speicherzellen in S: z: y: x: Die Zuordnung von Adressen zu Variablen kann während der Erstellung der Symboltabelle erfolgen. Die Adresse wird an dem Deklarationsknoten der Variable gespeichert. Jede Benutzung einer Variable hat als Attribut einen Zeiger auf den Deklarationsknoten der Variable. Im folgenden benutzen wir Übersetzungsfunktionen die eine Funktion ρ nehmen, die für jede Variable x die (Relativ-)Adresse von x liefert. Die Funktion ρ heißt Adress-Umgebung (Address Environment). 266 / 406 L-Wert und R-Wert Variablen können auf zwei Weisen verwendet werden. Beispiel: x = y + 1 Für y sind wir am Inhalt der Zelle, für x an der Adresse interessiert. L-Wert von x R-Wert von x codeR e ρ codeL e ρ = = Adresse von x Inhalt von x liefert den Code zur Berechnung des R-Werts von e in der Adress-Umgebung ρ analog für den L-Wert Achtung: Nicht jeder Ausdruck verfügt über einen L-Wert (z.B.: x + 1). 267 / 406 Übersetzungschema für Ausdrücke Wir definieren: codeR (e1 + e2 ) ρ = codeR (−e) ρ = codeR q ρ codeL x ρ = = ... codeR e1 ρ codeR e2 ρ add ... analog für die anderen binären Operatoren codeR e ρ neg ... analog für andere unäre Operatoren loadc q loadc (ρ x) 268 / 406 Lesen von Variablen codeR x ρ = codeL x ρ load Die Instruktion load lädt den Wert der Speicherzelle, deren Adresse oben auf dem Stack liegt. load 13 13 13 S[SP] = S[S[SP]]; 269 / 406 Schreiben von Variablen codeR (x = e) ρ = codeR e ρ codeL x ρ store Die Instruktion store schreibt den Inhalt der zweitobersten Speicherzelle in die Speicherzelle, deren Adresse oben auf dem Keller steht, lässt den geschriebenen Wert aber oben auf dem Keller liegen 13 13 store 13 S[S[SP]] = S[SP-1]; SP--; 270 / 406 Ausdrücke mit Variablen Beispiel: Code für e ≡x = y − 1 mit ρ = {x 7→ 4, y 7→ 7}. Dann liefert codeR e ρ: loadc 7 load loadc 1 sub loadc 4 store Optimierungen: Einführung von Spezialbefehlen für häufige Befehlsfolgen, hier etwa: loada q = storea q = loadc q load loadc q store 271 / 406 Die Synthesephase Kapitel 3: Anweisungen 272 / 406 Anweisungen und Anweisungsfolgen Ist e ein Ausdruck, dann ist e; eine Anweisung (Statement). Anweisungen liefern keinen Wert zurück. Folglich muss der SP vor und nach der Ausführung des erzeugten Codes gleich sein: code e; ρ = codeR e ρ pop Die Instruktion pop wirft das oberste Element des Kellers weg ... 1 pop SP--; 273 / 406 Die Synthesephase Kapitel 4: Bedingte Anweisungen 274 / 406 Übersetzung von Anweisungsfolgen Der Code für eine Anweisungsfolge ist die Konkatenation des Codes for die einzelnen Anweisungen in der Folge: code (s ss) ρ = code ε ρ code s ρ code ss ρ = // leere Folge von Befehlen Beachte: s ist Statement, ss ist eine Folge von Statements 275 / 406 Die Synthesephase Kapitel 5: Kontrollfluss 276 / 406 Sprünge Um von linearer Ausführungsreihenfolge abzuweichen, benötigen wir Sprünge: jump A A PC PC PC = A; 277 / 406 Bedingte Sprünge Ein bedingter Sprung verzweigt je nach Wert auf dem Keller: 1 jumpz A PC 0 PC jumpz A A PC PC if (S[SP] == 0) PC = A; SP--; 278 / 406 Verwaltung von Kontrollfluss Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge erzeugt werden. bei der Übersetung eines if (c) Konstrukts ist nicht klar, an welcher Adresse gesprungen werden muss, wenn c falsch ist 279 / 406 Verwaltung von Kontrollfluss Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge erzeugt werden. bei der Übersetung eines if (c) Konstrukts ist nicht klar, an welcher Adresse gesprungen werden muss, wenn c falsch ist Instruktionsfolgen können unterschiedlich arrangiert werden minimiere dabei die Anzahl der unbedingten Sprünge minimiere so dass weniger innerhalb Schleifen gesprungen wird ersetze weite Sprünge (far jumps) durch nahe Sprünge (near jumps) 279 / 406 Verwaltung von Kontrollfluss Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge erzeugt werden. bei der Übersetung eines if (c) Konstrukts ist nicht klar, an welcher Adresse gesprungen werden muss, wenn c falsch ist Instruktionsfolgen können unterschiedlich arrangiert werden minimiere dabei die Anzahl der unbedingten Sprünge minimiere so dass weniger innerhalb Schleifen gesprungen wird ersetze weite Sprünge (far jumps) durch nahe Sprünge (near jumps) organisiere die Instruktionen in Blöcke ohne Sprünge 279 / 406 Verwaltung von Kontrollfluss Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge erzeugt werden. bei der Übersetung eines if (c) Konstrukts ist nicht klar, an welcher Adresse gesprungen werden muss, wenn c falsch ist Instruktionsfolgen können unterschiedlich arrangiert werden minimiere dabei die Anzahl der unbedingten Sprünge minimiere so dass weniger innerhalb Schleifen gesprungen wird ersetze weite Sprünge (far jumps) durch nahe Sprünge (near jumps) organisiere die Instruktionen in Blöcke ohne Sprünge Dazu definieren wir: Definition Ein basic block ist eine Folge von Anweisungen ss, die keine Sprünge enthält eine ausgehenden Menge von Kanten zu anderen basic blocks wobei jede Kante mit einer Bedingung annotiert sein kann 279 / 406 Basic Blöcke in der C-Maschine Die CMA verfügt nur über einen bedingten Sprung, jumpz. code ss c Die ausgehenden Kanten haben eine spezielle Form: 280 / 406 Basic Blöcke in der C-Maschine Die CMA verfügt nur über einen bedingten Sprung, jumpz. code ss c Die ausgehenden Kanten haben eine spezielle Form: 1 eine Kante (unbedingter Sprung), mit jump übersetzt 280 / 406 Basic Blöcke in der C-Maschine Die CMA verfügt nur über einen bedingten Sprung, jumpz. code ss c Die ausgehenden Kanten haben eine spezielle Form: 1 eine Kante (unbedingter Sprung), mit jump übersetzt 2 eine Kante mit Bedingung c = 0 (jumpz), eine Kante ohne Bedinung (jump) 280 / 406 Basic Blöcke in der C-Maschine Die CMA verfügt nur über einen bedingten Sprung, jumpz. code ss c Die ausgehenden Kanten haben eine spezielle Form: 1 eine Kante (unbedingter Sprung), mit jump übersetzt 2 eine Kante mit Bedingung c = 0 (jumpz), eine Kante ohne Bedinung (jump) 3 eine Liste von Kanten (jumpi) und eine default Kante (jump); wird verwendet für switch Anweisungen (kommt später) 280 / 406 Beschreibung von Kontrollfluss Übersetzungen Der Einfachheit halber verwenden wir symbolische Sprungziele zur Beschreibung der Übersetzung von Kontrollflussinstruktionen. Ein zweiten Durchlauf ist nötig, um symbolische Adressen durch absolute Code-Adressen zu ersetzen. 281 / 406 Beschreibung von Kontrollfluss Übersetzungen Der Einfachheit halber verwenden wir symbolische Sprungziele zur Beschreibung der Übersetzung von Kontrollflussinstruktionen. Ein zweiten Durchlauf ist nötig, um symbolische Adressen durch absolute Code-Adressen zu ersetzen. Alternativ könnten direkt relative Sprünge eingeführt werden: relative Sprünge haben Ziele relative zum aktuellen PC oft reichen kleinere Adressen aus (; near jumps) der Code wird relokierbar, d. h. kann im Speicher unverändert hin und her geschoben werden (position independent code, PIC) Der erzeugte Code wäre im Prinzip direkt ausführbar. 281 / 406 Beschreibung von Kontrollfluss Übersetzungen Der Einfachheit halber verwenden wir symbolische Sprungziele zur Beschreibung der Übersetzung von Kontrollflussinstruktionen. Ein zweiten Durchlauf ist nötig, um symbolische Adressen durch absolute Code-Adressen zu ersetzen. Alternativ könnten direkt relative Sprünge eingeführt werden: relative Sprünge haben Ziele relative zum aktuellen PC oft reichen kleinere Adressen aus (; near jumps) der Code wird relokierbar, d. h. kann im Speicher unverändert hin und her geschoben werden (position independent code, PIC) Der erzeugte Code wäre im Prinzip direkt ausführbar. Basic Blöcke haben den Vorteil, dass sie später zur Programmoptimierung weiterverwendet werden können. (Z.B. Reduzierung von Sprüngen.) 281 / 406 Bedingte Anweisung, einseitig Betrachten wir zuerst s ≡ if ( c ) ss. Wir beschreiben die Code-Erzeugung zunächst ohne Basic-Blöcke. Idee: Lege den Code zur Auswertung von c und ss hintereinander in den Code-Speicher; Füge Sprung-Befehlen so ein, dass ein korrekter Kontroll-Fluss gewährleistet ist code s ρ = codeR c ρ jumpz A code ss ρ A : ... code R für c jumpz code für ss 282 / 406 Zweiseitiges if Betrachte nun s ≡ if ( c ) tt else ee. code c code Die gleiche Strategie liefert: code s ρ = tt code ee code R für c codeR c ρ jumpz jumpz A code für tt code tt ρ jump B A : code ee ρ B: jump code für ee ... 283 / 406 Beispiel für if-Anweisung Sei ρ = {x 7→ 4, y 7→ 7} und s ≡ if (x > y) x = x − y; else y = y − x; (i) (ii) (iii) Dann liefert code s ρ : loada 4 loada 7 gr jumpz A (i) loada 4 loada 7 sub storea 4 pop jump B (ii) A: B: loada 7 loada 4 sub storea 7 pop ... (iii) 284 / 406 Iterative Anweisungen Betrachte schließlich die Schleife s ≡ while (e) s0 . Dafür erzeugen wir: code s ρ = A : codeR e ρ jumpz B code R für e jumpz 0 code s ρ jump A B: ... code für s’ jump 285 / 406 Beispiel: Übersetzung von Schleifen Sei ρ = {a 7→ 7, b 7→ 8, c 7→ 9} und s das Statement: while (a > 0) {c = c + 1; a = a − b; } Dann liefert code s ρ die Folge: A: loada 7 loadc 0 gr jumpz B loada 9 loadc 1 add storea 9 pop loada 7 loada 8 sub storea 7 pop jump A B: ... 286 / 406 for-Schleifen Die for-Schleife s ≡ for (e1 ; e2 ; e3 ) s0 ist äquivalent zu der Statementfolge e1 ; while (e2 ) {s0 e3 ; } – sofern s0 keine continue-Anweisung enthält. Darum übersetzen wir: code s ρ = codeR e1 pop A : codeR e2 ρ jumpz B code s0 ρ codeR e3 ρ pop jump A B : ... 287 / 406 Das switch-Statement Idee: Unterstütze Mehrfachverzweigung in konstanter Zeit Benutze Sprungtabelle, die an der i-ten Stelle den Sprung an den Anfang der i-tem Alternative enthält. Eine Möglichkeit zur Realisierung besteht in der Einführung von indizierten Sprüngen. q jumpi B B+q PC PC PC = B + S[SP]; SP--; 288 / 406 Aufeinanderfolgende Alternativen Wir betrachten zunächst nur switch-Statements der folgenden Form: s ≡ switch (e) { case 0: ss0 break; case 1: ss1 break; .. . case k − 1: ssk−1 break; default: ssk } Dann ergibt sich für s die Instruktionsfolge: 289 / 406 Codeerzeugung für spezielle switch-Anweisungen code s ρ = codeR e ρ check 0 k B C0 : Ck : code ss0 ρ jump D ... code ssk ρ jump D B: D: jump C0 ... jump Ck ... Das Macro check 0 k B überprüft, ob der R-Wert von e im Intervall [0, k] liegt, und führt einen indizierten Sprung in die Tabelle B aus. Die Sprungtabelle enthält direkte Sprünge zu den jeweiligen Alternativen. Am Ende jeder Alternative steht ein Sprung hinter das switch-Statement. 290 / 406 Semantik des Makros check 0 k B = dup loadc 0 geq jumpz A dup loadc k leq jumpz A A: jumpi B pop loadc k jumpi B Weil der R-Wert von e noch zur Indizierung benötigt wird, muss er vor jedem Vergleich kopiert werden. Dazu dient der Befehl dup. 3 dup 3 3 S[SP+1] = S[SP]; SP++; Ist der R-Wert von e kleiner als 0 oder größer als k, ersetzen wir ihn vor dem indizierten Sprung durch k. 291 / 406 Übersetzung der Sprungtabelle Die Sprung-Tabelle liegt bei der beschriebenen Übersetzung hinter dem Code der Alternativen. Dies ist einfach zu realisieren, da die Sprungtabelle als L-Attribut beim Betrachten der Fälle generiert werden kann. Soll die Sprungtabelle hinter das check Makro gelegt werden muss eventuell das switch-Statement zweimal durchsucht werden. Beginnt die Tabelle mit u statt mit 0, müssen wir den R-Wert von e um u vermindern, bevor wir ihn als Index benutzen. Sind sämtliche möglichen Werte von e sicher im Intervall [0, k], können wir auf check verzichten. 292 / 406 Allgemeine Übersetzung der switch-Anweisung Im Allgemeinen können die Werte der Alternativen weit auseinander liegen. generiere eine folge von expliziten Tests, wie für die if-Anweisung 293 / 406 Allgemeine Übersetzung der switch-Anweisung Im Allgemeinen können die Werte der Alternativen weit auseinander liegen. generiere eine folge von expliziten Tests, wie für die if-Anweisung bei n verschiedenen Tests kann binäre Suche angewendet werden ; O(log n) Tests 293 / 406 Allgemeine Übersetzung der switch-Anweisung Im Allgemeinen können die Werte der Alternativen weit auseinander liegen. generiere eine folge von expliziten Tests, wie für die if-Anweisung bei n verschiedenen Tests kann binäre Suche angewendet werden ; O(log n) Tests hat die Menge der getesteten Werte kleine Lücken (≤ 3), so ist Sprungtabelle effizienter und platzsparender 293 / 406 Allgemeine Übersetzung der switch-Anweisung Im Allgemeinen können die Werte der Alternativen weit auseinander liegen. generiere eine folge von expliziten Tests, wie für die if-Anweisung bei n verschiedenen Tests kann binäre Suche angewendet werden ; O(log n) Tests hat die Menge der getesteten Werte kleine Lücken (≤ 3), so ist Sprungtabelle effizienter und platzsparender eventuell kann man mehrere Sprungtabellen für verschiedene, zusammenhängende Mengen generieren 293 / 406 Allgemeine Übersetzung der switch-Anweisung Im Allgemeinen können die Werte der Alternativen weit auseinander liegen. generiere eine folge von expliziten Tests, wie für die if-Anweisung bei n verschiedenen Tests kann binäre Suche angewendet werden ; O(log n) Tests hat die Menge der getesteten Werte kleine Lücken (≤ 3), so ist Sprungtabelle effizienter und platzsparender eventuell kann man mehrere Sprungtabellen für verschiedene, zusammenhängende Mengen generieren ein Suchbaum mit Tabellen kann durch profiling anders angeordnet werden, so dass häufig genommene Pfade weniger Tests erfordern 293 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion 294 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion reiche diesen Basic Block zur Übersetzung der Anweisungen runter 294 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion reiche diesen Basic Block zur Übersetzung der Anweisungen runter eine Zuweisung wird am Ende des Basic Blocks angehängt 294 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion reiche diesen Basic Block zur Übersetzung der Anweisungen runter eine Zuweisung wird am Ende des Basic Blocks angehängt eine zweiseitige if Anweisung erzeugt drei neue, leere Blöcke: 1 2 3 einen für den then-Zweig, verbunden mit aktuellem Block durch jumpz Kante einen für den else-Zweig, durch jump Kante verbunden einen für die nachfolgenden Anweisungen, verbunden mit thenund else-Zweig durch jump Kante 294 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion reiche diesen Basic Block zur Übersetzung der Anweisungen runter eine Zuweisung wird am Ende des Basic Blocks angehängt eine zweiseitige if Anweisung erzeugt drei neue, leere Blöcke: 1 2 3 einen für den then-Zweig, verbunden mit aktuellem Block durch jumpz Kante einen für den else-Zweig, durch jump Kante verbunden einen für die nachfolgenden Anweisungen, verbunden mit thenund else-Zweig durch jump Kante ähnlich für andere Konstrukte 294 / 406 Übersetzung in Basic Blocks Problem: Wie verknüpft man die verschiedenen Basic Blöcke? Idee: beim Übersetzen einer Funktion: erzeuge einen leeren Basic Block und speichere den Zeiger im Konten der Funktion reiche diesen Basic Block zur Übersetzung der Anweisungen runter eine Zuweisung wird am Ende des Basic Blocks angehängt eine zweiseitige if Anweisung erzeugt drei neue, leere Blöcke: 1 2 3 einen für den then-Zweig, verbunden mit aktuellem Block durch jumpz Kante einen für den else-Zweig, durch jump Kante verbunden einen für die nachfolgenden Anweisungen, verbunden mit thenund else-Zweig durch jump Kante ähnlich für andere Konstrukte Zur besseren Navigation sollten auch Rückwärtskanten zwischen den Blöcken eingefügt werden. 294 / 406 Felder Beispiel: int[11] a; Das Feld a enthält 11 Elemente und benötigt darum 11 Zellen. ρ a ist die Adresse des Elements a[0]. a[10] a[0] Definiere Funktion | · | um den Platzbedarf eines Typs zu berechnen: 1 falls t ein Basistyp |t| = k · |t0 | falls t ≡ t0 [k] Dann ergibt sich für die Deklaration d ≡ t1 x1 ; . . . tk xk ; ρ x1 = 1 ρ xi = ρ xi−1 + |ti−1 | für i > 1 | · | kann zur Übersetzungszeit berechnet werden, also auch ρ. Beachte: | · | ist nötig zur Implementierung von C’s sizeof Operator 295 / 406 Übersetzung von Felderzugriffen Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen. Sei t[c] a; die Deklaration eines Feldes a. 296 / 406 Übersetzung von Felderzugriffen Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen. Sei t[c] a; die Deklaration eines Feldes a. Um die Adresse des Elements a[i]zu bestimmen, müssen wir ρ a + |t| ∗ (R-Wert von i) ausrechnen. Folglich: codeL e1 [e2 ] ρ = codeR e1 ρ codeR e2 ρ loadc |t| mul add 296 / 406 Übersetzung von Felderzugriffen Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen. Sei t[c] a; die Deklaration eines Feldes a. Um die Adresse des Elements a[i]zu bestimmen, müssen wir ρ a + |t| ∗ (R-Wert von i) ausrechnen. Folglich: codeL e1 [e2 ] ρ = codeR e1 ρ codeR e2 ρ loadc |t| mul add Bemerkung: In C ist ein Feld ein Zeiger. Ein deklariertes Feld a ist eine Zeiger-Konstante, deren R-Wert die Anfangsadresse von a ist. Formal setzen wir für ein Feld e codeR e ρ = codeL e ρ In C sind äquivalent (als L-Werte, nicht vom Typ): 2[a] a[2] a+2 296 / 406 Strukturen (Records) Achtung: Komponenten-Namen von Strukturen dürfen sich überlappen. Hier: Komponenten-Umgebung ρst bezieht sich auf den gerade übersetzten Struktur-Typ st. Sei struct int a; int b; x; Teil einer Deklarationsliste. x erhält die erste freie Zelle des Platzes für die Struktur als Relativ-Adresse. Für die Komponenten vergeben wir Adressen relativ zum Anfang der Struktur, hier a 7→ 0, b 7→ 1. 297 / 406 Strukturen (Records) Achtung: Komponenten-Namen von Strukturen dürfen sich überlappen. Hier: Komponenten-Umgebung ρst bezieht sich auf den gerade übersetzten Struktur-Typ st. Sei struct int a; int b; x; Teil einer Deklarationsliste. x erhält die erste freie Zelle des Platzes für die Struktur als Relativ-Adresse. Für die Komponenten vergeben wir Adressen relativ zum Anfang der Struktur, hier a 7→ 0, b 7→ 1. Sei allgemein t ≡struct int a; int b;. Dann ist |t| = k X |ti | ρ c1 = 0 ρ ci = ρ ci−1 + |ti−1 | für i > 1 i=1 Damit erhalten wir: codeL (e.c) ρ = codeL e ρ loadc (ρ c) add 297 / 406 Die Synthesephase Kapitel 6: Zeiger und dynamische Speicherverwaltung 298 / 406 Zeiger in C Mit Zeiger (-Werten) rechnen, heißt in der Lage zu sein, 1 Zeiger zu erzeugen, d.h. Zeiger auf Speicherzellen zu setzen; sowie 2 Zeiger zu dereferenzieren, d. h. durch Zeiger auf die Werte von Speicherzellen zugreifen. Erzeugen von Zeigern: Die Anwendung des Adressoperators & liefert einen Zeiger auf eine Variable, d. h. deren Adresse (= b L-Wert). Deshalb: codeR (&e) ρ = codeL e ρ Beispiel: Sei struct int a; int b; x; mit ρ = {x 7→ 13, a 7→ 0, b 7→ 1} gegeben. Dann ist codeL (x.b) ρ = loadc 13 loadc 1 add 299 / 406 Dereferenzieren von Zeigern Die Anwendung des Operators * auf den Ausdruck e liefert den Inhalt der Speicherzelle, deren Adresse der R-Wert von e ist: codeL (∗e) ρ = codeR e ρ Beispiel: Betrachte für struct t { int a[7]; struct t *b; }; int i,j; struct t *pt; den Ausdruck e ≡((pt -> b) -> a)[i+1] Wegen e->a ≡ (*e).a gilt: codeL (e → a) ρ = codeR e ρ loadc (ρ a) add 300 / 406 Übersetzung von Dereferenzierungen (I) Sei ρ = {i 7→ 1, j 7→ 2, pt 7→ 3, a 7→ 0, b 7→ 7 }. b: struct t { int a[7]; struct t *b; }; int i,j; struct t *pt; Übersetze e ≡((pt -> b) -> a)[i+1] b: pt: j: i: Dann ist: codeL e ρ = codeR ((pt → b) → a) ρ codeR (i + 1) ρ loadc 1 mul add a: a: = codeR ((pt → b) → a) ρ loada 1 loadc 1 add loadc 1 mul add 301 / 406 Übersetzung von Dereferenzierungen (II) Für dereferenzierte Felder (*e).a ist der R-Wert gleich dem L-Wert von e plus Offset von a. Deshalb erhalten wir: codeR ((pt → b) → a) ρ = codeR (pt → b) ρ loadc 0 add = loada 3 loadc 7 add load loadc 0 add Damit ergibt sich insgesamt die Folge: loada 3 loadc 7 add load loadc 0 add loada 1 loadc 1 add loadc 1 mul add 302 / 406 Der Heap Zeiger gestatten auch den Zugriff auf anonyme, dynamisch erzeugte Datenelemente, deren Lebenszeit nicht dem LIFO-Prinzip unterworfen ist. ; Wir benötigen einen potentiell beliebig großen Speicherbereich H: den Heap (Halde). Implementierung: S H 0 MAX SP NP EP EP NP = b New Pointer; zeigt auf unterste belegte Haldenzelle. = b Extreme Pointer; zeigt auf die Zelle, auf die der SP maximal zeigen kann (innerhalb der aktuellen Funktion). 303 / 406 Invarianten des Heaps und des Stacks Stack und Heap dürfen sich nicht überschneiden 304 / 406 Invarianten des Heaps und des Stacks Stack und Heap dürfen sich nicht überschneiden eine Überschneidung kann bei jeder Erhöhung von SP eintreten (Stack Overflow) oder bei einer Erniedrigung des NP eintreten (Out Of Memory) 304 / 406 Invarianten des Heaps und des Stacks Stack und Heap dürfen sich nicht überschneiden eine Überschneidung kann bei jeder Erhöhung von SP eintreten (Stack Overflow) oder bei einer Erniedrigung des NP eintreten (Out Of Memory) im Gegensatz zum Stack Overflow kann ein Out Of Memory Fehler vom Programmierer abgefangen werden malloc liefert in diesem Fall NULL zurück, dass als (void*) 0 definiert ist 304 / 406 Invarianten des Heaps und des Stacks Stack und Heap dürfen sich nicht überschneiden eine Überschneidung kann bei jeder Erhöhung von SP eintreten (Stack Overflow) oder bei einer Erniedrigung des NP eintreten (Out Of Memory) im Gegensatz zum Stack Overflow kann ein Out Of Memory Fehler vom Programmierer abgefangen werden malloc liefert in diesem Fall NULL zurück, dass als (void*) 0 definiert ist EP reduziert die nötigen Überprüfungen auf Überschneidung auf den Funktionseintritt die Überprüfungen bei Heap-Allokationen bleiben erhalten 304 / 406 Dynamisch Allokierter Speicher Es gibt eine weitere Arte, Zeiger zu erzeugen: ein Aufruf von malloc liefert einen Zeiger auf eine Heap-Zelle: codeR malloc (e) ρ = codeR e ρ new NP NP n n new if (NP - S[SP] <= EP) S[SP] = NULL; else { NP = NP - S[SP]; S[SP] = NP; } 305 / 406 Freigabe von Speicherplatz Ein mit malloc allokierter Bereich muss irgendwann mit free wieder freigegeben werden. Probleme: Der freigegebene Speicherbereich könnte noch von anderen Zeigern referenziert (dangling references). Nach einiger Freigabe könnte der Speicher etwa so aussehen (fragmentation): frei 306 / 406 Mögliche Auswege: 1 Nimm an, der Programmierer weiß, was er tut. Verwalte dann die freien Abschnitte (etwa sortiert nach Größe) in einer speziellen Datenstruktur; ; malloc wird teuer 2 Tue nichts, d.h.: code free (e); ρ = codeR e ρ pop ; einfach und effizient, aber nicht für reaktive Progaramme 3 Benutze eine automatische, evtl. “konservative” Garbage-Collection, die gelegentlich sicher nicht mehr benötigten Heap-Platz einsammelt und dann malloc zur Verfügung stellt. 307 / 406 Die Synthesephase Kapitel 7: Funktionen 308 / 406 Aufbau einer Funktion Die Definition einer Funktion besteht aus einem Namen, mit dem sie aufgerufen werden kann; einer Spezifikation der formalen Parameter; evtl. einem Ergebnistyp; einem Anweisungsteil. In C gilt: codeR f ρ = loadc _f = Anfangsadresse des Codes für f Beachte: auch Funktions-Namen müssen eine Adresse zugewiesen bekommen da die Größe von Funktionen nicht vor der Übersetzung bekannt ist, müssen die Adressen der Funktionen anschließend eingetragen werden 309 / 406 Speicherverwaltung bei Funktionen int fac(int x) { if (x<=0) return 1; else return x*fac(x-1); } int main(void) { int n; n = fac(2) + fac(1); printf("%d", n); } Zu einem Ausführungszeitpunkt können mehrere Instanzen der gleichen Funktion aktiv sein, d. h. begonnen, aber noch nicht beendet sein. Der Rekursionsbaum im Beispiel: main fac fac fac fac printf fac 310 / 406 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: 311 / 406 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: lege einen speziellen Speicherbereich für jeden Aufruf einer Funktion an. 311 / 406 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: lege einen speziellen Speicherbereich für jeden Aufruf einer Funktion an. in sequentiellen Programmiersprachen können diese Speicherbereiche auf dem Keller verwaltet werden 311 / 406 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: lege einen speziellen Speicherbereich für jeden Aufruf einer Funktion an. in sequentiellen Programmiersprachen können diese Speicherbereiche auf dem Keller verwaltet werden jede Instanz einer Funktion erhält dadurch einen Bereich auf dem Stack 311 / 406 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: lege einen speziellen Speicherbereich für jeden Aufruf einer Funktion an. in sequentiellen Programmiersprachen können diese Speicherbereiche auf dem Keller verwaltet werden jede Instanz einer Funktion erhält dadurch einen Bereich auf dem Stack diese Bereiche heißen Keller-Rahmen (oder Stack Frame) 311 / 406 Kellerrahmen-Organisation SP lokale Variablen FP PCold FPold organisatorische Zellen EPold formale Parameter / Funktionswert FP = b Frame Pointer; zeigt auf die letzte organisatorische Zelle und wird zur Adressierung der formalen Parameter und lokalen Variablen benutzt. 312 / 406 Adressierung von Variablen im Kellerrahmen Die lokalen Variablen erhalten Relativadressen +1, +2, . . .. Die formalen Parameter liegen unterhalb der organisatorischen Zellen und haben deshalb negative Adressen relativ zu FP Diese Organisation ist besonders geeignet für Funktionsaufrufe mit variabler Argument-Anzahl wie z.B. printf. Den Speicherbereich für die Parameter recyclen wir zur Speicherung des Rückgabe werts der Funktion Vereinfachung: Der Rückgabewert passt in eine einzige Zelle. 313 / 406 Adressierung von Variablen im Kellerrahmen Die lokalen Variablen erhalten Relativadressen +1, +2, . . .. Die formalen Parameter liegen unterhalb der organisatorischen Zellen und haben deshalb negative Adressen relativ zu FP Diese Organisation ist besonders geeignet für Funktionsaufrufe mit variabler Argument-Anzahl wie z.B. printf. Den Speicherbereich für die Parameter recyclen wir zur Speicherung des Rückgabe werts der Funktion Vereinfachung: Der Rückgabewert passt in eine einzige Zelle. Die organisatorischen Zellen speichern die Register-Inhalte, die nach dem Funktions-Aufruf restauriert werden müssen. Bei einem Funktions-Aufruf muss der FP in eine organisatorische Zelle gerettet werden. 313 / 406 Adressierung von Variablen im Kellerrahmen Die lokalen Variablen erhalten Relativadressen +1, +2, . . .. Die formalen Parameter liegen unterhalb der organisatorischen Zellen und haben deshalb negative Adressen relativ zu FP Diese Organisation ist besonders geeignet für Funktionsaufrufe mit variabler Argument-Anzahl wie z.B. printf. Den Speicherbereich für die Parameter recyclen wir zur Speicherung des Rückgabe werts der Funktion Vereinfachung: Der Rückgabewert passt in eine einzige Zelle. Die organisatorischen Zellen speichern die Register-Inhalte, die nach dem Funktions-Aufruf restauriert werden müssen. Bei einem Funktions-Aufruf muss der FP in eine organisatorische Zelle gerettet werden. Unsere Übersetzungsaufgaben für Funktionen: 1 erzeuge Code für den Rumpf 2 erzeuge Code für Aufrufe 313 / 406 Bestimmung der Adress-Umgebung Variablen können in verschiedenen Symboltabellen liegen: 1 globale/externe, die außerhalb von Funktionen definiert werden; 2 lokale/interne/automatische (inklusive formale Parameter), die innerhalb von Funktionen definiert werden. Erweitere die Adress-Umgebung ρ so dass ρ Variablen auf Tuple (tag, a) ∈ {G, L} × Z abbildet. Idee: Das tag ist L: die Variable wurde in einer der lokalen Symboltabellen gefunden G: die Variable wurde in der globalen Symboltabelle gefunden Beispiel: int x, y; v ρ(v) void f(int v, int w) { x h , i int a; y h , i if (a>0) { v h , i int b; w h , i } else { a h , i int c; b h , i } c h , i } 314 / 406 Bestimmung der Adress-Umgebung Variablen können in verschiedenen Symboltabellen liegen: 1 globale/externe, die außerhalb von Funktionen definiert werden; 2 lokale/interne/automatische (inklusive formale Parameter), die innerhalb von Funktionen definiert werden. Erweitere die Adress-Umgebung ρ so dass ρ Variablen auf Tuple (tag, a) ∈ {G, L} × Z abbildet. Idee: Das tag ist L: die Variable wurde in einer der lokalen Symboltabellen gefunden G: die Variable wurde in der globalen Symboltabelle gefunden Beispiel: int x, y; v ρ(v) void f(int v, int w) { x hG,0i int a; y hG,1i if (a>0) { v h L , -3 i int b; w h L , -4 i } else { a hL ,1i int c; b hL ,2i } c hL ,2i } 314 / 406 Die Synthesephase Kapitel 8: Betreten und Verlassen von Funktionen 315 / 406 Position von Funktionsargumenten Die aktuellen Parameter werden von rechts nach links ausgewertet Der erste Parameter liegt direkt unterhalb der organisatorischen Zellen PC, FC, EP Für einen Prototypen τ f (τ1 x1 , . . . , τk xk ) setzen wir: x1 7→ (L, −2 − |τ1 |) xi 7→ (L, −2 − |τ1 | − . . . − |τi |) Die Reihenfolge ermöglicht Funktionen mit variabler Parameteranzahl (variadic functions) wie printf int printf(const char * format, ...); char *s = value "Hello %s!\nIt’s %i to %i!\n"; s "World" int main(void) { 5 printf(s ,"World", 5, 12); 12 return 0; ρ(pi ) hL, −3i hL, −4i hL, −5i hL, −6i } 316 / 406 Arbeitsteilung beim Funktionsaufruf Definition Sei f die aktuelle Funktion, die die Funktion g aufruft. f heißt caller g heißt callee Der Code für den Aufruf muss auf den Caller und den Callee verteilt werden. Die Aufteilung kann nur so erfolgen, dass der Teil, der von Informationen des Callers abhängt, auch dort erzeugt wird und analog für den Callee. Beobachtung: Den Platz für die aktuellen Parameter kennt nur der Caller: Beispiel: printf 317 / 406 Prinzip vom Funktionsaufruf und Rücksprung Aktionen beim Betreten von g: Bestimmung der aktuellen Parameter Retten von FP, EP Bestimmung der Anfangsadresse von g Setzen des neuen FP Retten von PC und Sprung an den Anfang von g 6. Setzen des neuen EP 7. Allokieren der lokalen Variablen 1. 2. 3. 4. 5. mark stehen in f call enter stehen in g alloc Aktionen bei Verlassen von g: Berechnen des Rückgabewerts Abspeichern des Rückgabewerts Rücksetzen der Register FP, EP, SP Rücksprung in den Code von f , d. h. Restauration des PC 4. Aufräumen des Stack 0. 1. 2. 3. return slide 318 / 406 Aufruf-Sequenz Damit erhalten wir für einen Aufruf für eine Funktion mit mindestens einem Parameter und einem Rückgabewert: codeR g(e1 , . . . , en ) ρ = codeR en ρ ... codeR e1 ρ mark codeR g ρ call slide (s − 1) wobei s der Platz für die aktuellen Parameter ist. Beachte: 319 / 406 Aufruf-Sequenz Damit erhalten wir für einen Aufruf für eine Funktion mit mindestens einem Parameter und einem Rückgabewert: codeR g(e1 , . . . , en ) ρ = codeR en ρ ... codeR e1 ρ mark codeR g ρ call slide (s − 1) wobei s der Platz für die aktuellen Parameter ist. Beachte: Von jedem Ausdruck, der als aktueller Parameter auftritt, wird jeweils der R-Wert berechnet ; Call-by-Value-Parameter-Übergabe. 319 / 406 Aufruf-Sequenz Damit erhalten wir für einen Aufruf für eine Funktion mit mindestens einem Parameter und einem Rückgabewert: codeR g(e1 , . . . , en ) ρ = codeR en ρ ... codeR e1 ρ mark codeR g ρ call slide (s − 1) wobei s der Platz für die aktuellen Parameter ist. Beachte: Von jedem Ausdruck, der als aktueller Parameter auftritt, wird jeweils der R-Wert berechnet ; Call-by-Value-Parameter-Übergabe. Die Funktion g kann auch ein Ausdruck sein, dessen R-Wert die Anfangs-Adresse der aufzurufenden Funktion liefert 319 / 406 Berechnung von R-Werten Ähnlich deklarierten Feldern, werden Funktions-Namen als konstante Zeiger auf Funktionen aufgefasst. Dabei ist der R-Wert dieses Zeigers gleich der Anfangs-Adresse der Funktion. Achtung! Für eine Variable int (*)() g sind die beiden Aufrufe (*g)() und g() äquivalent. Die Dereferenzierungen eines Funktions-Zeigers ist unnötig und wird ignoriert. Bei der Parameter-Übergabe von Strukturen werden diese kopiert. Folglich: codeR f ρ = codeR (∗e) ρ = codeR e ρ = loadc (ρ f ) codeR e ρ codeL e ρ move k f ein Funktions-Name e ein Funktions-Zeiger e eine Struktur der Größe k Neuer Befehl: move 320 / 406 Kopien von Speicherbereichen Die move Instruktion kopiert k Elemente auf den Stack. k move k for (i = k-1; i≥0; i--) S[SP+i] = S[S[SP]+i]; SP = SP+k–1; 321 / 406 Retten von EP und FP Der Befehl mark legt Platz für Rückgabewert und organisatorische Zellen an und rettet FP und EP. FP EP FP EP e e e mark S[SP+1] = EP; S[SP+2] = FP; SP = SP + 2; 322 / 406 Aufrufen einer Funktion Der Befehl call rettet den aktuellen Wert des PC als Fortsetzungs-Adresse und setzt FP und PC. FP q p call PC PC p q tmp = S[SP]; S[SP] = PC; FP = SP; PC = tmp; 323 / 406 Entfernen der Parameter beim Rücksprung Der Befehl slide kopiert den Rückgabewert an die korrekte Stelle: 42 m slide m 42 tmp = S[SP]; SP = SP-m; S[SP] = tmp; 324 / 406 Übersetzung einer Funktionsdefinition Entsprechend übersetzen wir eine Funktions-Definition: code t f (specs){body} _f: wobei q max k ρf = = = = enter q alloc k code ss ρf return ρ = // // setzen des EP Anlegen der lokalen Variablen // Verlassen der Funktion max + k wobei maximale Länge des lokalen Kellers Platz für die lokalen Variablen Adress-Umgebung für f erzeugt aus ρ, specs und body 325 / 406 Reservierung von Speicher auf dem Stack Der Befehl enter q setzt den EP auf den neuen Wert. Steht nicht mehr genügend Platz zur Verfügung, wird die Programm-Ausführung abgebrochen. EP q enter q EP = SP + q; if (EP ≥ NP) Error (“Stack Overflow”); 326 / 406 Reservierung von Speicher für lokale Variablen Der Befehl alloc k reserviert auf dem Keller Platz für die lokalen Variablen. k alloc k SP = SP + k; 327 / 406 Rücksprung aus einer Funktion Der Befehl return gibt den aktuellen Keller-Rahmen auf. D.h. er restauriert die Register PC, EP und FP und hinterlässt oben auf dem Keller den Rückgabe-Wert. PC FP EP p return PC FP EP e v p e v PC = S[FP]; EP = S[FP-2]; if (EP ≥ NP) Error (“Stack Overflow”); SP = FP-3; FP = S[SP+2]; 328 / 406 Überblick Funktionsaufruf Aufruf: f (e1 , . . . , en ): codeR en ρ ... codeR e1 ρ mark codeR f ρ call slide (s − 1) _f: enter q alloc k code ss ρf return 329 / 406 Zugriff auf Variablen, Parameter u. Rückgabewerte Zugriffe auf lokale Variablen oder formale Parameter erfolgen relativ zum aktuellen FP. Darum modifizieren wir codeL für Variablen-Namen. Für definieren wir loadc j tag = G codeL x ρ = loadrc j tag = L ρ x = (tag, j) Der Befehl loadrc j berechnet die Summe von FP und j. FP f loadrc j FP f f+j SP++; S[SP] = FP+j; 330 / 406 Makro-Befehle zum Zugriff auf lokale Variablen Als Optimierung führt man analog zu loada j und storea j die Befehle loadr j und storer j ein: loadr j = loadrc j load storer j = loadrc j; store Der Code für return e; entspricht einer Zuweisung an eine Variable mit Relativadresse −3. code return e; ρ = codeR e ρ storer -3 return 331 / 406 Beispiel zur Übersetzung von Funktionen Beispiel: Für die Funktion int fac(int x) { if (x<=0) return 1; else return x*fac (x-1); } erzeugen wir: _fac: enter q alloc 0 loadr -3 loadc 0 leq jumpz A loadc 1 storer -3 return jump B A: loadr -3 loadr -3 loadc 1 sub mark loadc _fac call slide 0 B: mul storer -3 return return Dabei ist ρfac : x 7→ (L, −3) und q = 1 + 1 + 3 = 5. 332 / 406 Themengebiet: Übersetzung ganzer Programme 333 / 406 Übersetzung von Programmen Vor der Programmausführung gilt: SP = −1 FP = EP = 0 PC = 0 NP = MAX Sei p ≡ V_defs F_def1 . . . F_defn , ein Programm, wobei F_defi eine Funktion fi definiert, von denen eine main heißt. Der Code für das Programm p enthält: Code für die Funktions-Definitionen F_defi ; Code zum Anlegen der globalen Variablen; Code für den Aufruf von main() die Instruktion halt. 334 / 406 Instruktionen für den Programmstart Dann definieren wir: code p ∅ = _f1 : _fn : wobei ∅ ρ k = b = b = b enter (k + 4) alloc (k + 1) mark loadc _main call slide k halt code F_def1 ρ .. . code F_defn ρ leere Adress-Umgebung; globale Adress-Umgebung; Platz für globale Variablen 335 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. 336 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. Rückgabewert überschreibt den ersten Parameter, slide entfernt die restlichen Parameter 336 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. Rückgabewert überschreibt den ersten Parameter, slide entfernt die restlichen Parameter Was passiert, wenn es keine Parameter gibt? 336 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. Rückgabewert überschreibt den ersten Parameter, slide entfernt die restlichen Parameter Was passiert, wenn es keine Parameter gibt? ; der Rückgabewert überschreibt eine Caller-lokale Variable 336 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. Rückgabewert überschreibt den ersten Parameter, slide entfernt die restlichen Parameter Was passiert, wenn es keine Parameter gibt? ; der Rückgabewert überschreibt eine Caller-lokale Variable Lösungen: erzwinge, dass jede Funktion mindestens einen formalen Parameter hat 336 / 406 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. Rückgabewert überschreibt den ersten Parameter, slide entfernt die restlichen Parameter Was passiert, wenn es keine Parameter gibt? ; der Rückgabewert überschreibt eine Caller-lokale Variable Lösungen: erzwinge, dass jede Funktion mindestens einen formalen Parameter hat Optimierung für Funktionen ohne Rückgabewert: füge keinen zusätzlichen Parameter ein ändere nur die Übersetzung beim Caller: falls slide -1 emittiert werden soll, erzeuge stattdessen slide 0 bzw. gar nichts 336 / 406 Peephole Optimization Die Erzeugten Instruktionen enthalten viele redundante Sequenzen, z.B.: alloc 0 slide 0 loadc 1 mul Peephole Optimierung sucht nach solchen Mustern und ersetzt sie durch einfachere (in diesem Fall keine) Instruktionen. 337 / 406 Übersetzung ganzer Programme Kapitel 1: Maschinen mit Registern 338 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) Speicherzugriffe sind langsam (selbst mit cache) 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) Speicherzugriffe sind langsam (selbst mit cache) jeder Zugriff auf den Stapel berechnet FP+k 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) Speicherzugriffe sind langsam (selbst mit cache) jeder Zugriff auf den Stapel berechnet FP+k pipelining ist schwierig: überlappen ∗(FP+k) und ∗(FP+k0 )? 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) Speicherzugriffe sind langsam (selbst mit cache) jeder Zugriff auf den Stapel berechnet FP+k pipelining ist schwierig: überlappen ∗(FP+k) und ∗(FP+k0 )? Selbst für alt Prozessoren (6502, Z80, 8086, etc.) gilt: reale Prozessoren haben mindestens ein Register ein Akkumulator Register speichert das Ergebnis 339 / 406 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: jede Operation (z.B. add) bezieht ihre Argumente und ihr Resultat über den Stack (; pro Addition drei Speicherzugriffe) Speicherzugriffe sind langsam (selbst mit cache) jeder Zugriff auf den Stapel berechnet FP+k pipelining ist schwierig: überlappen ∗(FP+k) und ∗(FP+k0 )? Selbst für alt Prozessoren (6502, Z80, 8086, etc.) gilt: reale Prozessoren haben mindestens ein Register ein Akkumulator Register speichert das Ergebnis eine Berechnung wie x=y+1-x ist billiger das Zwischenergebnis von 1-x muss nicht auf den Stapel geschrieben werden die Konstante 1 kann muss nicht auf den Stapel geschrieben werden 339 / 406 Moderne Register-Maschinen Entwicklung von Register-Maschinen: 340 / 406 Moderne Register-Maschinen Entwicklung von Register-Maschinen: Anzahl der Register war begrenzt durch Anzahl der Transistoren: nur wenige Register möglich Register hatten spezielle Funktionen, z.B. AX: Akkumulator, BX: zweites Argument/Resultat, CX: Zähler, DX: Datenzeiger mögliche Kontexte von Registern beschränkt (z.B. mul schreibt Ergebnis immer in AX:BX) 340 / 406 Moderne Register-Maschinen Entwicklung von Register-Maschinen: Anzahl der Register war begrenzt durch Anzahl der Transistoren: nur wenige Register möglich Register hatten spezielle Funktionen, z.B. AX: Akkumulator, BX: zweites Argument/Resultat, CX: Zähler, DX: Datenzeiger mögliche Kontexte von Registern beschränkt (z.B. mul schreibt Ergebnis immer in AX:BX) Anzahl der Register ist heute begrenzt durch Instruktionslänge jede Instruktion trägt Bits um Quell- und Zielregister auszuwählen jedes Register kann universell eingesetzt werden 340 / 406 Moderne Register-Maschinen Entwicklung von Register-Maschinen: Anzahl der Register war begrenzt durch Anzahl der Transistoren: nur wenige Register möglich Register hatten spezielle Funktionen, z.B. AX: Akkumulator, BX: zweites Argument/Resultat, CX: Zähler, DX: Datenzeiger mögliche Kontexte von Registern beschränkt (z.B. mul schreibt Ergebnis immer in AX:BX) Anzahl der Register ist heute begrenzt durch Instruktionslänge jede Instruktion trägt Bits um Quell- und Zielregister auszuwählen jedes Register kann universell eingesetzt werden Die wichtigsten Aufgaben: früher: bilde Programmkonstrukte auf komplexe Instruktionen ab heute: halte möglichst viele Werte gleichzeitig in Registern 340 / 406 Moderne Register-Maschinen Entwicklung von Register-Maschinen: Anzahl der Register war begrenzt durch Anzahl der Transistoren: nur wenige Register möglich Register hatten spezielle Funktionen, z.B. AX: Akkumulator, BX: zweites Argument/Resultat, CX: Zähler, DX: Datenzeiger mögliche Kontexte von Registern beschränkt (z.B. mul schreibt Ergebnis immer in AX:BX) Anzahl der Register ist heute begrenzt durch Instruktionslänge jede Instruktion trägt Bits um Quell- und Zielregister auszuwählen jedes Register kann universell eingesetzt werden Die wichtigsten Aufgaben: früher: bilde Programmkonstrukte auf komplexe Instruktionen ab heute: halte möglichst viele Werte gleichzeitig in Registern Semantik: Benutze M[·] für Speicherzugriff, Ri für Register, + für Addition (infix), := für Zuweisung, etc. 340 / 406 Instruktionsselektion: Übersicht Beispiel: Motorola MC68000 Dieser einfachste Prozessor der 680x0-Reihe besitzt 8 Daten- und 8 Adressregister; eine Vielzahl von Adressierungsarten Notation Dn An (An ) d(An ) d(An , Dm ) x x #x Beschreibung Datenregister direkt Adressregister direkt Adressregister indirekt Adressregister indirekt mit Displacement Adressregister indirekt mit Index und Displacement Absolut kurz Absolut lang Unmittelbar Semantik Dn An M[An ] M[An + d] M[An + Dm + d] M[x] M[x] x 341 / 406 Adressierung im MC68000 Der MC68000 ist eine 2-Adress-Maschine, d.h. ein Befehl darf maximal 2 Argumente enthalten. Die Instruktion: add D1 D2 addiert die Inhalte von D1 und D2 und speichert das Ergebnis nach und D2 Die meisten Befehle lassen sich auf Bytes, Wörter (2 Bytes) oder Doppelwörter (4 Bytes) anwenden. Das unterscheiden wir durch Anhängen von .B, .W, .D an das Mnemonic (ohne Suffix: Wort) Die Ausführungszeit eines Befehls ergibt sich (i.a.) aus den Kosten der Operation plus den Kosten für die Adressierung der Operanden 342 / 406 Kosten der Adressierung im MC68000 Eine add Instruktion benötigt 4 Zykel. Argumente verlängern diese Zeit wie folgt: Dn An (An ) d(An ) d(An , Dm ) x x #x Adressierungsart Datenregister direkt Adressregister direkt Adressregister indirekt Adressregister indirekt mit Displacement Adressregister indirekt mit Index und Displacement Absolut kurz Absolut lang Unmittelbar .B, .W 0 0 4 8 .D 0 0 8 12 10 14 8 12 4 12 16 8 343 / 406 Kosten der Adressierung im MC68000 Eine add Instruktion benötigt 4 Zykel. Argumente verlängern diese Zeit wie folgt: Dn An (An ) d(An ) d(An , Dm ) x x #x Adressierungsart Datenregister direkt Adressregister direkt Adressregister indirekt Adressregister indirekt mit Displacement Adressregister indirekt mit Index und Displacement Absolut kurz Absolut lang Unmittelbar .B, .W 0 0 4 8 .D 0 0 8 12 10 14 8 12 4 12 16 8 ; intelligente Auswahl der Argumente können viele move Instruktionen und Zwischenergebnisse überflüssig machen. 343 / 406 Beispiel Instruktionsselektion Betrachte die Kosten als Summe von Operation, 1. Argument und 2. Argument: o + a1 + a2 Die Instruktion benötigt: move.B 8(A1 , D1 .W), D5 4 + 10 + 0 = 14 Zykel Alternativ könnten wir erzeugen: adda adda move.B mit Gesamtkosten adda move.B mit Gesamtkosten #8, A1 D1 .W, A1 (A1 ), D5 32 Kosten: 8 + 8 + 0 = 16 Kosten: 8 + 0 + 0 = 8 Kosten: 4 + 4 + 0 = 8 oder: D1 .W, A1 8(A1 ), D5 Kosten: 8 + 0 + 0 = 8 Kosten: 4 + 8 + 0 = 12 20 344 / 406 Komplexe Instruktionen die verschieden Code-Sequenzen sind im Hinblick auf den Speicher und das Ergebnis äquivalent sie unterscheiden sich im Hinblick auf den Wert des Registers A1 sowie die gesetzten Bedingungs-Codes ein konkreter Algorithmus muss solche Randbedingungen berücksichtigen int b, i, a[100]; b = 2 + a[i]; Nehmen wir an, die Variablen werden relativ zu einem Framepointer A5 mit den Adressen −4, −6, −206 adressiert. Dann entspricht der Zuweisung das Stück Zwischen-Code: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; 345 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; Der Ausdruck entspricht dem Syntaxbaum: = + M 2 M + A5 −4 + ∗ + A5 −206 2 D1 M + A5 −6 346 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; Mögliche Auswahl an Instruktionen: move −6(A5 ), D1 = + M 2 M + A5 −4 + ∗ + A5 −206 2 D1 M + A5 −6 346 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; Mögliche Auswahl an Instruktionen: add D1 , D1 = + M 2 M + A5 −4 + D1 + A5 −206 ∗ 2 D1 M + A5 −6 346 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; −206(A5 , D1 ), D2 Mögliche Auswahl an Instruktionen: move = + M D2 2 + A5 −4 M + D1 + A5 −206 ∗ 2 D1 M + A5 −6 346 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; Mögliche Auswahl an Instruktionen: addq #2, D2 = D2 M + D2 2 + A5 −4 M + D1 + A5 −206 ∗ 2 D1 M + A5 −6 346 / 406 Instruktionsselektion für Anweisung Betrachte die Übersetzung von: M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]]; D2 , −4(A5 ) Mögliche Auswahl an Instruktionen: move = D2 M + D2 2 + A5 −4 M + D1 + A5 −206 ∗ 2 D1 M + A5 −6 346 / 406 Mögliche Code-Sequenzen Im Beispiel: move add move addq move −6(A5 ), D1 D1 , D1 −206(A5 , D1 ), D2 #2, D2 D2 , −4(A5 ) Gesamtkosten : Kosten: 12 Kosten: 4 Kosten: 14 Kosten: 4 Kosten: 12 46 347 / 406 Alternative Code-Sequenz Eine längere Code-Sequenz: move.L adda.L move mulu move.L adda.L adda.L move addq move.L adda.L move A5 , A1 #−6, A1 (A1 ), D1 #2, D1 A5 , A2 #−206, A2 D1 , A2 (A2 ), D2 #2, D2 A5 , A3 #−4, A3 D2 , (A3 ) Gesamtkosten : Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: Kosten: 4 12 8 44 4 12 8 8 4 4 12 8 124 348 / 406 Kriterien zur Instruktionsselektion Komplexe Instruktionen sind i.A. vorteilhaft: komplexere Adressierungsarten können den Code erheblich schneller und kürzer machen einfachere Befehlssequenzen benötigen mehr Hilfsregister die komplexere Folge überschreibt auch weniger Hilfsregister 349 / 406 Kriterien zur Instruktionsselektion Komplexe Instruktionen sind i.A. vorteilhaft: komplexere Adressierungsarten können den Code erheblich schneller und kürzer machen einfachere Befehlssequenzen benötigen mehr Hilfsregister die komplexere Folge überschreibt auch weniger Hilfsregister ; finde die schnellste Folge von Instruktionen für einen Syntaxbaum 349 / 406 Kriterien zur Instruktionsselektion Komplexe Instruktionen sind i.A. vorteilhaft: komplexere Adressierungsarten können den Code erheblich schneller und kürzer machen einfachere Befehlssequenzen benötigen mehr Hilfsregister die komplexere Folge überschreibt auch weniger Hilfsregister ; finde die schnellste Folge von Instruktionen für einen Syntaxbaum Idee: Berechne alle möglichen Instruktionsfolgen, berechne deren Kosten und wähle dann die beste aus. beschreibe alle erlaubten Instruktionen mit einer Grammatik: 1 2 3 verfügbare Registerklassen: Nichtterminale Operatoren und Konstantenklassen: Terminale Instruktionen: Regeln die Grammatik ist mehrdeutig, da viele Ableitungen den gleichen Baum beschreiben wähle die billigste aller Ableitungen, die zu einem Baum führen 349 / 406 Kriterien zur Instruktionsselektion Komplexe Instruktionen sind i.A. vorteilhaft: komplexere Adressierungsarten können den Code erheblich schneller und kürzer machen einfachere Befehlssequenzen benötigen mehr Hilfsregister die komplexere Folge überschreibt auch weniger Hilfsregister ; finde die schnellste Folge von Instruktionen für einen Syntaxbaum Idee: Berechne alle möglichen Instruktionsfolgen, berechne deren Kosten und wähle dann die beste aus. beschreibe alle erlaubten Instruktionen mit einer Grammatik: 1 2 3 verfügbare Registerklassen: Nichtterminale Operatoren und Konstantenklassen: Terminale Instruktionen: Regeln die Grammatik ist mehrdeutig, da viele Ableitungen den gleichen Baum beschreiben wähle die billigste aller Ableitungen, die zu einem Baum führen ; inferiere alle möglichen Ableitungen durch tree parsing 349 / 406 Beispiel Grammtik für Instruktionen Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beachte: zwei Registerklassen D (Data) und A (Address) wie vorher Arithmetik wird nur für Daten unterstützt Laden nur von Adressen in Addressregistern Zwischen Daten- und Adressregistern gibt es moves 350 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] D 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M A 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M D 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M + D D 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M + A D 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M + A c 351 / 406 Beispiel für eine Ableitung der Grammatik Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Beispiel Instruktion: M[A + c] M + A c Alternativ wäre auch eine Ableitung mit der Regel D → M[A + A] möglich. 351 / 406 Instruktionsselektion durch tree-parsing Idee: Berechnung von möglichen Ableitungen zu einem Baum durch tree parsing: berechne mögliche Ableitungen bottom-up, beginnend von den Blättern: 352 / 406 Instruktionsselektion durch tree-parsing Idee: Berechnung von möglichen Ableitungen zu einem Baum durch tree parsing: berechne mögliche Ableitungen bottom-up, beginnend von den Blättern: prüfe, welche rechte Regelseite passt wende diese Regel rückwärts an füge linke Regelseite zum Vorgänger im Baum hinzu 352 / 406 Instruktionsselektion durch tree-parsing Idee: Berechnung von möglichen Ableitungen zu einem Baum durch tree parsing: berechne mögliche Ableitungen bottom-up, beginnend von den Blättern: prüfe, welche rechte Regelseite passt wende diese Regel rückwärts an füge linke Regelseite zum Vorgänger im Baum hinzu oben angekommen, wähle die Ableitung mit den geringsten Kosten steige hinab und emittiere Instruktionen für jede angewandte Regel 352 / 406 Instruktionsselektion durch tree-parsing Idee: Berechnung von möglichen Ableitungen zu einem Baum durch tree parsing: berechne mögliche Ableitungen bottom-up, beginnend von den Blättern: prüfe, welche rechte Regelseite passt wende diese Regel rückwärts an füge linke Regelseite zum Vorgänger im Baum hinzu oben angekommen, wähle die Ableitung mit den geringsten Kosten steige hinab und emittiere Instruktionen für jede angewandte Regel Algorithmus ähnlich zu Cocke-Younger-Kasami (CYK)-Algorithmus zum Test ob eine Eingabe s in L(G) für G ∈ CFG. 352 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] M + A c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] M + 0 1 A ,D A c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] M + 0 1 A ,D A A2 , D1 c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] M A4 , D3 , A + A2 + 0 1 A ,D A A2 , D1 c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] A6 , D5 M A4 , D3 , A + A2 + 0 1 A ,D A A2 , D1 c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] A6 , D5 0 M A4 , D3 , A + A2 + 0 1 A ,D A A2 , D1 c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] A6 , D5 0 M A4 , D3 , A + A2 + 0 1 A ,D A A2 , D1 c 353 / 406 Beispiel für Instruktionsselektion Loads : D → M[A] D → M[A + A] Computations : D→c D→D+D Moves : D→A A →D Betrachte: M[A + c] A6 , D5 0 M A4 , D3 , A + A2 + 0 1 A ,D A2 A c 5,3 , D1 353 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat Wir verzichten auf eine ausführliche Behandlung: 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat Wir verzichten auf eine ausführliche Behandlung: auf modernen Prozessoren führen komplexe Instruktionen (wenn sie existieren) oft zu stalls in der Pipeline 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat Wir verzichten auf eine ausführliche Behandlung: auf modernen Prozessoren führen komplexe Instruktionen (wenn sie existieren) oft zu stalls in der Pipeline effiziente komplexe Instruktionen wie SIMD sind nicht durch Instruktionsselektion erzeugbar 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat Wir verzichten auf eine ausführliche Behandlung: auf modernen Prozessoren führen komplexe Instruktionen (wenn sie existieren) oft zu stalls in der Pipeline effiziente komplexe Instruktionen wie SIMD sind nicht durch Instruktionsselektion erzeugbar moderne Prozessoren haben Universalregister die Anwendbarkeit von Instruktionen hängt nicht von ihren Argumenten ab Verbesserung des Codes können auch auf den emittierten Funktionen geschehen 354 / 406 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) muss schnell durchgeführt werden übersetze die Anwendung der Regeln in einen endlichen, deterministischen Baum-Automaten funktioniert nur wenn jede Operation nur ein Ergebnis hat Wir verzichten auf eine ausführliche Behandlung: auf modernen Prozessoren führen komplexe Instruktionen (wenn sie existieren) oft zu stalls in der Pipeline effiziente komplexe Instruktionen wie SIMD sind nicht durch Instruktionsselektion erzeugbar moderne Prozessoren haben Universalregister die Anwendbarkeit von Instruktionen hängt nicht von ihren Argumenten ab Verbesserung des Codes können auch auf den emittierten Funktionen geschehen ; generiere nur einfachen Register-Code und optimiere diesen 354 / 406 Übersetzung ganzer Programme Kapitel 2: Codeerzeugung für die Register-C-Maschine 355 / 406 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. 356 / 406 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. arithmetische Operationen können oft nur auf diesen Registern ausgeführt werden Zugriffe auf den Speicher werden durch Adressen in Registern vorgenommen Register müssen gesichert werden, wenn Funktionen aufgerufen werden 356 / 406 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. arithmetische Operationen können oft nur auf diesen Registern ausgeführt werden Zugriffe auf den Speicher werden durch Adressen in Registern vorgenommen Register müssen gesichert werden, wenn Funktionen aufgerufen werden Die Übersetzung von Code für einen solchen Prozessor muss: 356 / 406 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. arithmetische Operationen können oft nur auf diesen Registern ausgeführt werden Zugriffe auf den Speicher werden durch Adressen in Registern vorgenommen Register müssen gesichert werden, wenn Funktionen aufgerufen werden Die Übersetzung von Code für einen solchen Prozessor muss: 1 Variablen und Funktionsargumente in Registern speichern 2 während eines Funktionsaufrufes die entsprechenden Register auf dem Keller sichern 3 beliebige Berechnungen mittels endlich vieler Register ausdrücken 356 / 406 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. arithmetische Operationen können oft nur auf diesen Registern ausgeführt werden Zugriffe auf den Speicher werden durch Adressen in Registern vorgenommen Register müssen gesichert werden, wenn Funktionen aufgerufen werden Die Übersetzung von Code für einen solchen Prozessor muss: 1 Variablen und Funktionsargumente in Registern speichern 2 während eines Funktionsaufrufes die entsprechenden Register auf dem Keller sichern 3 beliebige Berechnungen mittels endlich vieler Register ausdrücken ; betrachte nur die ersten zwei Punkte (und behandle den letzten Punkt als separates Problem) 356 / 406 Prinzip der Register C-Maschine Die R-CMa besitzt einen Stack, ein Code- und Heap-Segment wie die CMa, und zusätzlich zwei unbegrenzte Mengen an Registern. lokale Register sind R1 , R2 , . . . Ri , . . . globale Register sind R0 , R−1 , . . . Rj , . . . C 0 1 PC S 0 SP Rloc R1 R6 Rglob R0 R−4 357 / 406 Die Registersätze der R-CMa Die zwei Registersätze haben die folgenden Aufgaben: 1 die lokalen Register Ri speichern lokale Variablen einer Funktion speichern temporäre Zwischenergebnisse können effizient auf den Stack gerettet werden 358 / 406 Die Registersätze der R-CMa Die zwei Registersätze haben die folgenden Aufgaben: 1 die lokalen Register Ri speichern lokale Variablen einer Funktion speichern temporäre Zwischenergebnisse können effizient auf den Stack gerettet werden 2 die globalen Register Ri speichern Parameter einer Funktion speichern den Rückgabewert einer Funktion 358 / 406 Die Registersätze der R-CMa Die zwei Registersätze haben die folgenden Aufgaben: 1 die lokalen Register Ri speichern lokale Variablen einer Funktion speichern temporäre Zwischenergebnisse können effizient auf den Stack gerettet werden 2 die globalen Register Ri speichern Parameter einer Funktion speichern den Rückgabewert einer Funktion ; zur Verwaltung erweitern wir die Adressumgebung ρ 358 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert 359 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert Entsprechend definieren wir ρ : Var → {G, L, RG, RL} × N wie folgt: ρ x = hG, ai: Variable x ist an absoluter Adresse a gespeichert ρ x = hL, ai: Variable x ist an Adresse FP + a gespeichert ρ x = hRG, ai: Variable x ist im globalen Register Ra gespeichert ρ x = hRL, ai: Variable x ist im lokalen Register Ra gespeichert 359 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert Entsprechend definieren wir ρ : Var → {G, L, RG, RL} × N wie folgt: ρ x = hG, ai: Variable x ist an absoluter Adresse a gespeichert ρ x = hL, ai: Variable x ist an Adresse FP + a gespeichert ρ x = hRG, ai: Variable x ist im globalen Register Ra gespeichert ρ x = hRL, ai: Variable x ist im lokalen Register Ra gespeichert Beachte: eine Variable x kann nur einen Eintrag in ρ haben. Allerdings: 359 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert Entsprechend definieren wir ρ : Var → {G, L, RG, RL} × N wie folgt: ρ x = hG, ai: Variable x ist an absoluter Adresse a gespeichert ρ x = hL, ai: Variable x ist an Adresse FP + a gespeichert ρ x = hRG, ai: Variable x ist im globalen Register Ra gespeichert ρ x = hRL, ai: Variable x ist im lokalen Register Ra gespeichert Beachte: eine Variable x kann nur einen Eintrag in ρ haben. Allerdings: ρ kann unterschiedlich sein an verschiedenen Programmpunkten 359 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert Entsprechend definieren wir ρ : Var → {G, L, RG, RL} × N wie folgt: ρ x = hG, ai: Variable x ist an absoluter Adresse a gespeichert ρ x = hL, ai: Variable x ist an Adresse FP + a gespeichert ρ x = hRG, ai: Variable x ist im globalen Register Ra gespeichert ρ x = hRL, ai: Variable x ist im lokalen Register Ra gespeichert Beachte: eine Variable x kann nur einen Eintrag in ρ haben. Allerdings: ρ kann unterschiedlich sein an verschiedenen Programmpunkten d.h. x kann einem Register zugewiesen sein 359 / 406 Adressumgebung Eine Variable (global, lokal oder Parameter) kann in einem von vier verschiedenen Bereichen existieren. 1 Global: eine Variable ist global 2 Lokal: eine Variable liegt auf dem aktuellen Kellerrahmen 3 RG: eine Variable ist in einem globalen Register gespeichert 4 RL: eine Variable ist in einem lokalen Register gespeichert Entsprechend definieren wir ρ : Var → {G, L, RG, RL} × N wie folgt: ρ x = hG, ai: Variable x ist an absoluter Adresse a gespeichert ρ x = hL, ai: Variable x ist an Adresse FP + a gespeichert ρ x = hRG, ai: Variable x ist im globalen Register Ra gespeichert ρ x = hRL, ai: Variable x ist im lokalen Register Ra gespeichert Beachte: eine Variable x kann nur einen Eintrag in ρ haben. Allerdings: ρ kann unterschiedlich sein an verschiedenen Programmpunkten d.h. x kann einem Register zugewiesen sein und an einem anderen Punkt einer Speicherzelle 359 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben Register haben keine Adresse 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben Register haben keine Adresse der Code p=&x kann nicht übersetzt werden, wenn x einem Register zugewiesen ist 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben Register haben keine Adresse der Code p=&x kann nicht übersetzt werden, wenn x einem Register zugewiesen ist ermittle von welchen Variablen die Adresse genommen wurde weise diesen Variablen eine Speicherstelle auf dem Stack zu 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben Register haben keine Adresse der Code p=&x kann nicht übersetzt werden, wenn x einem Register zugewiesen ist ermittle von welchen Variablen die Adresse genommen wurde weise diesen Variablen eine Speicherstelle auf dem Stack zu globale Variablen sind im Speicher angesiedelt 360 / 406 Vergeben von Adressen an Variablen Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle zugewiesen werden auf der R-CMa existieren genug Register für alle Variablen Felder (arrays) und dynamische Strukturen bleiben aber weiterhin im Speicher Variablen, deren Adresse genommen wird, müssen auch im Speicher bleiben Register haben keine Adresse der Code p=&x kann nicht übersetzt werden, wenn x einem Register zugewiesen ist ermittle von welchen Variablen die Adresse genommen wurde weise diesen Variablen eine Speicherstelle auf dem Stack zu globale Variablen sind im Speicher angesiedelt möglicherweise können globale Variablen temporär in lokalen Registern gehalten werden 360 / 406 Codeerzeugung für die R-CMa Funktionen zur Codeerzeugung: Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden drei Funktionen: codei s ρ: generiert Code für Anweisung s codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt 361 / 406 Codeerzeugung für die R-CMa Funktionen zur Codeerzeugung: Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden drei Funktionen: codei s ρ: generiert Code für Anweisung s codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt wobei i gibt das zuletzt benutzte Register an, i + 1, i + 2, . . . sind unbenutzt. Das Ergebnis von codeiR und codeiL wird immer in das temporäre Register Ri gespeichert. 361 / 406 Codeerzeugung für die R-CMa Funktionen zur Codeerzeugung: Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden drei Funktionen: codei s ρ: generiert Code für Anweisung s codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt wobei i gibt das zuletzt benutzte Register an, i + 1, i + 2, . . . sind unbenutzt. Das Ergebnis von codeiR und codeiL wird immer in das temporäre Register Ri gespeichert. Beachte: Es ist nicht möglich Code zu erzeugen, der direkt in ein Register k < i schreibt. ; Der erzeugte Code hat braucht mehr Register als notwendig. 361 / 406 Codeerzeugung für die R-CMa Funktionen zur Codeerzeugung: Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden drei Funktionen: codei s ρ: generiert Code für Anweisung s codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt wobei i gibt das zuletzt benutzte Register an, i + 1, i + 2, . . . sind unbenutzt. Das Ergebnis von codeiR und codeiL wird immer in das temporäre Register Ri gespeichert. Beachte: Es ist nicht möglich Code zu erzeugen, der direkt in ein Register k < i schreibt. ; Der erzeugte Code hat braucht mehr Register als notwendig. Die Beseitigung von temporären Registern überlassen wir späteren Optimierungen (i.e. Registerallokation) 361 / 406 Übersetzung von Ausdrücken Sei folgende Funktion gegeben: void f(void) { int x,y,z; x = y+3*z; } Ordne den Variablen die ersten drei Register zu: ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i} Setzte t = 4 da R4 das erste freie Register ist. Erzeuge code4 x=y+3*z ρ = code4R y+3*z ρ move R1 R4 362 / 406 Übersetzung von Ausdrücken Sei folgende Funktion gegeben: void f(void) { int x,y,z; x = y+3*z; } Ordne den Variablen die ersten drei Register zu: ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i} Setzte t = 4 da R4 das erste freie Register ist. Erzeuge code4 x=y+3*z ρ code4R y+3*z ρ = code4R y+3*z ρ move R1 R4 = code5R 3*z ρ add R4 R2 R5 362 / 406 Übersetzung von Ausdrücken Sei folgende Funktion gegeben: void f(void) { int x,y,z; x = y+3*z; } Ordne den Variablen die ersten drei Register zu: ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i} Setzte t = 4 da R4 das erste freie Register ist. Erzeuge code4 x=y+3*z ρ = code4R y+3*z ρ move R1 R4 code4R y+3*z ρ = code5R 3*z ρ add R4 R2 R5 code5R 3*z ρ = code6R 3 ρ mul R5 R6 R3 362 / 406 Übersetzung von Ausdrücken Sei folgende Funktion gegeben: void f(void) { int x,y,z; x = y+3*z; } Ordne den Variablen die ersten drei Register zu: ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i} Setzte t = 4 da R4 das erste freie Register ist. Erzeuge code4 x=y+3*z ρ = code4R y+3*z ρ move R1 R4 code4R y+3*z ρ = code5R 3*z ρ add R4 R2 R5 code5R 3*z ρ = code6R 3 ρ mul R5 R6 R3 code6R 3 ρ = loadc R6 3 362 / 406 Übersetzung von Ausdrücken Sei folgende Funktion gegeben: void f(void) { int x,y,z; x = y+3*z; } Ordne den Variablen die ersten drei Register zu: ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i} Setzte t = 4 da R4 das erste freie Register ist. Erzeuge code4 x=y+3*z ρ = code4R y+3*z ρ move R1 R4 code4R y+3*z ρ = code5R 3*z ρ add R4 R2 R5 code5R 3*z ρ = code6R 3 ρ mul R5 R6 R3 code6R 3 ρ = loadc R6 3 ; die Zuweisung x=y+3*z wird in nur vier Instruktionen loadc R6 3; mul R5 R6 R3 ; add R4 R2 R5 ; move R1 R4 übersetzt 362 / 406 Übersetzen von Ausdrücken Sei op = {add, sub, div, mul, mod, le, gr, eq, leq, geq, and, or}. Die R-CMa kennt für jeden Operator op eine Instruktion op Ri Rj Rk wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist. Entsprechend erzeugen wir den Code wie folgt: codeiR e1 op e2 ρ = codeiR e1 ρ codeRi+1 e2 ρ op Ri Ri Ri+1 363 / 406 Übersetzen von Ausdrücken Sei op = {add, sub, div, mul, mod, le, gr, eq, leq, geq, and, or}. Die R-CMa kennt für jeden Operator op eine Instruktion op Ri Rj Rk wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist. Entsprechend erzeugen wir den Code wie folgt: codeiR e1 op e2 ρ = codeiR e1 ρ codeRi+1 e2 ρ op Ri Ri Ri+1 Beispiel: Übersetze 3*4 mit i = 4: code4R 3*4 ρ = code4R 3 ρ code5R 4 ρ mul R4 R4 R5 363 / 406 Übersetzen von Ausdrücken Sei op = {add, sub, div, mul, mod, le, gr, eq, leq, geq, and, or}. Die R-CMa kennt für jeden Operator op eine Instruktion op Ri Rj Rk wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist. Entsprechend erzeugen wir den Code wie folgt: codeiR e1 op e2 ρ = codeiR e1 ρ codeRi+1 e2 ρ op Ri Ri Ri+1 Beispiel: Übersetze 3*4 mit i = 4: code4R 3*4 ρ = loadc R4 3 loadc R5 4 mul R4 R4 R5 363 / 406 Verwaltung temporärer Register Beachte, dass temporäre Register wiederverwendet werden: Übersetze 3*4+3*4 mit t = 4: code4R 3*4+3*4 ρ = code4R 3*4 ρ code5R 3*4 ρ add R4 R4 R5 mit codeiR 3*4 ρ = loadc Ri 3 loadc Ri+1 4 mul Ri Ri Ri+1 ergibt sich code4R 3*4+3*4 ρ = 364 / 406 Verwaltung temporärer Register Beachte, dass temporäre Register wiederverwendet werden: Übersetze 3*4+3*4 mit t = 4: code4R 3*4+3*4 ρ = code4R 3*4 ρ code5R 3*4 ρ add R4 R4 R5 mit codeiR 3*4 ρ = loadc Ri 3 loadc Ri+1 4 mul Ri Ri Ri+1 ergibt sich code4R 3*4+3*4 ρ = loadc R4 3 loadc R5 4 mul R4 R4 R5 loadc R5 3 loadc R6 4 mul R5 R5 R6 add R4 R4 R5 364 / 406 Semantik der Operatoren Die Operatoren haben die folgende Semantik: add Ri Rj Rk sub Ri Rj Rk div Ri Rj Rk mul Ri Rj Rk mod Ri Rj Rk le Ri Rj Rk gr Ri Rj Rk eq Ri Rj Rk leq Ri Rj Rk geq Ri Rj Rk and Ri Rj Rk or Ri Rj Rk Ri ← Rj + Rk Ri ← Rj − Rk Ri ← Rj /Rk Ri ← Rj ∗ Rk Ri ← sgn(Rk )k wobei |Rj | = n|Rk | + k ∧ n ≥ 0, 0 ≤ k < |Rk | Ri ← if Rj < Rk then 1 else 0 Ri ← if Rj > Rk then 1 else 0 Ri ← if Rj = Rk then 1 else 0 Ri ← if Rj ≤ Rk then 1 else 0 Ri ← if Rj ≥ Rk then 1 else 0 Ri ← Rj & Rk // bit-wise and Ri ← Rj | Rk // bit-wise or 365 / 406 Semantik der Operatoren Die Operatoren haben die folgende Semantik: add Ri Rj Rk sub Ri Rj Rk div Ri Rj Rk mul Ri Rj Rk mod Ri Rj Rk le Ri Rj Rk gr Ri Rj Rk eq Ri Rj Rk leq Ri Rj Rk geq Ri Rj Rk and Ri Rj Rk or Ri Rj Rk Ri ← Rj + Rk Ri ← Rj − Rk Ri ← Rj /Rk Ri ← Rj ∗ Rk Ri ← sgn(Rk )k wobei |Rj | = n|Rk | + k ∧ n ≥ 0, 0 ≤ k < |Rk | Ri ← if Rj < Rk then 1 else 0 Ri ← if Rj > Rk then 1 else 0 Ri ← if Rj = Rk then 1 else 0 Ri ← if Rj ≤ Rk then 1 else 0 Ri ← if Rj ≥ Rk then 1 else 0 Ri ← Rj & Rk // bit-wise and Ri ← Rj | Rk // bit-wise or Beachte: alle Ergebnisse werden ≡232 berechnet 365 / 406 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri 366 / 406 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri Beachte: Wir verwenden dasselbe Register. 366 / 406 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri Beachte: Wir verwenden dasselbe Register. Beispiel: Übersetze -4 nach R5 : code5R -4 ρ = code5R 4 ρ neg R5 R5 366 / 406 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri Beachte: Wir verwenden dasselbe Register. Beispiel: Übersetze -4 nach R5 : code5R -4 ρ = loadc R5 4 neg R5 R5 366 / 406 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri Beachte: Wir verwenden dasselbe Register. Beispiel: Übersetze -4 nach R5 : code5R -4 ρ = loadc R5 4 neg R5 R5 Die Operatoren haben die folgende Semantik: not Ri Rj net Ri Rj Ri ← if Rj = 0 then 1 else 0 Ri ← −Rj 366 / 406 Datentransfer Instruktionen der R-CMa (I) Betrachte C-Ma Instruktionen für den Lesezugriff auf den Speicher: Instruktion load loadc c loadrc c loada c loadr c move k Semantik Intuition S[SP] ← S[S[SP]] lade Wert von Adresse SP ← SP + 1; S[SP] ← c lade Konstante SP ← SP + 1; S[SP] ← c + FP lade Konstante rel. zu FP loadc c; load lade globale Variable loadrc c; load lade lokale Variable ... kopiere k Werte auf Keller Die Varianten der R-CMa erweitern diese um Register-Argumente: Instruktion load Ri Rj loadc Ri c loadrc Ri c loada Ri c loadr Ri c move Ri Rj move k Rj Semantik Ri ← S[Rj ] Ri ← c Ri ← FP + c Ri ← S[c] Ri ← S[FP + c] Ri ← Rj k [S[SP + i] ← S[Rj + i]]i=0 Intuition lade Wert von Adresse lade Konstante lade Konstante rel. zu FP lade globale Variable lade lokale Variable transferiere Wert zw. Registern kopiere k Werte auf den Keller 367 / 406 Datentransfer Instruktionen der R-CMa (II) Betrachte C-Ma Instruktionen für das Schreiben in den Speicher: Instruktion store storea c storer c Semantik S[S[SP]] ← S[SP − 1]; SP ← SP − 1 loadc c; store loadrc c; store Intuition speichere Wert an Adresse schreibe globale Variable schreibe lokale Variable Die Varianten der R-CMa erweitern diese um Register-Argumente: Instruktion store Ri Rj storea c Ri storer c Ri Semantik S[Ri ] ← Rj S[c] ← Ri S[FP + c] ← Ri Intuition speichere Wert an Adresse schreibe globale Variable schreibe lokale Variable 368 / 406 Datentransfer Instruktionen der R-CMa (II) Betrachte C-Ma Instruktionen für das Schreiben in den Speicher: Instruktion store storea c storer c Semantik S[S[SP]] ← S[SP − 1]; SP ← SP − 1 loadc c; store loadrc c; store Intuition speichere Wert an Adresse schreibe globale Variable schreibe lokale Variable Die Varianten der R-CMa erweitern diese um Register-Argumente: Instruktion store Ri Rj storea c Ri storer c Ri Semantik S[Ri ] ← Rj S[c] ← Ri S[FP + c] ← Ri Intuition speichere Wert an Adresse schreibe globale Variable schreibe lokale Variable Beachte: alle Instruktionen benutzen die Intel-Konvention (im Ggs. zur AT&T Konvention): op dst src1 . . . srcn . Vergleiche: Instruktion loada Ri c storea c Ri Semantik Intuition Ri ← S[c] lade globale Variable S[c] ← Ri schreibe globale Variable 368 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} 369 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} Die Adresse einer Variablen wird wie folgt in Ri berechnet: loadc Ri a if ρ x = hG, ai codeiL x ρ = loadrc Ri a if ρ x = hL, ai 369 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} Die Adresse einer Variablen wird wie folgt in Ri berechnet: loadc Ri a if ρ x = hG, ai codeiL x ρ = loadrc Ri a if ρ x = hL, ai Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert! 369 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} Die Adresse einer Variablen wird wie folgt in Ri berechnet: loadc Ri a if ρ x = hG, ai codeiL x ρ = loadrc Ri a if ρ x = hL, ai Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert! Beobachtung: intuitiv: ein Register hat keine Adresse 369 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} Die Adresse einer Variablen wird wie folgt in Ri berechnet: loadc Ri a if ρ x = hG, ai codeiL x ρ = loadrc Ri a if ρ x = hL, ai Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert! Beobachtung: intuitiv: ein Register hat keine Adresse ein Register darf während der Codegenerierung nie als L-Wert auftreten 369 / 406 Übersetzung von Variablen Der Wert einer Variablen wird wie folgt in Register Ri geladen: if ρ x = hG, ai loada Ri a loadr Ri a if ρ x = hL, ai codeiR x ρ = move Ri Rj if ρ x = hr, ji ∧ r ∈ {RG, RL} Die Adresse einer Variablen wird wie folgt in Ri berechnet: loadc Ri a if ρ x = hG, ai codeiL x ρ = loadrc Ri a if ρ x = hL, ai Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert! Beobachtung: intuitiv: ein Register hat keine Adresse ein Register darf während der Codegenerierung nie als L-Wert auftreten dies hat Auswirkungen auf die Übersetzung von Zuweisungen 369 / 406 Übersetzung von Zuweisungen Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch: das Ermitteln das R-Wertes von 2*y, das Ermitteln des L-Wertes von x und einem store Befehl. Analog definieren wir: codeiR e1 = e2 ρ = codeiR e2 ρ codei+1 e1 ρ L store Ri+1 Ri 370 / 406 Übersetzung von Zuweisungen Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch: das Ermitteln das R-Wertes von 2*y, das Ermitteln des L-Wertes von x und einem store Befehl. Analog definieren wir: codeiR e1 = e2 ρ = codeiR e2 ρ codei+1 e1 ρ L store Ri+1 Ri Achtung: undefiniertes Resultat, falls e1 ≡ x und ρ x = hRL, ji. 370 / 406 Übersetzung von Zuweisungen Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch: das Ermitteln das R-Wertes von 2*y, das Ermitteln des L-Wertes von x und einem store Befehl. Analog definieren wir: codeiR e1 = e2 ρ = codeiR e2 ρ codei+1 e1 ρ L store Ri+1 Ri Achtung: undefiniertes Resultat, falls e1 ≡ x und ρ x = hRL, ji. Definiere Spezialfall: Sei ρ x = hRL, ji oder ρ x = hRL, ji, dann definiere codeiR x = e2 ρ = codeiR e2 ρ move Rj Ri 370 / 406 Übersetzung von Zuweisungen Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch: das Ermitteln das R-Wertes von 2*y, das Ermitteln des L-Wertes von x und einem store Befehl. Analog definieren wir: codeiR e1 = e2 ρ = codeiR e2 ρ codei+1 e1 ρ L store Ri+1 Ri Achtung: undefiniertes Resultat, falls e1 ≡ x und ρ x = hRL, ji. Definiere Spezialfall: Sei ρ x = hRL, ji oder ρ x = hRL, ji, dann definiere codeiR x = e2 ρ = codeiR e2 ρ move Rj Ri Beachte: codeiR x = e2 ρ = codejR e2 ρ wäre inkorrekt! 370 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ 371 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ Dereferenzieren von Zeigern zum Lesen (R-Wert): codeiR ∗e ρ = codeiL e ρ load Ri Ri 371 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ Dereferenzieren von Zeigern zum Lesen (R-Wert): codeiR ∗e ρ = codeiL e ρ load Ri Ri Dereferenzieren von Zeigern zum Schreiben (L-Wert): codeiL ∗e ρ = codeiR e ρ 371 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ Dereferenzieren von Zeigern zum Lesen (R-Wert): codeiR ∗e ρ = codeiL e ρ load Ri Ri Dereferenzieren von Zeigern zum Schreiben (L-Wert): codeiL ∗e ρ = codeiR e ρ Beachte: eine Variable x (int oder struct), deren Adresse genommen wurde, muss im Speicher allokiert werden, d.h. ρ x = hL, oi oder ρ x = hG, oi 371 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ Dereferenzieren von Zeigern zum Lesen (R-Wert): codeiR ∗e ρ = codeiL e ρ load Ri Ri Dereferenzieren von Zeigern zum Schreiben (L-Wert): codeiL ∗e ρ = codeiR e ρ Beachte: eine Variable x (int oder struct), deren Adresse genommen wurde, muss im Speicher allokiert werden, d.h. ρ x = hL, oi oder ρ x = hG, oi auf ein Feld (array) wird immer mittels Zeiger zugegriffen, muss also auch im Speicher allokiert werden 371 / 406 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ Dereferenzieren von Zeigern zum Lesen (R-Wert): codeiR ∗e ρ = codeiL e ρ load Ri Ri Dereferenzieren von Zeigern zum Schreiben (L-Wert): codeiL ∗e ρ = codeiR e ρ Beachte: eine Variable x (int oder struct), deren Adresse genommen wurde, muss im Speicher allokiert werden, d.h. ρ x = hL, oi oder ρ x = hG, oi auf ein Feld (array) wird immer mittels Zeiger zugegriffen, muss also auch im Speicher allokiert werden Optimierung: Speichere Elemente eines struct in Registern, während Zeigerzugriffe diese nicht verändern können 371 / 406 Übersetzung ganzer Programme Kapitel 3: Übersetzung von Anweisungen 372 / 406 Übersetzung von Anweisungen Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung übersetzen können: 373 / 406 Übersetzung von Anweisungen Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung übersetzen können: Definiere dazu: codei e1 = e2 ρ = codeiR e1 = e2 ρ Das temporäre Register Ri wird dabei ignoriert. Allgemeiner: codei e ρ = codeiR e ρ 373 / 406 Übersetzung von Anweisungen Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung übersetzen können: Definiere dazu: codei e1 = e2 ρ = codeiR e1 = e2 ρ Das temporäre Register Ri wird dabei ignoriert. Allgemeiner: codei e ρ = codeiR e ρ Zur Übersetzung von Kontrollfluss-Anweisungen definiere folgende Befehle: Instruktion jump A jumpz Ri A jumpi Ri A Semantik PC ← A if Ri = 0 then PC ← A PC ← A + Ri Intuition springe zur Adresse A springe wenn Ri = 0 springe indirekt 373 / 406 Übersetzung von bedingten Anweisungen code c code tt code ee Übersetzung von if ( c ) tt else ee. codei if(c) tt else ee ρ = codeiR c ρ jumpz Ri A codei tt ρ jump B A : codei ee ρ B: 374 / 406 Übersetzung von Schleifen Übersetzung von while Schleifen: codei while(e) s ρ = A : codeiR e ρ jumpz Ri B codei s ρ jump A B: 375 / 406 Übersetzung von Schleifen Übersetzung von while Schleifen: codei while(e) s ρ = A : codeiR e ρ jumpz Ri B codei s ρ jump A B: Übersetzung von for Schleifen: codei for(e1 ; e2 ; e3 ) s ρ = codeiR e1 ρ A : codeiR e2 ρ jumpz Ri B codei s ρ codeiR e3 ρ jump A B: 375 / 406 Übersetzung von Fallunterscheidungen Sei ein switch s mit k aufeinanderfolgenden case Fällen gegeben: switch (e) { case c0 : s0 ; break; . . . case ck−1 : sk−1 ; break; default: s; break; } d.h. ci + 1 = ci+1 für i = [0, k − 1]. 376 / 406 Übersetzung von Fallunterscheidungen Sei ein switch s mit k aufeinanderfolgenden case Fällen gegeben: switch (e) { case c0 : s0 ; break; . . . case ck−1 : sk−1 ; break; default: s; break; } d.h. ci + 1 = ci+1 für i = [0, k − 1]. Definiere codei s ρ wie folgt: codei s ρ = codeiR e ρ codei s0 ρ B: .. . jump D .. . C: checki c0 ck−1 B A0 : .. . jump A0 .. . jump Ak−1 Ak−1 : codei sk−1 ρ jump D 376 / 406 Übersetzung von Fallunterscheidungen Sei ein switch s mit k aufeinanderfolgenden case Fällen gegeben: switch (e) { case c0 : s0 ; break; . . . case ck−1 : sk−1 ; break; default: s; break; } d.h. ci + 1 = ci+1 für i = [0, k − 1]. Definiere codei s ρ wie folgt: codei s ρ = codeiR e ρ codei s0 ρ B: .. . jump D .. . C: checki c0 ck−1 B A0 : .. . jump A0 .. . jump Ak−1 Ak−1 : codei sk−1 ρ jump D checki l u B prüft ob l ≤ Ri < u gilt und springt entsprechend. 376 / 406 Übersetztung des checki Makros Das Makro checki l u B prüft ob l ≤ Ri < u. Sei k = u − l. wenn l ≤ Ri < u, springt es nach B + Ri − u wenn Ri < l oder Ri ≥ u springt es nach C 377 / 406 Übersetztung des checki Makros Das Makro checki l u B prüft ob l ≤ Ri < u. Sei k = u − l. wenn l ≤ Ri < u, springt es nach B + Ri − u wenn Ri < l oder Ri ≥ u springt es nach C Wir definieren: checki l u B = loadc Ri+1 l geq Ri+2 Ri Ri+1 jumpz Ri+2 E sub Ri Ri Ri+1 loadc Ri+1 k geq Ri+2 Ri Ri+1 jumpz Ri+2 D E : loadc Ri k D : jumpi Ri B 377 / 406 Übersetztung des checki Makros Das Makro checki l u B prüft ob l ≤ Ri < u. Sei k = u − l. wenn l ≤ Ri < u, springt es nach B + Ri − u wenn Ri < l oder Ri ≥ u springt es nach C Wir definieren: checki l u B = loadc Ri+1 l geq Ri+2 Ri Ri+1 jumpz Ri+2 E sub Ri Ri Ri+1 loadc Ri+1 k geq Ri+2 Ri Ri+1 jumpz Ri+2 D E : loadc Ri k D : jumpi Ri B B: .. . jump A0 .. . jump Ak−1 C: Beachte: ein Sprung jumpi Ri B mit Ri = k landet bei C. 377 / 406 Übersetzung ganzer Programme Kapitel 4: Übersetzung von Funktionen 378 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: keines der globalen Registern wird während der Ausführung der aktuellen Funktion benutzt 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: keines der globalen Registern wird während der Ausführung der aktuellen Funktion benutzt das i te Argument einer Funktion wir in Register Ri übergeben 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: keines der globalen Registern wird während der Ausführung der aktuellen Funktion benutzt das i te Argument einer Funktion wir in Register Ri übergeben der Rückgabewert einer Funktion wird in R0 gespeichert 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: keines der globalen Registern wird während der Ausführung der aktuellen Funktion benutzt das i te Argument einer Funktion wir in Register Ri übergeben der Rückgabewert einer Funktion wird in R0 gespeichert alle lokalen Register werden von der aufrufenden Funktion gespeichert 379 / 406 Registerverwaltung bei Funktionsaufrufen Die zwei Registersätze (global und lokal) werden wie folgt verwendet: lokale Variablen werden in lokalen Registern gespeichert (RL) Zwischenergebnisse werden auch in lokalen Registern gespeichert Parameter werden in globalen Registern gespeichert (RG) Konvention: keines der globalen Registern wird während der Ausführung der aktuellen Funktion benutzt das i te Argument einer Funktion wir in Register Ri übergeben der Rückgabewert einer Funktion wird in R0 gespeichert alle lokalen Register werden von der aufrufenden Funktion gespeichert Definition Sei f eine Funktion die g aufruft. Ein Register Ri heißt caller-saved, wenn f Ri sichert und g es überschreiben darf callee-saved, wenn f Ri nicht sichert und g es vor dem Rücksprung wiederherstellen muss 379 / 406 Übersetzung von Funktionsaufrufen Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt: codeiR g(e1 , . . . en ) ρ = codeiR e1 ρ move R−1 Ri .. . codeiR en ρ move R−n Ri codeiR g ρ saveloc R1 Ri−1 mark call Ri restoreloc R1 Ri−1 move Ri R0 380 / 406 Übersetzung von Funktionsaufrufen Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt: codeiR g(e1 , . . . en ) ρ = codeiR e1 ρ move R−1 Ri .. . codeiR en ρ move R−n Ri codeiR g ρ saveloc R1 Ri−1 mark call Ri restoreloc R1 Ri−1 move Ri R0 Neue Instruktionen: saveloc Ri Rj legt die Register Ri , Ri+1 . . . Rj auf dem Stapel ab (inklusive!) restoreloc Ri Rj nimmt die Register Rj , Rj−1 , . . . Ri vom Stapel runter 380 / 406 Übersetzung von Funktionsaufrufen Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt: codeiR g(e1 , . . . en ) ρ = codeiR e1 ρ move R−1 Ri .. . codeiR en ρ move R−n Ri codeiR g ρ saveloc R1 Ri−1 mark call Ri restoreloc R1 Ri−1 move Ri R0 Neue Instruktionen: saveloc Ri Rj legt die Register Ri , Ri+1 . . . Rj auf dem Stapel ab (inklusive!) restoreloc Ri Rj nimmt die Register Rj , Rj−1 , . . . Ri vom Stapel runter Argumente auf dem Stack: push Ri pro Wert und pop k für k Werte 380 / 406 Rückgabewerte einer Funktion Die globalen Register werden auch benutzt um den Rückgabewert zu übermitteln: codei return e ρ = codeiR e ρ move R0 Ri return 381 / 406 Rückgabewerte einer Funktion Die globalen Register werden auch benutzt um den Rückgabewert zu übermitteln: codei return e ρ = codeiR e ρ move R0 Ri return Alternative ohne Rückgabewert: codei return ρ = return 381 / 406 Rückgabewerte einer Funktion Die globalen Register werden auch benutzt um den Rückgabewert zu übermitteln: codei return e ρ = codeiR e ρ move R0 Ri return Alternative ohne Rückgabewert: codei return ρ = return Globale Register werden ansonsten im Funktionsrumpf nicht benutzt: Vorteil: an jeder Stelle im Rumpf können andere Funktionen aufgerufen werden ohne globale Register retten zu müssen Nachteil: beim Eintritt in eine Funktion müssen die globalen Register gesichert werden 381 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert die Parameter der Funktion stehen in den Registern R−1 , . . . R−n 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert die Parameter der Funktion stehen in den Registern R−1 , . . . R−n ρ0 ist die um lokale Variablen decls und Funktions-Parameter args erweiterte Umgebung ρ 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert die Parameter der Funktion stehen in den Registern R−1 , . . . R−n ρ0 ist die um lokale Variablen decls und Funktions-Parameter args erweiterte Umgebung ρ Argumente auf dem Stack: zusätzlich alloc k 382 / 406 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1 tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codel+n+1 ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert die Parameter der Funktion stehen in den Registern R−1 , . . . R−n ρ0 ist die um lokale Variablen decls und Funktions-Parameter args erweiterte Umgebung ρ Argumente auf dem Stack: zusätzlich alloc k Sind die move Instruktionen immer nötig? 382 / 406 Übersetzen ganzer Programme Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten. code1 P ρ = enter (k + 3) alloc k loadc R1 _main saveloc R1 R1 mark call R1 restoreloc R1 R1 halt _f1 : codei F1 ρ .. . _fn : codei Fn ρ 383 / 406 Übersetzen ganzer Programme Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten. code1 P ρ = enter (k + 3) alloc k loadc R1 _main saveloc R1 R1 mark call R1 restoreloc R1 R1 halt _f1 : codei F1 ρ .. . _fn : codei Fn ρ Diskussion: k sind die Anzahl der Stackplätze für globale Variablen saveloc R1 R0 wäre ausreichend (d.h. rette kein Register) ρ enthält die Adressen der Funktionen und globalen Variablen 383 / 406 Übersetzung der Fakultätsfunktion Betrachte: int fac(int x) { if (x<=0) then return 1; else return x*fac(x-1); } _fac: i=2 enter 5 move R1 R−1 move R2 R1 loadc R3 0 leq R2 R2 R3 jumpz R2 _A loadc R2 1 move R0 R2 return jump _B _A: i=3 i=4 i=3 3 mark+call save param. if (x<=0) to else return 1 _B: move R2 R1 move R3 R1 loadc R4 1 sub R3 R3 R4 move R−1 R3 loadc R3 _fac saveloc R1 R2 mark call R3 restoreloc R1 R2 move R3 R0 mul R2 R2 R3 move R0 R2 return return x*fac(x-1) x-1 fac(x-1) return x*... code is dead 384 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden Idee: Benutze ein Register Ri , das zur Zeit nicht genutzt wird, für den Wert eines anderen Registers Rj 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden Idee: Benutze ein Register Ri , das zur Zeit nicht genutzt wird, für den Wert eines anderen Registers Rj falls das Programm mehr als die verfügbaren Register braucht, allokiere einige Variablen auf dem Stack und nicht mehr in Registern 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden Idee: Benutze ein Register Ri , das zur Zeit nicht genutzt wird, für den Wert eines anderen Registers Rj falls das Programm mehr als die verfügbaren Register braucht, allokiere einige Variablen auf dem Stack und nicht mehr in Registern Wir benötigen eine Lösung für die folgenden Probleme: 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden Idee: Benutze ein Register Ri , das zur Zeit nicht genutzt wird, für den Wert eines anderen Registers Rj falls das Programm mehr als die verfügbaren Register braucht, allokiere einige Variablen auf dem Stack und nicht mehr in Registern Wir benötigen eine Lösung für die folgenden Probleme: bestimme, wann ein Register nicht benutzt ist 385 / 406 Realistische Register Maschinen Die R-CMa ist geeignet, auf einfache Weise Code für eine Registermaschine zu erzeugen. reale Maschinen haben nur eine fixe Anzahl an Registern die unendliche Menge an virtuellen Registern muss auf eine endliche Menge realer Register abgebildet werden Idee: Benutze ein Register Ri , das zur Zeit nicht genutzt wird, für den Wert eines anderen Registers Rj falls das Programm mehr als die verfügbaren Register braucht, allokiere einige Variablen auf dem Stack und nicht mehr in Registern Wir benötigen eine Lösung für die folgenden Probleme: bestimme, wann ein Register nicht benutzt ist weise mehreren virtuellen Registern das gleiche reale Register zu, wenn sie nicht gleichzeitig benutzt werden 385 / 406 Übersetzung ganzer Programme Kapitel 5: Liveness Analyse 386 / 406 Abstrakte Syntax Anstatt auf R-CMa Instruktionen zu arbeiten, benutzen wir folgende Pseudo-Sprache: • • • • • • • x, y, . . . R = e; R = M[e]; M[e1 ] = e2 ; if (e) s1 else s2 goto L; stop // // // // // // // Register Zuweisungen Lesen vom Speicher Schreiben in Speicher bedingter Sprung Sprung oder Schleife Funktion- oder Programmende 387 / 406 Abstrakte Syntax Anstatt auf R-CMa Instruktionen zu arbeiten, benutzen wir folgende Pseudo-Sprache: • • • • • • • x, y, . . . R = e; R = M[e]; M[e1 ] = e2 ; if (e) s1 else s2 goto L; stop // // // // // // // Register Zuweisungen Lesen vom Speicher Schreiben in Speicher bedingter Sprung Sprung oder Schleife Funktion- oder Programmende die Instruktionen können als Zwischensprache aufgefasst werden 387 / 406 Abstrakte Syntax Anstatt auf R-CMa Instruktionen zu arbeiten, benutzen wir folgende Pseudo-Sprache: • • • • • • • x, y, . . . R = e; R = M[e]; M[e1 ] = e2 ; if (e) s1 else s2 goto L; stop // // // // // // // Register Zuweisungen Lesen vom Speicher Schreiben in Speicher bedingter Sprung Sprung oder Schleife Funktion- oder Programmende die Instruktionen können als Zwischensprache aufgefasst werden ... oder als Abstraktion für R-CMa Instruktionen 387 / 406 Beispiel für Liveness Eine liveness Analyse bestimmt, an welchen Programmpunkten ein Register benutzt wird. Betrachte: 1: 2: 3: x = y + 2; y = 5; x = y + 3; 388 / 406 Beispiel für Liveness Eine liveness Analyse bestimmt, an welchen Programmpunkten ein Register benutzt wird. Betrachte: 1: 2: 3: x = y + 2; y = 5; x = y + 3; Der Wert von x an den Programmpunkten 1, 2 wird überschrieben bevor er benutzt wird. 388 / 406 Beispiel für Liveness Eine liveness Analyse bestimmt, an welchen Programmpunkten ein Register benutzt wird. Betrachte: 1: 2: 3: x = y + 2; y = 5; x = y + 3; Der Wert von x an den Programmpunkten 1, 2 wird überschrieben bevor er benutzt wird. Daher sagen wir, x ist tot (dead) an diesen Programmpunkten. 388 / 406 Motivation für liveness Analyse Eine liveness Analyse ist nützlich: Zuweisungen zu toten Variablen können entfernt werden solch unnütze Zuweisungen können durch andere Transformationen entstehen Definition Sei π = s1 ; . . . sn eine Folge von Anweisungen. Eine Variable x ist live (lebendig) am Programmpunkt s1 wenn die Variablen X nach sn live sind und 389 / 406 Motivation für liveness Analyse Eine liveness Analyse ist nützlich: Zuweisungen zu toten Variablen können entfernt werden solch unnütze Zuweisungen können durch andere Transformationen entstehen Definition Sei π = s1 ; . . . sn eine Folge von Anweisungen. Eine Variable x ist live (lebendig) am Programmpunkt s1 wenn die Variablen X nach sn live sind und x ∈ X und π enthält keine Definition von x or 389 / 406 Motivation für liveness Analyse Eine liveness Analyse ist nützlich: Zuweisungen zu toten Variablen können entfernt werden solch unnütze Zuweisungen können durch andere Transformationen entstehen Definition Sei π = s1 ; . . . sn eine Folge von Anweisungen. Eine Variable x ist live (lebendig) am Programmpunkt s1 wenn die Variablen X nach sn live sind und x ∈ X und π enthält keine Definition von x or π kann zerlegt werden in π = π1 kπ2 so dass k ist eine Benutzung von x und π 1 enthält keine Definition von x. s π1 k 389 / 406 Definition und Benutzung von Variablen Betrachte die Anweisungen s1 , . . . sn als Kanten in einem Graphen. Wir definieren Definition und Benutzung wie folgt: si ; Pos (e) Neg (e) x = e; x = M[e]; M[e1 ] = e2 ; Benutztung ∅ Vars (e) Vars (e) Vars (e) Vars (e) Vars (e1 ) ∪ Vars (e2 ) Definition ∅ ∅ ∅ {x} {x} ∅ 390 / 406 Definition und Benutzung von Variablen Betrachte die Anweisungen s1 , . . . sn als Kanten in einem Graphen. Wir definieren Definition und Benutzung wie folgt: si ; Pos (e) Neg (e) x = e; x = M[e]; M[e1 ] = e2 ; Benutztung ∅ Vars (e) Vars (e) Vars (e) Vars (e) Vars (e1 ) ∪ Vars (e2 ) Definition ∅ ∅ ∅ {x} {x} ∅ Beispiel: 1: 2: 3: x = y + 2; y = 5; x = y + 3; 390 / 406 Lebende und tote Variablen Definition Eine Variable x, die nicht live ist am Programmpunkt s entlang des Pfades π relativ zu X ist dead (tot) bei s relativ zu X. 391 / 406 Lebende und tote Variablen Definition Eine Variable x, die nicht live ist am Programmpunkt s entlang des Pfades π relativ zu X ist dead (tot) bei s relativ zu X. Beispiel: x = y + 2; 0 y = 5; 1 x = y + 3; 2 3 wobei X = ∅. Wie folgern: 0 1 2 3 live {y} ∅ {y} ∅ dead {x} {x, y} {x} {x, y} 391 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 y = 5; 2 x = y + 2; M[y] = x; 3 4 5 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 y = 5; 2 x = y + 2; M[y] = x; 3 4 5 ∅ 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 y = 5; 2 x = y + 2; M[y] = x; 3 4 {x, y} 5 ∅ 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 y = 5; 2 x = y + 2; M[y] = x; 3 4 {y} {x, y} 5 ∅ 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 y = 5; 2 x = y + 2; M[y] = x; 3 ∅ 4 {y} {x, y} 5 ∅ 392 / 406 Berechnung der liveness Information Wir berechnen die liveness Information durch ausführen des Program mit einer abstrakten Semantik: Idee: Ersetze jedes Anweisung si einer Kante k = (u, si , v) durch eine Funktion [[k]]] , die die Menge der lebenden Variablen L bei v in die Menge der lebenden Variablen bei u überführt. Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt: [[;]]] L [[Pos(e)]]] L [[x = e;]]] L [[x = M[e];]]] L [[M[e1 ] = e2 ;]]] L = L = [[Neg(e)]]] L = L ∪ Vars(e) = (L\{x}) ∪ Vars(e) = (L\{x}) ∪ Vars(e) = L ∪ Vars(e1 ) ∪ Vars(e2 ) Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr : [[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]] Beispiel mit X = ∅: x = y + 2; 1 {y} y = 5; 2 x = y + 2; M[y] = x; 3 ∅ 4 {y} {x, y} 5 ∅ 392 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei u lebendig 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei u lebendig es gibt unendlich viele Pfade von u nach stop 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei u lebendig es gibt unendlich viele Pfade von u nach stop Berechnung der Mengen L∗ [u]: 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei u lebendig es gibt unendlich viele Pfade von u nach stop Berechnung der Mengen L∗ [u]: 1 Stelle Gleichungssystem auf: L[stop] ⊇ X L[u] ⊇ [[k]]] (L[v]) k = (u, _, v) edge 393 / 406 Lebendige Variablen eines Programms Die Menge der lebendigen Variablen bei u ist nun gegeben durch: [ L∗ [u] = {[[π]]] X | π : u →∗ stop} Intuitiv: die Menge der lebendigen Variablen am bei stop ist X führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei u lebendig es gibt unendlich viele Pfade von u nach stop Berechnung der Mengen L∗ [u]: 1 Stelle Gleichungssystem auf: L[stop] ⊇ X L[u] ⊇ [[k]]] (L[v]) 2 k = (u, _, v) edge Berechnung der Gleichungen propagieren der Mengen von rechts nach links. Da Var endlich ist, finden wir einen Fixpunkt. 393 / 406 Beispiel der liveness Analyse Beachte: Die Information fließt Rückwärts bei der liveness Analyse. Beispiel: Fakultätsfunktion 0 x = M [I ]; 1 y = 1; L[0] L[1] L[2] L[3] L[4] L[5] L[6] L[7] ⊇ (L[1]\{x}) ∪ {I} ⊇ L[2]\{y} ⊇ (L[6] ∪ {x}) ∪ (L[3] ∪ {x}) ⊇ (L[4]\{y}) ∪ {x, y} ⊇ (L[5]\{x}) ∪ {x} ⊇ L[2] ⊇ L[7] ∪ {y, R} ⊇ ∅ 2 Neg(x > 1) 6 Pos(x > 1) 3 y = x ∗ y; M [R] = y; 7 4 x = x − 1; 5 1 2 7 6 2 5 4 3 1 0 394 / 406 Beispiel der liveness Analyse Beachte: Die Information fließt Rückwärts bei der liveness Analyse. Beispiel: Fakultätsfunktion 0 x = M [I ]; 1 y = 1; L[0] L[1] L[2] L[3] L[4] L[5] L[6] L[7] ⊇ (L[1]\{x}) ∪ {I} ⊇ L[2]\{y} ⊇ (L[6] ∪ {x}) ∪ (L[3] ∪ {x}) ⊇ (L[4]\{y}) ∪ {x, y} ⊇ (L[5]\{x}) ∪ {x} ⊇ L[2] ⊇ L[7] ∪ {y, R} ⊇ ∅ 2 Neg(x > 1) 6 Pos(x > 1) 3 y = x ∗ y; M [R] = y; 7 4 x = x − 1; 5 7 6 2 5 4 3 1 0 1 ∅ {y, R} {x, y, R} {x, y, R} {x, y, R} {x, y, R} {x, R} {I, R} 2 394 / 406 Beispiel der liveness Analyse Beachte: Die Information fließt Rückwärts bei der liveness Analyse. Beispiel: Fakultätsfunktion 0 x = M [I ]; 1 y = 1; L[0] L[1] L[2] L[3] L[4] L[5] L[6] L[7] ⊇ (L[1]\{x}) ∪ {I} ⊇ L[2]\{y} ⊇ (L[6] ∪ {x}) ∪ (L[3] ∪ {x}) ⊇ (L[4]\{y}) ∪ {x, y} ⊇ (L[5]\{x}) ∪ {x} ⊇ L[2] ⊇ L[7] ∪ {y, R} ⊇ ∅ 2 Neg(x > 1) 6 Pos(x > 1) 3 y = x ∗ y; M [R] = y; 7 4 x = x − 1; 5 7 6 2 5 4 3 1 0 1 ∅ {y, R} {x, y, R} {x, y, R} {x, y, R} {x, y, R} {x, R} {I, R} 2 dito 394 / 406 Übersetzung ganzer Programme Kapitel 6: Registerfärbung 395 / 406 Registerfärbung: Motivation Betrachte folgendes Program: 0 read(); read(); x = M[A]; y = x + 1; if (y) { z = x · x; M[A] = z; } else { t = −y · y; M[A] = t; } 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 4 6 5 7 z=x·x t = −y · y; M[A] = t; M[A] = z; 8 396 / 406 Registerfärbung: Motivation Betrachte folgendes Program: 0 read(); read(); x = M[A]; y = x + 1; if (y) { z = x · x; M[A] = z; } else { t = −y · y; M[A] = t; } 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 4 6 5 7 z=x·x t = −y · y; M[A] = t; M[A] = z; 8 Das Programm benutzt 5 Variablen (Register). 396 / 406 Registerfärbung: Motivation Betrachte folgendes Program: 0 read(); read(); x = M[A]; y = x + 1; if (y) { z = x · x; M[A] = z; } else { t = −y · y; M[A] = t; } 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 4 6 5 7 z=x·x t = −y · y; M[A] = t; M[A] = z; 8 Das Programm benutzt 5 Variablen (Register). Muss der Keller benutzt werden bei einer 4-Register Maschine? 396 / 406 Registerfärbung: Motivation Betrachte folgendes Program: 0 read(); read(); x = M[A]; y = x + 1; if (y) { z = x · x; M[A] = z; } else { t = −y · y; M[A] = t; } 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 4 6 5 7 z=x·x t = −y · y; M[A] = t; M[A] = z; 8 Das Programm benutzt 5 Variablen (Register). Muss der Keller benutzt werden bei einer 4-Register Maschine? Nein, benutze ein Register R für x, t, z! 396 / 406 Registerfärbung: Motivation Betrachte folgendes Program: 0 read(); read(); R = M[A]; y = R + 1; if (y) { R = R · R; M[A] = R; } else { R = −y · y; M[A] = R; } 1 R = M[A]; 2 y = R + 1; Neg (y) 3 Pos (y) 4 6 5 7 R = −y · y; R=R·R M[A] = R; M[A] = R; 8 Das Programm benutzt 5 Variablen (Register). Muss der Keller benutzt werden bei einer 4-Register Maschine? Nein, benutze ein Register R für x, t, z! 396 / 406 Wann reicht ein Register? Achtung: Mehrere Variablen können nur dann in einem Register gespeichert werden, wenn sich deren Lebensbereich (live range) nicht überlappen. 0 read(); 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 6 4 t = −y · y; z=x·x 7 5 M[A] = t; 8 7 6 5 4 3 2 1 0 L ∅ {A, z} {A, x} {A, t} {A, y} {A, x, y} {A, x} {A} ∅ M[A] = z; 8 397 / 406 Wann reicht ein Register? Achtung: Mehrere Variablen können nur dann in einem Register gespeichert werden, wenn sich deren Lebensbereich (live range) nicht überlappen. Der Lebensbereich ist definiert durch L[x] = {u | x ∈ L[u]} 0 read(); 1 x = M[A]; 2 y = x + 1; Neg (y) 3 Pos (y) 6 4 t = −y · y; z=x·x 7 5 M[A] = t; 8 7 6 5 4 3 2 1 0 L ∅ {A, z} {A, x} {A, t} {A, y} {A, x, y} {A, x} {A} ∅ M[A] = z; 8 397 / 406 Wann reicht ein Register? Achtung: Mehrere Variablen können nur dann in einem Register gespeichert werden, wenn sich deren Lebensbereich (live range) nicht überlappen. Der Lebensbereich ist definiert durch L[x] = {u | x ∈ L[u]} 0 read(); 1 x = M[A]; 2 x Neg (y) y = x + 1; 3 4 y t = −y · y; t Pos (y) 6 z=x·x 7 5 M[A] = t; 8 7 6 5 4 3 2 1 0 L ∅ {A, z} {A, x} {A, t} {A, y} {A, x, y} {A, x} {A} ∅ z M[A] = z; 8 397 / 406 Wann reicht ein Register? Achtung: Mehrere Variablen können nur dann in einem Register gespeichert werden, wenn sich deren Lebensbereich (live range) nicht überlappen. Der Lebensbereich ist definiert durch L[x] = {u | x ∈ L[u]} 0 read(); 1 live ranges: x = M[A]; 2 x Neg (y) y = x + 1; 3 4 y t = −y · y; t Pos (y) 6 z=x·x 7 5 M[A] = t; A x y t z {1, . . . , 7} {2, 3, 6} {3, 4} {5} {7} z M[A] = z; 8 397 / 406 Interferenz Graph Um herauszufinden, welche Lebensbereiche sich überlappen, konstruieren wir den Interferenz Graphen (interference graph) I = (Vars, EI ) wobei EI = {{x, y} | x 6= y, L[x] ∩ L[y] 6= ∅}. 0 read(); 1 x = M[A]; 2 x Neg (y) y = x + 1; 3 Pos (y) 4 y t = −y · y; 6 5 7 t z=x·x M[A] = t; z M[A] = z; 8 398 / 406 Interferenz Graph Um herauszufinden, welche Lebensbereiche sich überlappen, konstruieren wir den Interferenz Graphen (interference graph) I = (Vars, EI ) wobei EI = {{x, y} | x 6= y, L[x] ∩ L[y] 6= ∅}. EI enthält eine Kante für x 6= y wenn und nur wenn ein Programmpunk existiert, an dem x, y gleichzeitig lebendig sind. 0 read(); 1 x = M[A]; 2 x Neg (y) y = x + 1; 3 Pos (y) 4 y t = −y · y; 6 5 7 t z=x·x M[A] = t; z M[A] = z; 8 398 / 406 Interferenz Graph Um herauszufinden, welche Lebensbereiche sich überlappen, konstruieren wir den Interferenz Graphen (interference graph) I = (Vars, EI ) wobei EI = {{x, y} | x 6= y, L[x] ∩ L[y] 6= ∅}. EI enthält eine Kante für x 6= y wenn und nur wenn ein Programmpunk existiert, an dem x, y gleichzeitig lebendig sind. interference graph: 0 A read(); 1 x = M[A]; y 2 x Neg (y) y = x + 1; 3 t z Pos (y) 4 y t = −y · y; 6 5 7 t x z=x·x M[A] = t; z M[A] = z; 8 398 / 406 Interferenz Graph Um herauszufinden, welche Lebensbereiche sich überlappen, konstruieren wir den Interferenz Graphen (interference graph) I = (Vars, EI ) wobei EI = {{x, y} | x 6= y, L[x] ∩ L[y] 6= ∅}. EI enthält eine Kante für x 6= y wenn und nur wenn ein Programmpunk existiert, an dem x, y gleichzeitig lebendig sind. interference graph: 0 A read(); 1 x = M[A]; y 2 x Neg (y) y = x + 1; 3 4 y t = −y · y; t x t 6 z=x·x 7 5 M[A] = t; z Pos (y) z M[A] = z; Variablen, die nicht verbunden sind, können dem gleichen Register zugewiesen werden 8 398 / 406 Interferenz Graph Um herauszufinden, welche Lebensbereiche sich überlappen, konstruieren wir den Interferenz Graphen (interference graph) I = (Vars, EI ) wobei EI = {{x, y} | x 6= y, L[x] ∩ L[y] 6= ∅}. EI enthält eine Kante für x 6= y wenn und nur wenn ein Programmpunk existiert, an dem x, y gleichzeitig lebendig sind. interference graph: 0 A read(); 1 x = M[A]; y 2 x Neg (y) y = x + 1; 3 4 y t = −y · y; t x t 6 z=x·x 7 5 M[A] = t; z M[A] = z; 8 z Pos (y) Variablen, die nicht verbunden sind, können dem gleichen Register zugewiesen werden Farbe == Register 398 / 406 Registerfärbung Gregory J. Chaitin, Sviatoslav Sergeevich Lavrov, University of Maine (1981) Russian Academy of Sciences (1962) 399 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= c(v) für alle {u, v} ∈ E; 400 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= F c(v) für alle {u, v} ∈ E; wobei {c(u) | u ∈ V} minimal ist! 400 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= F c(v) für alle {u, v} ∈ E; wobei {c(u) | u ∈ V} minimal ist! Im Beispiel reichen drei Farben, aber: 400 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= F c(v) für alle {u, v} ∈ E; wobei {c(u) | u ∈ V} minimal ist! Im Beispiel reichen drei Farben, aber: Im Allgemeinen gibt es keine eindeutige minimale Färbung 400 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= F c(v) für alle {u, v} ∈ E; wobei {c(u) | u ∈ V} minimal ist! Im Beispiel reichen drei Farben, aber: Im Allgemeinen gibt es keine eindeutige minimale Färbung Es ist NP-vollständig herauszufinden, ob eine Färbung mit höchstens k Farben existiert 400 / 406 Registerfärbung: Problemstellung Gegeben: ungerichteter Graph (V, E). Gesucht: Minimale Färbung (coloring), d.h. eine Abbildung c : V → N mit c(u) 6= F c(v) für alle {u, v} ∈ E; wobei {c(u) | u ∈ V} minimal ist! Im Beispiel reichen drei Farben, aber: Im Allgemeinen gibt es keine eindeutige minimale Färbung Es ist NP-vollständig herauszufinden, ob eine Färbung mit höchstens k Farben existiert ; benutze Heuristiken und/oder beschränke das Problem auf Spezialfälle 400 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: 401 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: starte irgendwo mit Farbe 1 401 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: starte irgendwo mit Farbe 1 wähle die Farbe mit der kleinsten Nummer die ungleich aller benachbarten Knoten ist, die schon gefärbt sind 401 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: starte irgendwo mit Farbe 1 wähle die Farbe mit der kleinsten Nummer die ungleich aller benachbarten Knoten ist, die schon gefärbt sind hat ein Knoten eine Farbe, färbe alle Nachbarn, die noch keine Farbe haben 401 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: starte irgendwo mit Farbe 1 wähle die Farbe mit der kleinsten Nummer die ungleich aller benachbarten Knoten ist, die schon gefärbt sind hat ein Knoten eine Farbe, färbe alle Nachbarn, die noch keine Farbe haben betrachte alle Konten nacheinander 401 / 406 Lösungsweg Heuristik Idee: benutze eine greedy Heuristik: starte irgendwo mit Farbe 1 wähle die Farbe mit der kleinsten Nummer die ungleich aller benachbarten Knoten ist, die schon gefärbt sind hat ein Knoten eine Farbe, färbe alle Nachbarn, die noch keine Farbe haben betrachte alle Konten nacheinander forall (v ∈ V) c[v] = 0; forall (v ∈ V) color (v); void color (v) { if (c[v] 6= 0) return; neighbors = {u ∈ V | {u, v} ∈ E}; c[v] = {k > 0 | ∀ u ∈ neighbors : k 6= c(u)}; forall (u ∈ neighbors) if (c(u) = 0) color (u); } F 401 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein in der Praxis ist der Algorithmus aber nicht zu schlecht 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein in der Praxis ist der Algorithmus aber nicht zu schlecht verschiedene Strategien wurden sogar patentiert 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein in der Praxis ist der Algorithmus aber nicht zu schlecht verschiedene Strategien wurden sogar patentiert Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein sind! 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein in der Praxis ist der Algorithmus aber nicht zu schlecht verschiedene Strategien wurden sogar patentiert Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein sind! Idee: Aufteilung der Lebensbereiche (live range splitting) 402 / 406 Diskussion der greedy Heuristik der Algorithmus ist im Prinzip eine pre-order Tiefensuche die neue Farbe eines Knotens kann einfach berechnet werden, wenn die Nachbarn nach Farbe sortiert gespeichert werden theoretisch kann das Resultat beliebig schlechter als die optimale Lösung sein in der Praxis ist der Algorithmus aber nicht zu schlecht verschiedene Strategien wurden sogar patentiert Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein sind! Idee: Aufteilung der Lebensbereiche (live range splitting) Ausfürung: betrachte zunnächst nur die Aufteilung innheralb von Basisblöcken 402 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: A1 = x + y; M[A1 ] = z; x = x + 1; z = M[A1 ]; t = M[x]; A2 = x + t; M[A2 ] = z; y = M[x]; M[y] = t; L x, y, z x, z x x x, z x, z, t x, z, t x, t y, t 403 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: A1 = x + y; M[A1 ] = z; x = x + 1; z = M[A1 ]; t = M[x]; A2 = x + t; M[A2 ] = z; y = M[x]; M[y] = t; L x, y, z x, z x x x, z x, z, t x, z, t x, t y, t x z y t 403 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: A1 = x + y; M[A1 ] = z; x = x + 1; z = M[A1 ]; t = M[x]; A2 = x + t; M[A2 ] = z; y = M[x]; M[y] = t; L x, y, z x, z x x x, z x, z, t x, z, t x, t y, t x z y t 403 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: A1 = x + y; M[A1 ] = z; x1 = x + 1; z1 = M[A1 ]; t = M[x1 ]; A2 = x1 + t; M[A2 ] = z1 ; y1 = M[x1 ]; M[y1 ] = t; L x, y, z x, z x x1 x1 , z1 x1 , z1 , t x1 , z1 , t x1 , t y1 , t x z y t Die Lebensbereiche von x und z können geteilt werden. 403 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: x A1 = x + y; M[A1 ] = z; x1 = x + 1; z1 = M[A1 ]; t = M[x1 ]; A2 = x1 + t; M[A2 ] = z1 ; y1 = M[x1 ]; M[y1 ] = t; L x, y, z x, z x x1 x1 , z1 x1 , z1 , t x1 , z1 , t x1 , t y1 , t z y x1 z1 y1 t Die Lebensbereiche von x und z können geteilt werden. 403 / 406 Aufteilung von Lebensbereichen in Basisblöcken Beispiel: Betrachte die Registerfärbung des folgenden Blocks: x A1 = x + y; M[A1 ] = z; x1 = x + 1; z1 = M[A1 ]; t = M[x1 ]; A2 = x1 + t; M[A2 ] = z1 ; y1 = M[x1 ]; M[y1 ] = t; L x, y, z x, z x x1 x1 , z1 x1 , z1 , t x1 , z1 , t x1 , t y1 , t z y x1 z1 y1 t Die Lebensbereiche von x und z können geteilt werden. Das Aufteilen macht es möglich, x1 und y1 dem gleichen Register zuzuordnen. 403 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben Minimale Färbung kann in polynomineller Zeit gefunden werden 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben Minimale Färbung kann in polynomineller Zeit gefunden werden Achtung: dies ist ein Spezialfall: 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben Minimale Färbung kann in polynomineller Zeit gefunden werden Achtung: dies ist ein Spezialfall: nehme an, der Basisblock bildet eine Schleife und 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben Minimale Färbung kann in polynomineller Zeit gefunden werden Achtung: dies ist ein Spezialfall: nehme an, der Basisblock bildet eine Schleife und eine Variable v ist dem Register Ri am Anfang und Rj am Ende zugeordnet 404 / 406 Betrachtung von Lebendigkeitsbereichen Weiterführende Beobachtungen auf Basisblöcken: Die Interferenzgraphen auf Basisblöcken können auch als Intervalgraphen dargestellt werden: Größe der maximalen Clique entspricht Anzahl benötigter Farben Minimale Färbung kann in polynomineller Zeit gefunden werden Achtung: dies ist ein Spezialfall: nehme an, der Basisblock bildet eine Schleife und eine Variable v ist dem Register Ri am Anfang und Rj am Ende zugeordnet benötige Tausch-Operationen (; sub-optimal) 404 / 406 Registerfärbung in der Fakultätsfunktion Betrachte: def-use int fac(int x) { if (x<=0) then return 1; else return x*fac(x-1); } _A: −1 0 1 2 3 4 _fac: enter 5 move R1 R−1 move R2 R1 loadc R3 0 leq R2 R2 R3 jumpz R2 _A loadc R2 1 move R0 R2 return jump _B U D U D U D D U U D U D U _B: move R2 R1 move R3 R1 loadc R4 1 sub R3 R3 R4 move R−1 R3 loadc R3 _fac saveloc R1 R2 mark call R3 restoreloc R1 R2 move R3 R0 mul R2 R2 R3 move R0 R2 return return −1 0 1 2 3 4 U D U D D U U D D U D U U U D U D D U U D D U D U U 405 / 406 Registerfärbung in der Fakultätsfunktion Betrachte: def-use liveness int fac(int x) { if (x<=0) then return 1; else return x*fac(x-1); } −1 0 1 2 3 4 _A: −1 0 1 2 3 4 _fac: enter 5 move R1 R−1 move R2 R1 loadc R3 0 leq R2 R2 R3 jumpz R2 _A loadc R2 1 move R0 R2 return jump _B > ⊥ > | | | | | | | | > ⊥ > | | > ⊥ ⊥ > ⊥ > ⊥ _B: move R2 R1 move R3 R1 loadc R4 1 sub R3 R3 R4 move R−1 R3 loadc R3 _fac saveloc R1 R2 mark call R3 restoreloc R1 R2 move R3 R0 mul R2 R2 R3 move R0 R2 return return | | | | | | | | | | | | > | | | | | | ⊥ | | | | | | | | | | > | | > ⊥ ⊥ > ⊥ > ⊥ ⊥ | | | | ⊥ > | | ⊥ > ⊥ > > | | > ⊥ ⊥ > ⊥ 405 / 406 Registerfärbung in der Fakultätsfunktion Betrachte: def-use liveness coloring int fac(int x) { if (x<=0) then return 1; else return x*fac(x-1); } −1 0 1 2 3 4 _A: −1 0 1 2 3 4 _fac: enter 5 move R1 R−1 move R0 R1 loadc R−1 0 leq R0 R0 R−1 jumpz R2 _A loadc R2 1 move R0 R2 return jump _B > ⊥ > | > | | > | ⊥ ⊥ > ⊥ > ⊥ > ⊥ | | | | | | _B: move R2 R1 move R0 R1 loadc R−1 1 sub R0 R0 R−1 move R−1 R0 loadc R3 _fac saveloc R1 R2 mark call R3 restoreloc R1 R2 move R0 R0 mul R2 R2 R0 move R0 R2 return return > | | > ⊥ ⊥ > ⊥ > | | > | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | ⊥ ⊥ ⊥ ⊥ > | | ⊥ > ⊥ > ⊥ > | | ⊥ > ⊥ 405 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist die Regeln für die liveness-Analyse können verbessert werden 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist die Regeln für die liveness-Analyse können verbessert werden saveloc hält Register unnötig am Leben ; Zwischensprache 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist die Regeln für die liveness-Analyse können verbessert werden saveloc hält Register unnötig am Leben ; Zwischensprache ; Vorlesung Programmoptimierung 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist die Regeln für die liveness-Analyse können verbessert werden saveloc hält Register unnötig am Leben ; Zwischensprache ; Vorlesung Programmoptimierung Wie berechnet man die liveness Mengen zügig? 406 / 406 Ausblick Unterteilung der Lebensbereiche verbesserungsfähig: Betrachte den ganzen CFG einer Funktion Problem: verschiedene Definitionen fliessen in den gleich Basisblock übersetze Funktionen in eine single static assignment Form optimale Färbung möglich (allerdings müssen evtl. Register getauscht werden) ; Vorlesung Programmoptimierung liveness-Analyse verbesserungsfähig: nach x ← y + 1 ist x nur lebendig wenn y lebendig ist die Regeln für die liveness-Analyse können verbessert werden saveloc hält Register unnötig am Leben ; Zwischensprache ; Vorlesung Programmoptimierung Wie berechnet man die liveness Mengen zügig? ; Vorlesung Programmoptimierung 406 / 406