◦ ◦◦◦ ◦◦◦◦ ◦ ◦ ◦◦◦ ◦◦◦◦ ◦ ◦◦ TECHNISCHE UNIVERSITÄT MÜNCHEN FAKULTÄT FÜR INFORMATIK Compilerbau I Michael Petter, Axel Simon SoSe 2010 1 / 427 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 / 427 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 / 427 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 / 427 Themengebiet: Einführung 5 / 427 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 / 427 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 / 427 Ü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 / 427 Übersetzer allgemeiner Aufbau eines Übersetzers: Analyse Interndarstellung Synthese Compiler Compiler Programmtext Code 9 / 427 Übersetzer allgemeiner Aufbau eines Übersetzers: Analyse Interndarstellung Synthese Compiler Compiler Programmtext Code 9 / 427 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Analyse Programmtext (annotierter) Syntaxbaum 9 / 427 Übersetzer Die Analyse-Phase ist selbst unterteilt in mehrere Schritte Programmtext Analyse Scanner lexikalische Analyse: Aufteilung in Sinneinheiten Token-Strom (annotierter) Syntaxbaum 9 / 427 Ü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 / 427 Ü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 / 427 Themengebiet: Lexikalische Analyse 10 / 427 Die lexikalische Analyse Programmtext Scanner Token-Strom 11 / 427 Die lexikalische Analyse xyz + 42 Scanner xyz + 42 11 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Die lexikalische Analyse - Generierung: ... in unserem Fall: Spezifikation Generator Scanner 15 / 427 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 / 427 Lexikalische Analyse Kapitel 1: Grundlagen: Reguläre Ausdrücke 16 / 427 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 / 427 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 / 427 Stephen Kleene, Madison Wisconsin, 1909-1994 18 / 427 Reguläre Ausdrücke ... im Beispiel: ((a · b∗ )·a) (a | b) ((a · b)·(a · b)) 19 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Reguläre Ausdrücke Beispiel: Identifier in Java: le = [a-zA-Z_\$] di = [0-9] Id = {le} ({le} | {di})* 22 / 427 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 / 427 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 / 427 Lexikalische Analyse Kapitel 2: Grundlagen: Endliche Automaten 23 / 427 Endliche Automaten im Beispiel: a b 24 / 427 Endliche Automaten im Beispiel: a b Knoten: Zustände; Kanten: Übergänge; Beschriftungen: konsumierter Input 24 / 427 Michael O. Rabin, Stanford University Dana S. Scott, Carnegy Mellon University, Pittsburgh 25 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Berry-Sethi Verfahren ... im Beispiel: . ∗ (a|b) a(a|b) . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 Berry-Sethi Verfahren ... im Beispiel: . w = bbaa : . * | a | a b a b 30 / 427 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 / 427 Berry-Sethi Verfahren ... im Beispiel: . . * | a | a b a b 32 / 427 Berry-Sethi Verfahren ... im Beispiel: . . * | a 0 a b 1 | 2 a 3 b 4 32 / 427 Berry-Sethi Verfahren ... im Beispiel: . . * | 0 a 2 1 b | a 3 a 4 b 32 / 427 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 / 427 Berry-Sethi Verfahren Diskussion: Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren Der Automat ist i.a. nichtdeterministisch 34 / 427 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 / 427 Berry-Sethi Verfahren: 1. Schritt empty[r] = t gdw. ∈ [[r]] ... im Beispiel: . . * | 0 a 2 1 b | a 3 a 4 b 35 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Gerard Berry, Esterel Technologies Ravi Sethi, Research VR, Lucent Technologies 45 / 427 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 / 427 Teilmengenkonstruktion a ... im Beispiel: 0 a a b 3 a b a 2 a b a 1 4 b 47 / 427 Teilmengenkonstruktion a 0 a ... im Beispiel: a b 3 a b a 2 a b a 1 4 b 02 a 47 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Lexikalische Analyse Kapitel 3: Design eines Scanners 52 / 427 Design eines Scanners Eingabe (vereinfacht): eine Menge von Regeln: { action1 } { action2 } e1 e2 ... ek { actionk } 53 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Themengebiet: Syntaktische Analyse 60 / 427 Die syntaktische Analyse Token-Strom Parser Syntaxbaum Die syntaktische Analyse versucht, Tokens zu größeren Programmeinheiten zusammen zu fassen. 61 / 427 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 / 427 Diskussion: Auch Parser werden i.a. nicht von Hand programmiert, sondern aus einer Spezifikation generiert: Spezifikation Generator Parser 62 / 427 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 / 427 Syntaktische Analyse Kapitel 1: Grundlagen: Kontextfreie Grammatiken 63 / 427 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 / 427 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 / 427 Noam Chomsky, MIT John Backus, IBM (Erfinder von Fortran) 65 / 427 Konventionen Die Regeln kontextfreier Grammatiken sind von der Form: A→α mit A ∈ N , α ∈ (N ∪ T)∗ 66 / 427 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 / 427 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 / 427 ... 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 / 427 ... 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Spezielle Ableitungen ... im Beispiel: E 0 E 1 + T 0 T 1 F 1 ∗ T 1 F 2 F 2 int int name 73 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 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 / 427 Das kann jeder, ist ueberhaupt keine Herausforderung: Syntaktische Analyse 77 / 427 Kapitel 2: Überflüssige Nichtterminale und Regeln 77 / 427 Fingerübung: überflüssige Nichtterminale und Regeln Definition: A ∈ N heißt produktiv, falls A →∗ w für ein w ∈ T ∗ A ∈ N heißt erreichbar, falls S →∗ α A β für geeignete α, β ∈ (T ∪ N)∗ Beispiel: S A B C D → → → → → aBB | bD Bc Sd | C a BD 78 / 427 Fingerübung: überflüssige Nichtterminale und Regeln Definition: A ∈ N heißt produktiv, falls A →∗ w für ein w ∈ T ∗ A ∈ N heißt erreichbar, falls S →∗ α A β für geeignete α, β ∈ (T ∪ N)∗ Beispiel: S A B C D → → → → → aBB | bD Bc Sd | C a BD Produktive Nichtterminale: S, A, B, C Erreichbare Nichtterminale: S, B, C, D 78 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: S 1 S S 0 A 0 D B 0 B B 1 D 0 true C 0 A C true Produktivität And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: S 1 S S 0 A 0 D B 0 B B 1 D 0 true true C 0 A true C true Produktivität And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: true S 1 S S 0 A 0 D B 0 B B 1 D 0 true true C 0 true A true C true Produktivität And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: true true true S 1 S S 0 A 0 D B 0 B B 1 D 0 true true C 0 true A true C true Produktivität And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 And-Or-Graph Idee für Produktivität: And-Or-Graph für die Grammatik ... hier: true S 1 true S 0 A 0 B 0 B B 1 false D D 0 true true true S true C 0 A true C true Produktivität And-Knoten: Regeln Or-Knoten: Nichtterminale Kanten: ((B, i), B) für alle Regeln (B, i) (A, (B, i)) falls (B, i) ≡ B → α1 A α2 79 / 427 Algorithmus: 2N result = ∅; int count[P]; 2P rhs[N]; // // // Ergebnis-Menge Zähler für jede Regel Vorkommen in rechten Seiten forall (A ∈ N) rhs[A] = ∅; forall ((A, i) ∈ P) { count[(A, i)] = 0; init(A, i); } ... // // // // // // Initialisierung Initialisierung von rhs Die Hilfsfunktion init zählt die Nichtterminal-Vorkommen in der rechten Seite und vermerkt sie in der Datenstruktur rhs 80 / 427 Algorithmus (Fortsetzung): ... 2P W = {r | count[r] = 0}; while (W 6= ∅) { (A, i) = extract(W); if (A 6∈ result) { result = result ∪ {A}; forall (r ∈ rhs[A]) { count[r]−−; if (count[r] == 0) W = W ∪ {r}; } } } // // // // // // // // // // // // Workset end of end of end of forall if while Die Menge W verwaltet die Regeln, deren rechte Seiten nur produktive Nichtterminale enthalten 81 / 427 Produktivität ... im Beispiel: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Produktivität 82 / 427 Produktivität ... im Beispiel: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Produktivität 82 / 427 Produktivität ... im Beispiel: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Produktivität 82 / 427 Produktivität ... im Beispiel: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Produktivität 82 / 427 Produktivität ... im Beispiel: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Produktivität 82 / 427 Laufzeit: Die Initialisierung der Datenstrukturen erfordert lineare Laufzeit. Jede Regel wird maximal einmal in W eingefügt. Jedes A wird maximal einmal in result eingefügt. ==⇒ Der Gesamtaufwand ist linear in der Größe der Grammatik Korrektheit: Falls A in der j-ten Iteration der while-Schleife in result eingefügt, gibt es einen Ableitungsbaum für A der Höhe maximal j − 1 Für jeden Ableitungsbaum wird die Wurzel einmal in W eingefügt 83 / 427 Diskussion: Um den Test (A ∈ result) einfach zu machen, repräsentiert man die Menge result) durch ein Array. W wie auch die Mengen Listen repräsentieren rhs[A] wird man dagegen als 84 / 427 Diskussion: Um den Test (A ∈ result) einfach zu machen, repräsentiert man die Menge result) durch ein Array. W wie auch die Mengen Listen repräsentieren rhs[A] wird man dagegen als Der Algorithmus funktioniert auch, um kleinste Lösungen von Booleschen Ungleichungssystemen zu bestimmen Die Ermittlung der produktiven Nichtterminale kann benutzt werden, um festzustellen, ob L(G) 6= ∅ ist (→ Leerheitsproblem) 84 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Knoten: Nichtterminale Kanten: (A, B) falls B → α1 A α2 ∈ P 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S 1 S S 0 A 0 D B 0 B B 1 C 0 C D 0 A Knoten: Nichtterminale Kanten: (A, B) falls B → α1 A α2 ∈ P 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Knoten: Nichtterminale Kanten: (A, B) falls B → α1 A α2 ∈ P 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Das Nichtterminal A ist erreichbar, falls es im Abhängigkeitsgraphen einen Pfad von A nach S gibt 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Das Nichtterminal A ist erreichbar, falls es im Abhängigkeitsgraphen einen Pfad von A nach S gibt 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Das Nichtterminal A ist erreichbar, falls es im Abhängigkeitsgraphen einen Pfad von A nach S gibt 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Das Nichtterminal A ist erreichbar, falls es im Abhängigkeitsgraphen einen Pfad von A nach S gibt 85 / 427 Abhängigkeits-Graph Idee für Erreichbarkeit: Abhängigkeits-Graph ... hier: S D A B C Das Nichtterminal A ist erreichbar, falls es im Abhängigkeitsgraphen einen Pfad von A nach S gibt 85 / 427 Fazit: Erreichbarkeit in gerichteten Graphen kann mithilfe von DFS in linearer Zeit berechnet werden. Damit kann die Menge aller erreichbaren und produktiven Nichtterminale in linearer Zeit berechnet werden 86 / 427 Fazit: Erreichbarkeit in gerichteten Graphen kann mithilfe von DFS in linearer Zeit berechnet werden. Damit kann die Menge aller erreichbaren und produktiven Nichtterminale in linearer Zeit berechnet werden Eine Grammatik G heißt reduziert, wenn alle Nichtterminale von G sowohl produktiv wie erreichbar sind ... 86 / 427 Fazit: Erreichbarkeit in gerichteten Graphen kann mithilfe von DFS in linearer Zeit berechnet werden. Damit kann die Menge aller erreichbaren und produktiven Nichtterminale in linearer Zeit berechnet werden Eine Grammatik G heißt reduziert, wenn alle Nichtterminale von G sowohl produktiv wie erreichbar sind ... Satz: Zu jeder kontextfreien Grammatik G = (N, T, P, S) mit L(G) 6= ∅ kann in linearer Zeit eine reduzierte Grammatik G0 konstruiert werden mit L(G) = L(G0 ) 86 / 427 Konstruktion: 1. Schritt: Berechne die Teilmenge N1 ⊆ N aller produktiven Nichtterminale von G. Da L(G) 6= ∅ ist insbesondere S ∈ N1 . 2. Schritt: Konstruiere: P1 = {A → α ∈ P | A ∈ N1 ∧ α ∈ (N1 ∪ T)∗ } 87 / 427 Konstruktion: 1. Schritt: Berechne die Teilmenge N1 ⊆ N aller produktiven Nichtterminale von G. Da L(G) 6= ∅ ist insbesondere S ∈ N1 . 2. Schritt: Konstruiere: P1 = {A → α ∈ P | A ∈ N1 ∧ α ∈ (N1 ∪ T)∗ } 3. Schritt: Berechne die Teilmenge N2 ⊆ N 1 aller produktiven und erreichbaren Nichtterminale von G . Da L(G) 6= ∅ ist insbesondere S ∈ N2 . 4. Schritt: Konstruiere: P2 = {A → α ∈ P | A ∈ N2 ∧ α ∈ (N2 ∪ T)∗ } 87 / 427 Konstruktion: 1. Schritt: Berechne die Teilmenge N1 ⊆ N aller produktiven Nichtterminale von G. Da L(G) 6= ∅ ist insbesondere S ∈ N1 . 2. Schritt: Konstruiere: P1 = {A → α ∈ P | A ∈ N1 ∧ α ∈ (N1 ∪ T)∗ } 3. Schritt: Berechne die Teilmenge N2 ⊆ N 1 aller produktiven und erreichbaren Nichtterminale von G . Da L(G) 6= ∅ ist insbesondere S ∈ N2 . 4. Schritt: Konstruiere: Ergebnis: P2 = {A → α ∈ P | A ∈ N2 ∧ α ∈ (N2 ∪ T)∗ } G0 = (N2 , T, P2 , S) 87 / 427 ... im Beispiel: S A B C D → → → → → aBB | bD Bc Sd | C a BD 88 / 427 ... im Beispiel: S A B C D → → → → → aBB | bD Bc Sd | C a BD 88 / 427 ... im Beispiel: S A B C → → → → aBB Bc Sd | C a 88 / 427 ... im Beispiel: S A B C → → → → aBB Bc Sd | C a 88 / 427 ... im Beispiel: S → aBB B C → Sd → a | C 88 / 427 Syntaktische Analyse Kapitel 3: Grundlagen: Kellerautomaten 89 / 427 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 90 / 427 Friedrich L. Bauer, TUM Klaus Samelson, TUM 1957: Patent auf die Verwendung von Stapelspeicher zur Übersetzung von Programmiersprachen 91 / 427 Beispiel: Zustände: 0, 1, 2 Anfangszustand: 0 Endzustände: 0, 2 0 1 11 12 a a b b 11 11 2 2 92 / 427 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 92 / 427 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 93 / 427 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. 93 / 427 ... 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 94 / 427 ... 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) 94 / 427 ... 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) 94 / 427 ... 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) 94 / 427 ... 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) 94 / 427 ... 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) 94 / 427 ... 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) 94 / 427 ... 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) ) 94 / 427 Ein Berechnungsschritt wird durch die Relation beschrieben, wobei 2 ` ⊆ (Q∗ × T ∗ ) (α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ 95 / 427 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 , )} 95 / 427 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 95 / 427 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. 96 / 427 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 96 / 427 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 97 / 427 Marcel-Paul Schützenberger (1920-1996), Paris Anthony G. Öttinger, Präsident der ACM 1966-68 1961: On context free languages and pushdownautomata 98 / 427 Syntaktische Analyse Kapitel 4: Vorausschau-Mengen 99 / 427 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T∗ definieren wir: Firstk (L) = {u ∈ L | |u| < k} ∪ {u ∈ T k | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb 100 / 427 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T∗ definieren wir: Firstk (L) = {u ∈ L | |u| < k} ∪ {u ∈ T k | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb die Präfixe der Länge 2 100 / 427 Vorausschau-Mengen Rechenregeln: Firstk (_) ist verträglich mit Vereinigung und Konkatenation: Firstk (∅) Firstk (L1 ∪ L2 ) Firstk (L1 · L2 ) = = = := ∅ Firstk (L1 ) ∪ Firstk (L2 ) Firstk (Firstk (L1 ) · Firstk (L2 )) Firstk (L1 ) Firstk (L2 ) k − Konkatenation Beachte: Die Menge Dk = 2T ist endlich Die Operation: : Dk × Dk → Dk ist distributiv in jedem Argument: ≤k L∅ ∅L = = ∅ ∅ L (L1 ∪ L2 ) = (L L1 ) ∪ (L L2 ) (L1 ∪ L2 ) L = (L1 L) ∪ (L2 L) 101 / 427 Firstk Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: Firstk (α) = Firstk ({w ∈ T ∗ | α →∗ w}) Für k ≥ 1 gilt: Firstk (x) = {x} für x ∈ T ∪ {} Firstk (α1 α2 ) = Firstk (α1 ) Firstk (α2 ) 102 / 427 Firstk Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: Firstk (α) = Firstk ({w ∈ T ∗ | α →∗ w}) Für k ≥ 1 gilt: Firstk (x) = {x} für x ∈ T ∪ {} Firstk (α1 α2 ) = Firstk (α1 ) Firstk (α2 ) Frage: Wie berechnet man Firstk (A) ?? 102 / 427 Firstk Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge: Firstk (α) = Firstk ({w ∈ T ∗ | α →∗ w}) Für k ≥ 1 gilt: Firstk (x) = {x} für x ∈ T ∪ {} Firstk (α1 α2 ) = Firstk (α1 ) Firstk (α2 ) Frage: Idee: Wie berechnet man Firstk (A) ?? Stelle ein Ungleichungssystem auf! 102 / 427 First2 Beispiel: k=2 E T F → → → E+T 0 | T 1 T ∗F 0 | F 1 ( E ) 0 | name 1 | int 2 Jede Regel gibt Anlass zu einer Inklusionsbeziehung: First2 (E) ⊇ First2 (E + T) First2 (T) ⊇ First2 (T ∗ F) First2 (F) ⊇ First2 (( E )) First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (T) First2 (F) {name, int} 103 / 427 First2 Beispiel: k=2 E T F → → → E+T 0 | T 1 T ∗F 0 | F 1 ( E ) 0 | name 1 | int 2 Jede Regel gibt Anlass zu einer Inklusionsbeziehung: First2 (E) ⊇ First2 (E + T) First2 (T) ⊇ First2 (T ∗ F) First2 (F) ⊇ First2 (( E )) Eine Inklusion werden zu: First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (T) First2 (F) {name, int} First2 (E) ⊇ First2 (E + T) kann weiter vereinfacht First2 (E) ⊇ First2 (E) {+} First2 (T) 103 / 427 Ungleichungssystem für First2 Insgesamt erhalten wir das Ungleichungssystem: First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (E) {+} First2 (T) First2 (T) {∗} First2 (F) {(} First2 (E) {)} First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (T) First2 (F) {name, int} 104 / 427 Ungleichungssystem für Firstk Insgesamt erhalten wir das Ungleichungssystem: First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (E) {+} First2 (T) First2 (T) {∗} First2 (F) {(} First2 (E) {)} First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (T) First2 (F) {name, int} Allgemein: Firstk (A) ⊇ Firstk (X1 ) . . . Firstk (Xm ) für jede Regel A → X1 . . . Xm ∈ P mit Xi ∈ T ∪ N. 104 / 427 Ungleichungssystem für First2 Gesucht: möglichst kleine Lösung Algorithmus, der diese berechnet ... im Beispiel: First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (E) {+} First2 (T) First2 (T) {∗} First2 (F) {(} First2 (E) {)} First2 (E) ⊇ First2 (T) ⊇ First2 (F) ⊇ First2 (T) First2 (F) {name, int} ... hat die Lösung: E T F name, int, (name, (int, ((, name ∗, int ∗, name +, int + name, int, (name, (int, ((, name ∗, int ∗ name, int, (name, (int, (( 105 / 427 Ungleichungssystem für Firstk Beobachtung: Die Menge Dk der möglichen Werte für einen vollständigen Verband Firstk (A) bilden Die Operatoren auf den rechten Seiten der Ungleichungen sind monoton, d.h. verträglich mit ⊆ 106 / 427 Syntaktische Analyse Kapitel 5: Exkurs: Vollständige Verbände 107 / 427 Verbände Definition: Eine Menge D mit einer Relation v ⊆ falls für alle a, b, c ∈ D gilt: ava a v b ∧ b v a =⇒ a = b a v b ∧ b v c =⇒ a v c D × D ist ein Verband (Lattice) Reflexivita- t Anti − Symmetrie Transitivita- t Beispiele: 1. D = 2{a,b,c} a, b, c mit der Relation “⊆” : a, b a, c b, c a b c 108 / 427 Verbände (Beispiele) 3. Z mit der Relation “=” : -2 3. Z -1 0 1 2 mit der Relation “≤” : 2 1 0 -1 4. Z⊥ = Z ∪ {⊥} mit der Ordnung: -2 -1 0 1 2 ⊥ 109 / 427 Obere Schranken Definition: d ∈ D heißt obere Schranke für X ⊆ D falls xvd für alle x∈X 110 / 427 Obere Schranken Definition: d ∈ D heißt obere Schranke für X ⊆ D falls xvd für alle x∈X Definition: d heißt kleinste obere Schranke (lub) falls 1 d eine obere Schranke ist und 2 d v y für jede obere Schranke y für X. 110 / 427 Obere Schranken Definition: d ∈ D heißt obere Schranke für X ⊆ D falls xvd für alle x∈X Definition: d heißt kleinste obere Schranke (lub) falls 1 d eine obere Schranke ist und 2 d v y für jede obere Schranke y für X. Achtung: {0, 2, 4, . . .} ⊆ Z besitzt keine obere Schranke! {0, 2, 4} ⊆ Z besitzt die oberen Schranken 4, 5, 6, . . . 110 / 427 Vollständige Verbände Definition: Ein vollständiger Verband (cl) D ist eine Halbordnung, in der jede F Teilmenge X ⊆ D eine kleinste obere Schranke X ∈ D besitzt. Beachte: Jeder vollständige Verband besitzt → → F ein kleinstes Element ⊥ = ∅ ∈ D; F ein größtes Element > = D ∈ D. 111 / 427 Vollständige Verbände (Beispiele:) 1 2 3 4 5 D = 2{a,b,c} ist ein cl D = Z mit “=” ist keiner. D = Z mit “≤” ebenfalls nicht. D = Z⊥ auch nicht Mit einem zusätzlichen Symbol > erhalten wir den flachen Verband Z> ⊥ = Z ∪ {⊥, >} : > -2 -1 0 1 2 ⊥ 112 / 427 Untere Schranken Es gilt: Satz: In jedem vollständigen Verband D besitzt jede Teilmenge X ⊆ D eine größte untere Schranke X. F 113 / 427 Untere Schranken Es gilt: Satz: In jedem vollständigen Verband D besitzt jede Teilmenge X ⊆ D eine größte untere Schranke X. F Beweis Konstruiere: // U = {u ∈ D | ∀ x ∈ X : u v x}. die Menge der unteren Schranken von X 113 / 427 Untere Schranken Es gilt: Satz: In jedem vollständigen Verband D besitzt jede Teilmenge X ⊆ D eine größte untere Schranke X. F Beweis U = {u ∈ D | ∀ x ∈ X : u v x}. die Menge der unteren Schranken von X F Setze: g := U Behauptung: g = X Konstruiere: // F 113 / 427 Untere Schranken Beweis U = {u ∈ D | ∀ x ∈ X : u v x}. die Menge der unteren Schranken von X F Setze: g := U Behauptung: g = X Konstruiere: // F 1 g ist eine untere Schranke von X: Für x ∈ X gilt: u v x für alle u ∈ U ==⇒ x ist obere Schranke von U ==⇒ g v x 113 / 427 Untere Schranken Beweis U = {u ∈ D | ∀ x ∈ X : u v x}. die Menge der unteren Schranken von X F Setze: g := U Behauptung: g = X Konstruiere: // F 1 g ist eine untere Schranke von X: Für x ∈ X gilt: u v x für alle u ∈ U ==⇒ x ist obere Schranke von U ==⇒ g v x 2 g ist größte untere Schranke von X: Für jede untere Schranke u von X gilt: u∈U ==⇒ u v g 113 / 427 Verbände – grafisch 114 / 427 Verbände – grafisch Kleinste obere Schranke 114 / 427 Verbände – grafisch Kleinste obere Schranke Größte untere Schranke 114 / 427 Ungleichungssysteme über Verbänden Problem: Wir suchen Lösungen für Ungleichungssysteme der Form: xi w fi (x1 , . . . , xn ) 115 / 427 Ungleichungssysteme über Verbänden Problem: Wir suchen Lösungen für Ungleichungssysteme der Form: xi xi wobei: w D v ⊆ D×D fi : D n → D fi (x1 , . . . , xn ) Unbekannte Werte Ordnungsrelation Bedingung 115 / 427 Ungleichungssysteme über Verbänden Problem: Wir suchen Lösungen für Ungleichungssysteme der Form: xi xi wobei: D v ⊆ D×D fi : D n → D w fi (x1 , . . . , xn ) Unbekannte hier: Werte hier: Ordnungsrelation hier: Bedingung hier: Firstk (A) Dk = 2T ≤k ⊆ ... im Beispiel: Ungleichung für Firstk (A) [ Firstk (A) ⊇ {Firstk (X1 ) . . . Firstk (Xm ) | A → X1 . . . Xm ∈ P} 115 / 427 Ungleichungssysteme über Verbänden Problem: Wir suchen Lösungen für Ungleichungssysteme der Form: xi xi wobei: D v ⊆ D×D fi : D n → D w fi (x1 , . . . , xn ) Unbekannte hier: Werte hier: Ordnungsrelation hier: Bedingung hier: Firstk (A) Dk = 2T ≤k ⊆ ... im Beispiel: Ungleichung für Firstk (A) [ Firstk (A) ⊇ {Firstk (X1 ) . . . Firstk (Xm ) | A → X1 . . . Xm ∈ P} denn: x w d1 ∧ . . . ∧ x w dk gdw. xw F {d1 , . . . , dk } 115 / 427 Monotonie Definition: Eine Abbildung f : D1 → D2 heißt monoton, falls f (a) v f (b) für alle a v b. 116 / 427 Monotonie Definition: Eine Abbildung f : D1 → D2 heißt monoton, falls f (a) v f (b) für alle a v b. Beispiele: 1 D1 = D2 = 2U für eine Menge U und f x = (x ∩ a) ∪ b. Offensichtlich ist jedes solche f monoton 116 / 427 Monotonie Definition: Eine Abbildung f : D1 → D2 heißt monoton, falls f (a) v f (b) für alle a v b. Beispiele: 1 D1 = D2 = 2U 2 D1 = D2 = Z (mit der Ordnung “≤”). Dann gilt: für eine Menge U und f x = (x ∩ a) ∪ b. Offensichtlich ist jedes solche f monoton • inc x = x + 1 • dec x = x − 1 ist monoton. ist monoton. 116 / 427 Monotonie Definition: Eine Abbildung f : D1 → D2 heißt monoton, falls f (a) v f (b) für alle a v b. Beispiele: 1 D1 = D2 = 2U 2 D1 = D2 = Z (mit der Ordnung “≤”). Dann gilt: für eine Menge U und f x = (x ∩ a) ∪ b. Offensichtlich ist jedes solche f monoton • inc x = x + 1 • dec x = x − 1 • inv x = −x ist monoton. ist monoton. ist nicht monoton 116 / 427 Fixpunktiteration Gesucht: möglichst kleine Lösung für: xi w fi (x1 , . . . , xn ), i = 1, . . . , n wobei alle fi : Dn → D monoton sind. 117 / 427 Fixpunktiteration Gesucht: möglichst kleine Lösung für: xi w fi (x1 , . . . , xn ), i = 1, . . . , n wobei alle fi : Dn → D monoton sind. Idee: Betrachte F : Dn → Dn mit F(x1 , . . . , xn ) = (y1 , . . . , yn ) wobei yi = fi (x1 , . . . , xn ). 117 / 427 Fixpunktiteration Gesucht: möglichst kleine Lösung für: xi w fi (x1 , . . . , xn ), i = 1, . . . , n wobei alle fi : Dn → D monoton sind. Idee: Betrachte F : Dn → Dn mit F(x1 , . . . , xn ) = (y1 , . . . , yn ) wobei yi = fi (x1 , . . . , xn ). Sind alle fi monoton, dann auch F 117 / 427 Fixpunktiteration Gesucht: möglichst kleine Lösung für: xi w fi (x1 , . . . , xn ), i = 1, . . . , n wobei alle fi : Dn → D monoton sind. Idee: Betrachte F : Dn → Dn mit F(x1 , . . . , xn ) = (y1 , . . . , yn ) wobei yi = fi (x1 , . . . , xn ). Sind alle fi monoton, dann auch F Wir approximieren sukzessive eine Lösung. Wir konstruieren: ⊥, F ⊥, F 2 ⊥, F 3 ⊥, ... Hoffnung: Wir erreichen irgendwann eine Lösung ... ? 117 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , x1 x2 x3 v=⊆ ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} 118 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , v=⊆ x1 x2 x3 ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Iteration: x1 x2 x3 0 ∅ ∅ ∅ 1 {a} ∅ {c} 2 {a, c} ∅ {a, c} 3 {a, c} {a} {a, c} 4 dito dito dito 118 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , v=⊆ x1 x2 x3 ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Iteration: x1 x2 x3 0 ∅ ∅ ∅ 1 {a} ∅ {c} 2 {a, c} ∅ {a, c} 3 {a, c} {a} {a, c} 4 dito dito dito 118 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , v=⊆ x1 x2 x3 ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Iteration: x1 x2 x3 0 ∅ ∅ ∅ 1 {a} ∅ {c} 2 {a, c} ∅ {a, c} 3 {a, c} {a} {a, c} 4 dito dito dito 118 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , v=⊆ x1 x2 x3 ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Iteration: x1 x2 x3 0 ∅ ∅ ∅ 1 {a} ∅ {c} 2 {a, c} ∅ {a, c} 3 {a, c} {a} {a, c} 4 dito 118 / 427 Fixpunktiteration Beispiel: D = 2{a,b,c} , v=⊆ x1 x2 x3 ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Iteration: x1 x2 x3 0 ∅ ∅ ∅ 1 {a} ∅ {c} 2 {a, c} ∅ {a, c} 3 {a, c} {a} {a, c} 4 dito 118 / 427 Fixpunktiteration Offenbar gilt: Gilt F k ⊥ = F k+1 ⊥ , ist eine Lösung gefunden ⊥, F ⊥, F 2 ⊥, . . . bilden eine aufsteigende Kette : ⊥ v F⊥ v F2 ⊥ v ... Sind alle aufsteigenden Ketten endlich, gibt es k immer. 119 / 427 Fixpunktiteration Offenbar gilt: Gilt F k ⊥ = F k+1 ⊥ , ist eine Lösung gefunden ⊥, F ⊥, F 2 ⊥, . . . bilden eine aufsteigende Kette : ⊥ v F⊥ v F2 ⊥ v ... Sind alle aufsteigenden Ketten endlich, gibt es k immer. Die zweite Aussage folgt mit vollständiger Induktion: 119 / 427 Fixpunktiteration Offenbar gilt: Gilt F k ⊥ = F k+1 ⊥ , ist eine Lösung gefunden ⊥, F ⊥, F 2 ⊥, . . . bilden eine aufsteigende Kette : ⊥ v F⊥ v F2 ⊥ v ... Sind alle aufsteigenden Ketten endlich, gibt es k immer. Die zweite Aussage folgt mit vollständiger Induktion: Anfang: F 0 ⊥ = ⊥ v F 1 ⊥ 119 / 427 Fixpunktiteration Offenbar gilt: Gilt F k ⊥ = F k+1 ⊥ , ist eine Lösung gefunden ⊥, F ⊥, F 2 ⊥, . . . bilden eine aufsteigende Kette : ⊥ v F⊥ v F2 ⊥ v ... Sind alle aufsteigenden Ketten endlich, gibt es k immer. Die zweite Aussage folgt mit vollständiger Induktion: Anfang: F 0 ⊥ = ⊥ v F 1 ⊥ Schluss: Induktionsannahme: F i−1 ⊥ v F i ⊥. Dann F i ⊥ = F (F i−1 ⊥) v F (F i ⊥) = F i+1 ⊥ da F monoton ist 119 / 427 Fixpunktiteration Fazit: Wenn D endlich ist, finden wir über Fixpunktiteration mit Sicherheit eine Lösung Fragen: 1 Gibt es eine kleinste Lösung? 120 / 427 Fixpunktiteration Fazit: Wenn D endlich ist, finden wir über Fixpunktiteration mit Sicherheit eine Lösung Fragen: 1 Gibt es eine kleinste Lösung? 2 Wenn ja: findet Iteration die kleinste Lösung? 120 / 427 Fixpunktiteration Fazit: Wenn D endlich ist, finden wir über Fixpunktiteration mit Sicherheit eine Lösung Fragen: 1 Gibt es eine kleinste Lösung? 2 Wenn ja: findet Iteration die kleinste Lösung? 3 Was, wenn D nicht endlich ist? 120 / 427 Kleinster Fixpunkt Satz: Kleene In einer vollständigen Halbordnung D hat jede stetige Funktion f : D → D einen kleinsten Fixpunkt F d0 . Dieser ist gegeben durch d0 = k≥0 f k ⊥. 121 / 427 Kleinster Fixpunkt Satz: Kleene In einer vollständigen Halbordnung D hat jede stetige Funktion f : D → D einen kleinsten Fixpunkt F d0 . Dieser ist gegeben durch d0 = k≥0 f k ⊥. Bemerkung: Eine Funktion f heißt stetig, F falls für jede F aufsteigende Kette d0 v . . . v dm v . . . gilt: f ( m≥0 dm ) = m≥0 (f dm ) . Werden alle aufsteigenden Ketten irgendwann stabil, ist jede monotone Funktion automatisch stetig 121 / 427 Kleinster Fixpunkt Satz: Kleene In einer vollständigen Halbordnung D hat jede stetige Funktion f : D → D einen kleinsten Fixpunkt F d0 . Dieser ist gegeben durch d0 = k≥0 f k ⊥. Bemerkung: Eine Funktion f heißt stetig, F falls für jede F aufsteigende Kette d0 v . . . v dm v . . . gilt: f ( m≥0 dm ) = m≥0 (f dm ) . Werden alle aufsteigenden Ketten irgendwann stabil, ist jede monotone Funktion automatisch stetig Eine Halbordnung heißt vollständig (CPO), falls alle aufsteigenden Ketten kleinste obere Schranken haben Jeder vollständige Verband ist auch eine vollständige Halbordnung 121 / 427 Kleinster Fixpunkt Satz: Kleene In einer vollständigen Halbordnung D hatFjede stetige Funktion f : D → D einen kleinsten Fixpunkt d0 = k≥0 f k ⊥. Beweis: (1) f d0 = d0 : (2) d0 f d0 F m = f (f ⊥) F m≥0m+1 ⊥) wegen Stetigkeit = m≥0 (f F m+1 = ⊥t ⊥) m≥0 (f F m (f ⊥) = m≥0 = d0 ist kleinster Fixpunkt: Sei f d1 = d1 weiterer Fixpunkt. Wir zeigen: ∀ m ≥ 0 : f m ⊥ v d1 . m=0 : m>0 : ⊥ v Gelte f m−1 ⊥ v f m⊥ = v = d1 nach Definition d1 Dann folgt: f (f m−1 ⊥) f d1 wegen Monotonie d1 122 / 427 Kleinster Fixpunkt Bemerkung: Jede stetige Funktion ist auch monoton Betrachte die Menge: P = {x ∈ D | x w f x} Der kleinste Fixpunkt d0 ist in P und untere Schranke ==⇒ d0 ist der kleinste Wert x mit x w f x 123 / 427 Kleinster Fixpunkt Bemerkung: Jede stetige Funktion ist auch monoton Betrachte die Menge: P = {x ∈ D | x w f x} Der kleinste Fixpunkt d0 ist in P und untere Schranke ==⇒ d0 ist der kleinste Wert x mit x w f x Anwendung: Sei xi w fi (x1 , . . . , xn ), i = 1, . . . , n (∗) ein Ungleichungssystem, wobei alle fi : Dn → D monoton sind. 123 / 427 Kleinster Fixpunkt Bemerkung: Jede stetige Funktion ist auch monoton Betrachte die Menge: P = {x ∈ D | x w f x} Der kleinste Fixpunkt d0 ist in P und untere Schranke ==⇒ d0 ist der kleinste Wert x mit x w f x Anwendung: Sei xi w fi (x1 , . . . , xn ), i = 1, . . . , n (∗) ein Ungleichungssystem, wobei alle fi : Dn → D monoton sind. ==⇒ kleinste Lösung von (∗) == kleinster Fixpunkt von F 123 / 427 Kleinster Fixpunkt – für Firstk Der Kleenesche Fixpunkt-Satz liefert uns nicht nur die Existenz einer kleinsten Lösung sondern auch eine Charakterisierung Satz: Die Mengen Firstk ({w ∈ T ∗ | A →∗ w}) , A ∈ N, sind die kleinste Lösung des Ungleichungssystems: Firstk (A) ⊇ Firstk (X1 ) . . . Firstk (Xm ) , A → X1 . . . Xm ∈ P 124 / 427 Kleinster Fixpunkt – für Firstk Der Kleenesche Fixpunkt-Satz liefert uns nicht nur die Existenz einer kleinsten Lösung sondern auch eine Charakterisierung Satz: Die Mengen Firstk ({w ∈ T ∗ | A →∗ w}) , A ∈ N, sind die kleinste Lösung des Ungleichungssystems: Firstk (A) ⊇ Firstk (X1 ) . . . Firstk (Xm ) , A → X1 . . . Xm ∈ P Beweis-Idee: Sei F(m) (A) die m-te Approximation an den Fixpunkt. 1 Falls A →m u , dann Firstk (u) ⊆ F(m) (A). 2 Falls w ∈ F(m) (A) , dann A →∗ u für u ∈ T ∗ mit Firstk (u) = {w} 124 / 427 Fixpunktiteration – für Firstk Fazit: Wir können Firstk durch Fixpunkt-Iteration berechnen, d.h. durch wiederholtes Einsetzen. 125 / 427 Fixpunktiteration – für Firstk Fazit: Wir können Firstk durch Fixpunkt-Iteration berechnen, d.h. durch wiederholtes Einsetzen. Achtung: Naive Fixpunkt-Iteration ist ziemlich ineffizient Idee: Round Robin Iteration Benutze bei der Iteration nicht die Werte der letzten Iteration, sondern die jeweils aktuellen! 125 / 427 Round-Robin-Iteration D = 2{a,b,c} , Unser Mini-Beispiel: x1 x2 x3 v=⊆ ⊇ {a} ∪ x3 ⊇ x3 ∩ {a, b} ⊇ x1 ∪ {c} Die Round-Robin-Iteration: x1 x2 x3 1 {a} ∅ {a, c} 2 {a, c} {a} {a, c} 3 dito 126 / 427 Round-Robin-Iteration Der Code für Round Robin Iteration sieht so aus: for (i = 1; i ≤ n; i++) xi = ⊥; do { finished = true; for (i = 1; i ≤ n; i++) { new = fi (x1 , . . . , xn ); if (!(xi w new)) { finished = false; xi = xi t new; } } } while (!finished); 127 / 427 Round-Robin-Iteration Zur Korrektheit: Sei Sei (d) yi (d) xi die i-te Komponente von F d ⊥. der Wert von xi nach der i-ten RR-Iteration. Man zeigt: 1 (d) yi (d) v xi 128 / 427 Round-Robin-Iteration Zur Korrektheit: Sei Sei (d) yi (d) xi die i-te Komponente von F d ⊥. der Wert von xi nach der i-ten RR-Iteration. Man zeigt: (d) v xi (d) v zi 1 yi 2 xi (d) für jede Lösung (z1 , . . . , zn ) 128 / 427 Round-Robin-Iteration Zur Korrektheit: Sei Sei (d) yi (d) xi die i-te Komponente von F d ⊥. der Wert von xi nach der i-ten RR-Iteration. Man zeigt: (d) v xi (d) (d) v zi 1 yi 2 xi 3 Terminiert RR-Iteration nach d Runden, ist (d) (d) (x1 , . . . , xn ) eine Lösung für jede Lösung (z1 , . . . , zn ) 128 / 427 Round-Robin-Iteration – für First2 First2 (E) ⊇ First2 (E) {+} First2 (T) ∪ First2 (T) First2 (T) ⊇ First2 (T) {∗} First2 (F) ∪ First2 (F) First2 (F) ⊇ {(} First2 (E) {)} ∪ {name, int} Die RR-Iteration: First2 1 2 3 F T E name, int name, int name, int ( name, ( int ( name, ( int, name ∗, int ∗ ( name, ( int, name ∗, int ∗, name +, int + (( (( (( Der Einfachkeit halber haben wir in jeder Iteration nur die neuen Elemente vermerkt. 129 / 427 Round-Robin-Iteration – für First2 Diskussion: Die Länge h der längsten echt aufsteigenden Kette nennen wir auch Höhe von D ... Im Falle von Firstk ist die Höhe des Verbands exponentiell in k Die Anzahl der Runden von RR-Iteration ist beschränkt durch O(n · h) (n die Anzahl der Variablen) Die praktische Effizienz von RR-Iteration hängt allerdings auch von der Anordnung der Variablen ab Anstelle von RR-Iteration gibt es auch schnellere Fixpunkt-Verfahren, die aber im schlimmsten Fall immer noch exponentiell sind ==⇒ Man beschränkt sich i.a. auf kleine k ! 130 / 427 Syntaktische Analyse Kapitel 6: Top-down Parsing 131 / 427 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 132 / 427 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 •] 133 / 427 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 ... 134 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 Item-Kellerautomat ... im Beispiel: S 0 A 0 B 0 a b 135 / 427 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 ... 136 / 427 Philip M. Lewis, SUNY Richard E. Stearns, SUNY 1968: Syntax-Directed Transduction 137 / 427 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). 138 / 427 Topdown Parsing Problem: Konflikte zwischen Übergängen verhindern die Implementierung des Item-Kellerautomaten als deterministischer Kellerautomat. 139 / 427 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. 139 / 427 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. 139 / 427 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. 139 / 427 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 140 / 427 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. 141 / 427 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 β) = ∅ 141 / 427 Topdown Parsing Beispiel 1: S → E → if ( E ) S else S | while ( E ) S | E; id ist LL(1), da First1 (E) = {id} 142 / 427 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. 142 / 427 Vorausschau-Mengen Definition: Für eine Menge L ⊆ T ∗ definieren wir: First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L} Beispiel: ab aabb aaabbb 143 / 427 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 143 / 427 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 144 / 427 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. 144 / 427 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 ) 145 / 427 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 ) 145 / 427 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 . 146 / 427 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} 146 / 427 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 147 / 427 Schnelle Berechnung von Vorausschau-Mengen c a 0 b 1 3 2 c Vorgehen: Konstruiere den Variablen-Abhängigkeitsgraph zum Ungleichungssystem. 148 / 427 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 148 / 427 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 148 / 427 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 148 / 427 Schnelle Berechnung von Vorausschau-Mengen ... für unsere Beispiel-Grammatik: First1 : (, int, name S’ E T F 149 / 427 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). 150 / 427 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 ( ) 151 / 427 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 ) 152 / 427 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 153 / 427 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. 154 / 427 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 ... 154 / 427 Syntaktische Analyse Kapitel 8: Bottom-up Analyse 163 / 427 Donald E. Knuth, Stanford 164 / 427 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)∗ 165 / 427 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 165 / 427 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) 166 / 427 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 β γ ) 166 / 427 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 ( ) 166 / 427 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 ( ) 166 / 427 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 γ) = ∅ 166 / 427 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= ∅ 166 / 427 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) 167 / 427 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 168 / 427 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 169 / 427 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 , ) 169 / 427 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 170 / 427 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 → γ•] . 171 / 427 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 172 / 427 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 ... 172 / 427 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 :-) 173 / 427 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. 174 / 427 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) 175 / 427 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) 175 / 427 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 176 / 427 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•]} 177 / 427 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 ) •]} 178 / 427 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} 179 / 427 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. 180 / 427 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! 181 / 427 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) . 182 / 427 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 183 / 427 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. 184 / 427 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 185 / 427 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) 186 / 427 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 186 / 427 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 187 / 427 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 187 / 427 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) 188 / 427 LR(1)-Items S i0 γ0 A1 i1 γ1 A m im γm B i α β ... wobei γ0 . . . γm = γ 189 / 427 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 189 / 427 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}; 190 / 427 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. 190 / 427 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 ... 191 / 427 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 }} 191 / 427 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} 191 / 427 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 ]} ], ], ], ] ], ] ]} ], ]} 192 / 427 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 ]} ], ], ], ] ], ] ]} ], ]} 192 / 427 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, {, +, ∗}]} 192 / 427 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, { ) , +, ∗}]} 192 / 427 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 ) • ]} ], ]} 193 / 427 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 ) • ]} 193 / 427 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 ) • ]} 193 / 427 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 ) • , {, +, ∗}]} 193 / 427 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 ) •, { ) , +, ∗}]} 194 / 427 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 195 / 427 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’ 195 / 427 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) {, +, ∗}) = {, +} ∩ {∗} = ∅ 196 / 427 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. 197 / 427 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 198 / 427 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 199 / 427 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 199 / 427 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. 200 / 427 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 ... 200 / 427 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) 200 / 427 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 201 / 427 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. 202 / 427 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 ) ∩ {+} {. . .} = {} ∩ {+} = ∅ 202 / 427 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) ∩ {∗} {. . .} = {, +, ) } ∩ {∗} = ∅ = {} ∩ {+} = ∅ {, +, ) } ∩ {∗} ∅ 202 / 427 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) 203 / 427 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= ∅ 203 / 427 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 203 / 427 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 204 / 427 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 204 / 427 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•]} 205 / 427 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 ∅ 206 / 427 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} {. . .} = ∅ 207 / 427 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 208 / 427 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 ! 209 / 427 Syntaktische Analyse Kapitel 9: Zusammenfassung 210 / 427 Parsing Verfahren determinististische Sprachen = LR(1) = ... = LR(k) LALR(k) SLR(k) LR(0) reguläre Sprachen LL(1) LL(k) 211 / 427 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. 212 / 427 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 213 / 427 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 214 / 427 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 ( ) 215 / 427 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 216 / 427 Themengebiet: Die semantische Analyse 217 / 427 Semantische Analyse Scanner und Parser akzeptieren Programme mit korrekter Syntax. nicht alle lexikalisch und syntaktisch korrekten Programme machen Sinn 218 / 427 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 218 / 427 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; 218 / 427 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 218 / 427 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 218 / 427 Die semantische Analyse Kapitel 1: Attributierte Grammatiken 219 / 427 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 220 / 427 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. 220 / 427 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 221 / 427 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 221 / 427 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 221 / 427 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 221 / 427 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. 221 / 427 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 222 / 427 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 222 / 427 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 223 / 427 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) 224 / 427 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 224 / 427 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 225 / 427 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 225 / 427 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. 226 / 427 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 227 / 427 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 228 / 427 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 229 / 427 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 230 / 427 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 231 / 427 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. 232 / 427 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 233 / 427 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 234 / 427 Von den Abhängigkeiten zur Strategie Mögliche Strategien: 1 Der Benutzer soll die Strategie spezifizieren 234 / 427 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 234 / 427 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? 234 / 427 Linearisierung der Abhängigkeitsordnung Mögliche automatische Strategien: 235 / 427 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 235 / 427 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 235 / 427 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 236 / 427 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 236 / 427 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 236 / 427 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 236 / 427 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 237 / 427 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 237 / 427 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 238 / 427 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 238 / 427 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 239 / 427 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 240 / 427 Die lokalen Attribut-Abhängigkeiten: pre pre post post pre pre post post die Attributierung ist offenbar stark azyklisch 241 / 427 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) 241 / 427 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. 241 / 427 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. 242 / 427 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 242 / 427 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. 243 / 427 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); } } 244 / 427 Ausblick In der Übung: implementiere visitor pattern für C Grammatik prüfe, ob jeder Bezeichner deklariert wurde 245 / 427 Die semantische Analyse Kapitel 2: Symboltabellen 246 / 427 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 247 / 427 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 } 248 / 427 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); } 248 / 427 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... 248 / 427 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: 249 / 427 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 249 / 427 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: 250 / 427 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 250 / 427 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) 250 / 427 Ü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 251 / 427 Ü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 251 / 427 Ü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 251 / 427 (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. 252 / 427 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 253 / 427 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); } 254 / 427 Datenstrukturen für partielle Abbildungen Ideen: Liste von Paaren (w, i) ∈ String × int : O(1) O(n) ; zu teuer 255 / 427 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 255 / 427 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 255 / 427 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. 255 / 427 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 256 / 427 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) 257 / 427 Ü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 258 / 427 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 } 259 / 427 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 260 / 427 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 260 / 427 Praktische Implementierung: Sichtbarkeit Das Auflösen von Bezeichnern kann durch L-attributierte Grammatik erfolgen 261 / 427 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 261 / 427 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 261 / 427 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 261 / 427 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 261 / 427 Mehrfach- und Vorwärtsdeklarationen Manche Programmiersprachen verbieten eine Mehrfachdeklaration des selben Namens innerhalb eines Blocks. 262 / 427 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 262 / 427 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 262 / 427 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. 262 / 427 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; } 262 / 427 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); } 263 / 427 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))); } 263 / 427 Ü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 264 / 427 Ü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 264 / 427 Ü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”). 264 / 427 Mehrere Sorten von Bezeichnern Einige Programmiersprachen unterscheiden verschiedene Bezeichner: C: Variablennamen und Typnamen Java: Klassen, Methoden, Felder Haskell: Typnamen, Variablen, Operatoren mit Prioritäten 265 / 427 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 265 / 427 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! 265 / 427 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 ) 266 / 427 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 266 / 427 Fixity-Deklarationen in Haskell: Beobachtungen Nicht ohne Probleme: Scanner hat einen Zustand, der vom Parser bestimmt wird ; nicht mehr kontextfrei, braucht globale Datenstruktur 267 / 427 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 267 / 427 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 267 / 427 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. 267 / 427 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 | · · · 268 / 427 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 268 / 427 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 268 / 427 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 268 / 427 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: 269 / 427 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 269 / 427 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 269 / 427 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 269 / 427 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 269 / 427 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 269 / 427 Die semantische Analyse Kapitel 3: Typ-Überprüfung 270 / 427 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. 271 / 427 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 272 / 427 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; 273 / 427 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; 274 / 427 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 275 / 427 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 276 / 427 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 277 / 427 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 278 / 427 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 279 / 427 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. 280 / 427 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. 281 / 427 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: 282 / 427 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 283 / 427 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: 284 / 427 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 285 / 427 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? 286 / 427 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 286 / 427 Ü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 287 / 427 Ü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) 287 / 427 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. 288 / 427 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. 288 / 427 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. 289 / 427 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 289 / 427 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) 290 / 427 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 291 / 427 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) 292 / 427 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 292 / 427 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 292 / 427 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 292 / 427 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 293 / 427 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 294 / 427 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 295 / 427 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 296 / 427 Themengebiet: Codegenerierung 297 / 427 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 298 / 427 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 298 / 427 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 298 / 427 Codegenerierung Kapitel 1: Die C-Maschine 299 / 427 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) 300 / 427 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 300 / 427 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 301 / 427 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. 302 / 427 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. 302 / 427 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 303 / 427 Codegenerierung Kapitel 2: Ausdrucksauswertung 304 / 427 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 305 / 427 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 305 / 427 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 305 / 427 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. 306 / 427 Binäre Operatoren Operatoren mit zwei Argumenten werden wie folgt angewandt: 3 8 mul 24 SP--; S[SP] = S[SP] ∗ S[SP+1]; 307 / 427 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. 307 / 427 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. 307 / 427 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]; 308 / 427 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 309 / 427 Ausdrücke mit Variablen Variablen haben eine Speicherzellen in S: z: y: x: 310 / 427 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. 310 / 427 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. 310 / 427 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). 310 / 427 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). 311 / 427 Ü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) 312 / 427 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]]; 313 / 427 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--; 314 / 427 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 315 / 427 Codegenerierung Kapitel 3: Anweisungen 316 / 427 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--; 317 / 427 Codegenerierung Kapitel 4: Bedingte Anweisungen 318 / 427 Ü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 319 / 427 Codegenerierung Kapitel 5: Kontrollfluss 320 / 427 Sprünge Um von linearer Ausführungsreihenfolge abzuweichen, benötigen wir Sprünge: jump A A PC PC PC = A; 321 / 427 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--; 322 / 427 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 323 / 427 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) 323 / 427 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 323 / 427 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 323 / 427 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: 324 / 427 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 324 / 427 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) 324 / 427 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) 324 / 427 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. 325 / 427 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. 325 / 427 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.) 325 / 427 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 326 / 427 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 ... 327 / 427 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) 328 / 427 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 329 / 427 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: ... 330 / 427 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 : ... 331 / 427 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--; 332 / 427 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: 333 / 427 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. 334 / 427 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. 335 / 427 Ü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. 336 / 427 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 337 / 427 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 337 / 427 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 337 / 427 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 337 / 427 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 337 / 427 Ü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 338 / 427 Ü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 338 / 427 Ü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 338 / 427 Ü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 338 / 427 Ü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 338 / 427 Ü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. 338 / 427 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 339 / 427 Übersetzung von Felderzugriffen Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen. Sei t[c] a; die Deklaration eines Feldes a. 340 / 427 Ü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 340 / 427 Ü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 340 / 427 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. 341 / 427 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 341 / 427 Codegenerierung Kapitel 6: Zeiger und dynamische Speicherverwaltung 342 / 427 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 343 / 427 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 344 / 427 Ü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 345 / 427 Ü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 346 / 427 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). 347 / 427 Invarianten des Heaps und des Stacks Stack und Heap dürfen sich nicht überschneiden 348 / 427 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) 348 / 427 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 348 / 427 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 348 / 427 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; } 349 / 427 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 350 / 427 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. 351 / 427 Codegenerierung Kapitel 7: Funktionen 352 / 427 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 353 / 427 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 354 / 427 Speicherverwaltung von Funktionsvariablen Die formalen Parameter und lokalen Variablen der verschiedenen Aufrufe der selben Funktion (Instanzen) müssen auseinander gehalten werden. Idee zur Implementierung: 355 / 427 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. 355 / 427 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 355 / 427 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 355 / 427 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) 355 / 427 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. 356 / 427 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. 357 / 427 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. 357 / 427 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 357 / 427 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 } 358 / 427 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 } 358 / 427 Codegenerierung Kapitel 8: Betreten und Verlassen von Funktionen 359 / 427 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 } 360 / 427 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 361 / 427 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 362 / 427 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: 363 / 427 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. 363 / 427 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 363 / 427 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 364 / 427 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; 365 / 427 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; 366 / 427 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; 367 / 427 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; 368 / 427 Ü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 369 / 427 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”); 370 / 427 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; 371 / 427 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]; 372 / 427 Ü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 373 / 427 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; 374 / 427 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 375 / 427 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. 376 / 427 Themengebiet: Übersetzung ganzer Programme 377 / 427 Ü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. 378 / 427 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 379 / 427 Bemerkung zum Rückgabewerte Nach alter C Norm darf es nur einen Rückgabewert geben. 380 / 427 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 380 / 427 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? 380 / 427 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 380 / 427 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 380 / 427 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 380 / 427 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. 381 / 427 Übersetzung ganzer Programme Kapitel 1: Die Register C-Maschine 382 / 427 Codegenerierung für moderne Prozessoren Die C-Maschine und die JVM sind nicht sehr schnell: 383 / 427 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) 383 / 427 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) 383 / 427 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 383 / 427 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 )? 383 / 427 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 383 / 427 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 383 / 427 Moderne Register-Maschinen Entwicklung von Register-Maschinen: 384 / 427 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) 384 / 427 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 384 / 427 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 384 / 427 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. 384 / 427 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 385 / 427 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 386 / 427 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 387 / 427 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. 387 / 427 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 388 / 427 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]]; 389 / 427 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 390 / 427 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 390 / 427 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 390 / 427 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 390 / 427 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 390 / 427 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 390 / 427 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 391 / 427 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 392 / 427 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 393 / 427 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 393 / 427 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 393 / 427 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 393 / 427 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 394 / 427 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 395 / 427 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 395 / 427 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 395 / 427 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 395 / 427 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 395 / 427 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 395 / 427 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. 395 / 427 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: 396 / 427 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 396 / 427 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 396 / 427 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. 396 / 427 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 397 / 427 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 397 / 427 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 397 / 427 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 397 / 427 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 397 / 427 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 397 / 427 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 397 / 427 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 397 / 427 Praktische Umsetzung Instruktionsselektion durch Anwendung aller möglichen Regeln: kann nach verschiedene Kriterien optimieren (Laufzeit, Code-Größe, Parallelisierbarkeit, Stromverbrauch) 398 / 427 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 398 / 427 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 398 / 427 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: 398 / 427 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 398 / 427 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 398 / 427 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 398 / 427 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 398 / 427 Übersetzung ganzer Programme Kapitel 2: Codeerzeugung für die Register-C-Maschine 399 / 427 Motivation für die Register C-Maschine Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von Universalregistern. 400 / 427 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 400 / 427 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: 400 / 427 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 400 / 427 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) 400 / 427 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 401 / 427 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 402 / 427 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 402 / 427 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 ρ 402 / 427 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 403 / 427 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 403 / 427 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: 403 / 427 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 403 / 427 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 403 / 427 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 403 / 427 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 404 / 427 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 404 / 427 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 404 / 427 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 404 / 427 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 404 / 427 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 404 / 427 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 404 / 427 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 405 / 427 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. 405 / 427 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. 405 / 427 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) 405 / 427 Ü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 406 / 427 Ü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 406 / 427 Ü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 406 / 427 Ü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 406 / 427 Ü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 406 / 427 Ü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 ρ = codeRi+1 e1 ρ codei+2 R e2 ρ op Ri Ri+1 Ri+2 407 / 427 Ü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 ρ = codeRi+1 e1 ρ codei+2 R e2 ρ op Ri Ri+1 Ri+2 = code5R 3 ρ code6R 4 ρ mul R4 R5 R6 Beispiel: Übersetze 3*4 mit i = 4: code4R 3*4 ρ 407 / 427 Ü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 ρ = codeRi+1 e1 ρ codei+2 R e2 ρ op Ri Ri+1 Ri+2 = loadc R5 3 loadc R6 4 mul R4 R5 R6 Beispiel: Übersetze 3*4 mit i = 4: code4R 3*4 ρ 407 / 427 Verwaltung temporärer Register Beachte, dass temporäre Register wiederverwendet werden: Übersetze 3+4*3+4 mit t = 4: code4R 3*4+3*4 ρ = code5R 3*4 ρ code6R 3*4 ρ add R4 R5 R6 mit codeiR 3*4 ρ = loadc Ri+1 3 loadc Ri+2 4 mul Ri Ri+1 Ri+2 ergibt sich code4R 3*4+3*4 ρ = 408 / 427 Verwaltung temporärer Register Beachte, dass temporäre Register wiederverwendet werden: Übersetze 3+4*3+4 mit t = 4: code4R 3*4+3*4 ρ = code5R 3*4 ρ code6R 3*4 ρ add R4 R5 R6 mit codeiR 3*4 ρ = loadc Ri+1 3 loadc Ri+2 4 mul Ri Ri+1 Ri+2 ergibt sich code4R 3*4+3*4 ρ = loadc R6 3 loadc R7 4 mul R5 R6 R7 loadc R7 3 loadc R8 4 mul R6 R7 R8 add R4 R5 R6 408 / 427 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 409 / 427 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 409 / 427 Übersetzung unärer Operatoren Die unären Operatoren op = {neg, not} nehmen zwei Register: codeiR op e ρ = codeiR e ρ op Ri Ri 410 / 427 Ü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. 410 / 427 Ü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 410 / 427 Ü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 410 / 427 Ü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 410 / 427 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 loada Ri c loadr Ri c move Ri Rj move Rj k Semantik Ri ← S[Rj ] Ri ← 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 globale Variable lade lokale Variable transferiere Wert zw. Registern kopiere k Werte auf den Keller 411 / 427 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 412 / 427 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 412 / 427 Ü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} 413 / 427 Ü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 ρ = loadc Ri a if ρ x = hL, ai 413 / 427 Ü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 ρ = loadc Ri a if ρ x = hL, ai Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert! 413 / 427 Ü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 ρ = loadc 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 413 / 427 Ü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 ρ = loadc 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 413 / 427 Ü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 ρ = loadc 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 413 / 427 Ü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 414 / 427 Ü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. 414 / 427 Ü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 414 / 427 Ü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! 414 / 427 Zeiger und Datenstrukturen Zeiger und Zeigerarithmetik wird in Registern ausgeführt: Generieren von Zeigern: codeiR &e ρ = codeiL e ρ 415 / 427 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 415 / 427 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 ρ 415 / 427 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 415 / 427 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 415 / 427 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 415 / 427 Übersetzung ganzer Programme Kapitel 3: Übersetzung von Anweisungen 416 / 427 Übersetzung von Anweisungen Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung übersetzen können: 417 / 427 Ü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 ρ 417 / 427 Ü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 417 / 427 Übersetzung von bedingten Anweisungen code c code tt code ee Übersetzung von if ( c ) tt else ee. codeiR if(c) tt else ee ρ = codeiR c ρ jumpz Ri A codeiR tt ρ jump B A : codeiR ee ρ B: 418 / 427 Übersetzung von Schleifen Übersetzung von while Schleifen: codeiR while(e) s ρ =A: codeiR e ρ jumpz Ri B codei s ρ jump A B: 419 / 427 Übersetzung von Schleifen Übersetzung von while Schleifen: codeiR while(e) s ρ =A: codeiR e ρ jumpz Ri B codei s ρ jump A B: Übersetzung von for Schleifen: codeiR for(e1 ; e2 ; e3 ) s ρ = codeiR e1 ρ A : codeiR e2 ρ jumpz Ri B codei s ρ codeiR e3 ρ jump A B: 419 / 427 Ü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]. 420 / 427 Ü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 420 / 427 Ü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. 420 / 427 Ü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 421 / 427 Ü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 421 / 427 Ü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. 421 / 427 Übersetzung ganzer Programme Kapitel 4: Übersetzung von Funktionen 422 / 427 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) 423 / 427 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: 423 / 427 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 423 / 427 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 423 / 427 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 423 / 427 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 423 / 427 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 423 / 427 Ü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 call Ri restoreloc R1 Ri−1 move Ri R0 424 / 427 Ü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 call Ri restoreloc R1 Ri−1 move Ri R0 Neue Instruktionen: saveloc R1 Ri−1 legt die Register R1 , . . . Ri−1 , EP, SP und FP auf dem Stapel ab restoreloc R1 Ri−1 nimmt die Register FP, SP, EP, Ri−1 , . . . R1 vom Stapel runter 424 / 427 Ü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 call Ri restoreloc R1 Ri−1 move Ri R0 Neue Instruktionen: saveloc R1 Ri−1 legt die Register R1 , . . . Ri−1 , EP, SP und FP auf dem Stapel ab restoreloc R1 Ri−1 nimmt die Register FP, SP, EP, Ri−1 , . . . R1 vom Stapel runter Argumente auf dem Stack: push Ri pro Wert und pop k für k Werte 424 / 427 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 425 / 427 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 425 / 427 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 425 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei ss ρ0 return Randbedinungen: 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei ss ρ0 return Randbedinungen: saveloc speichert maximal q − 3 Register die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei 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 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei 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 ρ 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei 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 426 / 427 Übersetzung ganzer Funktionen Die Übersetzung einer Funktion ist damit wie folgt definiert: code1R tr f(args){decls ss} ρ = enter q move Rl+1 R−1 .. . move Rl+n R−n codei 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? 426 / 427 Übersetzen ganzer Programme Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten. code1R F ρ = enter (k + 3) alloc k loadc R1 _main saveloc R1 R1 call R1 restoreloc R1 R1 halt _f1 : codei F1 ρ .. . _fn : codei Fn ρ 427 / 427 Übersetzen ganzer Programme Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten. code1R F ρ = enter (k + 3) alloc k loadc R1 _main saveloc R1 R1 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 427 / 427 Ü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