Themengebiet: Einführung

Werbung
◦
◦◦◦
◦◦◦◦
◦ ◦
◦◦◦
◦◦◦◦
◦ ◦◦
Organisatorisches
TECHNISCHE UNIVERSITÄT MÜNCHEN
FAKULTÄT
FÜR
INFORMATIK
Master oder Bachelor ab 6. Semester mit 5 ECTS
Voraussetzungen
Compilerbau I
Informatik 1 & 2
Theoretische Informatik
Technische Informatik
Grundlegende Algorithmen
Michael Petter, Axel Simon
Aufbauende Vorlesungen
Virtuelle Maschinen
Programmoptimierung
Programmiersprachen
Praktikum Compilerbau
Hauptseminare
SoSe 2010
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
1 / 398
Organisatorisches
2 / 398
Vorläufiger Inhalt
Grundlagen Reguläre Ausdrücke und Automaten
Zeiten:
Von der Spezifizierung mit mehreren Regulären Ausdrücken zur
Implementation mit Automaten
Vorlesung: Mi. 14:15-15:45 Uhr
Übung: wird per Onlineabstimmung geklärt
Reduzierte kontextfreie Grammatiken und Kellerautomaten
Bottom-Up Syntaxanalyse
Abschlussprüfung
Attributsysteme
Klausur, Termin über TUM-online
Typüberprüfung
Erfolgreiches Lösen der Aufgaben wird als Bonus von 0.3
angerechnet.
Codegenerierung für Stackmaschinen
Registerverteilung
Einfache Optimierung
4 / 398
3 / 398
Interpreter
Programm
Themengebiet:
Eingabe
Einführung
Interpreter
Ausgabe
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
5 / 398
6 / 398
Prinzip eines Übersetzers:
Übersetzer
Übersetzer
Programm
Eine Vorberechnung auf dem Programm gestattet u.a.
Code
eine geschickte(re) Verwaltung der Variablen;
Erkennung und Umsetzung globaler Optimierungsmöglichkeiten.
Code
Maschine
Nachteil
Ausgabe
Die Übersetzung selbst dauert einige Zeit
Eingabe
Vorteil
Zwei Phasen:
1
Übersetzung des Programm-Texts in ein Maschinen-Programm;
2
Ausführung des Maschinen-Programms auf der Eingabe.
Die Ausführung des Programme wird effizienter
⇒ lohnt sich bei aufwendigen Programmen und solchen, die
mehrmals laufen.
8 / 398
7 / 398
Übersetzer
Übersetzer
allgemeiner Aufbau eines Übersetzers:
Analyse
Analyse
Interndarstellung
Synthese
Compiler
Programmtext
Code
Interndarstellung
Synthese
Compiler
Programmtext
Compiler
Compiler
allgemeiner Aufbau eines Übersetzers:
Code
9 / 398
Übersetzer
9 / 398
Übersetzer
Die Analyse-Phase ist selbst unterteilt in mehrere Schritte
Die Analyse-Phase ist selbst unterteilt in mehrere Schritte
Programmtext
Programmtext
Analyse
Analyse
Scanner
(annotierter) Syntaxbaum
lexikalische Analyse:
Aufteilung in Sinneinheiten
Token-Strom
(annotierter) Syntaxbaum
9 / 398
9 / 398
Übersetzer
Übersetzer
Die Analyse-Phase ist selbst unterteilt in mehrere Schritte
Die Analyse-Phase ist selbst unterteilt in mehrere Schritte
Programmtext
lexikalische Analyse:
Aufteilung in Sinneinheiten
Scanner
Token-Strom
Parser
syntaktische Analyse:
Erkennen der hierarchischen Struktur
Syntaxbaum
lexikalische Analyse:
Aufteilung in Sinneinheiten
Token-Strom
Analyse
Analyse
Scanner
Programmtext
Parser
syntaktische Analyse:
Erkennen der hierarchischen Struktur
Syntaxbaum
Type
Checker...
semantische Analyse:
Überprüfung/Ermittlung
semantischer Eigenschaften
(annotierter) Syntaxbaum
(annotierter) Syntaxbaum
9 / 398
9 / 398
Die lexikalische Analyse
Themengebiet:
Programmtext
Scanner
Token-Strom
Lexikalische Analyse
10 / 398
Die lexikalische Analyse
xyz + 42
11 / 398
Die lexikalische Analyse
Scanner
xyz + 42
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:
→
xyz, pi, ...
Konstanten wie
42, 3.14, ”abc”, ...
Operatoren wie
+, ...
→
reservierte Worte wie
→
11 / 398
Namen (Identifier) wie
→
if, int, ...
11 / 398
Die lexikalische Analyse
Die lexikalische Analyse
Scanner
xyz + 42
I
Sind Tokens erst einmal klassifiziert, kann man die Teilwörter
vorverarbeiten:
O C
xyz + 42
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:
Ein Token ist eine Folge von Zeichen, die zusammen eine Einheit
bilden.
Tokens werden in Klassen zusammen gefasst. Zum Beispiel:
→
Namen (Identifier) wie
→
xyz, pi, ...
→
Konstanten wie
42, 3.14, ”abc”, ...
Operatoren wie
+, ...
→
reservierte Worte wie
→
→
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
if, int, ...
12 / 398
11 / 398
Die lexikalische Analyse
Die lexikalische Analyse - Generierung:
Diskussion:
Vorteile
Produktivität Die Komponente lässt sich schneller herstellen
Scanner und Sieber werden i.a. in einer Komponente zusammen
gefasst, indem man dem Scanner nach Erkennen eines Tokens
gestattet, eine Aktion auszuführen
Korrektheit Die Komponente realisiert (beweisbar) die
Spezifikation.
Scanner werden i.a. nicht von Hand programmiert, sondern aus
einer Spezifikation generiert:
Spezifikation
Konstanten;
Generator
Effizienz Der Generator kann die erzeugte
Programmkomponente mit den effizientesten
Algorithmen ausstatten.
Scanner
14 / 398
13 / 398
Die lexikalische Analyse - Generierung:
Die lexikalische Analyse - Generierung:
Vorteile
... in unserem Fall:
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.
Spezifikation
Generator
Scanner
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 / 398
15 / 398
Die lexikalische Analyse - Generierung:
Lexikalische Analyse
... in unserem Fall:
Kapitel 1:
0
0 | [1-9][0-9]*
Generator
Grundlagen: Reguläre Ausdrücke
[1−9]
[0−9]
Spezifikation von Token-Klassen: Reguläre Ausdrücke;
Generierte Implementierung: Endliche Automaten + X
15 / 398
Reguläre Ausdrücke
Reguläre Ausdrücke
Grundlagen
Programmtext benutzt ein endliches Alphabet
Eingabe-Zeichen, z.B. ASCII
16 / 398
Grundlagen
Σ
von
Programmtext benutzt ein endliches Alphabet
Eingabe-Zeichen, z.B. ASCII
Σ
von
Die Menge der Textabschnitte einer Token-Klasse ist i.a.
regulär.
Die Menge der Textabschnitte einer Token-Klasse ist i.a.
regulär.
Reguläre Sprachen kann man mithilfe regulärer Ausdrücke
spezifizieren.
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
a∈E
( neues Symbol nicht aus Σ);
für alle a ∈ Σ;
(e1 | e2 ), (e1 · e2 ), e1 ∗ ∈ E
17 / 398
sofern
e1 , e2 ∈ E.
17 / 398
Reguläre Ausdrücke
... im Beispiel:
((a · b∗ )·a)
(a | b)
((a · b)·(a · b))
Stephen Kleene, Madison Wisconsin, 1909-1994
18 / 398
19 / 398
Reguläre Ausdrücke
Reguläre Ausdrücke
... im Beispiel:
... im Beispiel:
((a · b∗ )·a)
(a | b)
((a · b)·(a · b))
((a · b∗ )·a)
(a | b)
((a · b)·(a · b))
Achtung:
Achtung:
Wir unterscheiden zwischen Zeichen a, 0, $,... und Meta-Zeichen
(, |, ),...
Um (hässliche) Klammern zu sparen, benutzen wir
Operator-Präzedenzen:
∗
und lassen “·” weg
Wir unterscheiden zwischen Zeichen a, 0, $,... und Meta-Zeichen
(, |, ),...
Um (hässliche) Klammern zu sparen, benutzen wir
Operator-Präzedenzen:
∗
>·>|
e?
e+
19 / 398
Reguläre Ausdrücke
und verzichten auf “”
≡ ( | e)
≡ (e · e∗ )
19 / 398
Beachte:
Die Operatoren (_)∗ , ∪, · sind die entsprechenden
Operationen auf Wort-Mengen:
Spezifikationen benötigen eine Semantik
...im Beispiel:
Spezifikation
abab
a|b
ab∗ a
>·>|
und lassen “·” weg
Reale Spezifikations-Sprachen bieten zusätzliche Konstrukte:
(L)∗
L1 · L2
Semantik
{abab}
{a, b}
{abn a | n ≥ 0}
=
=
{w1 . . . wk | k ≥ 0, wi ∈ L}
{w1 w2 | w1 ∈ L1 , w2 ∈ L2 }
Für e ∈ EΣ definieren wir die spezifizierte Sprache [[e]] ⊆ Σ∗
induktiv durch:
[[]]
= {}
[[a]]
= {a}
[[e∗ ]]
= ([[e]])∗
[[e1 |e2 ]] = [[e1 ]] ∪ [[e2 ]]
[[e1 ·e2 ]] = [[e1 ]] · [[e2 ]]
21 / 398
20 / 398
Beachte:
Reguläre Ausdrücke
Die Operatoren (_)∗ , ∪, · sind die entsprechenden
Operationen auf Wort-Mengen:
(L)∗
L1 · L2
=
=
Beispiel:
{w1 . . . wk | k ≥ 0, wi ∈ L}
{w1 w2 | w1 ∈ L1 , w2 ∈ L2 }
Identifier in Java:
le = [a-zA-Z_\$]
di = [0-9]
Id = {le} ({le} | {di})*
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 / 398
22 / 398
Reguläre Ausdrücke
Beispiel:
Reguläre Ausdrücke
Beispiel:
Identifier in Java:
Identifier in Java:
le = [a-zA-Z_\$]
di = [0-9]
Id = {le} ({le} | {di})*
le = [a-zA-Z_\$]
di = [0-9]
Id = {le} ({le} | {di})*
Float = {di}* (\.{di}|{di}\.) {di}*((e|E)(\+|\-)?{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 / 398
22 / 398
Endliche Automaten
Lexikalische Analyse
im Beispiel:
Kapitel 2:
a
Grundlagen: Endliche Automaten
b
23 / 398
24 / 398
Endliche Automaten
im Beispiel:
a
b
Knoten: Zustände;
Michael O. Rabin, Stanford
University
Kanten: Übergänge;
Beschriftungen: konsumierter Input
24 / 398
Dana S. Scott, Carnegy Mellon
University, Pittsburgh
25 / 398
Endliche Automaten
Endliche Automaten
Definition
Definition
Ein nicht-deterministischer endlicher Automat (NFA) ist ein Tupel
A = (Q, Σ, δ, I, F) wobei:
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.
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 / 398
Endliche Automaten
26 / 398
Endliche Automaten
Berechnungen sind Pfade im Graphen.
Berechnungen sind Pfade im Graphen.
akzeptierende Berechnungen führen von I
nach
F.
akzeptierende Berechnungen führen von I
Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden
Pfades ...
a
nach
F.
Ein akzeptiertes Wort ist die Beschriftung eines akzeptierenden
Pfades ...
a
b
b
27 / 398
Endliche Automaten
Berry-Sethi Verfahren – Umwandlung von Regex in NFA
Dazu definieren wir den transitiven Abschluss δ ∗ von δ als
kleinste Menge δ 0 mit:
(p, , p) ∈ δ 0
(p, xw, q) ∈ δ 0
und
sofern
27 / 398
(p, x, p1 ) ∈ δ
Satz:
Für jeden regulären Ausdruck e kann (in linearer Zeit) ein NFA
konstruiert werden, der die Sprache [[e]] akzeptiert.
und (p1 , w, q) ∈ δ 0 .
δ ∗ beschreibt für je zwei Zustände, mit welchen Wörtern man
vom einen zum andern kommt
Idee:
Die Menge aller akzeptierten Worte, d.h. die von A akzeptierte
Sprache können wir kurz beschreiben als:
Der Automat verfolgt (konzepionell mithilfe einer Marke “•”), im
Syntaxbaum des regulären Ausdrucks, wohin man in e mit der
Eingabe w gelangen kann.
L(A) = {w ∈ Σ∗ | ∃ i ∈ I, f ∈ F : (i, w, f ) ∈ δ ∗ }
28 / 398
29 / 398
Berry-Sethi Verfahren
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
.
(a|b)∗ a(a|b)
w
:
.
*
|
b
.
*
a
|
a
= bbaa
b
a
a
|
|
b
a
b
a
30 / 398
Berry-Sethi Verfahren
30 / 398
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
w
= bbaa
.
:
w
:
.
*
|
b
.
*
a
|
a
= bbaa
b
a
a
|
|
b
a
b
a
30 / 398
Berry-Sethi Verfahren
30 / 398
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
w
= bbaa
.
:
w
:
.
*
b
.
*
a
|
a
= bbaa
|
a
a
|
b
a
30 / 398
b
|
a
b
30 / 398
Berry-Sethi Verfahren
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
w
= bbaa
.
:
w
:
.
*
|
b
.
*
a
|
a
= bbaa
b
a
a
|
|
b
a
b
a
30 / 398
Berry-Sethi Verfahren
30 / 398
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
w
= bbaa
.
:
w
:
.
*
b
.
*
a
|
a
= bbaa
|
a
a
|
b
|
b
a
b
a
30 / 398
Berry-Sethi Verfahren
30 / 398
Berry-Sethi Verfahren
... im Beispiel:
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
|
.
a
|
Leider gibt es eventuell mehrere gleiche Teilausdrücke
==⇒
a
Wir numerieren die Blätter durch ...
31 / 398
b
a
b
32 / 398
Berry-Sethi Verfahren
Berry-Sethi Verfahren
... im Beispiel:
... im Beispiel:
.
.
.
*
a
|
a
b
0
|
2
1
.
*
a
|
b
3
0
4
2
1
a
|
a
3
b
4
a
b
32 / 398
Berry-Sethi Verfahren
Berry-Sethi Verfahren
Die Konstruktion:
Zustände:
Anfangszustand:
Endzustand:
Übergangsrelation:
32 / 398
Diskussion:
•r, r• r Knoten von e;
•e;
e•;
Für Blätter r ≡ i x benötigen
wir: (•r, x, r•).
Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren
Der Automat ist i.a. nichtdeterministisch
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•)
34 / 398
33 / 398
Berry-Sethi Verfahren
Berry-Sethi Verfahren: 1. Schritt
empty[r] = t
Diskussion:
gdw.
∈ [[r]]
Die meisten Übergänge dienen dazu, im Ausdruck zu navigieren
Der Automat ist i.a. nichtdeterministisch
⇒
... im Beispiel:
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.
0
Idee: Generiere diese Attribute für die Knoten über DFS!
34 / 398
a
.
2
1
b
|
a
3
a
4
b
35 / 398
Berry-Sethi Verfahren: 1. Schritt
empty[r] = t
gdw.
Berry-Sethi Verfahren: 1. Schritt
empty[r] = t
∈ [[r]]
... im Beispiel:
gdw.
∈ [[r]]
... im Beispiel:
.
.
.
*
f
|
2
.
*
f
|
a
|
f
f
2
|
a
f
f
f
f
f
f
f
f
0
1
3
4
0
1
3
4
a
b
a
b
a
b
a
b
35 / 398
Berry-Sethi Verfahren: 1. Schritt
empty[r] = t
gdw.
35 / 398
Berry-Sethi Verfahren: 1. Schritt
empty[r] = t
∈ [[r]]
... im Beispiel:
gdw.
∈ [[r]]
... im Beispiel:
f
.
.
t
f
t
f
*
.
*
.
f
|
f
f
2
|
a
f
|
f
f
2
|
a
f
f
f
f
f
f
f
f
0
1
3
4
0
1
3
4
a
b
a
b
a
b
a
b
35 / 398
Berry-Sethi Verfahren: 1. Schritt
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= }
Implementierung:
DFS post-order Traversierung
Für Blätter r ≡
i
x
ist
35 / 398
... im Beispiel:
f
empty[r] = (x ≡ ).
.
Andernfalls:
empty[r1 | r2 ] = empty[r1 ] ∨ empty[r2 ]
empty[r1 · r2 ] = empty[r1 ] ∧ empty[r2 ]
empty[r1∗ ]
= t
empty[r1 ?]
= t
t
f
*
.
f
|
36 / 398
f
f
2
|
a
f
f
f
f
0
1
3
4
a
b
a
b
37 / 398
Berry-Sethi Verfahren: 2. Schritt
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= }
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:
... im Beispiel:
f
f
.
.
t
f
*
.
2
f
f
|
0
f
0
f
1 2a
f
1
a
|
3
f
3
b
f
*
.
2
f
01 f
4
f
4
a
t
|
0
f
0
b
1 2a
f
1
a
34
f
|
3
f
3
b
4
f
4
a
b
37 / 398
Berry-Sethi Verfahren: 2. Schritt
37 / 398
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= }
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:
... im Beispiel:
012
f
f
.
01
t
*
|
0
1 2a
f
1
a
*
34
f
|
3
f
3
b
.
01
t
.
2
f
01 f
0
f
2
f
a
4
|
0
f
0
b
.
2
f
01 f
4
f
2
f
1 2a
f
1
a
34
f
|
3
f
3
b
4
f
4
a
b
37 / 398
Berry-Sethi Verfahren: 2. Schritt
37 / 398
Berry-Sethi Verfahren: 3. Schritt
Implementierung:
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= }
DFS post-order Traversierung
... im Beispiel:
Für Blätter r ≡
i
x
ist
012
f
first[r] = {i | x 6≡ }.
Andernfalls:
first[r1 | r2 ]
first[r1 · r2 ]
first[r1∗ ]
first[r1 ?]
.
01
t
first[r
1 ] ∪ first[r2 ]
first[r1 ] ∪ first[r2 ]
first[r1 ]
= first[r1 ]
= first[r1 ]
=
=
*
falls empty[r1 ] = t
falls empty[r1 ] = f
|
0
38 / 398
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 / 398
Berry-Sethi Verfahren: 3. Schritt
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= }
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:
... im Beispiel:
012
f ∅
.
01
t
*
|
0
2
1
f
1
a
2
f ∅
a
*
34
f ∅
|
3
∅ f
3
b
.
01
2 t
.
2
f
01 f
0
f
012
f ∅
4
a
|
0
f
0
b
2
1
f
1
a
.
2
34
f ∅
34 f
01 f
4
f ∅
2
f ∅
a
|
3
∅ f
3
b
4
f ∅
4
a
b
39 / 398
Berry-Sethi Verfahren: 3. Schritt
39 / 398
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= }
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:
... im Beispiel:
012
f ∅
012
f ∅
.
01
2
f ∅
2 t
*
|
0
f
0
a
.
2
0 1 2 01 f
2
1
a
|
3
∅ f
3
b
*
34
f ∅
34 f
1
f
.
01
2
f ∅
2 t
a
2
0 1 2 01 f
4
f ∅
4
|
0
012 f
0
b
2
1
a
f 012
1
a
.
34
f ∅
34 f
|
3
∅ f
3
b
4
f ∅
4
a
b
39 / 398
Berry-Sethi Verfahren: 3. Schritt
39 / 398
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= }
Implementierung:
DFS pre-order Traversierung
... im Beispiel:
Für die Wurzel haben wir:
012
f ∅
next[e] = ∅
Ansonsten machen wir eine Fallunterscheidung über den Kontext:
.
01
r
r1 | r2
r1 · r2
r1∗
r1 ?
2 t
Regeln
next[r1 ]
next[r2 ]
next[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]
*
2
34 f
0 1 2 01 f
falls empty[r2 ] = t
falls empty[r2 ] = f
|
0
012 f
0
40 / 398
a
2
1
a
f 012
1
b
2
f ∅
.
34
f ∅
|
3
∅ f
3
a
4
f ∅
4
b
41 / 398
Berry-Sethi Verfahren: 4. Schritt
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= }
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:
... im Beispiel:
012
f ∅
.
01
2 t
2
f ∅
*
22
0 1 2 01 f
0 0
.
|
2
11
a
f 012
1
a
3 3
|
∅ f
3
b
.
01 01
2 t
*
34
f ∅
34 f
012 f
0
012 34
f ∅
4
a
0 0
|
012 f
0
b
a
.
22
0 1 2 01 f 01
44
f ∅
2 34
f ∅
34 34
f ∅
34 f
2
11
a
f 012
1
33
|
∅ f
3
b
a
44
f ∅
4
b
41 / 398
Berry-Sethi Verfahren: 4. Schritt
Berry-Sethi Verfahren: Integration
Implementierung:
Konstruktion: Erstelle Automat aus Attributen des Syntaxbaums:
DFS post-order Traversierung
Für Blätter r ≡
i
ist
x
Zustände: {•e} ∪ {i• | i Blatt}
Startzustand: •e
last[r] = {i | x 6≡ }.
Endzustände: last[e]
{•e} ∪ last[e]
Andernfalls:
last[r1 | r2 ]
last[r1 · r2 ]
last[r1∗ ]
last[r1 ?]
41 / 398
last[r1 ] ∪ last[r2 ]
last[r1 ] ∪ last[r2 ]
=
last[r2 ]
= last[r1 ]
= last[r1 ]
=
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.
falls empty[r2 ] = t
falls empty[r2 ] = f
Den resultierenden Automaten bezeichnen wir mit Ae .
42 / 398
43 / 398
Berry-Sethi Verfahren
... im Beispiel:
a
0
3
a
a
a
b
a
2
b
a
b
a
1
4
b
Gerard Berry, Esterel
Technologies
Bemerkung:
Ravi Sethi, Research VR,
Lucent Technologies
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 / 398
45 / 398
Der erwartete Automat:
Teilmengenkonstruktion
a
0
3
a
... im Beispiel:
a
a
b
a
a, b
2
b
a
a
a, b
b
a
1
4
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 / 398
Teilmengenkonstruktion
47 / 398
Teilmengenkonstruktion
a
a
0
3
a
... im Beispiel:
a
3
a
... im Beispiel:
a
b
a
0
a
a
b
a
2
2
b
b
a
a
b
a
b
1
a
4
1
b
4
b
02
02
a
a
a
b
1
b
47 / 398
Teilmengenkonstruktion
47 / 398
Teilmengenkonstruktion
a
a
0
3
a
... im Beispiel:
a
... im Beispiel:
a
b
a
0
3
a
a
a
2
2
b
b
a
a
b
a
1
b
4
4
b
a
02
a
1
b
a
a
b
a
023
a
02
a
023
a
b
a
a
b
a
b
b
1
1
b
b
14
b
47 / 398
47 / 398
Teilmengenkonstruktion
Teilmengenkonstruktion
Satz:
Satz:
Zu jedem nichtdeterministischen Automaten A = (Q, Σ, δ, I, F) kann
ein deterministischer Automat P(A) konstruiert werden mit
Zu jedem nichtdeterministischen Automaten A = (Q, Σ, δ, I, F) kann
ein deterministischer Automat P(A) konstruiert werden mit
L(A) = L(P(A))
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 / 398
Teilmengenkonstruktion
48 / 398
Teilmengenkonstruktion
Achtung:
Achtung:
Leider gibt es exponentiell viele Teilmengen von Q
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 ...
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
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 / 398
49 / 398
Teilmengenkonstruktion
Teilmengenkonstruktion
a
a
0
3
a
... im Beispiel:
a
a
... im Beispiel:
a
b
a b a b
0
a
a
2
b
b
a
a
b
a
1
b
4
a
1
b
02
a
b
a b a b
2
3
a
4
b
023
02
023
1
14
a
1
14
50 / 398
50 / 398
Teilmengenkonstruktion
Teilmengenkonstruktion
a
a
0
3
a
... im Beispiel:
a
a
... im Beispiel:
a
b
a b a b
0
a
a
2
b
b
a
a
b
a
b
1
a
4
1
b
02
a
b
a b a b
2
3
a
4
b
02
023
a
023
a
b
b
a
1
1
14
14
50 / 398
Teilmengenkonstruktion
50 / 398
Bemerkungen:
a
0
3
a
... im Beispiel:
a
a
b
a
a b a b
Bei einem Eingabewort der Länge
Mengen konstruiert
2
b
a
b
4
b
02
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
a
1
n
023
a
b
a
1
14
51 / 398
50 / 398
Bemerkungen:
Lexikalische Analyse
Bei einem Eingabewort der Länge
Mengen konstruiert
n
werden maximal O(n)
Kapitel 3:
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
Design eines Scanners
Zusammenfassend finden wir:
Satz:
Zu jedem regulären Ausdruck e kann ein deterministischer
Automat A = P(Ae ) konstruiert werden mit
L(A) = [[e]]
51 / 398
52 / 398
Design eines Scanners
Design eines Scanners
Eingabe (vereinfacht):
e1
e2
...
ek
Eingabe (vereinfacht):
eine Menge von Regeln:
{ action1 }
{ action2 }
e1
e2
{ actionk }
ek
eine Menge von Regeln:
...
Ausgabe:
...
...
...
{ action1 }
{ action2 }
{ actionk }
ein Programm, das
von der Eingabe ein maximales Präfix w
e1 | . . . | ek erfüllt;
das minimale i ermittelt, so dass
für
w actioni ausführt.
liest, das
w ∈ [[ei ]];
53 / 398
Implementierung:
53 / 398
Implementierung:
Idee (Fortsetzung):
Idee:
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;
Konstruiere den DFA P(Ae ) = (Q, Σ, δ, {q0 }, F) zu dem
Ausdruck e = (e1 | . . . | ek );
Der Zeiger B verfolgt die aktuelle Position.
Definiere die Mengen:
F1
F2
Fk
=
=
...
=
{q ∈ F | q ∩ last[e1 ] 6= ∅}
{q ∈ (F\F1 ) | q ∩ last[e2 ] 6= ∅}
s t d o u t
. w r i
{q ∈ (F\(F1 ∪ . . . ∪ Fk−1 )) | q ∩ last[ek ] 6= ∅}
t e l n
A
( " H a l
l o " ) ;
B
Für Eingabe w gilt:
δ ∗ (q0 , w) ∈ Fi
genau dann wenn
der Scanner für w actioni ausführen soll
54 / 398
Implementierung:
55 / 398
Implementierung:
Idee (Fortsetzung):
Idee (Fortsetzung):
Ist der aktuelle Zustand qB = ∅ , geben wir Eingabe bis zur
Postion A aus und setzen:
B := A;
A := ⊥;
qB := q0 ;
qA := ⊥
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
w r i
l o " ) ;
t e l n
A
4
A B
⊥ ⊥
55 / 398
( " H a l
l o " ) ;
B
4
56 / 398
Implementierung:
Implementierung:
Idee (Fortsetzung):
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
Ist der aktuelle Zustand qB = ∅ , geben wir Eingabe bis zur
Postion A aus und setzen:
B
qB
:= ⊥;
:= ⊥
( " H a l
:= A;
:= q0 ;
A
qA
l o " ) ;
:= ⊥;
:= ⊥
( " H a l
B
∅
w r i
l o " ) ;
A B
⊥ q0
t e l n
56 / 398
Erweiterung:
Zustände
56 / 398
Eingabe (verallgemeinert):
hstatei
e1
e2
{ action1
{ action2
yybegin(state1 ); }
yybegin(state2 ); }
{ actionk
yybegin(statek ); }
yybegin (statei );
setzt den Zustand auf
{
...
ek
Gelegentlich ist es nützlich, verschiedene Scanner-Zustände zu
unterscheiden.
}
In unterschiedlichen Zuständen sollen verschiedene
Tokenklassen erkannt werden können.
Der Aufruf
statei .
In Abhängigkeit der gelesenen Tokens kann der
Scanner-Zustand geändert werden
Beispiel:
eine Menge von Regeln:
Der Startzustand ist (z.B. bei JFlex) YYINITIAL.
... im Beispiel:
Kommentare
hYYINITIALi
hCOMMENTi
Innerhalb eines Kommentars werden Identifier, Konstanten,
Kommentare, ... nicht erkannt
00
{
}
/∗00
∗ /00
. | \n
00
{ yybegin(COMMENT); }
{ yybegin(YYINITIAL); }
{ }
58 / 398
57 / 398
Bemerkungen:
“.”
Themengebiet:
matcht alle Zeichen ungleich “\n”.
Für jeden Zustand generieren wir den entsprechenden Scanner.
Die Methode yybegin (STATE);
verschiedenen Scannern um.
schaltet zwischen den
Syntaktische Analyse
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 / 398
60 / 398
Die syntaktische Analyse
Token-Strom
Die syntaktische Analyse
Parser
Syntaxbaum
Token-Strom
Die syntaktische Analyse versucht, Tokens zu größeren
Programmeinheiten zusammen zu fassen.
Parser
Syntaxbaum
Die syntaktische Analyse versucht, Tokens zu größeren
Programmeinheiten zusammen zu fassen.
Solche Einheiten können sein:
61 / 398
Diskussion:
Ausdrücke;
→
bedingte Verzweigungen;
→
Statements;
→
Schleifen; ...
61 / 398
Diskussion:
Auch Parser werden i.a. nicht von Hand programmiert, sondern aus
einer Spezifikation generiert:
Spezifikation
→
Generator
Auch Parser werden i.a. nicht von Hand programmiert, sondern aus
einer Spezifikation generiert:
Parser
E→E{op}E
Generator
Spezifikation der hierarchischen Struktur: kontextfreie
Grammatiken
Generierte Implementierung: Kellerautomaten + X
62 / 398
62 / 398
Grundlagen:
Syntaktische Analyse
Kontextfreie Grammatiken
Programme einer Programmiersprache können unbeschränkt
viele Tokens enthalten, aber nur endlich viele Token-Klassen
Kapitel 1:
Als endliches Terminal-Alphabet T wählen wir darum die Menge
der Token-Klassen.
Grundlagen: Kontextfreie Grammatiken
Die Schachtelung von Programm-Konstrukten lässt sich elegant
mit Hilfe von kontextfreien Grammatiken beschreiben ...
63 / 398
64 / 398
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,
Noam Chomsky, MIT
John Backus, IBM
(Erfinder von Fortran)
P die Menge der Produktionen oder Regeln, und
S∈N
das Startsymbol
64 / 398
Konventionen
65 / 398
Konventionen
Die Regeln kontextfreier Grammatiken sind von der Form:
A→α
Die Regeln kontextfreier Grammatiken sind von der Form:
mit A ∈ N , α ∈ (N ∪ T)∗
A→α
mit A ∈ N , α ∈ (N ∪ T)∗
... im Beispiel:
Spezifizierte Sprache:
S
S
→
→
aSb
{an bn | n ≥ 0}
66 / 398
66 / 398
Konventionen
... 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 | ...
Die Regeln kontextfreier Grammatiken sind von der Form:
A→α
mit A ∈ N , α ∈ (N ∪ T)∗
... im Beispiel:
Spezifizierte Sprache:
S
S
→
→
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 / 398
67 / 398
... weitere Beispiele:
S
hstmti
hifi
hwhilei
hrexpi
hlexpi
→
→
→
→
→
→
hstmti
hifi | hwhilei | hrexpi;
if ( hrexpi ) hstmti else hstmti
while ( hrexpi ) hstmti
int | hlexpi | hlexpi = hrexpi
name | ...
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
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).
Die beiden Grammatiken beschreiben die gleiche Sprache
können wir durch das Paar (A, j)
68 / 398
67 / 398
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
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
... im Beispiel:
| int 4
E
Die beiden Grammatiken beschreiben die gleiche Sprache
69 / 398
68 / 398
Ableitung
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E
→
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E+T
69 / 398
E
→
→
E+T
T+T
69 / 398
Ableitung
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E
→
→
→
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E+T
T+T
T∗F+T
E
→
→
→
→
E+T
T+T
T∗F+T
T ∗ int + T
69 / 398
Ableitung
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
69 / 398
E
→
→
→
→
→
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
E
→
→
→
→
→
→
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
name ∗ int + T
69 / 398
Ableitung
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
69 / 398
E
→
→
→
→
→
→
→
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
... im Beispiel:
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
name ∗ int + T
name ∗ int + F
69 / 398
E
→
→
→
→
→
→
→
→
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
name ∗ int + T
name ∗ int + F
name ∗ int + int
69 / 398
Ableitung
Ableitung
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
Grammatiken sind Wortersetzungssysteme. Die Regeln geben die
möglichen Ersetzungsschritte an. Eine Folge solcher
Ersetzungsschritte α0 → . . . → αm heißt Ableitung.
Definition
Definition
Eine Ableitung → ist eine Relation auf Wörtern über N ∪ T, mit
Eine Ableitung → ist eine Relation auf Wörtern über N ∪ T, mit
... im Beispiel:
α → α0
E
→
→
→
→
→
→
→
→
... im Beispiel:
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
name ∗ int + T
name ∗ int + F
name ∗ int + int
gdw. α = α1 A α2 ∧ α0 = α1 β α2 für ein A → β ∈ P
α → α0
69 / 398
Ableitung
E+T
T+T
T∗F+T
T ∗ int + T
F ∗ int + T
name ∗ int + T
name ∗ int + F
name ∗ int + int
gdw. α = α1 A α2 ∧ α0 = α1 β α2 für ein A → β ∈ P
Den reflexiven und transitiven Abschluss von → schreiben wir:
→∗
69 / 398
Bemerkungen:
Die Relation →
hängt von der Grammatik ab
Die Relation →
In jedem Schritt einer Ableitung können wir:
∗
→
→
→
→
→
→
→
→
Ableitung
Bemerkungen:
∗
E
eine Stelle auswählen, wo wir ersetzen wollen, sowie
∗
eine Regel, wie wir ersetzen wollen.
Die von G
∗
spezifizierte Sprache ist:
∗
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}
L(G) = {w ∈ T | S → w}
Achtung:
Die Reihenfolge, in der disjunkte Teile abgeleitet werden, ist
unerheblich
70 / 398
Ableitungsbaum
70 / 398
Spezielle Ableitungen
Ableitungen eines Symbols stellt man als Ableitungsbaum dar:
... 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 0
Beachte:
E 1
+
T 0
T 1
F 1
∗
Neben beliebigen Ableitungen betrachtet man solche, bei denen stets
das linkeste (bzw. rechteste) Vorkommen eines Nichtterminals ersetzt
wird.
T 1
F 2
F 2
int
Diese heißen Links- (bzw. Rechts-) Ableitungen und werden
durch Index L bzw. R gekennzeichnet.
int
Links-(bzw. Rechts-) Ableitungen entsprechen einem links-rechts
(bzw. rechts-links) preorder-DFS-Durchlauf durch den
Ableitungsbaum
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
Reverse Rechts-Ableitungen entsprechen einem links-rechts
postorder-DFS-Durchlauf durch den Ableitungsbaum
71 / 398
72 / 398
Spezielle Ableitungen
Spezielle Ableitungen
... im Beispiel:
... im Beispiel:
E 0
E 1
+
T 0
∗
T 1
F 2
T 1
E 1
F 2
T 0
int
∗
T 1
int
F 1
E 0
T 1
F 2
F 2
int
int
F 1
name
+
name
Links-Ableitung:
(E, 0) (E, 1) (T, 0) (T, 1) (F, 1) (F, 2) (T, 1) (F, 2)
73 / 398
Spezielle Ableitungen
73 / 398
Spezielle Ableitungen
... im Beispiel:
... im Beispiel:
E 0
E 1
+
T 0
∗
T 1
F 2
T 1
E 1
F 2
T 0
int
+
T 1
F 2
F 2
int
int
F 1
name
Links-Ableitung:
Rechts-Ableitung:
∗
T 1
int
F 1
E 0
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:
(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)
(F, 1) (T, 1) (F, 2) (T, 0) (E, 1) (F, 2) (T, 1) (E, 0)
73 / 398
Eindeutige Grammatiken
Eindeutige Grammatiken
Die Konkatenation der Blätter des Ableitungsbaums t
wir auch mit yield(t) .
... im Beispiel:
bezeichnen
Definition:
Die Grammatik G heißt eindeutig, falls es zu jedem w ∈ T ∗
maximal einen Ableitungsbaum t von S gibt mit yield(t) = w.
E 0
E 1
+
T 0
T 1
F 1
73 / 398
∗
T 1
... in unserem Beispiel:
F 2
F 2
E
E
T
F
int
int
name
→
→
→
→
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
liefert die Konkatenation:
name ∗ int + int .
74 / 398
75 / 398
Fazit:
Syntaktische Analyse
Ein Ableitungsbaum repräsentiert eine mögliche hierarchische
Struktur eines Worts.
Kapitel 2:
Bei Programmiersprachen sind wir nur an Grammatiken
interessiert, bei denen die Struktur stets eindeutig ist
Grundlagen: Kellerautomaten
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 / 398
77 / 398
Grundlagen: Kellerautomaten
Durch kontextfreie Grammatiken spezifizierte Sprachen können durch
Kellerautomaten (Pushdown Automata) akzeptiert werden:
Friedrich L. Bauer, TUM
Klaus Samelson, TUM
1957: Patent auf die Verwendung von Stapelspeicher zur
Übersetzung von Programmiersprachen
Der Keller wird z.B. benötigt, um korrekte Klammerung zu überprüfen
78 / 398
Beispiel:
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
79 / 398
Beispiel:
0
1
11
12
a
a
b
b
11
11
2
2
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
0
1
11
12
a
a
b
b
11
11
2
2
Konventionen:
Wir unterscheiden nicht zwischen Kellersymbolen und Zuständen
Das rechteste / oberste Kellersymbol repräsentiert den Zustand
Jeder Übergang liest / modifiziert einen oberen Abschnitt des
Kellers
80 / 398
80 / 398
Kellerautomaten
Kellerautomaten
Definition:
Definition:
Formal definieren wir deshalb einen Kellerautomaten (PDA) als ein
Tupel: M = (Q, T, δ, q0 , F) mit:
Formal definieren wir deshalb einen Kellerautomaten (PDA) als ein
Tupel: M = (Q, T, δ, q0 , F) mit:
Q eine endliche Menge von Zuständen;
T
Q eine endliche Menge von Zuständen;
das Eingabe-Alphabet;
q0 ∈ Q
F⊆Q
T
der Anfangszustand;
die Menge der Endzustände und
δ ⊆ Q+ × (T ∪ {}) × Q∗
das Eingabe-Alphabet;
q0 ∈ Q
F⊆Q
eine endliche Menge von Übergängen
der Anfangszustand;
die Menge der Endzustände und
δ ⊆ Q+ × (T ∪ {}) × Q∗
eine endliche Menge von Übergängen
Mithilfe der Übergänge definieren wir Berechnungen von
Kellerautomaten
Der jeweilige Berechnungszustand (die aktuelle Konfiguration) ist ein
Paar:
(γ, w) ∈ Q∗ × T ∗
bestehend aus dem Kellerinhalt und dem noch zu lesenden Input.
81 / 398
... im Beispiel:
81 / 398
... im Beispiel:
0
1
11
12
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
a
a
b
b
11
11
2
2
0
1
11
12
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
(0 ,
a
a
b
b
11
11
2
2
a a a b b b)
82 / 398
... im Beispiel:
82 / 398
... 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
0
1
11
12
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
(1 1 , a a b b b)
(0 ,
82 / 398
a a a b b b)
`
`
a
a
b
b
11
11
2
2
(1 1 , a a b b b)
(1 1 1 , a b b b)
82 / 398
... im Beispiel:
... 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
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
`
(1 1 , a a b b b)
`
(1 1 1 , a b b b)
` (1 1 1 1 , b b b)
(0 ,
a a a b b b)
0
1
11
12
`
(1 1 ,
`
(1 1 1 ,
` (1 1 1 1 ,
`
(1 1 2 ,
a
a
b
b
11
11
2
2
a a b b b)
a b b b)
b b b)
b b)
82 / 398
... im Beispiel:
82 / 398
... 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
Zustände:
0, 1, 2
Anfangszustand: 0
Endzustände:
0, 2
a a b b b)
a b b b)
b b b)
b b)
b)
(0 ,
a a a b b b)
0
1
11
12
`
(1 1 ,
`
(1 1 1 ,
` (1 1 1 1 ,
`
(1 1 2 ,
`
(1 2 ,
`
(2 ,
a
a
b
b
11
11
2
2
a a b b b)
a b b b)
b b b)
b b)
b)
)
82 / 398
Ein Berechnungsschritt wird durch die Relation
beschrieben, wobei
2
82 / 398
Ein Berechnungsschritt wird durch die Relation
beschrieben, wobei
` ⊆ (Q∗ × T ∗ )
(α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ
2
` ⊆ (Q∗ × T ∗ )
(α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ
Bemerkungen:
Die Relation ` hängt natürlich vom Kellerautomaten M ab
Die reflexive und transitive Hülle von ` bezeichnen wir mit `∗
Dann ist die von M akzeptierte Sprache:
L(M) = {w ∈ T ∗ | ∃ f ∈ F : (q0 , w) `∗ (f , )}
83 / 398
83 / 398
Deterministischer Kellerautomat
Ein Berechnungsschritt wird durch die Relation
beschrieben, wobei
2
` ⊆ (Q∗ × T ∗ )
Definition:
(α γ, x w) ` (α γ 0 , w) für (γ, x, γ 0 ) ∈ δ
Der Kellerautomat M heißt deterministisch, falls jede Konfiguration
maximal eine Nachfolge-Konfiguration hat.
Bemerkungen:
Die Relation ` hängt natürlich vom Kellerautomaten M ab
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.
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
84 / 398
83 / 398
Deterministischer Kellerautomat
Kellerautomaten
Definition:
Der Kellerautomat M heißt deterministisch, falls jede Konfiguration
maximal eine Nachfolge-Konfiguration hat.
Satz:
Zu jeder kontextfreien Grammatik G = (N, T, P, S) kann ein
Kellerautomat M konstruiert werden mit L(G) = L(M) .
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.
Der Satz ist für uns so wichtig, dass wir zwei Konstruktionen für
Automaten angeben, motiviert durch die beiden speziellen
Ableitungsmöglichkeiten:
... im Beispiel:
0
1
11
12
a
a
b
b
MGL zur Bildung von Linksableitungen
11
11
2
2
MGR zur Bildung von reversen Rechtsableitungen
... ist das natürlich der Fall
84 / 398
85 / 398
Syntaktische Analyse
Kapitel 3:
Top-down Parsing
Marcel-Paul Schützenberger
(1920-1996), Paris
Anthony G. Öttinger, Präsident
der ACM 1966-68
1961: On context free languages and pushdownautomata
86 / 398
87 / 398
Item-Kellerautomat
Item-Kellerautomat
Unser Beispiel:
Konstruktion: Item-Kellerautomat
S
MGL
Rekonstruiere eine Linksableitung.
AB
[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 •]
==⇒ Die Zustände sind jetzt Items (= Regeln mit Punkt):
A → αβ ∈ P
Der Punkt gibt an, wieweit die Regel bereits abgearbeitet wurde
88 / 398
Item-Kellerautomat
B →
b
a
b
[S0 → • S]
[S0 → S •]
[S0 → • S] [S → • A B]
[S → • A B] [A → • a]
[A → a •]
[S → A • B]
[S → A • B] [B → • b]
[B → b •]
[S → A B •]
[S0 → S •]
89 / 398
Item-Kellerautomat
Der Item-Kellerautomat MGL
Shifts:
Reduce:
hinzu
Anfangszustand:
Endzustand:
Verifiziere sukzessive, dass die gewählte Regel mit der Eingabe
übereinstimmt.
Expansionen:
A → a
Dann konstruieren wir:
Expandiere Nichtterminale mithilfe einer Regel.
[A →α • β] ,
→
Wir fügen eine Regel: S0 → S
hat drei Arten von Übergängen:
... im Beispiel:
([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
S 0
Items der Form: [A → α •] heißen auch vollständig
Der Item-Kellerautomat schiebt den Punkt einmal um den
Ableitungsbaum herum ...
A 0
B 0
a
b
90 / 398
91 / 398
Item-Kellerautomat
Item-Kellerautomat
... im Beispiel:
... im Beispiel:
S 0
S 0
A 0
B 0
A 0
B 0
a
b
a
b
91 / 398
91 / 398
Item-Kellerautomat
Item-Kellerautomat
... im Beispiel:
... im Beispiel:
S 0
S 0
A 0
B 0
A 0
B 0
a
b
a
b
91 / 398
91 / 398
Item-Kellerautomat
Item-Kellerautomat
... im Beispiel:
... im Beispiel:
S 0
S 0
A 0
B 0
A 0
B 0
a
b
a
b
91 / 398
91 / 398
Item-Kellerautomat
Item-Kellerautomat
... im Beispiel:
... im Beispiel:
S 0
S 0
A 0
B 0
A 0
B 0
a
b
a
b
91 / 398
91 / 398
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.
Philip M. Lewis, SUNY
B →∗ w
Richard E. Stearns, SUNY
1968: Syntax-Directed Transduction
LL-Parsing basiert auf dem Item-Kellerautomaten und versucht,
die Expansionen deterministisch zu machen ...
92 / 398
Item-Kellerautomat
Beispiel:
S→
Topdown Parsing
| aSb
Problem:
Die Übergänge des zugehörigen Item-Kellerautomat:
0
1
2
3
4
5
6
7
8
9
93 / 398
[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
Konflikte zwischen Übergängen verhindern die Implementierung des
Item-Kellerautomaten als deterministischer Kellerautomat.
[S0 → • S] [S → •]
[S0 → • S] [S → • a S b]
[S → a • S b]
[S → a • S b] [S → •]
[S → a • S b] [S → • a S b]
[S → a S • b]
[S → a S • b]
[S → a S b•]
[S0 → S•]
[S0 → S•]
Konflikte gibt es zwischen den Übergängen (0, 1) bzw. zwischen
(3, 4).
94 / 398
Topdown Parsing
95 / 398
Topdown Parsing
Problem:
Problem:
Konflikte zwischen Übergängen verhindern die Implementierung des
Item-Kellerautomaten als deterministischer Kellerautomat.
Konflikte zwischen Übergängen verhindern die Implementierung des
Item-Kellerautomaten als deterministischer Kellerautomat.
Idee 1: GLL Parsing
Idee 1: GLL Parsing
Bei jedem Konflikt wird virtuell der Stack komplett kopiert und parallel
weiter geführt.
Bei jedem Konflikt wird virtuell der Stack komplett kopiert und parallel
weiter geführt.
Idee 2: Recursive Descent & Backtracking
Tiefensuche nach einer passenden Ableitung.
95 / 398
95 / 398
Topdown Parsing
Struktur des LL(1)-Parsers:
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.
Ausgabe
M
Idee 2: Recursive Descent & Backtracking
Tiefensuche nach einer passenden Ableitung.
Der Parser sieht ein Fenster der Länge 1 der Eingabe;
Idee 3: Recursive Descent & Lookahead
er realisiert im Wesentlichen den Item-Kellerautomaten;
Konflikte werden durch Betrachten des/der nächsten Zeichens gelöst.
die Tabelle M[q, w] enthält die jeweils zuwählende Regel
95 / 398
Topdown Parsing
96 / 398
Topdown Parsing
Idee:
Idee:
Benutze den Item-Kellerautomaten.
Benutze den Item-Kellerautomaten.
Benutze das nächste Zeichen, um die Regeln für die
Expansionen zu bestimmen
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.
Eine Grammatik heißt LL(1) , falls dies immer eindeutig möglich
ist.
Definition:
Eine reduzierte Grammatik heißt dann LL(1), falls für je zwei
verschiedene Regeln A → α , A → α0 ∈ P und jede Ableitung
S →∗L u A β mit u ∈ T ∗ gilt:
First1 (α β) ∩ First1 (α0 β) = ∅
97 / 398
Topdown Parsing
97 / 398
Topdown Parsing
Beispiel 1:
Beispiel 1:
S
→
E
→
if ( E ) S else S |
while ( E ) S |
E;
id
ist LL(1), da
First1 (E) = {id}
S
→
E
→
if ( E ) S else S |
while ( E ) S |
E;
id
ist LL(1), da
Beispiel 2:
S
→
E
→
First1 (E) = {id}
if ( E ) S else S |
if ( E ) S |
while ( E ) S |
E;
id
... ist nicht LL(k) für jedes k > 0.
98 / 398
98 / 398
Vorausschau-Mengen
Vorausschau-Mengen
Definition:
Definition:
Für eine Menge L ⊆ T ∗ definieren wir:
Für eine Menge L ⊆ T ∗ definieren wir:
First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L}
First1 (L) = { | ∈ L} ∪ {u ∈ T | ∃ v ∈ T ∗ : uv ∈ L}
Beispiel:
Beispiel:
ab
aabb
aaabbb
ab
aabb
aaabbb
die Präfixe der Länge 1
99 / 398
Vorausschau-Mengen
99 / 398
Vorausschau-Mengen
Rechenregeln:
Rechenregeln:
First1 (_) ist verträglich mit Vereinigung und Konkatenation:
First1 (_) ist verträglich mit Vereinigung und Konkatenation:
First1 (∅)
First1 (L1 ∪ L2 )
First1 (L1 · L2 )
=
=
=
:=
First1 (∅)
First1 (L1 ∪ L2 )
First1 (L1 · L2 )
∅
First1 (L1 ) ∪ First1 (L2 )
First1 (First1 (L1 ) · First1 (L2 ))
First1 (L1 ) First1 (L2 )
1 − Konkatenation
=
=
=
:=
∅
First1 (L1 ) ∪ First1 (L2 )
First1 (First1 (L1 ) · First1 (L2 ))
First1 (L1 ) First1 (L2 )
1 − Konkatenation
Beobachtung:
Seien L1 , L2 ⊆ T ∪ {} mit L1 6= ∅ 6= L2 . Dann ist:
L1
falls 6∈ L1
L1 L2 =
(L1 \{}) ∪ L2
sonst
Sind alle Regeln von G produktiv, sind alle Mengen First1 (A) nichtleer.
100 / 398
Vorausschau-Mengen
Vorausschau-Mengen
Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge:
Für α ∈ (N ∪ T)∗ sind wir interessiert an der Menge:
First1 (α) = First1 ({w ∈ T ∗ | α →∗ w})
Idee: Behandle separat:
100 / 398
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 )
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
101 / 398
a∈T
A → X1 . . . Xm ∈ P,
empty(X1 ) ∧ . . . ∧ empty(Xj−1 )
101 / 398
Vorausschau-Mengen
Vorausschau-Mengen
im Beispiel...
im Beispiel...
E
T
F
wobei
0
1
→ E+T
| T
→ T ∗F 0 | F 1
→ ( E ) 0 | name 1
E
T
F
| int 2
empty(E) = empty(T) = empty(F) = false .
wobei
→ E+T 0 | T 1
→ T ∗F 0 | F 1
→ ( E ) 0 | name 1
| int 2
empty(E) = empty(T) = empty(F) = false .
... erhalten wir:
F (S0 ) ⊇ F (E)
F (E) ⊇ F (T)
F (T) ⊇ F (F)
F (E) ⊇ F (E)
F (T) ⊇ F (T)
F (F) ⊇ { ( , name, int}
102 / 398
Schnelle Berechnung von Vorausschau-Mengen
102 / 398
Schnelle Berechnung von Vorausschau-Mengen
Beobachtung:
c
Die Form der Ungleichungen dieser Ungleichungssysteme ist:
bzw.
x w y
3
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
0
1
2
c
Konstruiere den Variablen-Abhängigkeitsgraph zum
Ungleichungssystem.
c
x0
x1
x2
x3
b
Vorgehen:
D = 2{a,b,c}
im Beispiel:
a
3
⊇ {a}
⊇ {b}
⊇ {c}
⊇ {c}
x1 ⊇ x0
x2 ⊇ x1
x3 ⊇ x2
x1 ⊇ x3
a
b
0
1
x3 ⊇ x3
2
c
103 / 398
Schnelle Berechnung von Vorausschau-Mengen
104 / 398
Schnelle Berechnung von Vorausschau-Mengen
c
c
3
3
a
b
a
b
0
1
0
1
2
c
2
Vorgehen:
c
Vorgehen:
Konstruiere den Variablen-Abhängigkeitsgraph zum
Ungleichungssystem.
Innerhalb einer starken Zusammenhangskomponente (→ Tarjan)
haben alle Variablen den gleichen Wert
Konstruiere den Variablen-Abhängigkeitsgraph zum
Ungleichungssystem.
Innerhalb einer starken Zusammenhangskomponente (→ Tarjan)
haben alle Variablen den gleichen Wert
Hat eine SZK keine eingehenden Kanten, erhält man ihren Wert,
indem man die kleinste obere Schranke aller Werte in der SZK
berechnet
104 / 398
104 / 398
Schnelle Berechnung von Vorausschau-Mengen
Schnelle Berechnung von Vorausschau-Mengen
a b c
3
a
0
... für unsere Beispiel-Grammatik:
1
2
First1 :
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
(, int, name
S’
E
T
F
105 / 398
104 / 398
Item-Kellerautomat als LL(1)-Parser
zurück zum Beispiel:
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 β } .
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•]
i0 S 0
... im Beispiel:
S → | aSb
S
0
a
1
i1 A1
b
0
in An
i B
w ∈ First1 (
106 / 398
in An
i B
Im Beispiel:
β0
Ungleichungssystem für Follow1 (B) =
Follow1 (S) ⊇ {}
Follow1 (B) ⊇ F (Xj )
falls
Follow1 (B) ⊇ Follow1 (A) falls
{First1 (β) | S
S→
| aSb
Die Übergänge des zugehörigen Item-Kellerautomat:
β
w ∈ First1 (
)
→∗L
)
107 / 398
β1
γ
0
β
Item-Kellerautomat als LL(1)-Parser
i0 S 0
i1 A1
S
β1
γ
Konflikte gibt es zwischen den Übergängen (0, 1) bzw. zwischen
(3, 4).
Item-Kellerautomat als LL(1)-Parser
β0
uBβ }
A → α B X1 . . . Xm ∈ P,
empty(X1 ) ∧ . . . ∧ empty(Xj−1 )
A → α B X1 . . . Xm ∈ P,
empty(X1 ) ∧ . . . ∧ empty(Xm )
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
Vorausschautabelle:
S
108 / 398
0
a
1
[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•]
b
0
109 / 398
Topdown-Parsing
Topdown-Parsing
Diskussion
Diskussion
Einer praktischen Implementation von LL(1)-Parsern über
recursive Descent steht nichts im Wege
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.
Jedoch kann nur eine Teilmenge der deterministisch
kontextfreien Sprachen auf diesem Weg gelesen werden.
Ausweg: Schritt von LL(1) auf LL(k)
Die Größe der auftretenden Mengen steigt mit k rapide
Leider reichen auch LL(k) Parser nicht aus, um alle
deterministisch kontextfreien Sprachen zu erkennen.
In praktischen Systemen wird darum meist nur der Fall k = 1
implementiert ...
110 / 398
110 / 398
Syntaktische Analyse
Kapitel 4:
Bottom-up Analyse
Donald E. Knuth, Stanford
112 / 398
111 / 398
Bottom-up Analyse
Bottom-up Analyse
Achtung:
Achtung:
Viele Grammatiken sind nicht LL(k) !
Viele Grammatiken sind nicht LL(k) !
Eine Grund dafür ist:
Eine Grund dafür ist:
Definition
Definition
Die Grammatik G heißt links-rekursiv, falls
Die Grammatik G heißt links-rekursiv, falls
A →+ A β
für ein
A ∈ N , β ∈ (T ∪ N)∗
A →+ A β
Beispiel:
113 / 398
E
T
F
für ein
A ∈ N , β ∈ (T ∪ N)∗
→ E+T 0 | T 1
→ T ∗F 0 | F 1
→ ( E ) 0 | name 1
| int 2
... ist links-rekursiv
113 / 398
Bottom-up Analyse
Bottom-up Analyse
Satz:
Satz:
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Beweis:
Beweis:
Sei A → A β | α ∈ P
und A erreichbar von S
Sei A → A β | α ∈ P
und A erreichbar von S
Annahme: G ist LL(k)
Annahme: G ist LL(k)
S
A
n
A
A
n
β
β
γ
Firstk (
)
114 / 398
Bottom-up Analyse
114 / 398
Bottom-up Analyse
Satz:
Satz:
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Beweis:
Beweis:
S
Sei A → A β | α ∈ P
und A erreichbar von S
A
n
A
Annahme: G ist LL(k)
A
S
Sei A → A β | α ∈ P
und A erreichbar von S
A
n
A
Annahme: G ist LL(k)
β
n
β
A
γ
A
α
Firstk (
β
n
β
γ
β
α
)
Firstk (
114 / 398
Bottom-up Analyse
)
114 / 398
Bottom-up Analyse
Satz:
Satz:
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Ist die Grammatik G reduziert und links-rekursiv, dann ist G nicht
LL(k) für jedes k.
Beweis:
Beweis:
Sei A → A β | α ∈ P
und A erreichbar von S
Sei A → A β | α ∈ P
und A erreichbar von S
Annahme: G ist LL(k)
Annahme: G ist LL(k)
⇒Firstk (α β n γ) ∩ Firstk (α β n+1 γ) = ∅
⇒Firstk (α β n γ) ∩ Firstk (α β n+1 γ) = ∅
Fall 1: β →∗ — Widerspruch !!!
Fall 2: β →∗ w 6= ==⇒ Firstk (α β k γ) ∩ Firstk (α β k+1 γ) 6= ∅
114 / 398
114 / 398
Shift-Reduce-Parser
Shift-Reduce-Parser
Beispiel:
S
A
B
Idee: Wir verzögern die Entscheidung ob wir reduzieren bis wir
wissen, ob die Eingabe einer rechten Seite entspricht!
Konstruktion:
Der Kellerautomat:
Shift-Reduce-Parser MGR
Zustände:
Anfangszustand:
Endzustand:
q0
a
A
b
AB
q0 S
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)
→
→
→
AB
a
b
q0 , f , a, b, A, B, S;
q0
f
a q0 a
A
b Ab
B
S
f
115 / 398
Shift-Reduce-Parser
Shift-Reduce-Parser
Konstruktion: Allgemein konstruieren wir einen Automaten
Konstruktion: Allgemein konstruieren wir einen Automaten
MGR = (Q, T, δ, q0 , F) mit:
Q = T ∪ N ∪ {q0 , f }
116 / 398
(q0 , f
MGR = (Q, T, δ, q0 , F) mit:
neu);
Q = T ∪ N ∪ {q0 , f }
F = {f };
Übergänge:
δ
=
(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
δ
=
{(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 , )
117 / 398
Shift-Reduce-Parser
117 / 398
Bottom-up Analyse
Offenbar gilt:
Idee:
Die Folge der Reduktionen entspricht einer reversen
Rechtsableitung für die Eingabe
Dazu versuchen wir, für den Shift-Reduce-Parser MGR
Reduktionsstellen zu identifizieren ...
Zur Korrektheit zeigt man, dass gilt:
(, w) `∗ (A, )
gdw.
Der Shift-Reduce-Kellerautomat MGR
nicht-deterministisch
Wir rekonstruieren reverse Rechtsableitungen!
A →∗ w
die
Betrachte eine Berechnung dieses Kellerautomaten:
(q0 α γ, v) ` (q0 α B, v) `∗ (q0 S, )
ist i.a. auch
α γ nennen wir zuverlässiges Präfix für das vollständige Item
[B → γ•] .
Um ein deterministisches Parse-Verfahren zu erhalten, muss
man die Reduktionsstellen identifizieren
==⇒ LR-Parsing
118 / 398
119 / 398
Bottom-up Analyse
Dann ist
αγ
Bottom-up Analyse
zuverlässig für [B → γ•] gdw.
A0 i0
Dann ist
S →∗R α B v
αγ
zuverlässig für [B → γ•] gdw.
A0 i0
A1 i1
α1
A2 i2
α2
B i
αm
A1 i1
α1
A2 i2
α2
S →∗R α B v
B i
αm
γ
γ
... wobei α = α1 . . . αm
... wobei α = α1 . . . αm
120 / 398
Bottom-up Analyse
Umgekehrt können wir zu jedem möglichen Wort α0 die Menge
aller möglicherweise später passenden Regeln ermitteln ...
120 / 398
Charakteristischer Automat
Das Item [B → γ • β]
α0 = α γ :
heißt gültig für
α0
gdw. S →∗R α B v mit
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:
A0 i0
A1 i1
α1
Zustände: Items
A2 i2
α2
Anfangszustand: [S0 → • S]
Endzustände: {[B → γ•] | B → γ ∈ P}
B i
αm
γ
Übergänge:
(1)
(2)
β
... wobei α = α1 . . . αm
([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.
:-)
122 / 398
121 / 398
Charakteristischer Automat
im Beispiel:
E
T
F
Charakteristischer Automat
im Beispiel:
→ E+T 0 | T 1
→ T ∗F 0 | F 1
→ ( E ) 0 | int 2
E
T
F
E
S’
E
E
S’
E
E+T
S’
+
E
E
E
E +T
E
T
E
E+ T
E
E
E+T
T
T
T
T∗F
E+T
T
T ∗F
E
∗
T
F
T
T∗ F
T
T∗F
T
F
(
F
(E)
T∗F
F
( E)
T
E
int
E
T
T
T ∗F
T
F
F
( E)
F
int
+
F
(E )
F
(
)
F
(E)
F
(E)
int
F
E +T
T
E
E+ T
T
T∗ F
F
(E )
E
E+T
∗
T
T∗F
F
(E)
F
F
T
F
E
T
F
T
E
T
E
T
S’
E
T
E
→ E+T 0 | T 1
→ T ∗F 0 | F 1
→ ( E ) 0 | int 2
E
)
int
F
int
F
123 / 398
int
123 / 398
Kanonischer LR(0)-Automat
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
6
int
E
... im Beispiel:
int
F
q0
9
+
int
4
F
int
0
T
Dazu konstruieren wir:
3
*
11
= {[S0 → • E],
{[E → • E + T],
{[E → • T],
{[T → • T ∗ F]}
{[T → • F],
{[F → • ( E ) ],
{[F → • int]}
(
(
F
E
5
T
(
)
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•]}
8
(
T
2
7
*
F
10
124 / 398
Kanonischer LR(0)-Automat
125 / 398
Kanonischer LR(0)-Automat
Beachte:
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]}
Der kanonische LR(0)-Automat kann auch direkt aus der Grammatik
konstruiert werden.
Man benötigt die Hilfsfunktion: δ∗ (-Abschluss)
q8
=
δ(q5 , E)
=
{[F → ( E • ) ]}
{[E → E • + T]}
q9
=
δ(q6 , T)
=
{[E → E + T•],
{[T → T • ∗ F]}
δ∗ (q) = q ∪ {[B → • γ] | ∃ [A → α • B0 β 0 ] ∈ q ,
∃ β ∈ (N ∪ T)∗ : B0 →∗ B β}
Dann definiert man:
q10
=
δ(q7 , F)
=
{[T → T ∗ F•]}
Anfangszustand δ∗ {[S0 → • S]}
q11
=
δ(q8 , ) )
=
{[F → ( E ) •]}
Zustände: Mengen von Items;
Endzustände: {q | ∃ A → α ∈ P : [A → α •] ∈ q}
Übergänge: δ(q, X) = δ∗ {[A → α X • β] | [A → α • X β] ∈ q}
127 / 398
126 / 398
LR(0)-Parser
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
... im Beispiel:
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.
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!
Achtung:
Dieser Parser ist nur dann deterministisch, wenn jeder Endzustand
des kanonischen LR(0)-Automaten keine Konflikte enthält.
128 / 398
129 / 398
LR(0)-Parser
LR(0)-Parser
Die Konstruktion des LR(0)-Parsers:
Zustände: Q ∪ {f }
Zur Korrektheit:
Man zeigt:
(f neu)
Anfangszustand: q0
Die akzeptierenden Berechnungen des LR(0)-Parsers stehen in
eins-zu-eins Beziehung zu denen des Shift-Reduce-Parsers MGR .
Endzustand: f
Übergänge:
Shift:
Reduce:
(p, a, p q)
(p q1 . . . qm , , p q)
Finish:
wobei
(q0 p, , f )
falls
falls
falls
Wir folgern:
q = δ(p, a) 6= ∅
[A → X1 . . . Xm •] ∈ qm ,
q = δ(p, A)
[S0 → S•] ∈ p
==⇒
==⇒
LR(G) = (Q, T, δ, q0 , F) .
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
130 / 398
LR(0)-Parser
131 / 398
LR(k)-Grammatik
Achtung:
Leider ist der LR(0)-Parser im allgemeinen nicht-deterministisch
Idee: Benutze k-Vorausschau, um Konflikte zu lösen.
Wir identifizieren zwei Gründe:
Definition:
Reduce-Reduce-Konflikt:
[A → γ •] , [A0 → γ 0 •] ∈ q
mit
Shift-Reduce-Konflikt:
[A → γ •] , [A0 → α • a β] ∈ q
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
∗
0 0 0
S →R α A w → α β x
A 6= A0 ∨ γ 6= γ 0
mit
a∈T
für einen Zustand q ∈ Q .
Solche Zustände nennen wir LR(0)-ungeeignet.
133 / 398
132 / 398
LR(k)-Grammatik
LR(k)-Grammatik
im Beispiel:
im Beispiel:
(1)
S→A | B
A→aAb | 0
(1)
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
S→A | B
A→aAb | 0
B→aBbb | 1
... ist nicht LL(k) für jedes k — aber LR(0) :
von einer der
Sei S →∗R α X w → α β w .
Formen:
Dann ist α β
A , B , an a A b , an a B b b , an 0 , an 1
(n ≥ 0)
(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
134 / 398
134 / 398
LR(k)-Grammatik
LR(k)-Grammatik
im Beispiel:
im Beispiel:
(3)
S→aAc
LR(1) :
... ist nicht LR(0), aber
A→bbA | b
Für S →∗R α X w → α β w
von einer der Formen:
mit
{y} = Firstk (w) ist
(3)
S→aAc
LR(1) :
Für S →∗R α X w → α β w
von einer der Formen:
αβy
a b2n b c , a b2n b b A c , a A c
... ist nicht LR(0), aber
A→bbA | b
mit
{y} = Firstk (w) ist
αβy
a b2n b c , a b2n b b A c , a A c
(4)
S→aAc
k ≥ 0:
... ist nicht LR(k) für jedes
A→bAb | b
Betrachte einfach die Rechtsableitungen:
S →∗R a bn A bn c → a bn b bn c
135 / 398
LR(1)-Items
Sei
135 / 398
LR(1)-Items
S i0
k = 1.
A1 i1
γ0
Idee: Wir statten Items mit 1-Vorausschau aus
γ1
A m im
Ein LR(1)-Item ist dann ein Paar:
[B → α • β, x] ,
x ∈ Follow1 (B) =
Dieses Item ist gültig für γ α falls:
S →∗R γ B w
mit
[
B i
γm
{First1 (ν) | S →∗ µ B ν}
α
β
{x} = First1 (w)
... wobei γ0 . . . γm = γ
136 / 398
LR(1)-Items
137 / 398
Der charakteristische LR(1)-Automat
S i0
Der Automat c(G, 1) :
A1 i1
γ0
γ1
Zustände: LR(1)-Items
A m im
Anfangszustand: [S0 → • S, ]
Endzustände: {[B → γ•, x] | B → γ ∈ P, x ∈ Follow1 (B)}
B i
γm
α
Ü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};
β
... wobei γ0 . . . γm = γ
Die Menge der gültigen LR(1)-Items für zuverlässige Präfixe
berechnen wir wieder mithilfe eines endlichen Automaten
137 / 398
138 / 398
Der charakteristische LR(1)-Automat
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 ...
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.
139 / 398
138 / 398
Der kanonische LR(1)-Automat
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:
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 }}
δ∗ (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}
139 / 398
139 / 398
Der kanonische LR(1)-Automat
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
]}
=
=
=
im Beispiel:
δ(q0 , E)
δ(q0 , T)
=
=
{[S0 → E •
{[E → E • + T
{[E → T •
{[T → T • ∗ F
],
]}
],
q3
=
δ(q0 , F)
q4
=
δ(q0 , int)
q5
=
δ(q0 , ( )
=
{[T → F •
=
{[F → ( • E )
{[E → • E + T
{[E → • T
{[T → • T ∗ F
{[T → • F
{[F → • ( E )
{[F → • int
q0
]}
{[F → int •
{[S0 → • E, {}],
{[E → • E + T, {, +}],
{[E → • T, {, +}],
{[T → • T ∗ F, {, +, ∗}],
{[T → • F, {, +, ∗}],
{[F → • ( E ) , {, +, ∗}],
{[F → • int, {, +, ∗}]}
=
]}
],
],
],
],
],
q1
=
δ(q0 , E)
=
],
]}
]}
140 / 398
q2
=
δ(q0 , T)
=
{[S0 → 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
]}
],
],
],
],
],
],
]}
]}
140 / 398
Der kanonische LR(1)-Automat
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
=
q2
=
im Beispiel:
δ(q0 , E)
δ(q0 , T)
=
=
q3
=
δ(q0 , F)
q4
=
δ(q0 , int)
q5
=
δ(q0 , ( )
=
{[T → F • , {, +, ∗}]}
q0
{[S0 → • E, {}],
{[E → • E + T, {, +}],
{[E → • T, {, +}],
{[T → • T ∗ F, {, +, ∗}],
{[T → • F, {, +, ∗}],
{[F → • ( E ) , {, +, ∗}],
{[F → • int, {, +, ∗}]}
=
{[F → int • , {, +, ∗}]}
=
{[S0 → E • , {}],
{[E → E • + T, {, +}]}
{[E → T • , {, +}],
{[T → T • ∗ F, {, +, ∗}]}
{[F → ( • 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, { ) , +, ∗}]}
140 / 398
Der kanonische LR(1)-Automat
140 / 398
Der kanonische LR(1)-Automat
im Beispiel:
q05
q6
=
=
δ(q5 , ( )
δ(q1 , +)
=
=
im Beispiel:
{[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 , ∗)
=
],
],
],
],
],
]}
q05
=
δ(q5 , ( )
=
]}
=
δ(q5 , E)
=
{[F → ( E • )
{[E → E • + T
]}
]}
q9
=
δ(q6 , T)
=
{[E → E + T •
{[T → T • ∗ F
],
],
],
],
],
q8
],
]}
{[T → T ∗ • F
{[F → • ( E )
{[F → • int
]}
q10
=
δ(q7 , F)
=
{[T → T ∗ F •
]}
q11
=
δ(q8 , ) )
=
{[F → ( E ) •
]}
q6
=
δ(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
],
],
q10
{[T → • F
{[F → • ( E )
],
{[F → • int
]}
q11
=
δ(q2 , ∗)
=
δ(q5 , E)
=
{[F → ( E • )
{[E → E • + T
]}
]}
=
δ(q6 , T)
=
{[E → E + T •
{[T → T • ∗ F
],
=
δ(q7 , F)
=
{[T → T ∗ F •
]}
=
δ(q8 , ) )
=
{[F → ( E ) •
]}
q6
=
=
δ(q5 , ( )
δ(q1 , +)
=
=
]}
141 / 398
Der kanonische LR(1)-Automat
im Beispiel:
q05
],
],
]}
=
141 / 398
Der kanonische LR(1)-Automat
{[T → T ∗ • F
{[F → • ( E )
{[F → • int
im Beispiel:
{[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
],
],
q05
=
δ(q5 , ( )
=
]}
=
δ(q5 , E)
=
{[F → ( E • )
{[E → E • + T
]}
]}
=
δ(q6 , T)
=
{[E → E + T •
{[T → T • ∗ F
],
]}
=
δ(q7 , F)
=
{[T → T ∗ F •
]}
=
δ(q8 , ) )
=
{[F → ( E ) •
]}
141 / 398
q6
=
δ(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 ) • , {, +, ∗}]}
141 / 398
Der kanonische LR(1)-Automat
Der kanonische LR(1)-Automat
im Beispiel:
+
1
im Beispiel:
int
E
int
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 ) •, { ) , +, ∗}]}
F
9
+
int
4
int
0
T
6
F
3
*
11
(
(
F
)
E
5
8
(
T
T
2
7
*
F
10
142 / 398
Der kanonische LR(1)-Automat
im Beispiel:
int
int
int
F
5
T
9’
Im Beispiel hat sich die Anzahl der Zustände fast verdoppelt
... und es kann noch schlimmer kommen
+
F
4’
int
( int
(
Diskussion:
T
6’
int
int
3
9
+
4
F
T
6
E
0
Der kanonische LR(1)-Automat
+
1
143 / 398
3’ E
(
T( F
5’
2 (
T
F
)
)
8’
7
(
*
2’
*
11’
(8
E
Die Konflikte in den Zuständen q1 , q2 , q9 sind nun aufgelöst !
z.B. haben wir für:
*
11
F
mit:
10
7’
*
q9 = {[E → E + T•, {, +}],
{[T → T • ∗ F, {, +, ∗}]}
F
{, +} ∩ (First1 (∗ F) {, +, ∗}) = {, +} ∩ {∗} = ∅
10’
143 / 398
Der LR(1)-Parser:
144 / 398
Der LR(1)-Parser:
Mögliche Aktionen sind:
shift
// Shift-Operation
reduce (A → γ) // Reduktion mit Ausgabe
error
// Fehler
action
... im Beispiel:
Ausgabe
E
T
F
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.
145 / 398
→
→
→
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
146 / 398
Der kanonische LR(1)-Automat
Allgemein:
Der kanonische LR(1)-Automat
Allgemein:
Wir identifizieren zwei Konflikte:
Reduce-Reduce-Konflikt:
[A → γ •, x] , [A0 → γ 0 •, x] ∈ q
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} .
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 .
für einen Zustand q ∈ Q .
Solche Zustände nennen wir jetzt LR(1)-ungeeignet
Solche Zustände nennen wir jetzt LR(k)-ungeeignet
147 / 398
Spezielle LR(k)-Teilklassen
147 / 398
Spezielle LR(k)-Teilklassen
Satz:
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.
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 ...
148 / 398
Spezielle LR(k)-Teilklassen
Simple-LR(k)
Satz:
Idee 1: Benutze Followk -Mengen zur Konflikt-Lösung
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.
Reduce-Reduce-Konflikt:
Falls für [A → γ •] , [A0 → γ 0 •] ∈ q
Shift-Reduce-Konflikt:
Falls für [A → γ •] , [A0 → α • a β] ∈ q
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)
Die Zuordnung ist unabhängig vom Zustand
Die Zuordnung hängt vom Zustand ab
mit
a∈T,
Followk (A) ∩ ({a} Firstk (β) Followk (A0 )) 6= ∅
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:
2
mit A 6= A0 ∨ γ 6= γ 0 ,
Followk (A) ∩ Followk (A0 ) 6= ∅
Diskussion:
1
148 / 398
für einen Zustand q ∈ Q.
==⇒ Simple LR(k)
==⇒
LALR(k)
Dann nennen wir den Zustand q SLR(k)-ungeeignet
148 / 398
149 / 398
Simple-LR(k)
Simple-LR(k)
Definition:
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.
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 ) ∩ {+} {. . .}
= {} ∩ {+}
= ∅
150 / 398
Simple-LR(k)
150 / 398
Lookahead-LR(k)
Idee 2: Berechne Followk -Mengen für jedes Item
Idee 2: – abhängig vom Zustand q
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.
Für [A → α • β] ∈ q definieren wir:
Λk (q, [A → α • β])
... im Beispiel:
=
//
{Firstk (w) | S0 →∗R γ A w ∧ δ(q0 , γ α) = q}
⊆ Followk (A)
Bei unserer Beispiel-Grammatik treten Konflikte möglicherweise in
den Zuständen q1 , q2 , q9 auf:
q1
= {[S0 → E•],
{[E → E • + T]}
q2
= {[E → T•],
{[T → T • ∗ F]}
q9
= {[E → E + T•],
{[T → T • ∗ F]}
Follow1 (S0 ) ∩ {+} {. . .}
= {} ∩ {+}
= ∅
Follow1 (E) ∩ {∗} {. . .}
=
=
Follow1 (E) ∩ {∗} {. . .}
= {, +, ) } ∩ {∗}
= ∅
{, +, ) } ∩ {∗}
∅
150 / 398
Lookahead-LR(k)
151 / 398
Lookahead-LR(k)
Idee 2: Berechne Followk -Mengen für jedes Item
Idee 2: – abhängig vom Zustand q
Idee 2: Berechne Followk -Mengen für jedes Item
Idee 2: – abhängig vom Zustand q
Für [A → α • β] ∈ q definieren wir:
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
0
A 6= A0 ∨ γ 6= γ 0
Λk (q, [A → α • β])
=
//
{Firstk (w) | S0 →∗R γ A w ∧ δ(q0 , γ α) = q}
⊆ Followk (A)
Reduce-Reduce-Konflikt:
[A → γ •] , [A0 → γ 0 •] ∈ q
wobei:
0
mit
0
Λk (q, [A → γ •]) ∩ Λk (q, [A → γ •]) 6= ∅
A 6= A0 ∨ γ 6= γ 0
wobei:
0
Λk (q, [A → γ •]) ∩ Λk (q, [A → γ •]) 6= ∅
Shift-Reduce-Konflikt:
[A → γ •] , [A0 → α • a β] ∈ q
mit
a∈T
wobei:
0
Λk (q, [A → γ •]) ∩ ({a} Firstk (β) Λk (q, [A → α • a β])) 6= ∅
Solche Zustände nennen wir jetzt LALR(k)-ungeeignet
151 / 398
151 / 398
Lookahead-LR(k)
Lookahead-LR(k)
Definition:
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
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
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
152 / 398
Lookahead-LR(k)
im Beispiel:
Lookahead-LR(k)
S →
A →
B →
im Beispiel:
AbB | B
a | bB
A
Der kanonische LR(0)-Automat hat dann die folgenden Zustände:
q0
q1
0
=
=
δ(q0 , S)
=
152 / 398
{[S → • 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•]}
=
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 ∅
153 / 398
Lookahead-LR(k)
154 / 398
Spezielle Bottom-up Verfahren mit LR(G)
im Beispiel:
2
1
Konfliktzustand:
q5 = {[S → A • b B],
{[B → A•]}
6
S
b
0
Diskussion:
A
a a
a
B
3
Das Beispiel ist folglich nicht SLR(1), aber LALR(1)
A
7
b
Das Beispiel ist nicht so an den Haaren herbei gezogen, wie es
scheint ...
b
B
A
4
5
b
8
9
Umbenennung: A⇒L
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])
{}
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
Auflösung mit LALR(1):
Λ1 (q5 , [B → A•]) ∩ First1 (b) {. . .} = {} ∩ {b} {. . .} = ∅
155 / 398
156 / 398
LALR(1)
Syntaktische Analyse
Für k = 1 lassen sich die Mengen Λk (q, [A → α • β]) wieder effizient
berechnen
Kapitel 5:
Das verbesserte Ungleichungssystem für Λ1 :
Λ1 (q0 , [S0 → • S])
⊇
Λ1 (q, [A → α X • β]) ⊇
Λ1 (q, [A → • γ])
⊇
Λ1 (q, [A → • γ])
⊇
==⇒
Zusammenfassung
{}
Λ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 !
158 / 398
157 / 398
Parsing Verfahren
Parsing Verfahren
determinististische Sprachen
determinististische Sprachen
= LR(1) = ... = LR(k)
= LR(1) = ... = LR(k)
LALR(k)
SLR(k)
LALR(k)
LR(0)
SLR(k)
reguläre
Sprachen
LR(0)
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.
reguläre
Sprachen
LL(1)
Durch LR(0)-Grammatiken lassen sich alle präfixfreien
deterministisch kontextfreien Sprachen beschreiben
LL(k)
Die Sprachklassen zu LL(k)-Grammatiken bilden dagegen eine
Hierarchie innerhalb der deterministisch kontextfreien Sprachen.
160 / 398
159 / 398
Lexikalische und syntaktische Analyse:
Lexikalische und syntaktische Analyse:
Vom Regulären Ausdruck zum Endlichen Automaten
Prinzip von Spezifikation und Realisierung:
012 34
f ∅
0
0 | [1-9][0-9]*
Generator
*
|
0 0
2
11
a
f 012
012 f
0
[0−9]
1
a
0
2 34
f ∅
.
22
34 f
0 1 2 01 f 01
[1−9]
a
.
01 01
2 t
33
3
b
a
a
a
b
34 34
f ∅
∅ f
3
a
|
a
2
b
a
44
f ∅
4
b
a
1
4
b
b
Vom Endlichen Automaten zum Scanner
E→E{op}E
Generator
02
023
a
w r i
t e l n
( " H a l
l o " ) ;
b
a
1
161 / 398
A
4
14
B
∅
162 / 398
Lexikalische und syntaktische Analyse:
Lexikalische und syntaktische Analyse:
Vom charakteristischen zum kanonischen Automaten:
E
Berechnung von Vorausschau-Mengen:
F (S ) ⊇
F (E) ⊇
F (T) ⊇
0
F (E)
F (T)
F (F)
F (E) ⊇ F (E)
F (T) ⊇ F (T)
F (F) ⊇ { ( , name, int}
S’
E
E
E+T
E
T
T
T∗F
S’
E
E
E +T
E
T
T
T ∗F
T
F
+
+
E
(, int, name
a b c
S’
E
T
1
T
E
E+ T
E
E+T
int
T
1
∗
T∗ F
int
int
F
T
T
T∗F
F
0
F
3
T
F
(
F
(E)
F
int
F
( E)
F
int
F
(
E
F
(E )
E
5
)
F
(E)
T
T
2
*
Vom Item-Kellerautomat zum LL(1)-Parser:
i0 S 0
i1 A 1
A 0
B 0
in A n
i B
a
b
7
F
10
Vom Shift-Reduce-Parser zum LR(1)-Parser:
β0
S i0
+
1
β1
δ
int
γ1
A m im
0
F
(
α
3
F
5
B i
γm
T
β
T
6’
int
int
3’ E
(
T( F
5’
2 (
T
2’
9’
+
F
4’
( int
M
)
9
+
4
int
Ausgabe
T
6
int
E
A1 i1
γ0
β
γ
w ∈ First1 (
)
8
(
(
int
S 0
*
11
(
F
2
9
+
4
3
a
0
T
6
int
E
T
F
int
*
11
F
)
E
*
11’
(8
action
)
8’
(
*
7
*
F
10
7’
F
10’
Ausgabe
goto
163 / 398
164 / 398
Semantische Analyse
Scanner und Parser akzeptieren Programme mit korrekter Syntax.
nicht alle lexikalisch und syntaktisch korrekten Programme
machen Sinn
Themengebiet:
Die semantische Analyse
166 / 398
165 / 398
Semantische Analyse
Semantische Analyse
Scanner und Parser akzeptieren Programme mit korrekter Syntax.
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
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
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;
166 / 398
166 / 398
Semantische Analyse
Semantische Analyse
Scanner und Parser akzeptieren Programme mit korrekter Syntax.
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
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
diese Programme werden als fehlerhaft zurückgewiesen
Sprachdefinition definiert was fehlerhaft bedeutet
semantische Analysen sind hierzu erforderlich, die
semantische Analysen sind hierzu erforderlich, die
Bezeichner eindeutig machen;
die Typen von Variablen ermitteln;
Bezeichner eindeutig machen;
die Typen von Variablen ermitteln;
semantische Analysen dienen auch dazu
semantische Analysen dienen auch dazu
Möglichkeiten zur Programm-Optimierung zu finden;
über möglicherweise fehlerhafte Programme zu warnen
Möglichkeiten zur Programm-Optimierung zu finden;
über möglicherweise fehlerhafte Programme zu warnen
; semantische Analysen attributieren den Syntaxbaum
166 / 398
166 / 398
Attributierte Grammatiken
Die semantische Analyse
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
Kapitel 1:
greift auf bereits berechnete Informationen von Nachbarknoten zu
und
berechnen daraus neue Informationen für den lokalen Knoten und
andere Nachbarknoten
Attributierte Grammatiken
damit die Eingabe-Werte eines Knotens bei der Berechnung
bereits vorliegen, müssen die Knoten des Syntaxbaums in einer
bestimmten Reihenfolge durchlaufen werden
168 / 398
167 / 398
Beispiel: Berechnung des Prädikats empty[r]
Attributierte Grammatiken
viele Berechnungen der semantischen Analyse als auch der
Code-Generierung arbeiten auf dem Syntaxbaum.
Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b):
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
|
0
Definition
a
2
1
b
|
a
3
a
4
b
Eine attributierte Grammatik ist eine CFG erweitert um:
Attribute für jedes Nichtterminal;
lokale Attribut-Gleichungen.
168 / 398
169 / 398
Beispiel: Berechnung des Prädikats empty[r]
Beispiel: Berechnung des Prädikats empty[r]
Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b):
Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b):
.
.
.
*
|
2
.
*
f
f
|
a
f
|
2
f
|
a
f
f
f
f
f
f
f
f
0
1
3
4
0
1
3
4
a
b
a
b
a
b
a
b
169 / 398
Beispiel: Berechnung des Prädikats empty[r]
169 / 398
Beispiel: Berechnung des Prädikats empty[r]
Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b):
Betrachte den Syntaxbaum des regulären Ausdrucks (a|b)*a(a|b):
f
.
.
t
f
t
f
*
.
*
.
f
|
f
f
2
|
a
f
|
f
f
2
|
a
f
f
f
f
f
f
f
f
0
1
3
4
0
1
3
4
a
b
a
b
a
b
a
b
; Werte von empty[r] werden von unten nach oben berechnet.
169 / 398
Idee zur Implementierung
169 / 398
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:
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
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
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
170 / 398
170 / 398
Berechnungsregeln für empty
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.
Wie das Attribut lokal zu berechnen ist, ergibt sich aus dem Typ des
Knotens:
der Einfachheit halber benutzen wir fortlaufende Indizes:
für Blätter: r ≡
i
andernfalls:
x
definieren wir
empty[r1 | r2 ] =
empty[r1 · r2 ] =
empty[r1∗ ]
=
empty[r1 ?]
=
empty[0] :
empty[i] :
empty[r] = (x ≡ ).
das Attribut des aktuellen Knotens
das Attribut des i-ten Sohns (i > 0)
empty[r1 ] ∨ empty[r2 ]
empty[r1 ] ∧ empty[r2 ]
t
t
172 / 398
171 / 398
Spezifizierung von allgemeinen Attributsystemen
Das empty Attributs ist rein synthetisch, daher kann es durch
einfache strukturelle Induktion definiert werden.
Die lokalen Berechnungen der Attributwerte müssen von einem
globalen Algorithmus zusammen gesetzt werden
Dazu benötigen wir:
wir benötigen einen einfachen und flexiblen Mechanismus, mit
dem wir über allgemeine Attribute an einem Knoten und seinen
Nachfolgern reden können.
1
der Einfachheit halber benutzen wir fortlaufende Indizes:
empty[0] :
empty[i] :
Beobachtungen:
2
eine Besuchsreihenfolge der Knoten des Baums;
eine lokale Berechnungsreihenfolgen
Die Auswertungsstrategie muss mit den Attribut-Abhängigkeiten
kompatibel sein
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
172 / 398
Beobachtungen:
173 / 398
Beobachtung:
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
Zur Ermittlung einer Auswertungsstrategie reicht es nicht, sich
die lokalen Attribut-Abhängigkeiten anzusehen.
Die Auswertungsstrategie muss mit den Attribut-Abhängigkeiten
kompatibel sein
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
Wir geben Abhängigkeiten als gerichtete Kanten an:
empty
empty
|
Die Variablen-Abhängigkeiten können aber auch komplizierter
sein.
empty
; Pfeil zeigt in die Richtung des Informationsflusses
173 / 398
174 / 398
Simultane Berechnung von mehreren Attributen
RE Auswertung: Regeln für Alternative
Berechne empty, first, next von regulären Ausdrücken:
x
|
empty[0] := (x ≡ )
first[0]
:= {x | x 6= }
// (keine Gleichung für next )
:
root:
empty[0] :=
first[0]
:=
next[0]
:=
next[1]
:=
:
empty[0] :=
first[0]
:=
next[1]
:=
next[2]
:=
:
empty[1]
first[1]
∅
next[0]
f
f
root
e
x
e
|
e
n
n
f
f
empty[1] ∨ empty[2]
first[1] ∪ first[2]
next[0]
next[0]
e
n
f
e
n
n
f
e
n
175 / 398
RE Auswertung: Regeln für Konkatenation
·
176 / 398
RE Auswertung: Regeln für Kleene-Stern und ‘?’
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
:
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
.
n
∗
f
e
f
e
f
e
*
n
n
f
e
n
f
e
?
n
n
177 / 398
Problem bei allgemeinen Attributen
Berechnung der Abhängigkeiten
Betrachte Abhängigkeiten in jedem möglichen Baum:
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]
h
Beispiel: Grammatik S → a | b; Attributenabhängigkeiten:
h
h
h
i
a
j
i
k
S
178 / 398
j
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.
j
Initialisiere S(s) = ∅ für s ∈ N und setze S(s) = {G} wobei G der
Abhängigkeitsgraph von s ∈ T ist.
k
h
i
b
j
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.
k
Enthält keiner der berechneten Graphen einen Zyklus, so kann jeder
Ableitungsbaum berechnet werden.
179 / 398
180 / 398
Stark azyklische Attributierung
Idee: Berechne einen Abhängigkeitsgraphen für jedes Symbol.
Von den Abhängigkeiten zur Strategie
Mögliche Strategien:
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
S
i
j
j
k
182 / 398
181 / 398
Von den Abhängigkeiten zur Strategie
Von den Abhängigkeiten zur Strategie
Mögliche Strategien:
1
Mögliche Strategien:
Der Benutzer soll die Strategie spezifizieren
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
182 / 398
Von den Abhängigkeiten zur Strategie
182 / 398
Linearisierung der Abhängigkeitsordnung
Mögliche Strategien:
1
Der Benutzer soll die Strategie spezifizieren
2
Berechne eine Strategie anhand der Abhängigkeiten
Mögliche automatische Strategien:
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?
182 / 398
183 / 398
Linearisierung der Abhängigkeitsordnung
Linearisierung der Abhängigkeitsordnung
Mögliche automatische Strategien:
1
Mögliche automatische Strategien:
Bedarfsgetriebene Auswertung
1
Beginne mit der Berechnung eines Attributs.
Sind die Argument-Attribute noch nicht berechnet, berechne
rekursiv deren Werte
Besuche die Knoten des Baum nach Bedarf
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
183 / 398
Beispiel bedarfsgetriebene Auswertung
183 / 398
Beispiel bedarfsgetriebene Auswertung
Berechne next der Blätter von ((a|b)∗ a(a|b)):
Berechne next der Blätter von ((a|b)∗ a(a|b)):
|
:
next[1]
next[2]
:= next[0]
:= next[0]
·
:
next[1] := first[2] ∪ (empty[2] ? next[0]: ∅)
next[2] := next[0]
|
:
next[1]
next[2]
·
:
next[1] := first[2] ∪ (empty[2] ? next[0]: ∅)
next[2] := next[0]
.
.
.
*
a
|
a
b
0
:= next[0]
:= next[0]
.
*
|
2
a
1
n
a
|
b
3
a
4
b
0
n
|
2
a
1
n
b
n
3
n
4
184 / 398
Beispiel bedarfsgetriebene Auswertung
Beispiel bedarfsgetriebene Auswertung
Berechne next der Blätter von ((a|b)∗ a(a|b)):
Berechne next der Blätter von ((a|b)∗ a(a|b)):
|
:
next[1]
next[2]
:= next[0]
:= next[0]
·
:
next[1] := first[2] ∪ (empty[2] ? next[0]: ∅)
next[2] := next[0]
0
next[1]
next[2]
·
:
next[1] := first[2] ∪ (empty[2] ? next[0]: ∅)
next[2] := next[0]
n
.
a
b
:
n
n
e f
a
n
3
|
n
e f
a
|
b
n
a
4
184 / 398
0
n
.
*
2
e f
1
:= next[0]
:= next[0]
.
*
a
|
n
.
|
184 / 398
b
n
e f
1
e f
2
a
n
3
|
n
e f
b
n
4
184 / 398
Bedarfsgetriebene Auswertung
Bedarfsgetriebene Auswertung
Diskussion:
Diskussion:
Die Reihenfolge hängt i.a. vom zu attributierenden Baum ab.
Die Reihenfolge hängt i.a. vom zu attributierenden Baum ab.
Der Algorithmus muss sich merken, welche Attribute er bereits
berechnete
Der Algorithmus muss sich merken, welche Attribute er bereits
berechnete
Der Algorithmus besucht manche Knoten unnötig oft.
Der Algorithmus ist nicht-lokal
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
185 / 398
185 / 398
Diskussion
Diskussion
Annahme: Jedes Attribut ist entweder inherit oder synthetisch.
Dann gilt:
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
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
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
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
186 / 398
Implementierung der lokalen Auswertung:
Pre-Order vs. Post-Order
186 / 398
Praktische Implementierung der Nummerierung
Idee:
Betrachte Beispiel: Nummerierung der Blätter eines Baums:
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)
.
.
*
a
|
a
0
b
1
|
2
a
3
b
Root:
pre[0] := 0
pre[1] := pre[0]
post[0] := post[1]
Node:
pre[1] := pre[0]
pre[2] := post[1]
post[0] := post[2]
Leaf:
post[0]
4
187 / 398
:= pre[0] + 1
188 / 398
Die lokalen Attribut-Abhängigkeiten:
pre
pre
post
post
pre
pre
Die lokalen Attribut-Abhängigkeiten:
pre
post
post
pre
post
post
pre
pre
die Attributierung ist offenbar stark azyklisch
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)
189 / 398
Die lokalen Attribut-Abhängigkeiten:
pre
post
post
pre
189 / 398
L-Attributierung
Definition
pre
pre
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.
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.
189 / 398
L-Attributierung
190 / 398
Praktische Benutzung
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.
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
Idee:
Knoten können in Typen eingeteilt werden — entsprechend den
Nichtterminalen auf der linken Seite
partitioniere alle Attribute A = A1 ∪ . . . ∪ An so dass für alle
Attribute in Ai jeder Knoten L-attributiert ist
Unterschiedliche Nichtterminale benötigen evt. unterschiedliche
Mengen von Attributen.
für jede Attributmenge Ai führe eine depth-first Traversierung
durch
; Bestimme Attribute mit Hinblick auf wenige Traversierungen
190 / 398
191 / 398
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);
}
}
Ausblick
In der Übung:
implementiere visitor pattern für C Grammatik
prüfe, ob jeder Bezeichner deklariert wurde
193 / 398
192 / 398
Symboltabellen
Die semantische Analyse
Betrachte folgenden Java Kode:
void foo() {
int A;
void bar() {
double A;
A = 0.5;
write(A);
}
A = 2;
bar();
write(A);
}
Kapitel 2:
Symboltabellen
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
195 / 398
194 / 398
Gültigkeitsbereiche von Bezeichnern
void foo() {
int A;
void bar() {
double A;
A = 0.5;
write(A);
}
A = 2;
bar();
write(A);
}



































Gültigkeitsbereiche von Bezeichnern
void foo() {
int A;
void bar() {
double A;
A = 0.5;
write(A);
Gültigkeitsbereich von int A
}
A = 2;
bar();
write(A);





Gültigkeitsbereich von double A
}
196 / 398
196 / 398
Gültigkeitsbereiche von Bezeichnern
Sichtbarkeitsregeln in objektorientierten Prog.spr.
1
2
3
4
5
6
7
8
9
10
void foo() {
int A;
void bar() {
double A;
A = 0.5;
write(A);
}
A = 2;
bar();
write(A);





Gültigkeitsbereich von double A
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:
}
Verwaltungs von Bezeichnern kann kompliziert werden...
196 / 398
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; }
}
197 / 398
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();
}
}
Beobachtung:
Beobachtungen:
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
197 / 398
198 / 398
Dynamische Auflösung von Funktionen
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();
}
}
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:
Beobachtungen:
der Typ von x ist Foo oder Bar, je nach Wert von b
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
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)
198 / 398
198 / 398
Überprüfung von Bezeichnern
Überprüfung von Bezeichnern
Aufgabe: Finde zu jeder Benutzung eines Bezeichners
Aufgabe: Finde zu jeder Benutzung eines Bezeichners
Idee:
Idee:
die zugehörige Deklaration
1
die zugehörige Deklaration
ersetze Bezeichner durch eindeutige Nummern
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
2
finde zu jedem Benutzung eines Bezeichners die Deklaration
199 / 398
199 / 398
Überprüfung von Bezeichnern
(1) Ersetze Bezeichner durch eindeutige Namen
Aufgabe: Finde zu jeder Benutzung eines Bezeichners
die zugehörige Deklaration
Idee für Algorithmus:
Idee:
1
ersetze Bezeichner durch eindeutige Nummern
Eingabe: Folge von Strings
Ausgabe: 1 Folge von Nummern
das Vergleichen von Nummern ist schneller
das Ersetzen von gleichen Namen durch eine Nummer spart
Speicher
2
2
Tabelle, die zu Nummern die Strings auflistet
Wende diesen Algorithmus im Scanner auf jeden Bezeichner an.
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
199 / 398
Beispiel für die Anwendung des Algorithmus
Eingabe:
das
das
Ausgabe:
0
schwein
schwein
1
2
3
ist
dem
dem
1
schwein
menschen
4
0
1
3
ist
5
Spezifikation der Implementierung
was
Idee:
benutze eine partielle Abbildung: S : String→int
verwalte einen Zähler int count = 0; für die Anzahl der bereits
gefundenen Wörter
wurst
2
200 / 398
6
und
Damit definieren wir eine Funktion int getIndex(String w):
0
1
2
3
4
5
6
int getIndex(String w) {
if (S (w) ≡ undefined) {
S = S ⊕ {w 7→ count};
return count++;
else return S (w);
}
das
schwein
ist
dem
was
menschen
wurst
201 / 398
202 / 398
Datenstrukturen für partielle Abbildungen
Datenstrukturen für partielle Abbildungen
Ideen:
Ideen:
Liste von Paaren (w, i) ∈ String × int :
O(1)
O(n)
; zu teuer
Liste von Paaren (w, i) ∈ String × int :
O(1)
O(n)
; zu teuer
balancierte Bäume :
O(log(n))
O(log(n))
; zu teuer
203 / 398
Datenstrukturen für partielle Abbildungen
203 / 398
Datenstrukturen für partielle Abbildungen
Ideen:
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)
Liste von Paaren (w, i) ∈ String × int :
O(1)
O(n)
; zu teuer
balancierte Bäume :
O(log(n))
O(log(n))
; zu teuer
Hash Tables :
O(1)
O(1)
zumindest im Mittel
; 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.
203 / 398
Implementierung mit Hash-Tabellen
203 / 398
Berechnung einer Hash-Tabelle am Beispiel
Mit
lege ein Feld M von hinreichender Größe m an
wähle eine Hash-Funktion H : String → [0, m − 1] mit den
Eigenschaften:
m=7
und
H0
erhalten wir:
0
H(w) ist leicht zu berechnen
H streut die vorkommenden Wörter gleichmäßig über [0, m − 1]
1
2
Mögliche Wahlen:
3
= (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)
4
5
6
schwein 1
menschen 5
was 4
ist 2
das 0
dem 3
wurst 6
Das Argument-Wert-Paar (w, i) legen wir dann in M[H(w)] ab
Um den Wert des Worts w zu finden, müssen wir w mit allen Worten x
vergleichen, für die H(w) = H(x)
204 / 398
205 / 398
Überprüfung von Bezeichnern: (2) Symboltabellen
Überprüfe die korrekte Benutzung von Bezeichnern:
Durchmustere den Syntaxbaum in einer geeigneten Reihenfolge,
die
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;
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
Abbildung von Namen auf Zahlen:
0
1
2
a
b
c
}
206 / 398
Der zugehörige Syntaxbaum
207 / 398
Der zugehörige Syntaxbaum
d Deklaration
d Deklaration
b Basis-Block
b Basis-Block
a Zuweisung
a Zuweisung
b
b
b
b
d
d
b
d
0 int
a
b
1 int
b
d
b
b
0 int
a
if
=
=
>
1
b
1 int
if
b
5
>
b
b
1
3
1
b
d
d
b
3
a
2 int
=
=
2 int
=
=
1
1
d
a
0 int
2 int
b
d
2 int
a
b
d
a
0 int
b
b
1
d
b
5
2
1
2
1
2
2
208 / 398
Praktische Implementierung: Sichtbarkeit
208 / 398
Praktische Implementierung: Sichtbarkeit
Das Auflösen von Bezeichnern kann durch L-attributierte
Grammatik erfolgen
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
209 / 398
209 / 398
Praktische Implementierung: Sichtbarkeit
Praktische Implementierung: Sichtbarkeit
Das Auflösen von Bezeichnern kann durch L-attributierte
Grammatik erfolgen
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
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
a
b
a
c
a
b
c
a
b
vor if-Anweisung
then-Zweig
else-Zweig
209 / 398
Praktische Implementierung: Sichtbarkeit
209 / 398
Mehrfach- und Vorwärtsdeklarationen
Manche Programmiersprachen verbieten eine Mehrfachdeklaration
des selben Namens innerhalb eines Blocks.
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
209 / 398
Mehrfach- und Vorwärtsdeklarationen
210 / 398
Mehrfach- und Vorwärtsdeklarationen
Manche Programmiersprachen verbieten eine Mehrfachdeklaration
des selben Namens innerhalb eines Blocks.
Manche Programmiersprachen verbieten eine Mehrfachdeklaration
des selben Namens innerhalb eines Blocks.
weise jedem Block eine eindeutige Nummer zu
weise jedem Block eine eindeutige Nummer zu
speichere für jede Deklaration auch die Nummer des Blocks, zu
der sie gehört
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
210 / 398
210 / 398
Mehrfach- und Vorwärtsdeklarationen
Mehrfach- und Vorwärtsdeklarationen
Manche Programmiersprachen verbieten eine Mehrfachdeklaration
des selben Namens innerhalb eines Blocks.
Manche Programmiersprachen verbieten eine Mehrfachdeklaration
des selben Namens innerhalb eines Blocks.
weise jedem Block eine eindeutige Nummer zu
weise jedem Block eine eindeutige Nummer zu
speichere für jede Deklaration auch die Nummer des Blocks, zu
der sie gehört
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
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.
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;
}
210 / 398
210 / 398
Deklaration von Funktionsnamen
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
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);
}
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)));
}
211 / 398
Überladung von Namen
211 / 398
Überladung von Namen
Ähnliche Abhängigkeiten gelten für objekt-orientierte Sprachen:
Ä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 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
212 / 398
212 / 398
Überladung von Namen
Mehrere Sorten von Bezeichnern
Einige Programmiersprachen unterscheiden verschiedene
Bezeichner:
Ähnliche Abhängigkeiten gelten für objekt-orientierte Sprachen:
C: Variablennamen und Typnamen
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
Java: Klassen, Methoden, Felder
Haskell: Typnamen, Variablen, Operatoren mit Prioritäten
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”).
212 / 398
Mehrere Sorten von Bezeichnern
213 / 398
Mehrere Sorten von Bezeichnern
Einige Programmiersprachen unterscheiden verschiedene
Bezeichner:
Einige Programmiersprachen unterscheiden verschiedene
Bezeichner:
C: Variablennamen und Typnamen
C: Variablennamen und Typnamen
Java: Klassen, Methoden, Felder
Java: Klassen, Methoden, Felder
Haskell: Typnamen, Variablen, Operatoren mit Prioritäten
Haskell: Typnamen, Variablen, Operatoren mit Prioritäten
In einigen Fällen verändern Deklarationen in der Sprache die
Bedeutung eines Bezeichners:
In einigen Fällen verändern Deklarationen in der Sprache die
Bedeutung eines Bezeichners:
Der Parser informiert den Scanner über eine neue Deklaration
Der Parser informiert den Scanner über eine neue Deklaration
Der Scanner generiert verschiedene Token für einen Bezeichner
Der Scanner generiert verschiedene Token für einen Bezeichner
Der Parser generiert einen Syntaxbaum, der von den bisherigen
Deklarationen abhängt
Der Parser generiert einen Syntaxbaum, der von den bisherigen
Deklarationen abhängt
Interaktion zwischen Parser und Scanner: problematisch!
213 / 398
Fixity-Deklaration in Haskell
Fixity-Deklaration in Haskell
Haskell erlaubt beliebige binäre Operatoren aus (?!^&|=+-_*/)+ .
In Haskell Standard-Bibliothek:
infixr
infixl
infixl
infix
8
7
6
4
Haskell erlaubt beliebige binäre Operatoren aus (?!^&|=+-_*/)+ .
In Haskell Standard-Bibliothek:
infixr
infixl
infixl
infix
^
*,/
+,==,/=
Grammatik ist generisch:
Exp0 → Exp0 LOp0 Exp1
|
Exp1 ROp0 Exp0
|
Exp1 Op0 Exp1
|
Exp1
..
.
Exp9
Exp
→
|
|
|
→
|
213 / 398
8
7
6
4
^
*,/
+,==,/=
Grammatik ist generisch:
Exp0 → Exp0 LOp0 Exp1
|
Exp1 ROp0 Exp0
|
Exp1 Op0 Exp1
|
Exp1
..
.
Exp9 LOp9 Exp
Exp ROp9 Exp9
Exp Op9 Exp
Exp
ident | num
( Exp0 )
Exp9
Exp
214 / 398
→
|
|
|
→
|
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
214 / 398
Fixity-Deklarationen in Haskell: Beobachtungen
Fixity-Deklarationen in Haskell: Beobachtungen
Nicht ohne Probleme:
Nicht ohne Probleme:
Scanner hat einen Zustand, der vom Parser bestimmt wird
; nicht mehr kontextfrei, braucht globale Datenstruktur
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
215 / 398
215 / 398
Fixity-Deklarationen in Haskell: Beobachtungen
Fixity-Deklarationen in Haskell: Beobachtungen
Nicht ohne Probleme:
Nicht ohne Probleme:
Scanner hat einen Zustand, der vom Parser bestimmt wird
; nicht mehr kontextfrei, braucht globale Datenstruktur
Scanner hat einen Zustand, der vom Parser bestimmt wird
; nicht mehr kontextfrei, braucht globale Datenstruktur
ein Stück Kode kann mehrere Semantiken haben
ein Stück Kode kann mehrere Semantiken haben
syntaktische Korrektheit hängt evtl. von importierten Modulen ab
syntaktische Korrektheit hängt evtl. von importierten Modulen ab
Fehlermeldungen des Parsers schwer verständlich
Fehlermeldungen des Parsers schwer verständlich
Im GHC Haskell Compiler werden alle Operatoren als LOp0 gelesen
und der AST anschliessend transformiert.
215 / 398
Typbezeichner und Variablen in C
Typbezeichner und Variablen in C
Die C Grammatik unterscheidet zwischen Terminal typedef-name
und identifier.
Betrachte folgende Liste von Deklarationen:
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
→
215 / 398
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 | · · ·
(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
216 / 398
216 / 398
Typbezeichner und Variablen in C
Typbezeichner und Variablen in C
Die C Grammatik unterscheidet zwischen Terminal typedef-name
und identifier.
Betrachte folgende Liste von Deklarationen:
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
→
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:
(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
Parser trägt point_t in Typen-Tabelle ein wenn die declaration
Regel reduziert wird
Parserzustand hat mindestens ein Lookahead-Token
Parserzustand hat mindestens ein Lookahead-Token
Scanner hat point_t in zweiter Zeile also schon als identifier
gelesen
216 / 398
Typbezeichner und Variablen in C: Lösungen
Typbezeichner und Variablen in C: Lösungen
Relevante C Grammatik:
declaration
declaration-specifier
declarator
→
→
|
→
216 / 398
Relevante C Grammatik:
(declaration-specifier)+ declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
declaration
declaration-specifier
declarator
Lösung schwierig:
→
→
|
→
(declaration-specifier)+ declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
Lösung schwierig:
versuche, Lookahead-Token im Parser zu ändern
217 / 398
Typbezeichner und Variablen in C: Lösungen
Typbezeichner und Variablen in C: Lösungen
Relevante C Grammatik:
declaration
declaration-specifier
declarator
→
→
|
→
217 / 398
Relevante C Grammatik:
(declaration-specifier)+ declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
declaration
declaration-specifier
declarator
Lösung schwierig:
→
→
|
→
(declaration-specifier)+ declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
Lösung schwierig:
versuche, Lookahead-Token im Parser zu ändern
versuche, Lookahead-Token im Parser zu ändern
füge folgende Regel zur Grammatik hinzu:
typedef-name → identifier
füge folgende Regel zur Grammatik hinzu:
typedef-name → identifier
registriere den Typnamen früher
217 / 398
217 / 398
Typbezeichner und Variablen in C: Lösungen
Typbezeichner und Variablen in C: Lösungen
Relevante C Grammatik:
declaration
declaration-specifier
declarator
Relevante C Grammatik:
(declaration-specifier) declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
declaration
declaration-specifier
+
→
→
|
→
declarator
Lösung schwierig:
→
→
|
→
(declaration-specifier)+ declarator ;
static | volatile · · · typedef
void | char | char · · · typedef-name
identifier | · · ·
Lösung schwierig:
versuche, Lookahead-Token im Parser zu ändern
versuche, Lookahead-Token im Parser zu ändern
füge folgende Regel zur Grammatik hinzu:
typedef-name → identifier
füge folgende Regel zur Grammatik hinzu:
typedef-name → identifier
registriere den Typnamen früher
registriere den Typnamen früher
separiere Regel für typedef Produktion
separiere Regel für typedef Produktion
rufe alternative declarator Produktion auf, die identifier als
Typenamen registriert
217 / 398
217 / 398
Ziel der Typ-Überprüfung
Die semantische Analyse
In modernen (imperativen / objektorientierten / funktionalen)
Programmiersprachen besitzen Variablen und Funktionen einen Typ,
z.B. int, void*, struct { int x; int y; }.
Kapitel 3:
Typ-Überprüfung
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.
218 / 398
Typ-Ausdrücke
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
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
219 / 398
als Abkürzung:
typedef struct { int x; int y; } point_t;
Beispiele für Typkonstruktoren:
Verbunde: struct { t1 a1 ; . . . tk ak ; }
zur Konstruktion rekursiver Typen:
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
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;
wir benutzen (t1 , . . . , tk ) als Tupel-Typen
220 / 398
221 / 398
Typ-Prüfung
Typ-Prüfung am Syntax-Baum
Prüfe Ausdruck *a[f(b->c)]+2:
Aufgabe:
+
Gegeben: eine Menge von Typ-Deklarationen Γ = {t1 x1 ; . . . tm xm ; }
Überprüfe: Kann ein Ausdruck e mit dem Typ t versehen werden?
∗
2
[ ]
Beispiel:
( )
a
f
struct list { int info; struct list* next; };
int f(struct list* l) { return 1; };
struct { struct list* c;}* b;
int* a[11];
.
∗
c
b
Idee:
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
Betrachte den Ausdruck:
*a[f(b->c)]+2;
222 / 398
Typ-Systeme
Typ-System für C-ähnliche Sprachen
Formal betrachten wir Aussagen der Form:
Weitere Regeln für diverse Typausdrücke:
Γ `e : t
//
Array:
(In der Typ-Umgebung Γ hat e den Typ t)
Array:
Axiome:
Struct:
Const: Γ ` c : tc
Var:
Γ ` x : Γ(x)
(tc
(x
Typ der Konstante c)
Variable)
App:
Regeln:
Ref:
223 / 398
Op:
Γ `e : t
Γ ` &e : t∗
Deref:
Cast:
Γ ` e : t∗
Γ ` ∗e : t
Γ ` 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
224 / 398
Beispiel: Typ-Prüfung
225 / 398
Beispiel: Typ-Prüfung
Ausdruck *a[f(b->c)]+2 und Γ = {
Ausdruck *a[f(b->c)]+2:
struct list { int info; struct list* next; };
int f(struct list* l);
struct { struct list* c;}* b;
int* a[11];
+
int
}:
int ∗
int ∗ [ ]
2
[ ]
( )
a
f
.
∗
c
2
int
[ ]
( )
a
int (struct list ∗)
+
∗
∗
int
f
int
.
struct {struct list ∗ c;}
∗
struct {struct list ∗ c;} ∗
b
struct list ∗
c
b
226 / 398
227 / 398
Gleichheit von Typen
Strukturelle Typ-Gleichheit
Alternative Interpretation von Gleichheit (gilt nicht in C):
Zusammenfassung Typprüfung:
Welche Regel an einem Knoten angewendet werden muss,
ergibt sich aus den Typen für die bereits bearbeiteten
Kinderknoten
Semantisch können wir zwei rekursive Typen t1 , t2 als gleich
betrachten, falls sie die gleiche Menge von Pfaden zulassen.
Dazu muss die Gleichheit von Typen festgestellt werden.
Beispiel:
Typgleichheit in C:
struct A {} und struct B {} werden als verschieden
betrachtet
struct list1 {
int info;
struct {
int info;
struct list1* next;
}* next;
}
struct list {
int info;
struct list* next;
}
; 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;
Sei struct list* l oder struct list1* l. Beide erlauben
l->info
Nach typedef int C; haben C und int den gleichen Typ.
l->next->info
jedoch hat l jeweils einen anderen Typen in C.
228 / 398
Algorithmus zum Test auf Semantische Gleichheit
Idee:
229 / 398
Regeln für Wohlgetyptheit
t
s∗ t∗
t
Verwalte Äquivalenz-Anfragen für je zwei Typausdrücke
s
Sind die beiden Ausdrücke syntaktisch gleich, ist alles gut
A t
A= s
t
s
t
Andernfalls reduziere die Äquivalenz-Anfrage zwischen
Äquivalenz-Anfragen zwischen (hoffentlich) einfacheren anderen
Typausdrücken
struct {s1 a1 ; ... sm am ; }
Nehmen wir an, rekursive Typen würden mit Hilfe von
Typ-Gleichungen der Form:
A=t
struct {t1 a1 ; ... tm am ; }
s1 t1
sm tm
eingeführt (verzichte auf ein Γ). Dann definieren wir folgende Regeln:
230 / 398
Beispiel:
231 / 398
Beweis Beispiel:
A
B
= 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; }
struct {int info;
struct {int info; B ∗ next; } ∗ next; }
struct{int info; A ∗ next; }
struct{int info; A ∗ next; }
struct {int info; A ∗ next; } = B
Dazu konstruieren wir:
int
int
B
struct{int info; . . . ∗ next; }
A ∗ ... ∗
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; }
232 / 398
B
233 / 398
Implementierung
Implementierung
Stoßen wir bei der Konstruktion des Beweisbaums auf eine
Äquivalenz-Anfrage, auf die keine Regel anwendbar ist, sind die
Typen ungleich
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
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
Taucht eine Äquivalenz-Anfrage ein weiteres Mal auf, sind die
Typen der Anfrage per Definition gleich
Terminierung?
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
234 / 398
234 / 398
Überladung und Koersion
Überladung und Koersion
Manche Operatoren wie z.B. + sind überladen:
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*)
+ 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
je nach Typ hat der Operator + ein unterschiedliche
Implementation
welche Implementierung ausgewählt wird, entscheiden die
Argument-Typen
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)
235 / 398
235 / 398
Koersion von Integer-Typen in C: Promotion
Koersion von Integer-Typen in C: Promotion
C enthält spezielle Koersionsregeln für Integer: 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.
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.
236 / 398
236 / 398
Teiltypen
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
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
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.
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.
1
2
3
Erweitere Teiltypen-Beziehungen der Basistypen auf komplexe Typen
237 / 398
Beispiel: Teiltypen
237 / 398
Regeln für Wohlgetyptheit von Teiltypen
Betrachte:
string extractInfo( struct { string info; } x) {
return x.info;
}
t
s∗ t∗
t
s
Wir möchten dass extractInfo für alle Argument-Strukturen
funktioniert, die eine Komponente string info besitzen
A t
A= s
s
t
t
Wann t1 ≤ t2 gelten soll, beschreiben wir durch Regeln
Die Idee ist vergleichbar zur Anwendbarkeit auf Unterklassen
(aber allgemeiner)
struct {s1 a1 ; ... sm am ; } struct {tj1 aj1 ; ... tjk ajk ; }
sj1 tj1
sjk tjk
238 / 398
Regeln und Beispiel für Teiltypen
s0 (s1 , . . . , sm )
s0 t0
t1 s1
Regeln und Beispiel für Teiltypen
t0 (t1 , . . . , tm )
s0 (s1 , . . . , sm )
tm sm
s0 t0
Beispiele:
struct {int a; int b; }
int (int)
int (float)
239 / 398
t1 s1
t0 (t1 , . . . , tm )
tm sm
Beispiele:
struct {float a; }
float (float)
float (int)
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
240 / 398
240 / 398
Regeln und Beispiel für Teiltypen
s0 (s1 , . . . , sm )
s0 t0
Regeln und Beispiel für Teiltypen
t0 (t1 , . . . , tm )
t1 s1
s0 (s1 , . . . , sm )
tm sm
s0 t0
Beispiele:
t0 (t1 , . . . , tm )
t1 s1
tm sm
Beispiele:
struct {int a; int b; }
int (int)
int (float)
struct {float a; }
float (float)
float (int)
≤
6≤
≤
struct {int a; int b; }
int (int)
int (float)
Achtung:
≤
6
≤
≤
struct {float a; }
float (float)
float (int)
Achtung:
Bei Argumenten dreht sich die Anordnung der Typen gerade um
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
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
240 / 398
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;
=
=
=
=
240 / 398
Teiltypen: Anwendung der Regeln (II)
Prüfe ob S2 ≤ S1 :
R1 (R1 ) f ; }
int b; S1 (S1 ) f ; }
R2 (S2 ) f ; }
int b; S2 (R2 ) f ; }
S1 R1
int
int
f
int
S1 (S1 )
=
=
=
=
struct {int a;
struct {int a;
struct {int a;
struct {int a;
int
R1 (R1 )
f
S2 (R2 )
S1 (S1 )
S2 S1
S1 R2
a
S1 R1
R1 (R1 ) f ; }
int b; S1 (S1 ) f ; }
R2 (S2 ) f ; }
int b; S2 (R2 ) f ; }
S2 S1
a, b
a
R1
S1
R2
S2
R1 S1
int
int
f
S1 (S1 )
R2 (S2 )
S1 R2
S2 S1
241 / 398
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;
Diskussion
R1 (R1 ) f ; }
int b; S1 (S1 ) f ; }
R2 (S2 ) f ; }
int b; S2 (R2 ) f ; }
Um die Beweisbäume nicht in den Himmel wachsen zu lassen,
werden Zwischenknoten oft ausgelassen
S 2 R1
a
int
int
Strukturelle Teiltypen sind sehr mächtig und deshalb nicht ganz
leicht zu durchschauen.
f
S2 (R2 )
R1 (R1 )
S 2 R1
Java verallgemeinert Strukturen zu Objekten / Klassen.
Teiltyp-Beziehungen zwischen Klassen müssen explizit deklariert
werden
R1 R2
a
int
242 / 398
int
Durch Vererbung wird sichergestellt, dass Unterklassen über die
(sichtbaren) Komponenten der Oberklasse verfügen
f
R1 (R1 )
R2 (S2 )
R1 R2
S2 R1
Überdecken einer Komponente mit einer anderen gleichen
Namens ist möglich — aber nur, wenn diese keine Methode ist
243 / 398
244 / 398
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
Themengebiet:
der Code ist nur ein weiteres Attribut im Syntaxbaum
die Codegenerierung benutzt die schon berechneten Attribute
Die Synthesephase
245 / 398
Codegenerierung: Überblick
246 / 398
Codegenerierung: Überblick
Vom AST des Programs erzeugen wir induktiv Instruktionen:
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
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
der Code ist nur ein weiteres Attribut im Syntaxbaum
die Codegenerierung benutzt die schon berechneten Attribute
die Codegenerierung benutzt die schon berechneten Attribute
Um die Codeerzeugung zu spezifizieren, benötigt man
Um die Codeerzeugung zu spezifizieren, benötigt man
die Semantik der Ausgangssprache (C Standard)
die Semantik der Ausgangssprache (C Standard)
die Semantik der Maschineninstruktionen
die Semantik der Maschineninstruktionen
; Wir definieren zunächst die Machineninstruktionen
246 / 398
246 / 398
Die C-Maschine (CMA)
Die Synthesephase
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)
Kapitel 1:
Die C-Maschine
247 / 398
248 / 398
Die C-Maschine (CMA)
Virtuelle Maschinen
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)
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
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
0 1
PC
0
SP
S
249 / 398
248 / 398
Komponenten der CMA
Komponenten der CMA
Zur Implementierung der CMA verwenden wir folgende Strukturen:
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.
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.
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.
250 / 398
Die Ausführung von Programmen
250 / 398
Die Synthesephase
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.
Kapitel 2:
while (true) {
IR = C[PC]; PC++;
execute (IR);
}
Ausdrucksauswertung
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
251 / 398
252 / 398
Einfache Ausdrücke und Wertzuweisungen
Einfache Ausdrücke und Wertzuweisungen
Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus
Das heißt: erzeuge eine Instruktionsfolge, die
Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus
Das heißt: erzeuge eine Instruktionsfolge, die
den Wert des Ausdrucks ermittelt und dann
den Wert des Ausdrucks ermittelt und dann
oben auf dem Keller ablegt
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
253 / 398
Einfache Ausdrücke und Wertzuweisungen
253 / 398
Generelles Prinzip
die Argumente für Instruktionen werden oben auf dem Keller
erwartet;
die Ausführung einer Instruktion konsumiert ihre Argumente;
Aufgabe: werte den Ausdruck (1 + 7) ∗ 3 aus
Das heißt: erzeuge eine Instruktionsfolge, die
möglicherweise berechnete Ergebnisse werden oben auf dem
Keller wieder abgelegt.
den Wert des Ausdrucks ermittelt und dann
oben auf dem Keller ablegt
Idee:
berechne erst die Werte für die Teilausdrücke;
loadc q
q
merke diese Zwischenergebnisse oben auf dem Keller;
wende dann den Operator an
; ähnlich wie postfix Notation des Taschenrechners aus der
ersten Übung
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.
254 / 398
253 / 398
Binäre Operatoren
Binäre Operatoren
Operatoren mit zwei Argumenten werden wie folgt angewandt:
3
8
mul
Operatoren mit zwei Argumenten werden wie folgt angewandt:
3
8
24
SP--;
S[SP] = S[SP] ∗ S[SP+1];
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.
255 / 398
255 / 398
Binäre Operatoren
Vergleiche und unäre Operatoren
Beispiel: Der Operator
Operatoren mit zwei Argumenten werden wie folgt angewandt:
3
8
leq
7
3
24
mul
leq
1
Einstellige Operatoren wie neg und not konsumieren dagegen ein
Argument und erzeugen einen Wert:
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.
8
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.
neg
-8
S[SP] = – S[SP];
255 / 398
Zusammensetzung von Instruktionen
256 / 398
Ausdrücke mit Variablen
Variablen haben eine Speicherzellen in S:
Beispiel: Erzeuge Code für 1 + 7:
loadc 1
loadc 7
z:
y:
x:
add
Ausführung dieses Codes:
loadc 1
1
loadc 7
7
1
add
8
257 / 398
Ausdrücke mit Variablen
258 / 398
Ausdrücke mit Variablen
Variablen haben eine Speicherzellen in S:
Variablen haben eine Speicherzellen in S:
z:
y:
x:
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.
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.
258 / 398
258 / 398
Ausdrücke mit Variablen
L-Wert und R-Wert
Variablen haben eine Speicherzellen in S:
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.
z:
y:
x:
L-Wert von x
R-Wert von 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.
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:
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).
Nicht jeder Ausdruck verfügt über einen L-Wert (z.B.: x + 1).
259 / 398
258 / 398
Übersetzungschema für Ausdrücke
Lesen von Variablen
Wir definieren:
codeR x ρ =
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)
codeL x ρ
load
Die Instruktion load lädt den Wert der Speicherzelle, deren Adresse
oben auf dem Stack liegt.
13
load
13
13
S[SP] = S[S[SP]];
260 / 398
Schreiben von Variablen
261 / 398
Ausdrücke mit Variablen
codeR (x = e) ρ
= codeR e ρ
Beispiel: Code für e ≡x = y − 1 mit ρ = {x 7→ 4, y 7→ 7}.
Dann liefert codeR e ρ:
codeL x ρ
store
loadc 7
load
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
store
loadc 1
sub
loadc 4
store
Optimierungen:
Einführung von Spezialbefehlen für häufige Befehlsfolgen, hier etwa:
13
loada q
=
13
storea q
=
loadc q
load
loadc q
store
S[S[SP]] = S[SP-1];
SP--;
262 / 398
263 / 398
Anweisungen und Anweisungsfolgen
Die Synthesephase
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:
Kapitel 3:
code e; ρ = codeR e ρ
pop
Anweisungen
Die Instruktion pop wirft das oberste Element des Kellers weg ...
1
pop
SP--;
264 / 398
265 / 398
Übersetzung von Anweisungsfolgen
Die Synthesephase
Der Code für eine Anweisungsfolge ist die Konkatenation des Codes
for die einzelnen Anweisungen in der Folge:
Kapitel 4:
Bedingte Anweisungen
code (s ss) ρ =
code s ρ
code ss ρ
=
//
code ε ρ
leere Folge von Befehlen
Beachte: s ist Statement, ss ist eine Folge von Statements
266 / 398
267 / 398
Sprünge
Die Synthesephase
Um von linearer Ausführungsreihenfolge abzuweichen, benötigen wir
Sprünge:
Kapitel 5:
jump A
Kontrollfluss
A
PC
PC
PC = A;
268 / 398
269 / 398
Bedingte Sprünge
Verwaltung von Kontrollfluss
Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge
erzeugt werden.
Ein bedingter Sprung verzweigt je nach Wert auf dem Keller:
1
bei der Übersetung eines if (c) Konstrukts ist nicht klar, an
welcher Adresse gesprungen werden muss, wenn c falsch ist
jumpz A
PC
0
PC
jumpz A
A
PC
PC
if (S[SP] == 0) PC = A;
SP--;
270 / 398
Verwaltung von Kontrollfluss
271 / 398
Verwaltung von Kontrollfluss
Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge
erzeugt werden.
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
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)
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
271 / 398
Verwaltung von Kontrollfluss
271 / 398
Basic Blöcke in der C-Maschine
Zur Übersetzung von Kontrollfluss Anweisungen müssen Sprünge
erzeugt werden.
Die CMA verfügt nur über einen bedingten Sprung, jumpz.
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
code ss
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)
c
organisiere die Instruktionen in Blöcke ohne Sprünge
Dazu definieren wir:
Die ausgehenden Kanten haben eine spezielle Form:
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
271 / 398
272 / 398
Basic Blöcke in der C-Maschine
Basic Blöcke in der C-Maschine
Die CMA verfügt nur über einen bedingten Sprung, jumpz.
Die CMA verfügt nur über einen bedingten Sprung, jumpz.
code ss
code ss
c
c
Die ausgehenden Kanten haben eine spezielle Form:
1
Die ausgehenden Kanten haben eine spezielle Form:
eine Kante (unbedingter Sprung), mit jump übersetzt
1
2
eine Kante (unbedingter Sprung), mit jump übersetzt
eine Kante mit Bedingung c = 0 (jumpz), eine Kante ohne
Bedinung (jump)
272 / 398
Basic Blöcke in der C-Maschine
272 / 398
Beschreibung von Kontrollfluss Übersetzungen
Der Einfachheit halber verwenden wir symbolische Sprungziele zur
Beschreibung der Übersetzung von Kontrollflussinstruktionen.
Die CMA verfügt nur über einen bedingten Sprung, jumpz.
Ein zweiten Durchlauf ist nötig, um symbolische Adressen durch
absolute Code-Adressen zu ersetzen.
code ss
c
Die ausgehenden Kanten haben eine spezielle Form:
1
2
3
eine Kante (unbedingter Sprung), mit jump übersetzt
eine Kante mit Bedingung c = 0 (jumpz), eine Kante ohne
Bedinung (jump)
eine Liste von Kanten (jumpi) und eine default Kante (jump); wird
verwendet für switch Anweisungen (kommt später)
272 / 398
Beschreibung von Kontrollfluss Übersetzungen
273 / 398
Beschreibung von Kontrollfluss Übersetzungen
Der Einfachheit halber verwenden wir symbolische Sprungziele zur
Beschreibung der Übersetzung von Kontrollflussinstruktionen.
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.
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:
Alternativ könnten direkt relative Sprünge eingeführt werden:
relative Sprünge haben Ziele relative zum aktuellen PC
relative Sprünge haben Ziele relative zum aktuellen PC
oft reichen kleinere Adressen aus (; near jumps)
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 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.
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.)
273 / 398
273 / 398
Bedingte Anweisung, einseitig
Zweiseitiges if
Betrachten wir zuerst s ≡ if ( c ) ss.
Wir beschreiben die Code-Erzeugung zunächst ohne Basic-Blöcke.
Betrachte nun s ≡ if ( c ) tt else ee.
Idee:
Lege den Code zur Auswertung von c und ss hintereinander in
den Code-Speicher;
code
Füge Sprung-Befehlen so ein, dass ein korrekter Kontroll-Fluss
gewährleistet ist
code s ρ
=
codeR c ρ
jumpz A
code ss ρ
A : ...
code
c
Die gleiche Strategie liefert:
code s ρ
code R für c
=
jumpz
codeR c ρ
jumpz
jumpz A
code für tt
jump
jump B
A : code ee ρ
B:
ee
code R für c
code tt ρ
code für ss
code
tt
code für ee
...
274 / 398
Beispiel für if-Anweisung
275 / 398
Iterative Anweisungen
Sei ρ = {x 7→ 4, y 7→ 7} und
s ≡ if (x > y)
x = x − y;
else y = y − x;
Betrachte schließlich die Schleife s ≡ while (e) s0 . Dafür erzeugen wir:
(i)
(ii)
(iii)
code s ρ
=
code R für e
A : codeR e ρ
Dann liefert code s ρ :
jumpz
jumpz B
loada 4
loada 7
gr
jumpz A
loada 4
loada 7
sub
storea 4
pop
jump B
(i)
(ii)
A:
B:
code s0 ρ
loada 7
loada 4
sub
storea 7
pop
...
code für s’
jump A
B:
jump
...
(iii)
276 / 398
Beispiel: Übersetzung von Schleifen
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:
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
277 / 398
code s ρ
loada 7
loada 8
sub
storea 7
pop
jump A
B:
...
278 / 398
codeR e1
pop
A : codeR e2 ρ
jumpz B
code s0 ρ
codeR e3 ρ
pop
jump A
B : ...
=
279 / 398
Das switch-Statement
Aufeinanderfolgende Alternativen
Idee:
Unterstütze Mehrfachverzweigung in konstanter Zeit
Wir betrachten zunächst nur switch-Statements der folgenden Form:
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
s
≡
jumpi B
case k − 1: ssk−1 break;
default: ssk
B+q
PC
switch (e) {
case 0: ss0 break;
case 1: ss1 break;
..
.
}
PC
Dann ergibt sich für s die Instruktionsfolge:
PC = B + S[SP];
SP--;
280 / 398
Codeerzeugung für spezielle switch-Anweisungen
281 / 398
Semantik des Makros
check 0 k B
code s ρ
=
codeR e ρ
check 0 k B
C0 :
Ck :
code ss0 ρ
jump D
...
code ssk ρ
jump D
B:
D:
=
dup
loadc 0
geq
jumpz A
jump C0
...
jump Ck
...
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.
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.
3
Die Sprungtabelle enthält direkte Sprünge zu den jeweiligen
Alternativen.
dup
3
3
S[SP+1] = S[SP];
SP++;
Am Ende jeder Alternative steht ein Sprung hinter das
switch-Statement.
Ist der R-Wert von e kleiner als 0 oder größer als k, ersetzen wir
ihn vor dem indizierten Sprung durch k.
282 / 398
Übersetzung der Sprungtabelle
283 / 398
Allgemeine Übersetzung der switch-Anweisung
Im Allgemeinen können die Werte der Alternativen weit auseinander
liegen.
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.
generiere eine folge von expliziten Tests, wie für die
if-Anweisung
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.
284 / 398
285 / 398
Allgemeine Übersetzung der switch-Anweisung
Allgemeine Übersetzung der switch-Anweisung
Im Allgemeinen können die Werte der Alternativen weit auseinander
liegen.
Im Allgemeinen können die Werte der Alternativen weit auseinander
liegen.
generiere eine folge von expliziten Tests, wie für die
if-Anweisung
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
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
285 / 398
Allgemeine Übersetzung der switch-Anweisung
285 / 398
Allgemeine Übersetzung der switch-Anweisung
Im Allgemeinen können die Werte der Alternativen weit auseinander
liegen.
Im Allgemeinen können die Werte der Alternativen weit auseinander
liegen.
generiere eine folge von expliziten Tests, wie für die
if-Anweisung
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
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
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
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
285 / 398
Übersetzung in Basic Blocks
285 / 398
Übersetzung in Basic Blocks
Problem: Wie verknüpft man die verschiedenen Basic Blöcke?
Idee:
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
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
286 / 398
286 / 398
Übersetzung in Basic Blocks
Übersetzung in Basic Blocks
Problem: Wie verknüpft man die verschiedenen Basic Blöcke?
Idee:
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
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 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
286 / 398
Übersetzung in Basic Blocks
286 / 398
Übersetzung in Basic Blocks
Problem: Wie verknüpft man die verschiedenen Basic Blöcke?
Idee:
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
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:
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
1
2
3
ähnlich für andere Konstrukte
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.
286 / 398
Felder
Übersetzung von Felderzugriffen
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].
286 / 398
Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen.
a[10]
Sei t[c] a; die Deklaration eines Feldes a.
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
287 / 398
288 / 398
Übersetzung von Felderzugriffen
Übersetzung von Felderzugriffen
Erweitere codeL und codeR auf Ausdrücke mit indizierten Feldzugriffen.
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:
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
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
288 / 398
Strukturen (Records)
288 / 398
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.
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
i=1
|ti |
ρ c1 = 0
ρ ci = ρ ci−1 + |ti−1 | für i > 1
Damit erhalten wir:
codeL (e.c) ρ
=
codeL e ρ
loadc (ρ c)
add
289 / 398
289 / 398
Zeiger in C
Die Synthesephase
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:
Kapitel 6:
Zeiger und dynamische Speicherverwaltung
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) ρ
290 / 398
=
loadc 13
loadc 1
add
291 / 398
Dereferenzieren von Zeigern
Übersetzung von Dereferenzierungen (I)
Sei ρ = {i 7→ 1, j 7→ 2, pt 7→ 3, a 7→ 0, b 7→ 7 }.
Die Anwendung des Operators * auf den Ausdruck e liefert den Inhalt
der Speicherzelle, deren Adresse der R-Wert von e ist:
b:
struct t { int a[7]; struct t *b; };
int i,j;
struct t *pt;
codeL (∗e) ρ = codeR e ρ
Beispiel: Betrachte für
Übersetze e ≡((pt -> b) -> a)[i+1]
struct t { int a[7]; struct t *b; };
int i,j;
struct t *pt;
Dann ist:
codeL e ρ
Wegen e->a ≡ (*e).a gilt:
codeL (e → a) ρ
b:
pt:
j:
i:
den Ausdruck e ≡((pt -> b) -> a)[i+1]
=
= codeR e ρ
loadc (ρ a)
add
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
292 / 398
Übersetzung von Dereferenzierungen (II)
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:
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
=
Damit ergibt sich insgesamt die Folge:
loada 3
loadc 7
add
load
loadc 0
add
loada 1
loadc 1
add
293 / 398
loada 3
loadc 7
add
load
loadc 0
add
S
H
0
MAX
SP
loadc 1
mul
add
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).
295 / 398
294 / 398
Invarianten des Heaps und des Stacks
Invarianten des Heaps und des Stacks
Stack und Heap dürfen sich nicht überschneiden
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)
296 / 398
296 / 398
Invarianten des Heaps und des Stacks
Invarianten des Heaps und des Stacks
Stack und Heap dürfen sich nicht überschneiden
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)
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
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
296 / 398
Dynamisch Allokierter Speicher
Freigabe von Speicherplatz
Es gibt eine weitere Arte, Zeiger zu erzeugen:
ein Aufruf von malloc liefert einen Zeiger auf eine Heap-Zelle:
codeR malloc (e) ρ =
Ein mit malloc allokierter Bereich muss irgendwann mit free
wieder freigegeben werden.
Probleme:
codeR e ρ
new
NP
296 / 398
Der freigegebene Speicherbereich könnte noch von anderen
Zeigern referenziert (dangling references).
NP
Nach einiger Freigabe könnte der Speicher etwa so aussehen
(fragmentation):
n
new
n
if (NP - S[SP] <= EP) S[SP] = NULL; else {
NP = NP - S[SP];
S[SP] = NP;
}
frei
298 / 398
297 / 398
Mögliche Auswege:
1
2
Die Synthesephase
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
Kapitel 7:
Tue nichts, d.h.:
code free (e); ρ
=
Funktionen
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.
299 / 398
300 / 398
Aufbau einer Funktion
Speicherverwaltung bei Funktionen
Die Definition einer Funktion besteht aus
int fac(int x) {
if (x<=0) return 1;
else return x*fac(x-1);
}
einem Namen, mit dem sie aufgerufen werden kann;
einer Spezifikation der formalen Parameter;
evtl. einem Ergebnistyp;
int main(void) {
int n;
n = fac(2) + fac(1);
printf("%d", n);
}
einem Anweisungsteil.
In C gilt:
codeR f ρ
=
loadc _f
=
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:
Anfangsadresse des Codes für f
Beachte:
main
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
fac
fac
fac
fac
printf
fac
301 / 398
Speicherverwaltung von Funktionsvariablen
302 / 398
Speicherverwaltung von Funktionsvariablen
Die formalen Parameter und lokalen Variablen der verschiedenen
Aufrufe der selben Funktion (Instanzen) müssen auseinander
gehalten werden.
Die formalen Parameter und lokalen Variablen der verschiedenen
Aufrufe der selben Funktion (Instanzen) müssen auseinander
gehalten werden.
Idee zur Implementierung:
Idee zur Implementierung:
lege einen speziellen Speicherbereich für jeden Aufruf einer
Funktion an.
303 / 398
Speicherverwaltung von Funktionsvariablen
303 / 398
Speicherverwaltung von Funktionsvariablen
Die formalen Parameter und lokalen Variablen der verschiedenen
Aufrufe der selben Funktion (Instanzen) müssen auseinander
gehalten werden.
Die formalen Parameter und lokalen Variablen der verschiedenen
Aufrufe der selben Funktion (Instanzen) müssen auseinander
gehalten werden.
Idee zur Implementierung:
Idee zur Implementierung:
lege einen speziellen Speicherbereich für jeden Aufruf einer
Funktion an.
lege einen speziellen Speicherbereich für jeden Aufruf einer
Funktion an.
in sequentiellen Programmiersprachen können diese
Speicherbereiche auf dem Keller verwaltet werden
in sequentiellen Programmiersprachen können diese
Speicherbereiche auf dem Keller verwaltet werden
jede Instanz einer Funktion erhält dadurch einen Bereich auf
dem Stack
303 / 398
303 / 398
Speicherverwaltung von Funktionsvariablen
Kellerrahmen-Organisation
SP
Die formalen Parameter und lokalen Variablen der verschiedenen
Aufrufe der selben Funktion (Instanzen) müssen auseinander
gehalten werden.
lokale Variablen
Idee zur Implementierung:
FP
PCold
FPold
lege einen speziellen Speicherbereich für jeden Aufruf einer
Funktion an.
organisatorische
Zellen
EPold
in sequentiellen Programmiersprachen können diese
Speicherbereiche auf dem Keller verwaltet werden
formale Parameter /
Funktionswert
jede Instanz einer Funktion erhält dadurch einen Bereich auf
dem Stack
FP =
b Frame Pointer; zeigt auf die letzte organisatorische Zelle und
wird zur Adressierung der formalen Parameter und lokalen Variablen
benutzt.
diese Bereiche heißen Keller-Rahmen (oder Stack Frame)
304 / 398
303 / 398
Adressierung von Variablen im Kellerrahmen
Adressierung von Variablen im Kellerrahmen
Die lokalen Variablen erhalten Relativadressen +1, +2, . . ..
Die lokalen Variablen erhalten Relativadressen +1, +2, . . ..
Die formalen Parameter liegen unterhalb der organisatorischen
Zellen und haben deshalb negative Adressen relativ zu FP
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.
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.
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.
305 / 398
Adressierung von Variablen im Kellerrahmen
Bestimmung der Adress-Umgebung
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
305 / 398
305 / 398
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
}
306 / 398
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) {
G
,0i
x
h
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
}
Die Synthesephase
Kapitel 8:
Betreten und Verlassen von Funktionen
306 / 398
Position von Funktionsargumenten
Arbeitsteilung beim Funktionsaufruf
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 |)
Definition
Sei f die aktuelle Funktion, die die Funktion g aufruft.
f heißt caller
xi 7→ (L, −2 − |τ1 | − . . . − |τi |)
g heißt callee
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;
}
307 / 398
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.
ρ(pi )
hL, −3i
hL, −4i
hL, −5i
hL, −6i
Beobachtung:
Den Platz für die aktuellen Parameter kennt nur der Caller:
Beispiel: printf
308 / 398
Prinzip vom Funktionsaufruf und Rücksprung
Aufruf-Sequenz
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.
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.




mark 




stehen in f





call 



enter stehen in g
alloc






return





slide
309 / 398
310 / 398
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:
311 / 398
Aufruf-Sequenz
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 ) ρ
Damit erhalten wir für einen Aufruf für eine Funktion mit mindestens
einem Parameter und einem Rückgabewert:
= codeR en ρ
...
codeR e1 ρ
mark
codeR g ρ
call
slide (s − 1)
codeR g(e1 , . . . , en ) ρ
wobei s der Platz für die aktuellen Parameter ist.
Beachte:
= 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.
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
311 / 398
Berechnung von R-Werten
311 / 398
Kopien von Speicherbereichen
Die move Instruktion kopiert k Elemente auf den Stack.
Ä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.
k
move k
Folglich:
codeR f ρ
=
codeR (∗e) ρ =
codeR e ρ
=
f ein Funktions-Name
e ein Funktions-Zeiger
loadc (ρ f )
codeR e ρ
codeL e ρ
move k
for (i = k-1; i≥0; i--)
S[SP+i] = S[S[SP]+i];
SP = SP+k–1;
e eine Struktur der Größe k
Neuer Befehl: move
312 / 398
Retten von EP und FP
Aufrufen einer Funktion
Der Befehl call rettet den aktuellen Wert des PC als
Fortsetzungs-Adresse und setzt FP und PC.
Der Befehl mark legt Platz für Rückgabewert und organisatorische
Zellen an und rettet FP und EP.
FP
EP
313 / 398
FP
EP
e
e
FP
q
e
p
call
mark
S[SP+1] = EP;
S[SP+2] = FP;
SP = SP + 2;
PC
PC
p
q
tmp = S[SP];
S[SP] = PC;
FP = SP;
PC = tmp;
314 / 398
315 / 398
Entfernen der Parameter beim Rücksprung
Übersetzung einer Funktionsdefinition
Der Befehl slide kopiert den Rückgabewert an die korrekte Stelle:
Entsprechend übersetzen wir eine Funktions-Definition:
code
42
t f (specs){body}
_f:
m
slide m
enter q
alloc k
code ss ρf
return
42
wobei q
max
k
ρf
tmp = S[SP];
SP = SP-m;
S[SP] = tmp;
=
=
=
=
ρ
=
//
//
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
316 / 398
Reservierung von Speicher auf dem Stack
317 / 398
Reservierung von Speicher für lokale Variablen
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.
Der Befehl alloc k reserviert auf dem Keller Platz für die lokalen
Variablen.
EP
q
k
enter q
alloc k
EP = SP + q;
if (EP ≥ NP)
Error (“Stack Overflow”);
SP = SP + k;
319 / 398
318 / 398
Rücksprung aus einer Funktion
Überblick Funktionsaufruf
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
Aufruf: f (e1 , . . . , en ):
codeR en ρ
...
codeR e1 ρ
mark
codeR f ρ
call
slide (s − 1)
p
e
v
_f:
PC = S[FP]; EP = S[FP-2];
if (EP ≥ NP) Error (“Stack Overflow”);
SP = FP-3; FP = S[SP+2];
320 / 398
enter q
alloc k
code ss ρf
return
321 / 398
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
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:
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
loadrc j
f
FP
f
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.
f+j
code return e; ρ
SP++;
S[SP] = FP+j;
=
codeR e ρ
storer -3
return
322 / 398
323 / 398
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);
}
Themengebiet:
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:
Übersetzung ganzer Programme
mul
storer -3
return
return
Dabei ist ρfac : x 7→ (L, −3) und q = 1 + 1 + 3 = 5.
324 / 398
Übersetzung von Programmen
325 / 398
Instruktionen für den Programmstart
Dann definieren wir:
Vor der Programmausführung gilt:
SP = −1
FP = EP = 0
code p ∅
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.
_f1 :
Der Code für das Programm p enthält:
Code für die Funktions-Definitionen F_defi ;
_fn :
Code zum Anlegen der globalen Variablen;
enter (k + 4)
alloc (k + 1)
mark
loadc _main
call
slide k
halt
code F_def1 ρ
..
.
code F_defn ρ
Code für den Aufruf von main()
wobei ∅
ρ
k
die Instruktion halt.
326 / 398
=
b
=
b
=
b
leere Adress-Umgebung;
globale Adress-Umgebung;
Platz für globale Variablen
327 / 398
Bemerkung zum Rückgabewerte
Bemerkung zum Rückgabewerte
Nach alter C Norm darf es nur einen Rückgabewert geben.
Nach alter C Norm darf es nur einen Rückgabewert geben.
Rückgabewert überschreibt den ersten Parameter, slide entfernt
die restlichen Parameter
328 / 398
Bemerkung zum Rückgabewerte
328 / 398
Bemerkung zum Rückgabewerte
Nach alter C Norm darf es nur einen Rückgabewert geben.
Nach alter C Norm darf es nur einen Rückgabewert geben.
Rückgabewert überschreibt den ersten Parameter, slide entfernt
die restlichen Parameter
Rückgabewert überschreibt den ersten Parameter, slide entfernt
die restlichen Parameter
Was passiert, wenn es keine Parameter gibt?
Was passiert, wenn es keine Parameter gibt?
; der Rückgabewert überschreibt eine Caller-lokale Variable
328 / 398
Bemerkung zum Rückgabewerte
328 / 398
Bemerkung zum Rückgabewerte
Nach alter C Norm darf es nur einen Rückgabewert geben.
Nach alter C Norm darf es nur einen Rückgabewert geben.
Rückgabewert überschreibt den ersten Parameter, slide entfernt
die restlichen Parameter
Rückgabewert überschreibt den ersten Parameter, slide entfernt
die restlichen Parameter
Was passiert, wenn es keine Parameter gibt?
Was passiert, wenn es keine Parameter gibt?
; der Rückgabewert überschreibt eine Caller-lokale Variable
; der Rückgabewert überschreibt eine Caller-lokale Variable
Lösungen:
Lösungen:
erzwinge, dass jede Funktion mindestens einen formalen
Parameter hat
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
328 / 398
328 / 398
Peephole Optimization
Übersetzung ganzer Programme
Die Erzeugten Instruktionen enthalten viele redundante Sequenzen,
z.B.:
alloc 0
Kapitel 1:
Maschinen mit Registern
slide 0
loadc 1
mul
Peephole Optimierung sucht nach solchen Mustern und ersetzt sie
durch einfachere (in diesem Fall keine) Instruktionen.
329 / 398
Codegenerierung für moderne Prozessoren
330 / 398
Codegenerierung für moderne Prozessoren
Die C-Maschine und die JVM sind nicht sehr schnell:
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)
331 / 398
Codegenerierung für moderne Prozessoren
331 / 398
Codegenerierung für moderne Prozessoren
Die C-Maschine und die JVM sind nicht sehr schnell:
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)
jede Operation (z.B. add) bezieht ihre Argumente und ihr
Resultat über den Stack (; pro Addition drei Speicherzugriffe)
Speicherzugriffe sind langsam (selbst mit cache)
Speicherzugriffe sind langsam (selbst mit cache)
jeder Zugriff auf den Stapel berechnet FP+k
331 / 398
331 / 398
Codegenerierung für moderne Prozessoren
Codegenerierung für moderne Prozessoren
Die C-Maschine und die JVM sind nicht sehr schnell:
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)
jede Operation (z.B. add) bezieht ihre Argumente und ihr
Resultat über den Stack (; pro Addition drei Speicherzugriffe)
Speicherzugriffe sind langsam (selbst mit cache)
Speicherzugriffe sind langsam (selbst mit cache)
jeder Zugriff auf den Stapel berechnet FP+k
jeder Zugriff auf den Stapel berechnet FP+k
pipelining ist schwierig: überlappen ∗(FP+k) und ∗(FP+k0 )?
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
331 / 398
331 / 398
Codegenerierung für moderne Prozessoren
Moderne Register-Maschinen
Entwicklung von Register-Maschinen:
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
332 / 398
331 / 398
Moderne Register-Maschinen
Moderne Register-Maschinen
Entwicklung von Register-Maschinen:
Anzahl der Register war begrenzt durch Anzahl der Transistoren:
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)
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
332 / 398
332 / 398
Moderne Register-Maschinen
Moderne Register-Maschinen
Entwicklung von Register-Maschinen:
Anzahl der Register war begrenzt durch Anzahl der Transistoren:
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)
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
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
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
Die wichtigsten Aufgaben:
früher: bilde Programmkonstrukte auf komplexe Instruktionen ab
heute: halte möglichst viele Werte gleichzeitig in Registern
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.
332 / 398
Instruktionsselektion: Übersicht
332 / 398
Adressierung im MC68000
Beispiel: Motorola MC68000
Dieser einfachste Prozessor der 680x0-Reihe besitzt
Der MC68000 ist eine 2-Adress-Maschine, d.h. ein Befehl darf
maximal 2 Argumente enthalten. Die Instruktion:
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
add D1 D2
Semantik
Dn
An
M[An ]
M[An + d]
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)
M[An + Dm + d]
Die Ausführungszeit eines Befehls ergibt sich (i.a.) aus den
Kosten der Operation plus den Kosten für die Adressierung der
Operanden
M[x]
M[x]
x
333 / 398
Kosten der Adressierung im MC68000
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
334 / 398
Eine add Instruktion benötigt 4 Zykel.
Argumente verlängern diese Zeit wie folgt:
.B, .W
0
0
4
8
.D
0
0
8
12
Dn
An
(An )
d(An )
10
14
d(An , Dm )
8
12
4
12
16
8
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.
335 / 398
335 / 398
Beispiel Instruktionsselektion
Komplexe Instruktionen
Betrachte die Kosten als Summe von Operation, 1. Argument und 2.
Argument: o + a1 + a2
Die Instruktion
benötigt:
die verschieden Code-Sequenzen sind im Hinblick auf den
Speicher und das Ergebnis äquivalent
move.B 8(A1 , D1 .W), D5
4 + 10 + 0 = 14 Zykel
sie unterscheiden sich im Hinblick auf den Wert des Registers A1
sowie die gesetzten Bedingungs-Codes
ein konkreter Algorithmus muss solche Randbedingungen
berücksichtigen
Alternativ könnten wir erzeugen:
adda
adda
move.B
mit Gesamtkosten
32
adda
move.B
mit Gesamtkosten
int b, i, a[100];
b = 2 + a[i];
Kosten: 8 + 8 + 0 = 16
Kosten: 8 + 0 + 0 = 8
Kosten: 4 + 4 + 0 = 8
#8, A1
D1 .W, A1
(A1 ), D5
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:
oder:
Kosten: 8 + 0 + 0 = 8
Kosten: 4 + 8 + 0 = 12
D1 .W, A1
8(A1 ), D5
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
20
336 / 398
Instruktionsselektion für Anweisung
337 / 398
Instruktionsselektion für Anweisung
Betrachte die Übersetzung von:
Betrachte die Übersetzung von:
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
Mögliche Auswahl an Instruktionen: move
Der Ausdruck entspricht dem Syntaxbaum:
=
=
M
+
2
+
A5
−6(A5 ), D1
M
+
M
2
+
−4
+
A5
A5
−4
+
∗
+
−206
2
D1
M
∗
+
M
−206
A5
2
D1
+
A5
M
+
−6
A5
−6
338 / 398
Instruktionsselektion für Anweisung
338 / 398
Instruktionsselektion für Anweisung
Betrachte die Übersetzung von:
Betrachte die Übersetzung von:
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
Mögliche Auswahl an Instruktionen: add D1 , D1
Mögliche Auswahl an Instruktionen: move
=
=
M
M
+
2
+
A5
−206(A5 , D1 ), D2
M
−4
+
A5
+
D1
+
A5
−206
+
−4
D1
D1
+
A5
M
M
+
∗
2
D2
2
−206
∗
2
D1
+
A5
M
+
−6
A5
338 / 398
−6
338 / 398
Instruktionsselektion für Anweisung
Instruktionsselektion für Anweisung
Betrachte die Übersetzung von:
Betrachte die Übersetzung von:
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
M[A5 − 4] = 2 + M[A5 − 206 + 2 · M[A5 − 6]];
Mögliche Auswahl an Instruktionen: addq #2, D2
Mögliche Auswahl an Instruktionen: move
=
=
D2
M
D2
−4
M
A5
D1
A5
−206
D1
D2
M
−4
+
∗
2
+
2
+
+
+
D2
M
+
2
+
A5
D2 , −4(A5 )
D1
+
A5
M
−206
∗
2
D1
M
+
A5
+
−6
A5
−6
338 / 398
Mögliche Code-Sequenzen
338 / 398
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
Im Beispiel:
move
add
move
addq
move
−6(A5 ), D1
D1 , D1
−206(A5 , D1 ), D2
#2, D2
D2 , −4(A5 )
Kosten: 12
Kosten: 4
Kosten: 14
Kosten: 4
Kosten: 12
Gesamtkosten :
46
A5 , A1
#−6, A1
(A1 ), D1
#2, D1
A5 , A2
#−206, A2
D1 , A2
(A2 ), D2
#2, D2
A5 , A3
#−4, A3
D2 , (A3 )
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Kosten:
Gesamtkosten :
4
12
8
44
4
12
8
8
4
4
12
8
124
339 / 398
340 / 398
Kriterien zur Instruktionsselektion
Kriterien zur Instruktionsselektion
Komplexe Instruktionen sind i.A. vorteilhaft:
Komplexe Instruktionen sind i.A. vorteilhaft:
komplexere Adressierungsarten können den Code erheblich
schneller und kürzer machen
komplexere Adressierungsarten können den Code erheblich
schneller und kürzer machen
einfachere Befehlssequenzen benötigen mehr Hilfsregister
einfachere Befehlssequenzen benötigen mehr Hilfsregister
die komplexere Folge überschreibt auch weniger Hilfsregister
die komplexere Folge überschreibt auch weniger Hilfsregister
; finde die schnellste Folge von Instruktionen für einen
Syntaxbaum
341 / 398
341 / 398
Kriterien zur Instruktionsselektion
Kriterien zur Instruktionsselektion
Komplexe Instruktionen sind i.A. vorteilhaft:
Komplexe Instruktionen sind i.A. vorteilhaft:
komplexere Adressierungsarten können den Code erheblich
schneller und kürzer machen
komplexere Adressierungsarten können den Code erheblich
schneller und kürzer machen
einfachere Befehlssequenzen benötigen mehr Hilfsregister
einfachere Befehlssequenzen benötigen mehr Hilfsregister
die komplexere Folge überschreibt auch weniger Hilfsregister
die komplexere Folge überschreibt auch weniger Hilfsregister
; finde die schnellste Folge von Instruktionen für einen
Syntaxbaum
; 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:
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
1
2
3
die Grammatik ist mehrdeutig, da viele Ableitungen den gleichen
Baum beschreiben
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
wähle die billigste aller Ableitungen, die zu einem Baum führen
; inferiere alle möglichen Ableitungen durch tree parsing
341 / 398
Beispiel Grammtik für Instruktionen
Beachte:
Loads :
D → M[A]
D → M[A + A]
Computations :
D→c
D→D+D
341 / 398
Beispiel für eine Ableitung der Grammatik
Loads :
D → M[A]
D → M[A + A]
Moves :
D→A
A →D
Beispiel Instruktion: M[A + c]
Computations :
D→c
D→D+D
Moves :
D→A
A →D
D
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
342 / 398
Beispiel für eine Ableitung der Grammatik
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
Computations :
D→c
D→D+D
343 / 398
Beispiel für eine Ableitung der Grammatik
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
M
M
A
D
343 / 398
Computations :
D→c
D→D+D
Moves :
D→A
A →D
343 / 398
Beispiel für eine Ableitung der Grammatik
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
Computations :
D→c
D→D+D
Beispiel für eine Ableitung der Grammatik
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
M
M
+
+
D
D
A
Computations :
D→c
D→D+D
Moves :
D→A
A →D
D
343 / 398
Beispiel für eine Ableitung der Grammatik
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
A
Computations :
D→c
D→D+D
343 / 398
Beispiel für eine Ableitung der Grammatik
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Beispiel Instruktion: M[A + c]
M
M
+
+
c
A
Computations :
D→c
D→D+D
Moves :
D→A
A →D
c
Alternativ wäre auch eine Ableitung mit der Regel D → M[A + A]
möglich.
343 / 398
Instruktionsselektion durch tree-parsing
343 / 398
Instruktionsselektion durch tree-parsing
Idee: Berechnung von möglichen Ableitungen zu einem Baum 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:
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
344 / 398
344 / 398
Instruktionsselektion durch tree-parsing
Instruktionsselektion durch tree-parsing
Idee: Berechnung von möglichen Ableitungen zu einem Baum 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:
berechne mögliche Ableitungen bottom-up, beginnend von den
Blättern:
prüfe, welche rechte Regelseite passt
prüfe, welche rechte Regelseite passt
wende diese Regel rückwärts an
wende diese Regel rückwärts an
füge linke Regelseite zum Vorgänger im Baum hinzu
füge linke Regelseite zum Vorgänger im Baum hinzu
oben angekommen, wähle die Ableitung mit den geringsten
Kosten
oben angekommen, wähle die Ableitung mit den geringsten
Kosten
steige hinab und emittiere Instruktionen für jede angewandte
Regel
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.
344 / 398
Beispiel für Instruktionsselektion
Loads :
D → M[A]
D → M[A + A]
Computations :
D→c
D→D+D
344 / 398
Beispiel für Instruktionsselektion
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Betrachte: M[A + c]
Computations :
D→c
D→D+D
Moves :
D→A
A →D
Betrachte: M[A + c]
M
M
+
A
A0 , D1
c
+
A
c
345 / 398
Beispiel für Instruktionsselektion
Loads :
D → M[A]
D → M[A + A]
Computations :
D→c
D→D+D
345 / 398
Beispiel für Instruktionsselektion
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Betrachte: M[A + c]
Computations :
D→c
D→D+D
Moves :
D→A
A →D
Betrachte: M[A + c]
M
M
A4 , D3 , A + A2
A0 , D1
A
+
A2 , D1
A0 , D1
c
A
345 / 398
+
A2 , D1
c
345 / 398
Beispiel für Instruktionsselektion
Loads :
D → M[A]
D → M[A + A]
Computations :
D→c
D→D+D
Beispiel für Instruktionsselektion
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Betrachte: M[A + c]
Computations :
D→c
D→D+D
Moves :
D→A
A →D
Betrachte: M[A + c]
A6 , D5
A6 , D5
M
0
M
A4 , D3 , A + A2
+
A0 , D1
A4 , D3 , A + A2
A2 , D1
A
+
A0 , D1
c
A
A2 , D1
c
345 / 398
345 / 398
Beispiel für Instruktionsselektion
Loads :
D → M[A]
D → M[A + A]
Computations :
D→c
D→D+D
Beispiel für Instruktionsselektion
Moves :
D→A
A →D
Loads :
D → M[A]
D → M[A + A]
Betrachte: M[A + c]
Computations :
D→c
D→D+D
Moves :
D→A
A →D
Betrachte: M[A + c]
A6 , D5
0
A6 , D5
M
4
3
A ,D , A + A
A0 , D1
A
+
2
0
M
A4 , D3 , A + A2
A2 , D1
A0 , D1
c
A
+
A2
5,3
, D1
c
345 / 398
Praktische Umsetzung
345 / 398
Praktische Umsetzung
Instruktionsselektion durch Anwendung aller möglichen Regeln:
Instruktionsselektion durch Anwendung aller möglichen Regeln:
kann nach verschiedene Kriterien optimieren (Laufzeit,
Code-Größe, Parallelisierbarkeit, Stromverbrauch)
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
346 / 398
346 / 398
Praktische Umsetzung
Praktische Umsetzung
Instruktionsselektion durch Anwendung aller möglichen Regeln:
Instruktionsselektion durch Anwendung aller möglichen Regeln:
kann nach verschiedene Kriterien optimieren (Laufzeit,
Code-Größe, Parallelisierbarkeit, Stromverbrauch)
muss schnell durchgeführt werden
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
übersetze die Anwendung der Regeln in einen endlichen,
deterministischen Baum-Automaten
funktioniert nur wenn jede Operation nur ein Ergebnis hat
funktioniert nur wenn jede Operation nur ein Ergebnis hat
Wir verzichten auf eine ausführliche Behandlung:
346 / 398
Praktische Umsetzung
346 / 398
Praktische Umsetzung
Instruktionsselektion durch Anwendung aller möglichen Regeln:
Instruktionsselektion durch Anwendung aller möglichen Regeln:
kann nach verschiedene Kriterien optimieren (Laufzeit,
Code-Größe, Parallelisierbarkeit, Stromverbrauch)
muss schnell durchgeführt werden
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
übersetze die Anwendung der Regeln in einen endlichen,
deterministischen Baum-Automaten
funktioniert nur wenn jede Operation nur ein Ergebnis hat
funktioniert nur wenn jede Operation nur ein Ergebnis hat
Wir verzichten auf eine ausführliche Behandlung:
Wir verzichten auf eine ausführliche Behandlung:
auf modernen Prozessoren führen komplexe Instruktionen (wenn
sie existieren) oft zu stalls in der Pipeline
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
346 / 398
Praktische Umsetzung
346 / 398
Praktische Umsetzung
Instruktionsselektion durch Anwendung aller möglichen Regeln:
Instruktionsselektion durch Anwendung aller möglichen Regeln:
kann nach verschiedene Kriterien optimieren (Laufzeit,
Code-Größe, Parallelisierbarkeit, Stromverbrauch)
muss schnell durchgeführt werden
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
übersetze die Anwendung der Regeln in einen endlichen,
deterministischen Baum-Automaten
funktioniert nur wenn jede Operation nur ein Ergebnis hat
funktioniert nur wenn jede Operation nur ein Ergebnis hat
Wir verzichten auf eine ausführliche Behandlung:
Wir verzichten auf eine ausführliche Behandlung:
auf modernen Prozessoren führen komplexe Instruktionen (wenn
sie existieren) oft zu stalls in der Pipeline
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
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
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
346 / 398
346 / 398
Motivation für die Register C-Maschine
Übersetzung ganzer Programme
Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von
Universalregistern.
Kapitel 2:
Codeerzeugung für die Register-C-Maschine
347 / 398
Motivation für die Register C-Maschine
348 / 398
Motivation für die Register C-Maschine
Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von
Universalregistern.
Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von
Universalregistern.
arithmetische Operationen können oft nur auf diesen Registern
ausgeführt werden
arithmetische Operationen können oft nur auf diesen Registern
ausgeführt werden
Zugriffe auf den Speicher werden durch Adressen in Registern
vorgenommen
Zugriffe auf den Speicher werden durch Adressen in Registern
vorgenommen
Register müssen gesichert werden, wenn Funktionen aufgerufen
werden
Register müssen gesichert werden, wenn Funktionen aufgerufen
werden
Die Übersetzung von Code für einen solchen Prozessor muss:
348 / 398
Motivation für die Register C-Maschine
Motivation für die Register C-Maschine
Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von
Universalregistern.
Ein moderner RISC-Prozessor besitzt eine fixe Anzahl von
Universalregistern.
arithmetische Operationen können oft nur auf diesen Registern
ausgeführt werden
arithmetische Operationen können oft nur auf diesen Registern
ausgeführt werden
Zugriffe auf den Speicher werden durch Adressen in Registern
vorgenommen
Zugriffe auf den Speicher werden durch Adressen in Registern
vorgenommen
Register müssen gesichert werden, wenn Funktionen aufgerufen
werden
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
3
348 / 398
Die Übersetzung von Code für einen solchen Prozessor muss:
1
Variablen und Funktionsargumente in Registern speichern
während eines Funktionsaufrufes die entsprechenden Register
auf dem Keller sichern
2
beliebige Berechnungen mittels endlich vieler Register
ausdrücken
3
während eines Funktionsaufrufes die entsprechenden Register
auf dem Keller sichern
beliebige Berechnungen mittels endlich vieler Register
ausdrücken
; betrachte nur die ersten zwei Punkte (und behandle den letzten
Punkt als separates Problem)
348 / 398
348 / 398
Prinzip der Register C-Maschine
Die Registersätze der R-CMa
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 , . . .
Die zwei Registersätze haben die folgenden Aufgaben:
1
0 1
PC
0
SP
die lokalen Register Ri
speichern lokale Variablen einer Funktion
speichern temporäre Zwischenergebnisse
können effizient auf den Stack gerettet werden
C
S
Rloc
R1
R6
Rglob
R0
R−4
350 / 398
349 / 398
Die Registersätze der R-CMa
Die Registersätze der R-CMa
Die zwei Registersätze haben die folgenden Aufgaben:
1
Die zwei Registersätze haben die folgenden Aufgaben:
die lokalen Register Ri
1
speichern lokale Variablen einer Funktion
speichern temporäre Zwischenergebnisse
können effizient auf den Stack gerettet werden
2
die lokalen Register Ri
speichern lokale Variablen einer Funktion
speichern temporäre Zwischenergebnisse
können effizient auf den Stack gerettet werden
die globalen Register Ri
2
speichern Parameter einer Funktion
speichern den Rückgabewert einer Funktion
die globalen Register Ri
speichern Parameter einer Funktion
speichern den Rückgabewert einer Funktion
; zur Verwaltung erweitern wir die Adressumgebung ρ
350 / 398
350 / 398
Adressumgebung
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
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
351 / 398
351 / 398
Adressumgebung
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
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
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:
Beachte: eine Variable x kann nur einen Eintrag in ρ haben.
Allerdings:
ρ kann unterschiedlich sein an verschiedenen Programmpunkten
351 / 398
Adressumgebung
351 / 398
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
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
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
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
351 / 398
Vergeben von Adressen an Variablen
351 / 398
Vergeben von Adressen an Variablen
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
auf der R-CMa existieren genug Register für alle Variablen
auf der R-CMa existieren genug Register für alle Variablen
Felder (arrays) und dynamische Strukturen bleiben aber
weiterhin im Speicher
Felder (arrays) und dynamische Strukturen bleiben aber
weiterhin im Speicher
Variablen, deren Adresse genommen wird, müssen auch im
Speicher bleiben
352 / 398
352 / 398
Vergeben von Adressen an Variablen
Vergeben von Adressen an Variablen
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
auf der R-CMa existieren genug Register für alle Variablen
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
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
Register haben keine Adresse
der Code p=&x kann nicht übersetzt werden, wenn x einem
Register zugewiesen ist
352 / 398
Vergeben von Adressen an Variablen
352 / 398
Vergeben von Adressen an Variablen
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
auf der R-CMa existieren genug Register für alle Variablen
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
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
Register haben keine Adresse
der Code p=&x kann nicht übersetzt werden, wenn x einem
Register zugewiesen ist
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
ermittle von welchen Variablen die Adresse genommen wurde
weise diesen Variablen eine Speicherstelle auf dem Stack zu
globale Variablen sind im Speicher angesiedelt
352 / 398
352 / 398
Vergeben von Adressen an Variablen
Codeerzeugung für die R-CMa
Funktionen zur Codeerzeugung:
Vor der Codeerzeugung müssen jeder Variable eine Speicherzelle
zugewiesen werden
Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden
drei Funktionen:
auf der R-CMa existieren genug Register für alle Variablen
codei s ρ: generiert Code für Anweisung s
Felder (arrays) und dynamische Strukturen bleiben aber
weiterhin im Speicher
Variablen, deren Adresse genommen wird, müssen auch im
Speicher bleiben
codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt
codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt
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
352 / 398
353 / 398
Codeerzeugung für die R-CMa
Codeerzeugung für die R-CMa
Funktionen zur Codeerzeugung:
Funktionen zur Codeerzeugung:
Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden
drei Funktionen:
Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden
drei Funktionen:
codei s ρ: generiert Code für Anweisung s
codei s ρ: generiert Code für Anweisung s
codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt
codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt
codeiL
codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt
e ρ: generiere Code der den L-Wert e nach Ri schreibt
wobei
wobei
i gibt das zuletzt benutzte Register an, i + 1, i + 2, . . . sind
unbenutzt.
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.
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.
353 / 398
Codeerzeugung für die R-CMa
353 / 398
Übersetzung von Ausdrücken
Funktionen zur Codeerzeugung:
Sei folgende Funktion gegeben:
Sei s eine Anweisung und e ein Ausdruck. Definiere die folgenden
drei Funktionen:
codei s ρ: generiert Code für Anweisung s
void f(void) {
int x,y,z;
x = y+3*z;
}
Ordne den Variablen die ersten drei Register zu:
codeiR e ρ: generiere Code der den R-Wert e nach Ri schreibt
codeiL e ρ: generiere Code der den L-Wert e nach Ri schreibt
ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i}
wobei
Setzte t = 4 da R4 das erste freie Register ist. Erzeuge
i gibt das zuletzt benutzte Register an, i + 1, i + 2, . . . sind
unbenutzt.
code4 x=y+3*z ρ
Das Ergebnis von codeiR und codeiL wird immer in das temporäre
Register Ri gespeichert.
= code4R y+3*z ρ
move R1 R4
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)
354 / 398
353 / 398
Übersetzung von Ausdrücken
Sei folgende Funktion gegeben:
Übersetzung von Ausdrücken
void f(void) {
int x,y,z;
x = y+3*z;
}
Sei folgende Funktion gegeben:
Ordne den Variablen die ersten drei Register zu:
Ordne den Variablen die ersten drei Register zu:
ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i}
ρ = {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 ρ
void f(void) {
int x,y,z;
x = y+3*z;
}
Setzte t = 4 da R4 das erste freie Register ist. Erzeuge
code4 x=y+3*z ρ
= code4R y+3*z ρ
move R1 R4
= code5R 3*z ρ
add R4 R2 R5
354 / 398
= 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
354 / 398
Übersetzung von Ausdrücken
Sei folgende Funktion gegeben:
Übersetzung von Ausdrücken
void f(void) {
int x,y,z;
x = y+3*z;
}
Ordne den Variablen die ersten drei Register zu:
Ordne den Variablen die ersten drei Register zu:
ρ = {x 7→ hRL, 1i, y 7→ hRL, 2i, z 7→ hRL, 3i}
ρ = {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 ρ
void f(void) {
int x,y,z;
x = y+3*z;
}
Sei folgende Funktion gegeben:
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 ρ
move R1 R4
code4R y+3*z ρ
= code5R 3*z ρ
add R4 R2 R5
code4R y+3*z ρ
= code5R 3*z ρ
add R4 R2 R5
code5R 3*z ρ
= code6R 3 ρ
mul R5 R6 R3
code5R 3*z ρ
= code6R 3 ρ
mul R5 R6 R3
code6R 3 ρ
code6R 3 ρ
= loadc R6 3
354 / 398
Übersetzen von Ausdrücken
= 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
Ü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
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
op Ri Rj Rk
wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist.
wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist.
Entsprechend erzeugen wir den Code wie folgt:
Entsprechend erzeugen wir den Code wie folgt:
codeiR
e1 op e2 ρ
codeiR e1 ρ
codei+1
R e2 ρ
=
354 / 398
codeiR e1 op e2 ρ
codeiR e1 ρ
codei+1
R e2 ρ
op Ri Ri Ri+1
=
op Ri Ri Ri+1
Beispiel: Übersetze 3*4 mit i = 4:
code4R 3*4 ρ
code4R 3 ρ
code5R 4 ρ
mul R4 R4 R5
=
355 / 398
Übersetzen von Ausdrücken
355 / 398
Verwaltung temporärer Register
Beachte, dass temporäre Register wiederverwendet werden:
Übersetze 3*4+3*4 mit t = 4:
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
code4R 3*4+3*4 ρ
=
op Ri Rj Rk
wobei Ri das Zielregister, Rj das erste und Rk das zweite Argument ist.
mit
codeiR 3*4 ρ
Entsprechend erzeugen wir den Code wie folgt:
codeiR e1 op e2 ρ
=
codeiR e1 ρ
codei+1
R e2 ρ
op Ri Ri Ri+1
=
code4R 3*4+3*4 ρ
=
loadc Ri 3
loadc Ri+1 4
mul Ri Ri Ri+1
ergibt sich
Beispiel: Übersetze 3*4 mit i = 4:
code4R 3*4 ρ
code4R 3*4 ρ
code5R 3*4 ρ
add R4 R4 R5
=
loadc R4 3
loadc R5 4
mul R4 R4 R5
355 / 398
356 / 398
Verwaltung temporärer Register
Semantik der Operatoren
Beachte, dass temporäre Register wiederverwendet werden:
Übersetze 3*4+3*4 mit t = 4:
code4R 3*4+3*4 ρ
=
Die Operatoren haben die folgende Semantik:
code4R 3*4 ρ
code5R 3*4 ρ
add R4 R4 R5
add Ri Rj Rk
sub Ri Rj Rk
div Ri Rj Rk
mul Ri Rj Rk
mod Ri Rj Rk
mit
codeiR 3*4 ρ
=
loadc Ri 3
loadc Ri+1 4
mul Ri Ri Ri+1
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
ergibt sich
code4R 3*4+3*4 ρ
=
loadc R4 3
loadc R5 4
mul R4 R4 R5
loadc R5 3
loadc R6 4
mul R5 R5 R6
add R4 R4 R5
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
357 / 398
356 / 398
Semantik der Operatoren
Übersetzung unärer Operatoren
Die unären Operatoren op = {neg, not} nehmen zwei Register:
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
codeiR op e ρ
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
=
codeiR e ρ
op Ri Ri
Beachte: alle Ergebnisse werden ≡232 berechnet
357 / 398
Übersetzung unärer Operatoren
Übersetzung unärer Operatoren
Die unären Operatoren op = {neg, not} nehmen zwei Register:
codeiR op e ρ
=
358 / 398
Die unären Operatoren op = {neg, not} nehmen zwei Register:
codeiR e ρ
op Ri Ri
codeiR op e ρ
Beachte: Wir verwenden dasselbe Register.
=
codeiR e ρ
op Ri Ri
Beachte: Wir verwenden dasselbe Register.
Beispiel: Übersetze -4 nach R5 :
code5R -4 ρ
358 / 398
=
code5R 4 ρ
neg R5 R5
358 / 398
Übersetzung unärer Operatoren
Übersetzung unärer Operatoren
Die unären Operatoren op = {neg, not} nehmen zwei Register:
codeiR
codeiR
=
op e ρ
Die unären Operatoren op = {neg, not} nehmen zwei Register:
codeiR op e ρ
eρ
op Ri Ri
codeiR e ρ
op Ri Ri
=
Beachte: Wir verwenden dasselbe Register.
Beachte: Wir verwenden dasselbe Register.
Beispiel: Übersetze -4 nach R5 :
Beispiel: Übersetze -4 nach R5 :
code5R -4 ρ
=
code5R -4 ρ
loadc R5 4
neg R5 R5
=
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
358 / 398
Datentransfer Instruktionen der R-CMa (I)
Datentransfer Instruktionen der R-CMa (II)
Betrachte C-Ma Instruktionen für den Lesezugriff auf den Speicher:
Instruktion
load
loadc c
loadrc c
loada c
loadr c
move k
Betrachte C-Ma Instruktionen für das Schreiben in den Speicher:
Semantik
Intuition
S[SP] ← S[S[SP]]
lade Wert von Adresse
SP ← SP + 1; S[SP] ← c
lade Konstante
SP ← SP + 1; S[SP] ← c + FP lade Konstante rel. zu FP
loadc c; load
lade globale Variable
loadrc c; load
lade lokale Variable
...
kopiere k Werte auf Keller
Die Varianten der R-CMa erweitern diese um Register-Argumente:
Instruktion
load Ri Rj
loadc Ri c
loadrc Ri c
loada Ri c
loadr Ri c
move Ri Rj
move k Rj
Semantik
Ri ← S[Rj ]
Ri ← c
Ri ← FP + c
Ri ← S[c]
Ri ← S[FP + c]
Ri ← Rj
k
[S[SP + i] ← S[Rj + i]]i=0
Intuition
lade Wert von Adresse
lade Konstante
lade Konstante rel. zu FP
lade globale Variable
lade lokale Variable
transferiere Wert zw. Registern
kopiere k Werte auf den Keller
storea c
storer c
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
360 / 398
Übersetzung von Variablen
Betrachte C-Ma Instruktionen für das Schreiben in den Speicher:
Semantik
S[S[SP]] ← S[SP − 1];
SP ← SP − 1
loadc c; store
loadrc c; store
Instruktion
store
359 / 398
Datentransfer Instruktionen der R-CMa (II)
Instruktion
store
358 / 398
Der Wert einer Variablen wird wie folgt in Register Ri geladen:

if ρ x = hG, ai
 loada Ri a
i
loadr Ri a
if ρ x = hL, ai
codeR x ρ =

move Ri Rj
if ρ x = hr, ji ∧ r ∈ {RG, RL}
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
360 / 398
361 / 398
Übersetzung von Variablen
Ü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}
Der Wert einer Variablen wird wie folgt in Register Ri geladen:

if ρ x = hG, ai
 loada Ri a
loadr Ri a
if ρ x = hL, ai
codeiR x ρ =

move Ri Rj
if ρ x = hr, ji ∧ r ∈ {RG, RL}
Die Adresse einer Variablen wird wie folgt in Ri berechnet:
loadc Ri a
if ρ x = hG, ai
codeiL x ρ =
loadrc Ri a
if ρ x = hL, ai
Die Adresse einer Variablen wird wie folgt in Ri berechnet:
loadc Ri a
if ρ x = hG, ai
codeiL x ρ =
loadrc Ri a
if ρ x = hL, ai
Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert!
361 / 398
Übersetzung von Variablen
361 / 398
Übersetzung von Variablen
Der Wert einer Variablen wird wie folgt in Register Ri geladen:

if ρ x = hG, ai
 loada Ri a
i
loadr Ri a
if ρ x = hL, ai
codeR x ρ =

move Ri Rj
if ρ x = hr, ji ∧ r ∈ {RG, RL}
Der Wert einer Variablen wird wie folgt in Register Ri geladen:

if ρ x = hG, ai
 loada Ri a
i
loadr Ri a
if ρ x = hL, ai
codeR x ρ =

move Ri Rj
if ρ x = hr, ji ∧ r ∈ {RG, RL}
Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert!
Achtung: für ρ x = hRG, ji und ρ x = hRL, ji is codeiL nicht definiert!
Beobachtung:
Beobachtung:
Die Adresse einer Variablen wird wie folgt in Ri berechnet:
loadc Ri a
if ρ x = hG, ai
codeiL x ρ =
loadrc Ri a
if ρ x = hL, ai
Die Adresse einer Variablen wird wie folgt in Ri berechnet:
loadc Ri a
if ρ x = hG, ai
codeiL x ρ =
loadrc Ri a
if ρ x = hL, ai
intuitiv: ein Register hat keine Adresse
intuitiv: ein Register hat keine Adresse
ein Register darf während der Codegenerierung nie als L-Wert
auftreten
361 / 398
Übersetzung von Variablen
361 / 398
Übersetzung von Zuweisungen
Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch:
Der Wert einer Variablen wird wie folgt in Register Ri geladen:

if ρ x = hG, ai
 loada Ri a
i
loadr Ri a
if ρ x = hL, ai
codeR x ρ =

move Ri Rj
if ρ x = hr, ji ∧ r ∈ {RG, RL}
das Ermitteln das R-Wertes von 2*y,
das Ermitteln des L-Wertes von x und
einem store Befehl.
Analog definieren wir:
Die Adresse einer Variablen wird wie folgt in Ri berechnet:
loadc Ri a
if ρ x = hG, ai
codeiL x ρ =
loadrc Ri a
if ρ x = hL, ai
codeiR e1 = e2 ρ = codeiR e2 ρ
codei+1
e1 ρ
L
store Ri+1 Ri
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
361 / 398
362 / 398
Übersetzung von Zuweisungen
Übersetzung von Zuweisungen
Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch:
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.
das Ermitteln das R-Wertes von 2*y,
das Ermitteln des L-Wertes von x und
einem store Befehl.
Analog definieren wir:
codeiR
Analog definieren wir:
e1 = e2 ρ =
codeiR e2 ρ
codei+1
e1 ρ
L
codeiR e1 = e2 ρ = codeiR e2 ρ
codei+1
e1 ρ
L
store Ri+1 Ri
store Ri+1 Ri
Achtung: undefiniertes Resultat, falls e1 ≡ x und ρ x = hRL, ji.
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
362 / 398
Übersetzung von Zuweisungen
362 / 398
Zeiger und Datenstrukturen
Zuweisungen wie x=2*y wurden auf der C-Ma übersetzt durch:
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
das Ermitteln das R-Wertes von 2*y,
das Ermitteln des L-Wertes von x und
einem store Befehl.
codeiR &e ρ =
codeiL e ρ
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!
363 / 398
362 / 398
Zeiger und Datenstrukturen
Zeiger und Datenstrukturen
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
codeiR &e ρ =
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
codeiL e ρ
codeiR &e ρ =
codeiL e ρ
Dereferenzieren von Zeigern zum Lesen (R-Wert):
Dereferenzieren von Zeigern zum Lesen (R-Wert):
codeiR ∗e ρ
codeiR ∗e ρ
= codeiL e ρ
load Ri Ri
= codeiL e ρ
load Ri Ri
Dereferenzieren von Zeigern zum Schreiben (L-Wert):
codeiL ∗e ρ
363 / 398
= codeiR e ρ
363 / 398
Zeiger und Datenstrukturen
Zeiger und Datenstrukturen
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
codeiR &e ρ =
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
codeiL e ρ
codeiR &e ρ =
codeiL e ρ
Dereferenzieren von Zeigern zum Lesen (R-Wert):
Dereferenzieren von Zeigern zum Lesen (R-Wert):
codeiR ∗e ρ
codeiR ∗e ρ
= codeiL e ρ
load Ri Ri
Dereferenzieren von Zeigern zum Schreiben (L-Wert):
codeiL ∗e ρ
= codeiL e ρ
load Ri Ri
Dereferenzieren von Zeigern zum Schreiben (L-Wert):
= codeiR e ρ
codeiL ∗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
= 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
363 / 398
Zeiger und Datenstrukturen
Übersetzung ganzer Programme
Zeiger und Zeigerarithmetik wird in Registern ausgeführt:
Generieren von Zeigern:
codeiR &e ρ =
363 / 398
codeiL e ρ
Dereferenzieren von Zeigern zum Lesen (R-Wert):
codeiR ∗e ρ
= codeiL e ρ
Kapitel 3:
load Ri Ri
Übersetzung von Anweisungen
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
363 / 398
Übersetzung von Anweisungen
364 / 398
Übersetzung von Anweisungen
Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung
übersetzen können:
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 ρ
365 / 398
365 / 398
Übersetzung von Anweisungen
Übersetzung von bedingten Anweisungen
Neben dem Ausdruck e1 = e2 müssen wir e1 = e2 auch als Anweisung
übersetzen können:
code
Definiere dazu:
codei e1 = e2 ρ
code
c
code
tt
= codeiR e1 = e2 ρ
Übersetzung von if ( c ) tt else ee.
Das temporäre Register Ri wird dabei ignoriert. Allgemeiner:
codei e ρ = codeiR e ρ
codei if(c) tt else ee ρ
=
Semantik
PC ← A
if Ri = 0 then PC ← A
PC ← A + Ri
codeiR c ρ
jumpz Ri A
Zur Übersetzung von Kontrollfluss-Anweisungen definiere folgende
Befehle:
Instruktion
jump A
jumpz Ri A
jumpi Ri A
ee
codei tt ρ
jump B
Intuition
springe zur Adresse A
springe wenn Ri = 0
springe indirekt
A : codei ee ρ
B:
365 / 398
Übersetzung von Schleifen
Übersetzung von Schleifen
Übersetzung von while Schleifen:
codei while(e) s ρ
366 / 398
Übersetzung von while Schleifen:
= A : codeiR e ρ
codei while(e) s ρ
= A : codeiR e ρ
jumpz Ri B
jumpz Ri B
codei s ρ
codei s ρ
jump A
jump A
B:
B:
Übersetzung von for Schleifen:
codei for(e1 ; e2 ; e3 ) s ρ
=
codeiR e1 ρ
A : codeiR e2 ρ
jumpz Ri B
codei s ρ
codeiR e3 ρ
jump A
B:
367 / 398
Übersetzung von Fallunterscheidungen
367 / 398
Ü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].
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 ρ
checki c0 ck−1 B
A0 :
..
.
codei s0 ρ
jump D
..
.
B:
..
.
jump A0
..
.
jump Ak−1
C:
Ak−1 : codei sk−1 ρ
jump D
368 / 398
368 / 398
Übersetztung des checki Makros
Ü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 ρ
checki c0 ck−1 B
A0 :
..
.
codei s0 ρ
jump D
..
.
B:
..
.
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
jump A0
..
.
jump Ak−1
C:
Ak−1 : codei sk−1 ρ
jump D
checki l u B prüft ob l ≤ Ri < u gilt und springt entsprechend.
369 / 398
368 / 398
Übersetztung des checki Makros
Übersetztung des checki Makros
Das Makro checki l u B prüft ob l ≤ Ri < u. Sei k = u − l.
Das Makro checki l u B prüft ob l ≤ Ri < u. Sei k = u − l.
wenn Ri < l oder Ri ≥ u springt es nach C
Wir definieren:
wenn Ri < l oder Ri ≥ u springt es nach C
Wir definieren:
wenn l ≤ Ri < u, springt es nach B + Ri − u
checki l u B
wenn l ≤ Ri < u, springt es nach B + Ri − u
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
=
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.
369 / 398
369 / 398
Registerverwaltung bei Funktionsaufrufen
Übersetzung ganzer Programme
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)
Kapitel 4:
Übersetzung von Funktionen
370 / 398
371 / 398
Registerverwaltung bei Funktionsaufrufen
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:
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
371 / 398
Registerverwaltung bei Funktionsaufrufen
371 / 398
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
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
371 / 398
Registerverwaltung bei Funktionsaufrufen
371 / 398
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
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
371 / 398
371 / 398
Übersetzung von Funktionsaufrufen
Übersetzung von Funktionsaufrufen
Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt:
codeiR
Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt:
codeiR
g(e1 , . . . en ) ρ =
codeiR g(e1 , . . . en ) ρ =
e1 ρ
move R−1 Ri
..
.
codeiR en ρ
move R−n Ri
codeiR g ρ
saveloc R1 Ri−1
mark
call Ri
restoreloc R1 Ri−1
move Ri R0
codeiR e1 ρ
move R−1 Ri
..
.
codeiR en ρ
move R−n Ri
codeiR g ρ
saveloc R1 Ri−1
mark
call Ri
restoreloc R1 Ri−1
move Ri R0
Neue Instruktionen:
saveloc Ri Rj legt die Register Ri , Ri+1 . . . Rj auf dem Stapel ab
(inklusive!)
restoreloc Ri Rj nimmt die Register Rj , Rj−1 , . . . Ri vom Stapel
runter
372 / 398
372 / 398
Übersetzung von Funktionsaufrufen
Rückgabewerte einer Funktion
Ein Funktionsaufruf g(e1 , . . . en ) wird nun wie folgt übersetzt:
codeiR g(e1 , . . . en ) ρ =
Die globalen Register werden auch benutzt um den Rückgabewert zu
übermitteln:
codeiR e1 ρ
move R−1 Ri
..
.
codei return e ρ =
codeiR e ρ
move R0 Ri
codeiR
en ρ
move R−n Ri
i
codeR g ρ
saveloc R1 Ri−1
mark
call Ri
restoreloc R1 Ri−1
move Ri R0
return
Neue Instruktionen:
saveloc Ri Rj legt die Register Ri , Ri+1 . . . Rj auf dem Stapel ab
(inklusive!)
restoreloc Ri Rj nimmt die Register Rj , Rj−1 , . . . Ri vom Stapel
runter
Argumente auf dem Stack: push Ri pro Wert und pop k für k Werte
373 / 398
372 / 398
Rückgabewerte einer Funktion
Rückgabewerte einer Funktion
Die globalen Register werden auch benutzt um den Rückgabewert zu
übermitteln:
codei return e ρ =
Die globalen Register werden auch benutzt um den Rückgabewert zu
übermitteln:
codeiR e ρ
codei return e ρ =
move R0 Ri
return
return
Alternative ohne Rückgabewert:
i
code return ρ
codeiR e ρ
move R0 Ri
Alternative ohne Rückgabewert:
codei return ρ
= 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
373 / 398
373 / 398
Übersetzung ganzer Funktionen
Übersetzung ganzer Funktionen
Die Übersetzung einer Funktion ist damit wie folgt definiert:
1
code tr f(args){decls ss} ρ =
Die Übersetzung einer Funktion ist damit wie folgt definiert:
code1 tr f(args){decls ss} ρ =
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
Randbedinungen:
Randbedinungen:
saveloc speichert maximal q − 3 Register
374 / 398
Übersetzung ganzer Funktionen
Übersetzung ganzer Funktionen
Die Übersetzung einer Funktion ist damit wie folgt definiert:
code1 tr f(args){decls ss} ρ =
374 / 398
Die Übersetzung einer Funktion ist damit wie folgt definiert:
code1 tr f(args){decls ss} ρ =
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
Randbedinungen:
Randbedinungen:
saveloc speichert maximal q − 3 Register
saveloc speichert maximal q − 3 Register
die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert
die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert
die Parameter der Funktion stehen in den Registern R−1 , . . . R−n
374 / 398
Übersetzung ganzer Funktionen
Übersetzung ganzer Funktionen
Die Übersetzung einer Funktion ist damit wie folgt definiert:
code1 tr f(args){decls ss} ρ =
374 / 398
Die Übersetzung einer Funktion ist damit wie folgt definiert:
code1 tr f(args){decls ss} ρ =
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
Randbedinungen:
Randbedinungen:
saveloc speichert maximal q − 3 Register
saveloc speichert maximal q − 3 Register
ρ0 ist die um lokale Variablen decls und Funktions-Parameter args
erweiterte Umgebung ρ
ρ0 ist die um lokale Variablen decls und Funktions-Parameter args
erweiterte Umgebung ρ
die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert
die Parameter der Funktion stehen in den Registern R−1 , . . . R−n
die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert
die Parameter der Funktion stehen in den Registern R−1 , . . . R−n
Argumente auf dem Stack: zusätzlich alloc k
374 / 398
374 / 398
Übersetzung ganzer Funktionen
Übersetzen ganzer Programme
Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten.
Die Übersetzung einer Funktion ist damit wie folgt definiert:
1
code tr f(args){decls ss} ρ =
code1 P ρ
enter q
move Rl+1 R−1
..
.
move Rl+n R−n
codel+n+1 ss ρ0
return
Randbedinungen:
saveloc speichert maximal q − 3 Register
die lokalen Variablen sind in den Registern R1 , . . . Rl gespeichert
die Parameter der Funktion stehen in den Registern R−1 , . . . R−n
enter (k + 3)
alloc k
loadc R1 _main
saveloc R1 R1
mark
call R1
restoreloc R1 R1
halt
_f1 : codei F1 ρ
..
.
=
_fn : codei Fn ρ
ρ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?
374 / 398
Übersetzen ganzer Programme
Übersetzung der Fakultätsfunktion
Betrachte:
Ein Programm P = F1 ; . . . Fn muss eine main Funktion enthalten.
1
code P ρ
375 / 398
enter (k + 3)
alloc k
loadc R1 _main
saveloc R1 R1
mark
call R1
restoreloc R1 R1
halt
_f1 : codei F1 ρ
..
.
int fac(int x) {
if (x<=0) then
return 1;
else
return x*fac(x-1);
}
=
_fac:
i=2
i
_fn : code 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
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
375 / 398
Realistische Register Maschinen
376 / 398
Realistische Register Maschinen
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
reale Maschinen haben nur eine fixe Anzahl an Registern
377 / 398
377 / 398
Realistische Register Maschinen
Realistische Register Maschinen
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
reale Maschinen haben nur eine fixe Anzahl an Registern
reale Maschinen haben nur eine fixe Anzahl an Registern
die unendliche Menge an virtuellen Registern muss auf eine
endliche Menge realer Register abgebildet werden
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
377 / 398
Realistische Register Maschinen
377 / 398
Realistische Register Maschinen
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
reale Maschinen haben nur eine fixe Anzahl an Registern
reale Maschinen haben nur eine fixe Anzahl an Registern
die unendliche Menge an virtuellen Registern muss auf eine
endliche Menge realer Register abgebildet werden
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
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
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:
377 / 398
Realistische Register Maschinen
377 / 398
Realistische Register Maschinen
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
Die R-CMa ist geeignet, auf einfache Weise Code für eine
Registermaschine zu erzeugen.
reale Maschinen haben nur eine fixe Anzahl an Registern
reale Maschinen haben nur eine fixe Anzahl an Registern
die unendliche Menge an virtuellen Registern muss auf eine
endliche Menge realer Register abgebildet werden
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
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
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:
Wir benötigen eine Lösung für die folgenden Probleme:
bestimme, wann ein Register nicht benutzt ist
bestimme, wann ein Register nicht benutzt ist
weise mehreren virtuellen Registern das gleiche reale Register
zu, wenn sie nicht gleichzeitig benutzt werden
377 / 398
377 / 398
Abstrakte Syntax
Übersetzung ganzer Programme
Anstatt auf R-CMa Instruktionen zu arbeiten, benutzen wir folgende
Pseudo-Sprache:
Kapitel 5:
•
•
•
•
•
•
•
Liveness Analyse
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
379 / 398
378 / 398
Abstrakte Syntax
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
//
//
//
//
//
//
//
Anstatt auf R-CMa Instruktionen zu arbeiten, benutzen wir folgende
Pseudo-Sprache:
Register
Zuweisungen
Lesen vom Speicher
Schreiben in Speicher
bedingter Sprung
Sprung oder Schleife
Funktion- oder Programmende
•
•
•
•
•
•
•
die Instruktionen können als Zwischensprache aufgefasst
werden
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
379 / 398
Beispiel für Liveness
379 / 398
Beispiel für Liveness
Eine liveness Analyse bestimmt, an welchen Programmpunkten ein
Register benutzt wird.
Eine liveness Analyse bestimmt, an welchen Programmpunkten ein
Register benutzt wird.
Betrachte:
Betrachte:
1:
2:
3:
x = y + 2;
y = 5;
x = y + 3;
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.
380 / 398
380 / 398
Beispiel für Liveness
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
Eine liveness Analyse bestimmt, an welchen Programmpunkten ein
Register benutzt wird.
Definition
Betrachte:
1:
2:
3:
x = y + 2;
y = 5;
x = y + 3;
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
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.
380 / 398
Motivation für liveness Analyse
381 / 398
Motivation für liveness Analyse
Eine liveness Analyse ist nützlich:
Eine liveness Analyse ist nützlich:
Zuweisungen zu toten Variablen können entfernt werden
Zuweisungen zu toten Variablen können entfernt werden
solch unnütze Zuweisungen können durch andere
Transformationen entstehen
solch unnütze Zuweisungen können durch andere
Transformationen entstehen
Definition
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
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
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
k
π1
381 / 398
Definition und Benutzung von Variablen
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 )
381 / 398
Betrachte die Anweisungen s1 , . . . sn als Kanten in einem Graphen.
Wir definieren Definition und Benutzung wie folgt:
Definition
∅
∅
∅
{x}
{x}
∅
si
;
Pos (e)
Neg (e)
x = e;
x = M[e];
M[e1 ] = e2 ;
Beispiel:
Benutztung
∅
Vars (e)
Vars (e)
Vars (e)
Vars (e)
Vars (e1 ) ∪ Vars (e2 )
1:
2:
3:
382 / 398
Definition
∅
∅
∅
{x}
{x}
∅
x = y + 2;
y = 5;
x = y + 3;
382 / 398
Lebende und tote Variablen
Lebende und tote Variablen
Definition
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.
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}
383 / 398
383 / 398
Berechnung der liveness Information
Berechnung der liveness Information
Wir berechnen die liveness Information durch ausführen des Program
mit einer abstrakten Semantik:
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.
384 / 398
Berechnung der liveness Information
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.
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
384 / 398
Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt:
= L
= [[Neg(e)]]] L = L ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= L ∪ Vars(e1 ) ∪ Vars(e2 )
[[;]]] 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 ]]]
384 / 398
384 / 398
Berechnung der liveness Information
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.
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
Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt:
= L
= [[Neg(e)]]] L = L ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= L ∪ Vars(e1 ) ∪ Vars(e2 )
[[;]]] L
[[Pos(e)]]] L
[[x = e;]]] L
[[x = M[e];]]] L
[[M[e1 ] = e2 ;]]] L
Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr :
Beispiel mit X = ∅:
x = y + 2;
1
Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr :
[[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]]
y = 5;
2
Beispiel mit X = ∅:
x = y + 2;
x = y + 2; M[y] = x;
3
4
= L
= [[Neg(e)]]] L = L ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= L ∪ Vars(e1 ) ∪ Vars(e2 )
5
1
[[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]]
y = 5;
2
x = y + 2; M[y] = x;
3
4
5
∅
384 / 398
Berechnung der liveness Information
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.
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
Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt:
= L
= [[Neg(e)]]] L = L ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= L ∪ Vars(e1 ) ∪ Vars(e2 )
[[;]]] L
[[Pos(e)]]] L
[[x = e;]]] L
[[x = M[e];]]] L
[[M[e1 ] = e2 ;]]] L
Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr :
]
Beispiel mit X = ∅:
x = y + 2;
1
]
[[π]] = [[k1 ]] ◦ . . . ◦ [[kr ]]
y = 5;
2
4
Beispiel mit X = ∅:
x = y + 2;
5
{x, y}
1
∅
[[;]]] L
[[Pos(e)]]] L
[[x = e;]]] L
[[x = M[e];]]] L
[[M[e1 ] = e2 ;]]] L
Mit [[k]]] definieren wir nun die Semantik [[π]]] von Pfaden π = k1 . . . kr :
Beispiel mit X = ∅:
x = y + 2;
1
y = 5;
2
∅
4
{y}
{x, y}
Beispiel mit X = ∅:
x = y + 2;
5
1
∅
5
{x, y}
∅
384 / 398
= 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 :
]
x = y + 2; M[y] = x;
3
4
{y}
Sei L = 2Vars . Für alle k = (_, s, _) , definiere [[k]]] = [[s]]] wie folgt:
= L
= [[Neg(e)]]] L = L ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= (L\{x}) ∪ Vars(e)
= L ∪ Vars(e1 ) ∪ Vars(e2 )
[[π]] = [[k1 ]] ◦ . . . ◦ [[kr ]]
x = y + 2; M[y] = x;
3
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:
]
y = 5;
2
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.
]
[[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]]
384 / 398
Berechnung der liveness Information
[[;]]] 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 :
]
x = y + 2; M[y] = x;
3
384 / 398
384 / 398
{y}
[[π]]] = [[k1 ]]] ◦ . . . ◦ [[kr ]]]
y = 5;
2
x = y + 2; M[y] = x;
3
∅
4
{y}
{x, y}
5
∅
384 / 398
Lebendige Variablen eines Programms
Lebendige Variablen eines Programms
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
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
385 / 398
Lebendige Variablen eines Programms
385 / 398
Lebendige Variablen eines Programms
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
Intuitiv:
Intuitiv:
die Menge der lebendigen Variablen am bei stop ist X
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
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
385 / 398
Lebendige Variablen eines Programms
385 / 398
Lebendige Variablen eines Programms
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
Intuitiv:
Intuitiv:
die Menge der lebendigen Variablen am bei stop ist X
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
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
es gibt unendlich viele Pfade von u nach stop
Berechnung der Mengen L∗ [u]:
Berechnung der Mengen L∗ [u]:
1
Stelle Gleichungssystem auf:
L[stop] ⊇ X
L[u]
⊇ [[k]]] (L[v])
385 / 398
k = (u, _, v) edge
385 / 398
Lebendige Variablen eines Programms
Beispiel der liveness Analyse
Beachte: Die Information fließt Rückwärts bei der liveness Analyse.
Die Menge der lebendigen Variablen bei u ist nun gegeben durch:
[
L∗ [u] =
{[[π]]] X | π : u →∗ stop}
Beispiel: Fakultätsfunktion
Intuitiv:
x = M [I ];
die Menge der lebendigen Variablen am bei stop ist X
1
führt ein Pfad von u nach stop auf dem x lebendig ist, so ist x bei
u lebendig
y = 1;
es gibt unendlich viele Pfade von u nach stop
Neg(x > 1)
Berechnung der Mengen L∗ [u]:
1
2
2
M [R] = y;
k = (u, _, v) edge
4
7
Berechnung der Gleichungen propagieren der Mengen von
rechts nach links. Da Var endlich ist, finden wir einen Fixpunkt.
⊇
⊇
⊇
⊇
⊇
⊇
⊇
⊇
(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}
∅
Pos(x > 1)
1
5
2
7
6
2
5
4
3
1
0
3
6
Stelle Gleichungssystem auf:
L[stop] ⊇ X
L[u]
⊇ [[k]]] (L[v])
L[0]
L[1]
L[2]
L[3]
L[4]
L[5]
L[6]
L[7]
0
y = x ∗ y;
x = x − 1;
386 / 398
385 / 398
Beispiel der liveness Analyse
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;
Neg(x > 1)
6
2
⊇ (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}
⊇ ∅
Pos(x > 1)
3
M [R] = y;
7
L[0]
L[1]
L[2]
L[3]
L[4]
L[5]
L[6]
L[7]
4
5
y = x ∗ y;
x = x − 1;
Beachte: Die Information fließt Rückwärts bei der liveness Analyse.
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}
Beispiel: Fakultätsfunktion
x = M [I ];
1
y = 1;
Neg(x > 1)
2
L[0]
L[1]
L[2]
L[3]
L[4]
L[5]
L[6]
L[7]
0
6
2
Pos(x > 1)
7
6
2
5
4
3
1
0
3
M [R] = y;
7
⊇
⊇
⊇
⊇
⊇
⊇
⊇
⊇
4
5
y = x ∗ y;
x = x − 1;
386 / 398
(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}
∅
1
∅
{y, R}
{x, y, R}
{x, y, R}
{x, y, R}
{x, y, R}
{x, R}
{I, R}
2
dito
386 / 398
Registerfärbung: Motivation
Übersetzung ganzer Programme
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;
}
Kapitel 6:
Registerfärbung
1
x = M[A];
2
y = x + 1;
Neg (y)
3
Pos (y)
4
6
5
7
t = −y · y;
z=x·x
M[A] = t;
M[A] = z;
8
387 / 398
388 / 398
Registerfärbung: Motivation
Registerfärbung: Motivation
Betrachte folgendes Program:
Betrachte folgendes Program:
0
0
read();
read();
x = M[A];
y = x + 1;
if (y) {
z = x · x;
M[A] = z;
} else {
t = −y · y;
M[A] = t;
}
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
4
Pos (y)
6
t = −y · y;
z=x·x
5
7
M[A] = t;
M[A] = z;
1
x = M[A];
2
y = x + 1;
Neg (y)
3
Pos (y)
4
6
5
7
t = −y · y;
z=x·x
M[A] = t;
8
M[A] = z;
8
Das Programm benutzt 5 Variablen (Register).
Das Programm benutzt 5 Variablen (Register).
Muss der Keller benutzt werden bei einer 4-Register Maschine?
388 / 398
Registerfärbung: Motivation
388 / 398
Registerfärbung: Motivation
Betrachte folgendes Program:
Betrachte folgendes Program:
0
0
read();
read();
x = M[A];
y = x + 1;
if (y) {
z = x · x;
M[A] = z;
} else {
t = −y · y;
M[A] = t;
}
read();
read();
R = M[A];
y = R + 1;
if (y) {
R = R · R;
M[A] = R;
} else {
R = −y · y;
M[A] = R;
}
1
x = M[A];
2
y = x + 1;
Neg (y)
3
4
Pos (y)
6
t = −y · y;
z=x·x
5
7
M[A] = t;
M[A] = z;
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;
8
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!
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!
388 / 398
Wann reicht ein Register?
388 / 398
Wann reicht ein Register?
Achtung: Mehrere Variablen können nur dann in einem Register
gespeichert werden, wenn sich deren Lebensbereich (live range)
nicht überlappen.
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
0
read();
1
x = M[A];
2
y = x + 1;
Neg (y)
3
4
Pos (y)
6
t = −y · y;
z=x·x
5
7
M[A] = t;
L[x] = {u | x ∈ L[u]}
0
8
7
6
5
4
3
2
1
0
read();
L
∅
{A, z}
{A, x}
{A, t}
{A, y}
{A, x, y}
{A, x}
{A}
∅
1
x = M[A];
2
y = x + 1;
Neg (y)
3
Pos (y)
4
6
5
7
t = −y · y;
M[A] = z;
z=x·x
M[A] = t;
8
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
389 / 398
389 / 398
Wann reicht ein Register?
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
2
y = x + 1;
3
Neg (y)
Pos (y)
4
y
t = −y · y;
6
5
7
t
z=x·x
M[A] = t;
z
L[x] = {u | x ∈ L[u]}
0
read();
L
∅
{A, z}
{A, x}
{A, t}
{A, y}
{A, x, y}
{A, x}
{A}
∅
8
7
6
5
4
3
2
1
0
x = M[A];
x
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
1
live ranges:
x = M[A];
2
x
M[A] = z;
Pos (y)
4
y
t = −y · y;
6
5
7
t
A
x
y
t
z
y = x + 1;
3
Neg (y)
z=x·x
M[A] = t;
{1, . . . , 7}
{2, 3, 6}
{3, 4}
{5}
{7}
z
M[A] = z;
8
8
389 / 398
Interferenz Graph
389 / 398
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= ∅}.
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
0
read();
read();
1
1
x = M[A];
x = M[A];
2
x
Neg (y)
2
Pos (y)
4
y
t = −y · y;
6
5
7
t
x
y = x + 1;
3
Neg (y)
z=x·x
M[A] = t;
z
Pos (y)
4
y
t = −y · y;
6
5
7
t
M[A] = z;
y = x + 1;
3
z=x·x
M[A] = t;
8
z
M[A] = z;
8
390 / 398
Interferenz Graph
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= ∅}.
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
390 / 398
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
x = M[A];
x
2
x
Neg (y)
4
y
t = −y · y;
t
t
Pos (y)
z
Neg (y)
6
5
7
y = x + 1;
3
4
y
t = −y · y;
z=x·x
M[A] = t;
y
x
2
x
y = x + 1;
3
A
read();
1
z
t
M[A] = z;
6
z=x·x
5
7
M[A] = t;
8
t
Pos (y)
z
M[A] = z;
z
Variablen, die nicht
verbunden sind, können
dem gleichen Register
zugewiesen werden
8
390 / 398
390 / 398
Interferenz Graph
Registerfärbung
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
x
2
x
Neg (y)
y = x + 1;
3
4
y
t = −y · y;
6
5
7
t
t
Pos (y)
z=x·x
M[A] = t;
z
M[A] = z;
8
z
Variablen, die nicht
verbunden sind, können
dem gleichen Register
zugewiesen werden
Gregory J. Chaitin,
Sviatoslav Sergeevich Lavrov,
University of Maine (1981)
Russian Academy of Sciences
(1962)
Farbe == Register
390 / 398
391 / 398
Registerfärbung: Problemstellung
Registerfärbung: Problemstellung
Gegeben: ungerichteter Graph (V, E).
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;
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!
392 / 398
392 / 398
Registerfärbung: Problemstellung
Registerfärbung: Problemstellung
Gegeben: ungerichteter Graph (V, E).
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!
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 Beispiel reichen drei Farben, aber:
Im Allgemeinen gibt es keine eindeutige minimale Färbung
392 / 398
392 / 398
Registerfärbung: Problemstellung
Registerfärbung: Problemstellung
Gegeben: ungerichteter Graph (V, E).
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!
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 Beispiel reichen drei Farben, aber:
Im Allgemeinen gibt es keine eindeutige minimale Färbung
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
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
392 / 398
Lösungsweg Heuristik
392 / 398
Lösungsweg Heuristik
Idee: benutze eine greedy Heuristik:
Idee: benutze eine greedy Heuristik:
starte irgendwo mit Farbe 1
393 / 398
Lösungsweg Heuristik
393 / 398
Lösungsweg Heuristik
Idee: benutze eine greedy Heuristik:
Idee: benutze eine greedy Heuristik:
starte irgendwo mit Farbe 1
starte irgendwo mit Farbe 1
wähle die Farbe mit der kleinsten Nummer die ungleich aller
benachbarten Knoten ist, die schon gefärbt sind
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
393 / 398
393 / 398
Lösungsweg Heuristik
Lösungsweg Heuristik
Idee: benutze eine greedy Heuristik:
Idee: benutze eine greedy Heuristik:
starte irgendwo mit Farbe 1
starte irgendwo mit Farbe 1
wähle die Farbe mit der kleinsten Nummer die ungleich aller
benachbarten Knoten ist, die schon gefärbt sind
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
hat ein Knoten eine Farbe, färbe alle Nachbarn, die noch keine
Farbe haben
betrachte alle Konten nacheinander
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
393 / 398
Diskussion der greedy Heuristik
393 / 398
Diskussion der greedy Heuristik
der Algorithmus ist im Prinzip eine pre-order Tiefensuche
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
394 / 398
Diskussion der greedy Heuristik
394 / 398
Diskussion der greedy Heuristik
der Algorithmus ist im Prinzip eine pre-order Tiefensuche
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
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
theoretisch kann das Resultat beliebig schlechter als die
optimale Lösung sein
in der Praxis ist der Algorithmus aber nicht zu schlecht
394 / 398
394 / 398
Diskussion der greedy Heuristik
Diskussion der greedy Heuristik
der Algorithmus ist im Prinzip eine pre-order Tiefensuche
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
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
theoretisch kann das Resultat beliebig schlechter als die
optimale Lösung sein
in der Praxis ist der Algorithmus aber nicht zu schlecht
in der Praxis ist der Algorithmus aber nicht zu schlecht
verschiedene Strategien wurden sogar patentiert
verschiedene Strategien wurden sogar patentiert
Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein
sind!
394 / 398
Diskussion der greedy Heuristik
394 / 398
Diskussion der greedy Heuristik
der Algorithmus ist im Prinzip eine pre-order Tiefensuche
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
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
theoretisch kann das Resultat beliebig schlechter als die
optimale Lösung sein
in der Praxis ist der Algorithmus aber nicht zu schlecht
in der Praxis ist der Algorithmus aber nicht zu schlecht
verschiedene Strategien wurden sogar patentiert
verschiedene Strategien wurden sogar patentiert
Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein
sind!
Der Algorithmus funktioniert besser, wenn die Lebensbereiche klein
sind!
Idee: Aufteilung der Lebensbereiche (live range splitting)
Idee: Aufteilung der Lebensbereiche (live range splitting)
Ausfürung: betrachte zunnächst nur die Aufteilung innheralb von
Basisblöcken
394 / 398
394 / 398
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
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;
395 / 398
L
x, y, z
x, z
x
x
x, z
x, z, t
x, z, t
x, t
y, t
x
z
y
t
395 / 398
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
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;
x
z
Aufteilung von Lebensbereichen in Basisblöcken
y
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.
395 / 398
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
395 / 398
Aufteilung von Lebensbereichen in Basisblöcken
Beispiel: Betrachte die Registerfärbung des folgenden Blocks:
x
z
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;
y
x1
z1
y1
t
Die Lebensbereiche von x und z können geteilt werden.
L
x, y, z
x, z
x
x1
x1 , z1
x1 , z1 , t
x1 , z1 , t
x1 , t
y1 , t
x
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.
395 / 398
Betrachtung von Lebendigkeitsbereichen
395 / 398
Betrachtung von Lebendigkeitsbereichen
Weiterführende Beobachtungen auf Basisblöcken:
Weiterführende Beobachtungen auf Basisblöcken:
Die Interferenzgraphen auf Basisblöcken können auch als
Intervalgraphen dargestellt werden:
396 / 398
396 / 398
Betrachtung von Lebendigkeitsbereichen
Betrachtung von Lebendigkeitsbereichen
Weiterführende Beobachtungen auf Basisblöcken:
Die Interferenzgraphen auf Basisblöcken können auch als
Intervalgraphen dargestellt werden:
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
Größe der maximalen Clique entspricht Anzahl benötigter Farben
Minimale Färbung kann in polynomineller Zeit gefunden werden
396 / 398
Betrachtung von Lebendigkeitsbereichen
396 / 398
Betrachtung von Lebendigkeitsbereichen
Weiterführende Beobachtungen auf Basisblöcken:
Die Interferenzgraphen auf Basisblöcken können auch als
Intervalgraphen dargestellt werden:
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:
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
396 / 398
Betrachtung von Lebendigkeitsbereichen
396 / 398
Betrachtung von Lebendigkeitsbereichen
Weiterführende Beobachtungen auf Basisblöcken:
Die Interferenzgraphen auf Basisblöcken können auch als
Intervalgraphen dargestellt werden:
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
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)
396 / 398
396 / 398
Registerfärbung in der Fakultätsfunktion
Registerfärbung in der Fakultätsfunktion
int fac(int x) {
if (x<=0) then
return 1;
else
return x*fac(x-1);
}
int fac(int x) {
if (x<=0) then
return 1;
else
return x*fac(x-1);
}
Betrachte: def-use
_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
_A:
−1 0 1 2 3 4
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
Betrachte: def-use liveness
−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
_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
_A:
−1 0 1 2 3 4
>
⊥
>
|
|
|
|
|
|
|
|
>
⊥
>
|
|
>
⊥ ⊥
>
⊥
>
⊥
_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
|
|
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
⊥
|
|
|
>
|
|
⊥
>
⊥
_A:
−1 0 1 2 3 4
>
⊥
>
|
> |
|
|
>
⊥ ⊥
>
⊥
>
⊥
>
⊥
⊥
>
|
| >
⊥ ⊥
>
⊥
397 / 398
Unterteilung der Lebensbereiche verbesserungsfähig:
int fac(int x) {
if (x<=0) then
return 1;
else
return x*fac(x-1);
}
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
>
|
| >
⊥ ⊥
>
⊥
Ausblick
Betrachte: def-use liveness coloring
_fac:
|
|
|
|
|
|
|
|
|
|
>
⊥ ⊥ |
397 / 398
Registerfärbung in der Fakultätsfunktion
>
|
|
|
|
|
|
_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
−1 0 1 2 3 4
>
|
|
>
⊥ ⊥
>
⊥
>
|
| >
|
|
|
|
|
|
|
|
⊥ ⊥
>
|
|
⊥
>
⊥
>
⊥
|
|
|
|
|
|
|
|
|
|
|
|
Betrachte den ganzen CFG einer Funktion
>
|
|
|
|
|
|
|
|
|
|
⊥ ⊥
>
|
|
⊥
>
⊥
397 / 398
Ausblick
398 / 398
Ausblick
Unterteilung der Lebensbereiche verbesserungsfähig:
Unterteilung der Lebensbereiche verbesserungsfähig:
Betrachte den ganzen CFG einer Funktion
Betrachte den ganzen CFG einer Funktion
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
übersetze Funktionen in eine single static assignment Form
398 / 398
398 / 398
Ausblick
Ausblick
Unterteilung der Lebensbereiche verbesserungsfähig:
Unterteilung der Lebensbereiche verbesserungsfähig:
Betrachte den ganzen CFG einer Funktion
Betrachte den ganzen CFG einer Funktion
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
übersetze Funktionen in eine single static assignment Form
übersetze Funktionen in eine single static assignment Form
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
; Vorlesung Programmoptimierung
398 / 398
Ausblick
398 / 398
Ausblick
Unterteilung der Lebensbereiche verbesserungsfähig:
Unterteilung der Lebensbereiche verbesserungsfähig:
Betrachte den ganzen CFG einer Funktion
Betrachte den ganzen CFG einer Funktion
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
übersetze Funktionen in eine single static assignment Form
übersetze Funktionen in eine single static assignment Form
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
die Regeln für die liveness-Analyse können verbessert werden
398 / 398
Ausblick
398 / 398
Ausblick
Unterteilung der Lebensbereiche verbesserungsfähig:
Unterteilung der Lebensbereiche verbesserungsfähig:
Betrachte den ganzen CFG einer Funktion
Betrachte den ganzen CFG einer Funktion
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
übersetze Funktionen in eine single static assignment Form
übersetze Funktionen in eine single static assignment Form
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
die Regeln für die liveness-Analyse können verbessert werden
die Regeln für die liveness-Analyse können verbessert werden
saveloc hält Register unnötig am Leben ; Zwischensprache
saveloc hält Register unnötig am Leben ; Zwischensprache
; Vorlesung Programmoptimierung
398 / 398
398 / 398
Ausblick
Ausblick
Unterteilung der Lebensbereiche verbesserungsfähig:
Unterteilung der Lebensbereiche verbesserungsfähig:
Betrachte den ganzen CFG einer Funktion
Betrachte den ganzen CFG einer Funktion
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
Problem: verschiedene Definitionen fliessen in den gleich
Basisblock
übersetze Funktionen in eine single static assignment Form
übersetze Funktionen in eine single static assignment Form
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
optimale Färbung möglich (allerdings müssen evtl. Register
getauscht werden)
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
; Vorlesung Programmoptimierung
liveness-Analyse verbesserungsfähig:
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
nach x ← y + 1 ist x nur lebendig wenn y lebendig ist
die Regeln für die liveness-Analyse können verbessert werden
die Regeln für die liveness-Analyse können verbessert werden
saveloc hält Register unnötig am Leben ; Zwischensprache
saveloc hält Register unnötig am Leben ; Zwischensprache
; Vorlesung Programmoptimierung
Wie berechnet man die liveness Mengen zügig?
; Vorlesung Programmoptimierung
Wie berechnet man die liveness Mengen zügig?
; Vorlesung Programmoptimierung
398 / 398
398 / 398
Herunterladen