mirror of
https://github.com/MartinThoma/LaTeX-examples.git
synced 2025-04-25 06:18:05 +02:00
268 lines
No EOL
11 KiB
TeX
268 lines
No EOL
11 KiB
TeX
%!TEX root = Programmierparadigmen.tex
|
|
\chapter{Compilerbau}
|
|
\index{Compilerbau|(}
|
|
|
|
Wenn man über Compiler redet, meint man üblicherweise \enquote{vollständige Übersetzer}:
|
|
|
|
\begin{definition}\xindex{Compiler}%
|
|
Ein \textbf{Compiler} ist ein Programm $C$, das den Quelltext eines Programms
|
|
$A$ in eine ausführbare Form übersetzen kann.
|
|
\end{definition}
|
|
|
|
Jedoch gibt es verschiedene Ebenen der Interpretation bzw. Übersetzung:
|
|
\begin{enumerate}
|
|
\item \textbf{Reiner Interpretierer}: TCL, Unix-Shell
|
|
\item \textbf{Vorübersetzung}: Java-Bytecode, Pascal P-Code, Python\footnote{Python hat auch \texttt{.pyc}-Dateien, die Python-Bytecode enthalten.}, Smalltalk-Bytecode
|
|
\item \textbf{Laufzeitübersetzung}: JavaScript\footnote{JavaScript wird nicht immer zur Laufzeit übersetzt. Früher war es üblich, dass JavaScript nur interpretiert wurde.}
|
|
\item \textbf{Vollständige Übersetzung}: C, C++, Fortran
|
|
\end{enumerate}
|
|
|
|
Zu sagen, dass Python eine interpretierte Sprache ist, ist in etwa so korrekt
|
|
wie zu sagen, dass die Bibel ein Hardcover-Buch ist.\footnote{Quelle: stackoverflow.com/a/2998544, danke Alex Martelli für diesen Vergleich.}
|
|
|
|
Reine Interpretierer lesen den Quelltext Anweisung für Anweisung und führen
|
|
diese direkt aus.
|
|
|
|
\todo[inline]{Bild}
|
|
|
|
Bei der \textit{Interpretation nach Vorübersetzung} wird der Quelltext analysiert
|
|
und in eine für den Interpretierer günstigere Form übersetzt. Das kann z.~B.
|
|
durch
|
|
\begin{itemize}
|
|
\item Zuordnung Bezeichnergebrauch - Vereinbarung\todo{?}
|
|
\item Transformation in Postfixbaum
|
|
\item Typcheck, wo statisch möglich
|
|
\end{itemize}
|
|
geschehen. Diese Vorübersetzung ist nicht unbedingt maschinennah.
|
|
|
|
\todo[inline]{Bild}
|
|
|
|
Die \textit{Just-in-time-Compiler}\xindex{Compiler!Just-in-time}\index{JIT|see{Just-in-time Compiler}} (kurz: JIT-Compiler) betreiben
|
|
Laufzeitübersetzung. Folgendes sind Vor- bzw. Nachteile von Just-in-time Compilern:
|
|
\begin{itemize}
|
|
\item schneller als reine Interpretierer
|
|
\item Speichergewinn: Quelle kompakter als Zielprogramm\todo{Was ist hier gemeint?}
|
|
\item Schnellerer Start des Programms
|
|
\item Langsamer (pro Funktion) als vollständige Übersetzung
|
|
\item kann dynamisch ermittelte Laufzeiteigenschaften berücksichtigen (dynamische Optimierung)
|
|
\end{itemize}
|
|
|
|
Moderne virtuelle Maschinen für Java und für .NET nutzen JIT-Compiler.
|
|
|
|
Bei der \textit{vollständigen Übersetzung} wird der Quelltext vor der ersten
|
|
Ausführung des Programms $A$ in Maschinencode (z.~B. x86, SPARC) übersetzt.
|
|
|
|
\todo[inline]{Bild}
|
|
|
|
\section{Funktionsweise}
|
|
Üblicherweise führt ein Compiler folgende Schritte aus:
|
|
\begin{enumerate}
|
|
\item Lexikalische Analyse
|
|
\item Syntaktische Analyse
|
|
\item Semantische Analyse
|
|
\item Zwischencodeoptimierung
|
|
\item Codegenerierung
|
|
\item Assemblieren und Binden
|
|
\end{enumerate}
|
|
|
|
\section{Lexikalische Analyse}\xindex{Analyse!lexikalische}%
|
|
In der lexikalischen Analyse wird der Quelltext als Sequenz von Zeichen betrachtet.
|
|
Sie soll bedeutungstragende Zeichengruppen, sog. \textit{Tokens}\xindex{Token},
|
|
erkennen und unwichtige Zeichen, wie z.~B. Kommentare überspringen. Außerdem
|
|
sollen Bezeichner identifiziert und in einer \textit{Stringtabelle}\xindex{Stringtabelle}
|
|
zusammengefasst werden.
|
|
|
|
\begin{beispiel}
|
|
\todo[inline]{Beispiel erstellen}
|
|
\end{beispiel}
|
|
|
|
\subsection{Reguläre Ausdrücke}\xindex{Ausdrücke!reguläre}
|
|
\begin{beispiel}[Regulärere Ausdrücke]
|
|
Folgender regulärer Ausdruck erkennt Float-Konstanten in C nach
|
|
ISO/IEC 9899:1999 §6.4.4.2:
|
|
|
|
$((0|\dots|9)^*.(0|\dots|9)^+)|((0|\dots|9)^+.)$
|
|
\end{beispiel}
|
|
|
|
\begin{satz}
|
|
Jede reguläre Sprache wird von einem (deterministischen) endlichen
|
|
Automaten akzeptiert.
|
|
\end{satz}
|
|
|
|
TODO: Bild einfügen
|
|
|
|
Zu jedem regulären Ausdruck im Sinne der theoretischen Informatik kann ein
|
|
nichtdeterministischer Automat generiert werden. Dieser kann mittels
|
|
Potenzmengenkonstruktion\footnote{\url{http://martin-thoma.com/potenzmengenkonstruktion/}}
|
|
in einen deterministischen Automaten überführen. Dieser kann dann mittels
|
|
Äquivalenzklassen minimiert werden.
|
|
|
|
\todo[inline]{Alle Schritte beschreiben}
|
|
|
|
\subsection{Lex}\index{Lex|(}\index{Flex|see{Lex}}
|
|
Lex ist ein Programm, das beim Übersetzerbau benutzt wird um Tokenizer für die
|
|
lexikalische Analyse zu erstellen. Flex ist eine Open-Source Variante davon.
|
|
|
|
Eine Flex-Datei besteht aus 3 Teilen, die durch \texttt{\%\%} getrennt werden:
|
|
|
|
\begin{verbatim}
|
|
Definitionen: Definiere Namen
|
|
%%
|
|
Regeln: Definiere reguläre Ausdrücke und
|
|
zugehörige Aktionen (= Code)
|
|
%%
|
|
Code: zusätzlicher Code
|
|
\end{verbatim}
|
|
|
|
\subsubsection{Reguläre Ausdrücke in Flex}
|
|
\begin{table}
|
|
\begin{tabular}{ll}
|
|
x & Zeichen 'x' erkennen \\
|
|
"xy" & Zeichenkette 'xy' erkennen \\
|
|
\textbackslash & Zeichen 'x' erkennen (TODO) \\
|
|
$[xyz]$ & Zeichen $x$, $y$ oder $z$ erkennen \\
|
|
$[a-z]$ & Alle Kleinbuchstaben erkennen \\
|
|
$[\^a-z]$ & Alle Zeichen außer Kleinbuchstaben erkennen \\
|
|
$x|y$ & $x$ oder $y$ erkennen \\
|
|
(x) & x erkennen \\
|
|
x* & 0, 1 oder mehrere Vorkommen von x erkennen \\
|
|
x+ & 1 oder mehrere Vorkommen von x erkennen \\
|
|
x? & 0 oder 1 Vorkommen von x erkennen \\
|
|
\{Name\} & Expansion der Definition Name \\
|
|
\textbackslash t, \textbackslash n, \textbackslash rq & Tabulator, Zeilenumbruch, Wagenrücklauf erkennen \\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\index{Lex|)}
|
|
|
|
|
|
\section{Syntaktische Analyse}\xindex{Analyse!syntaktische}%
|
|
In der syntaktischen Analyse wird überprüft, ob die Tokenfolge zur
|
|
kontextfreien Sprache\todo{Warum kontextfrei?} gehört. Außerdem soll die
|
|
hierarchische Struktur der Eingabe erkannt werden.\todo{Was ist gemeint?}
|
|
|
|
Ausgegeben wird ein \textbf{abstrakter Syntaxbaum}\xindex{Syntaxbaum!abstrakter}.
|
|
|
|
\begin{beispiel}[Abstrakter Syntaxbaum]
|
|
TODO
|
|
\end{beispiel}
|
|
|
|
\section{Semantische Analyse}\xindex{Analyse!semantische}%
|
|
Die semantische Analyse arbeitet auf einem abstrakten Syntaxbaum und generiert
|
|
einen attributierten Syntaxbaum\xindex{Syntaxbaum!attributeriter}.
|
|
|
|
Sie führt eine kontextsensitive Analyse durch. Dazu gehören:
|
|
\begin{itemize}
|
|
\item \textbf{Namensanalyse}: Beziehung zwischen Deklaration und Verwendung\todo{?}
|
|
\item \textbf{Typanalyse}: Bestimme und prüfe Typen von Variablen, Funktionen, \dots \todo{?}
|
|
\item \textbf{Konsistenzprüfung}: Wurden alle Einschränkungen der Programmiersprache eingehalten?\todo{?}
|
|
\end{itemize}
|
|
|
|
\begin{beispiel}[Attributeriter Syntaxbaum]
|
|
TODO
|
|
\end{beispiel}
|
|
|
|
\section{Zwischencodeoptimierung}
|
|
Hier wird der Code in eine sprach- und zielunabhängige Zwischensprache transformiert.
|
|
Dabei sind viele Optimierungen vorstellbar. Ein paar davon sind:
|
|
\begin{itemize}
|
|
\item \textbf{Konstantenfaltung}: Ersetze z.~B. $3+5$ durch $8$.
|
|
\item \textbf{Kopienfortschaffung}: Setze Werte von Variablen direkt ein
|
|
\item \textbf{Code verschieben}: Führe Befehle vor der Schleife aus, statt in der Schleife \todo{?}
|
|
\item \textbf{Gemeinsame Teilausdrücke entfernen}: Es sollen doppelte Berechnungen vermieden werden \todo{Beispiel?}
|
|
\item \textbf{Inlining}: Statt Methode aufzurufen, kann der Code der Methode an der Aufrufstelle eingebaut werden.
|
|
\end{itemize}
|
|
|
|
\section{Codegenerierung}
|
|
Der letzte Schritt besteht darin, aus dem generiertem Zwischencode den
|
|
Maschinencode oder Assembler zu erstellen. Dabei muss folgendes beachtet werden:
|
|
\begin{itemize}
|
|
\item \textbf{Konventionen}: Wie werden z.~B. im Laufzeitsystem Methoden aufgerufen?
|
|
\item \textbf{Codeauswahl}: Welche Befehle kennt das Zielsystem?
|
|
\item \textbf{Scheduling}: In welcher Reihenfolge sollen die Befehle angeordnet werden?
|
|
\item \textbf{Registerallokation}: Welche Zwischenergebnisse sollen in welchen Prozessorregistern gehalten werden?
|
|
\item \textbf{Nachoptimierung}\todo{?}
|
|
\end{itemize}
|
|
|
|
\section{Weiteres}
|
|
\subsection{First- und Follow}
|
|
\begin{definition}[$k$-Präfix]\xindex{Präfix}\xindex{\# (Compilerbau)}%
|
|
Sei $G = (\Sigma, V, P, S)$ eine Grammatik, $k \in \mdn_{> 0}$ und
|
|
$x \in (V \cup \Sigma)^*$ mit
|
|
\[x = x_1 \dots x_m \text{ mit } x_i \in (V \cup \Sigma) \text{ wobei } i \in 1, \dots, m\]
|
|
|
|
Dann heißt $\tilde{x} \in (V \cup \Sigma \cup \Set{\#})^+$ ein $k$-\textbf{Präfix} von $x$,
|
|
wenn gilt:
|
|
\[\tilde{x} =
|
|
\begin{cases}
|
|
x\# &\text{falls } x = x_1 \dots x_m \text{ und } m < k\\
|
|
x_1 \dots x_k &\text{sonst}
|
|
\end{cases}\]
|
|
wobei $\#$ das Ende der Eingabe bezeichnet. In diesem Fall schreibt man
|
|
\[ \tilde{x} = k\ :\ x\]
|
|
\end{definition}
|
|
|
|
\begin{beispiel}[$k$-Präfixe]
|
|
Sei $G = (\Set{A, B, C, S}, \Set{a, b, c}, P, S)$ mit
|
|
\begin{align*}
|
|
P = \{ &A \rightarrow aa | ab, \\
|
|
&B \rightarrow AC,\\
|
|
&C \rightarrow c,\\
|
|
&S \rightarrow ABC\}
|
|
\end{align*}
|
|
|
|
Dann gilt:
|
|
\begin{bspenum}
|
|
\item $A = 1 : S$
|
|
\item $a = 1 : S$
|
|
\end{bspenum}
|
|
|
|
\todo[inline]{Das ist ein Problem! Damit wäre $k : x$ nicht wohldefiniert!}
|
|
\end{beispiel}
|
|
|
|
\begin{definition}[First- und Follow-Menge]\xindex{Firstkx@$\text{First}_k(x)$}\xindex{Followkx@$\text{Follow}_k(x)$}%
|
|
Sei $G = (\Sigma, V, P, S)$ eine Grammatik und $x \in V$.
|
|
|
|
\begin{defenum}
|
|
\item $\begin{aligned}[t]\First_k (x) := \{u \in (V \cup \Sigma)^+ | &\exists y \in \Sigma^*:\\
|
|
&x \Rightarrow^* y\\
|
|
\land &u = k : y \}\end{aligned}$
|
|
\item $\begin{aligned}[t]\Follow_k(x) := \{u \in (V \cup \Sigma)^+ | &\exists m, y \in (V \cup \Sigma)^* :\\
|
|
&S \Rightarrow^* mxy\\
|
|
\land &u \in \First_k(y)\}\end{aligned}$
|
|
\end{defenum}
|
|
\end{definition}
|
|
|
|
\begin{beispiel}[First- und Follow-Mengen\footnotemark]
|
|
Sei $G = (\Sigma, V, P, E)$ mit
|
|
\begin{align*}
|
|
\Sigma &= \Set{+, *, (, )}\\
|
|
V &= \Set{T, T', E, E', F}\\
|
|
P &= \{ E \rightarrow T E'\\
|
|
&\hphantom{= \{ } E' \rightarrow \varepsilon | +TE'\\
|
|
&\hphantom{= \{ } T \rightarrow FT'\\
|
|
&\hphantom{= \{ } T' \rightarrow \varepsilon | *FT'\\
|
|
&\hphantom{= \{ } F \rightarrow \id | (E)\}
|
|
\end{align*}
|
|
|
|
Dann gilt:
|
|
\begin{bspenum}
|
|
\item $\First(E) = \First(T) = \First(F) = \Set{\id, (\ )}$
|
|
\item $\First(E') = \Set{\#, +}$
|
|
\item $\First(T') = \Set{\#, *}$
|
|
\item $\Follow(E) = \Follow(E') = \Set{\#, )}$
|
|
\item $\Follow(T) = \Follow(T') = \Set{\#, ), +}$
|
|
\item $\Follow(F) = \Set{\#, ), +, *}$
|
|
\end{bspenum}
|
|
\end{beispiel}
|
|
\footnotetext{Folie 348}
|
|
|
|
\section{Literatur}
|
|
Ich kann das folgende Buch empfehlen:
|
|
|
|
\textit{Compiler - Prinzipien, Techniken und Werkzeuge}. Alfred V. Aho, Monica S. Lam,
|
|
Ravi Sethi und Jeffry D. Ullman. Pearson Verlag, 2. Auflage, 2008. ISBN 978-3-8273-7097-6.
|
|
|
|
Es ist mit über 1200 Seiten zwar etwas dick, aber dafür sehr einfach geschrieben.
|
|
|
|
\index{Compilerbau|)} |