Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

📚 The CoCalc Library - books, templates and other resources

132928 views
License: OTHER
1
%!TEX root = Programmierparadigmen.tex
2
\chapter{Compilerbau}
3
\index{Compilerbau|(}
4
5
Wenn man über Compiler redet, meint man üblicherweise \enquote{vollständige Übersetzer}:
6
7
\begin{definition}\xindex{Compiler}%
8
Ein \textbf{Compiler} ist ein Programm $C$, das den Quelltext eines Programms
9
$A$ in eine ausführbare Form übersetzen kann.
10
\end{definition}
11
12
Jedoch gibt es verschiedene Ebenen der Interpretation bzw. Übersetzung:
13
\begin{enumerate}
14
\item \textbf{Reiner Interpretierer}: TCL, Unix-Shell
15
\item \textbf{Vorübersetzung}: Java-Bytecode, Pascal P-Code, Python\footnote{Python hat auch \texttt{.pyc}-Dateien, die Python-Bytecode enthalten.}, Smalltalk-Bytecode
16
\item \textbf{Laufzeitübersetzung}: JavaScript\footnote{JavaScript wird nicht immer zur Laufzeit übersetzt. Früher war es üblich, dass JavaScript nur interpretiert wurde.}
17
\item \textbf{Vollständige Übersetzung}: C, C++, Fortran
18
\end{enumerate}
19
20
Zu sagen, dass Python eine interpretierte Sprache ist, ist in etwa so korrekt
21
wie zu sagen, dass die Bibel ein Hardcover-Buch ist.\footnote{Quelle: stackoverflow.com/a/2998544, danke Alex Martelli für diesen Vergleich.}
22
23
Reine Interpretierer lesen den Quelltext Anweisung für Anweisung und führen
24
diese direkt aus.
25
26
\todo[inline]{Bild}
27
28
Bei der \textit{Interpretation nach Vorübersetzung} wird der Quelltext analysiert
29
und in eine für den Interpretierer günstigere Form übersetzt. Das kann z.~B.
30
durch
31
\begin{itemize}
32
\item Zuordnung Bezeichnergebrauch - Vereinbarung\todo{?}
33
\item Transformation in Postfixbaum
34
\item Typcheck, wo statisch möglich
35
\end{itemize}
36
geschehen. Diese Vorübersetzung ist nicht unbedingt maschinennah.
37
38
\todo[inline]{Bild}
39
40
Die \textit{Just-in-time-Compiler}\xindex{Compiler!Just-in-time}\index{JIT|see{Just-in-time Compiler}} (kurz: JIT-Compiler) betreiben
41
Laufzeitübersetzung. Folgendes sind Vor- bzw. Nachteile von Just-in-time Compilern:
42
\begin{itemize}
43
\item schneller als reine Interpretierer
44
\item Speichergewinn: Quelle kompakter als Zielprogramm (vgl. \cref{bsp:code-kompaktheit})
45
\item Schnellerer Start des Programms
46
\item Langsamer (pro Funktion) als vollständige Übersetzung
47
\item kann dynamisch ermittelte Laufzeiteigenschaften berücksichtigen (dynamische Optimierung)
48
\end{itemize}
49
50
\begin{beispiel}[Code-Kompaktheit]\label{bsp:code-kompaktheit}%
51
Man betrachte folgende Codestücke:
52
53
\inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=Hello.java]{java}{scripts/java/Hello.java}
54
\inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=hello-world.c]{c}{scripts/c/hello-world.c}
55
56
Nun zum Größenvergleich:
57
58
\begin{itemize}
59
\item Der C-Code ist 83 Byte groß,
60
\item der Java-Codee ist 123 Byte groß,
61
\item der generierte Java Bytecode ist 416 Byte groß und
62
\item der erzeugt Maschinencode aus C ist 8565 Byte groß.
63
\end{itemize}
64
\end{beispiel}
65
66
Moderne virtuelle Maschinen für Java und für .NET nutzen JIT-Compiler.
67
68
Bei der \textit{vollständigen Übersetzung} wird der Quelltext vor der ersten
69
Ausführung des Programms $A$ in Maschinencode (z.~B. x86, SPARC) übersetzt.
70
71
\todo[inline]{Bild}
72
73
\section{Funktionsweise}
74
Üblicherweise führt ein Compiler folgende Schritte aus:
75
\begin{enumerate}
76
\item Lexikalische Analyse
77
\item Syntaktische Analyse
78
\item Semantische Analyse
79
\item Zwischencodeoptimierung
80
\item Codegenerierung
81
\item Assemblieren und Binden
82
\end{enumerate}
83
84
\section{Lexikalische Analyse}\xindex{Analyse!lexikalische}%
85
In der lexikalischen Analyse wird der Quelltext als Sequenz von Zeichen betrachtet.
86
Sie soll bedeutungstragende Zeichengruppen, sog. \textit{Tokens}\xindex{Token},
87
erkennen und unwichtige Zeichen, wie z.~B. Kommentare überspringen. Außerdem
88
sollen Bezeichner identifiziert und in einer \textit{Stringtabelle}\xindex{Stringtabelle}
89
zusammengefasst werden.
90
91
\begin{beispiel}
92
\todo[inline]{Beispiel erstellen}
93
\end{beispiel}
94
95
\subsection{Reguläre Ausdrücke}\xindex{Ausdrücke!reguläre}
96
\begin{beispiel}[Regulärere Ausdrücke]
97
Folgender regulärer Ausdruck erkennt Float-Konstanten in C nach
98
ISO/IEC 9899:1999 §6.4.4.2:
99
100
$((0|\dots|9)^*.(0|\dots|9)^+)|((0|\dots|9)^+.)$
101
\end{beispiel}
102
103
\begin{satz}
104
Jede reguläre Sprache wird von einem (deterministischen) endlichen
105
Automaten akzeptiert.
106
\end{satz}
107
108
TODO: Bild einfügen
109
110
Zu jedem regulären Ausdruck im Sinne der theoretischen Informatik kann ein
111
nichtdeterministischer Automat generiert werden. Dieser kann mittels
112
Potenzmengenkonstruktion\footnote{\url{http://martin-thoma.com/potenzmengenkonstruktion/}}
113
in einen deterministischen Automaten überführen. Dieser kann dann mittels
114
Äquivalenzklassen minimiert werden.
115
116
\todo[inline]{Alle Schritte beschreiben}
117
118
\subsection{Lex}\index{Lex|(}\index{Flex|see{Lex}}
119
Lex ist ein Programm, das beim Übersetzerbau benutzt wird um Tokenizer für die
120
lexikalische Analyse zu erstellen. Flex ist eine Open-Source Variante davon.
121
122
Eine Flex-Datei besteht aus 3 Teilen, die durch \texttt{\%\%} getrennt werden:
123
124
\begin{verbatim}
125
Definitionen: Definiere Namen
126
%%
127
Regeln: Definiere reguläre Ausdrücke und
128
zugehörige Aktionen (= Code)
129
%%
130
Code: zusätzlicher Code
131
\end{verbatim}
132
133
\subsubsection{Reguläre Ausdrücke in Flex}
134
\begin{table}
135
\begin{tabular}{ll}
136
x & Zeichen 'x' erkennen \\
137
"xy" & Zeichenkette 'xy' erkennen \\
138
\textbackslash & Zeichen 'x' erkennen (TODO) \\
139
$[xyz]$ & Zeichen $x$, $y$ oder $z$ erkennen \\
140
$[a-z]$ & Alle Kleinbuchstaben erkennen \\
141
$[\^a-z]$ & Alle Zeichen außer Kleinbuchstaben erkennen \\
142
$x|y$ & $x$ oder $y$ erkennen \\
143
(x) & x erkennen \\
144
x* & 0, 1 oder mehrere Vorkommen von x erkennen \\
145
x+ & 1 oder mehrere Vorkommen von x erkennen \\
146
x? & 0 oder 1 Vorkommen von x erkennen \\
147
\{Name\} & Expansion der Definition Name \\
148
\textbackslash t, \textbackslash n, \textbackslash rq & Tabulator, Zeilenumbruch, Wagenrücklauf erkennen \\
149
\end{tabular}
150
\end{table}
151
152
\index{Lex|)}
153
154
155
\section{Syntaktische Analyse}\xindex{Analyse!syntaktische}%
156
In der syntaktischen Analyse wird überprüft, ob die Tokenfolge zur
157
kontextfreien Sprache\todo{Warum kontextfrei?} gehört. Außerdem soll die
158
hierarchische Struktur der Eingabe erkannt werden.\todo{Was ist gemeint?}
159
160
Ausgegeben wird ein \textbf{abstrakter Syntaxbaum}\xindex{Syntaxbaum!abstrakter}.
161
162
\begin{beispiel}[Abstrakter Syntaxbaum]
163
TODO
164
\end{beispiel}
165
166
\subsection{Abstrakte Syntax}\xindex{Syntax!abstrakte}%
167
\begin{beispiel}[Abstrakte Syntax für reguläre Ausdrücke\footnotemark]
168
Die Grammatik $G = (\Set{\text{\texttt{char}}, (, ), \cup, \cdot, *, \varepsilon}, \Set{R}, P, R)$ mit den Produktionen
169
\[R \rightarrow \text{\texttt{char}} | \varepsilon | ( R \cup R ) | (R \cdot R) | (R)^*\]
170
erzeugt einfache reguläre Ausdrücke.
171
172
Die zugehörige abstrakte Syntax ist
173
174
\begin{align*}
175
\text{RegExp} &= \text{Char } | \text{ Epsilon } | \text{ Union } | \text{ Concatenation } | \text{ KleeneClosure }\\
176
\text{Union} &:: \text{RegExp RegExp}\\
177
\text{Concatenation} &:: \text{RegExp RegExp}\\
178
\text{KleeneClosure} &:: \text{RegExp}\\
179
\text{Char} &= \text{\texttt{char}}\\
180
\text{Epsilon} &= \varepsilon\\
181
\end{align*}
182
\end{beispiel}
183
\footnotetext{Klausur vom SS2012}
184
185
\begin{beispiel}[Abstrakte Syntax für reguläre Ausdrücke\footnotemark]
186
Die Grammatik $G = (\Set{\text{\texttt{char}}, (, ), \cup, \cdot, *, \varepsilon}, \Set{R}, P, R)$ mit den Produktionen
187
\[R \rightarrow \text{\texttt{char}} | \varepsilon | ( R \cup R ) | (R \cdot R) | (R)^*\]
188
erzeugt einfache reguläre Ausdrücke.
189
190
Die zugehörige abstrakte Syntax ist
191
192
\begin{align*}
193
\text{RegExp} &= \text{Char } | \text{ Epsilon } | \text{ Union } | \text{ Concatenation } | \text{ KleeneClosure }\\
194
\text{Union} &:: \text{RegExp RegExp}\\
195
\text{Concatenation} &:: \text{RegExp RegExp}\\
196
\text{KleeneClosure} &:: \text{RegExp}\\
197
\text{Char} &= \text{\texttt{char}}\\
198
\text{Epsilon} &= \varepsilon\\
199
\end{align*}
200
\end{beispiel}
201
\footnotetext{Klausur vom SS2012}
202
203
\section{Semantische Analyse}\xindex{Analyse!semantische}%
204
Die semantische Analyse arbeitet auf einem abstrakten Syntaxbaum und generiert
205
einen attributierten Syntaxbaum\xindex{Syntaxbaum!attributeriter}.
206
207
Sie führt eine kontextsensitive Analyse durch. Dazu gehören:
208
\begin{itemize}
209
\item \textbf{Namensanalyse}: Beziehung zwischen Deklaration und Verwendung\todo{?}
210
\item \textbf{Typanalyse}: Bestimme und prüfe Typen von Variablen, Funktionen, \dots \todo{?}
211
\item \textbf{Konsistenzprüfung}: Wurden alle Einschränkungen der Programmiersprache eingehalten?\todo{?}
212
\end{itemize}
213
214
\begin{beispiel}[Attributeriter Syntaxbaum]
215
TODO
216
\end{beispiel}
217
218
\section{Zwischencodeoptimierung}
219
Hier wird der Code in eine sprach- und zielunabhängige Zwischensprache transformiert.
220
Dabei sind viele Optimierungen vorstellbar. Ein paar davon sind:
221
\begin{itemize}
222
\item \textbf{Konstantenfaltung}: Ersetze z.~B. $3+5$ durch $8$.
223
\item \textbf{Kopienfortschaffung}: Setze Werte von Variablen direkt ein
224
\item \textbf{Code verschieben}: Führe Befehle vor der Schleife aus, statt in der Schleife \todo{?}
225
\item \textbf{Gemeinsame Teilausdrücke entfernen}: Es sollen doppelte Berechnungen vermieden werden \todo{Beispiel?}
226
\item \textbf{Inlining}: Statt Methode aufzurufen, kann der Code der Methode an der Aufrufstelle eingebaut werden.
227
\end{itemize}
228
229
\section{Codegenerierung}
230
Der letzte Schritt besteht darin, aus dem generiertem Zwischencode den
231
Maschinencode oder Assembler zu erstellen. Dabei muss folgendes beachtet werden:
232
\begin{itemize}
233
\item \textbf{Konventionen}: Wie werden z.~B. im Laufzeitsystem Methoden aufgerufen?
234
\item \textbf{Codeauswahl}: Welche Befehle kennt das Zielsystem?
235
\item \textbf{Scheduling}: In welcher Reihenfolge sollen die Befehle angeordnet werden?
236
\item \textbf{Registerallokation}: Welche Zwischenergebnisse sollen in welchen Prozessorregistern gehalten werden?
237
\item \textbf{Nachoptimierung}\todo{?}
238
\end{itemize}
239
240
\section{Weiteres}
241
\subsection{First- und Follow}
242
\begin{definition}[$k$-Anfang]\xindex{k-Anfang@$k$-Anfang}\index{Anfang|see{$k$-Anfang}}\xindex{\# (Compilerbau)}%
243
Sei $G = (\Sigma, V, P, S)$ eine Grammatik, $k \in \mdn_{> 0}$ und
244
$x \in \Sigma^*$ mit
245
\[x = x_1 \dots x_m \text{ mit } x_i \in \Sigma \text{ wobei } i \in 1, \dots, m\]
246
247
Dann heißt $\tilde{x} \in (\Sigma \cup \Set{\#})^+$ ein $k$-\textbf{Anfang} von $x$,
248
wenn gilt:
249
\[\tilde{x} =
250
\begin{cases}
251
x\# &\text{falls } x = x_1 \dots x_m \text{ und } m < k\\
252
x_1 \dots x_k &\text{sonst}
253
\end{cases}\]
254
wobei $\#$ das Ende der Eingabe bezeichnet. In diesem Fall schreibt man
255
\[ \tilde{x} = k\ :\ x\]
256
\end{definition}
257
258
\begin{beispiel}[$k$-Anfang]
259
Sei $G = (\Set{A, B, C, S}, \Set{a, b, c}, P, S)$ mit
260
\begin{align*}
261
P = \{ &A \rightarrow aa | ab, \\
262
&B \rightarrow AC,\\
263
&C \rightarrow c,\\
264
&S \rightarrow ABC\}
265
\end{align*}
266
267
Dann gilt:
268
\begin{multicols}{2}
269
\begin{bspenum}
270
\item $a = 1 : aaaa$
271
\item $a = 1 : a$
272
\item $a = 1 : aba$
273
\item $ab = 2 : aba$
274
\item $aba = 3 : aba$
275
\item $aba\# = 4 : aba$
276
\end{bspenum}
277
\end{multicols}
278
\end{beispiel}
279
280
\begin{definition}[First- und Follow-Menge]\xindex{Firstkx@$\text{First}_k(x)$}\xindex{Followkx@$\text{Follow}_k(x)$}%
281
Sei $G = (\Sigma, V, P, S)$ eine Grammatik und $x \in (V \cup \Sigma)$.
282
283
\begin{defenum}
284
\item $\begin{aligned}[t]\First_k (x) := \{u \in \Sigma^+ | \exists &y \in \Sigma^*:\\
285
&x \Rightarrow^* y\\
286
\land &u = k : y \}\end{aligned}$
287
\item $\First(x) := \First_1(x)$
288
\item $\begin{aligned}[t]\Follow_k(x) := \{u \in \Sigma^+ | \exists &m, y \in (V \cup \Sigma)^* :\\
289
&S \Rightarrow^* mxy\\
290
\land &u \in \First_k(y)\}\end{aligned}$
291
\item $\Follow(x) := \Follow_1(x)$
292
\end{defenum}
293
\end{definition}
294
295
Die $\First_k(x)$-Menge beinhaltet also alle Terminalfolgen, die entweder $k$
296
Terminale lang sind oder kürzer sind und dafür mit \# enden und die zugleich
297
der Anfang von Ableitungen von $x$ sind.
298
299
Die $\Follow_k(x)$-Menge hingegen hat alle Terminalfolgen der Länge $k$ oder kürzer
300
und dafür mit \# am Ende, die aus einer Ableitung des Startsymbols $S \Rightarrow^* mxy$
301
auf die Teilfolge $x$ folgen können.
302
303
Mit der $\Follow_k(x)$-Menge kann man also zu jedem Zeitpunkt sagen, was momentan
304
folgen darf. Wenn der Parser etwas anderes liest, ist ein Fehler passiert.
305
306
Da jede Terminalfolge, die sich aus $S$ folgern lässt mit \# endet, gilt immer:
307
\[\# \in \Follow_k(x)\]
308
309
\begin{beispiel}[First- und Follow-Mengen\footnotemark]
310
Sei $G = (\Sigma, V, P, E)$ mit
311
\begin{align*}
312
\Sigma &= \Set{+, *, (, )}\\
313
V &= \Set{T, T', E, E', F}\\
314
P &= \{ E \rightarrow T E'\\
315
&\hphantom{= \{ } E' \rightarrow \varepsilon | +TE'\\
316
&\hphantom{= \{ } T \rightarrow FT'\\
317
&\hphantom{= \{ } T' \rightarrow \varepsilon | *FT'\\
318
&\hphantom{= \{ } F \rightarrow \id | (E)\}
319
\end{align*}
320
321
Dann gilt:
322
\begin{bspenum}
323
\item $\First(E) = \First(T) = \First(F) = \Set{\id, (\ )}$
324
\item $\First(E') = \Set{\#, +}$
325
\item $\First(T') = \Set{\#, *}$
326
\item $\Follow(E) = \Follow(E') = \Set{\#, )}$
327
\item $\Follow(T) = \Follow(T') = \Set{\#, ), +}$
328
\item $\Follow(F) = \Set{\#, ), +, *}$
329
\end{bspenum}
330
\end{beispiel}
331
\footnotetext{Folie 348}
332
333
\section{Literatur}
334
Ich kann das folgende Buch empfehlen:
335
336
\textit{Compiler - Prinzipien, Techniken und Werkzeuge}. Alfred V. Aho, Monica S. Lam,
337
Ravi Sethi und Jeffry D. Ullman. Pearson Verlag, 2. Auflage, 2008. ISBN 978-3-8273-7097-6.
338
339
Es ist mit über 1200 Seiten zwar etwas dick, aber dafür sehr einfach geschrieben.
340
341
\index{Compilerbau|)}
342