Compilerbau I

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