Compilerbau I

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