📚 The CoCalc Library - books, templates and other resources
License: OTHER
%!TEX root = Programmierparadigmen.tex1\chapter{Compilerbau}2\index{Compilerbau|(}34Wenn man über Compiler redet, meint man üblicherweise \enquote{vollständige Übersetzer}:56\begin{definition}\xindex{Compiler}%7Ein \textbf{Compiler} ist ein Programm $C$, das den Quelltext eines Programms8$A$ in eine ausführbare Form übersetzen kann.9\end{definition}1011Jedoch gibt es verschiedene Ebenen der Interpretation bzw. Übersetzung:12\begin{enumerate}13\item \textbf{Reiner Interpretierer}: TCL, Unix-Shell14\item \textbf{Vorübersetzung}: Java-Bytecode, Pascal P-Code, Python\footnote{Python hat auch \texttt{.pyc}-Dateien, die Python-Bytecode enthalten.}, Smalltalk-Bytecode15\item \textbf{Laufzeitübersetzung}: JavaScript\footnote{JavaScript wird nicht immer zur Laufzeit übersetzt. Früher war es üblich, dass JavaScript nur interpretiert wurde.}16\item \textbf{Vollständige Übersetzung}: C, C++, Fortran17\end{enumerate}1819Zu sagen, dass Python eine interpretierte Sprache ist, ist in etwa so korrekt20wie zu sagen, dass die Bibel ein Hardcover-Buch ist.\footnote{Quelle: stackoverflow.com/a/2998544, danke Alex Martelli für diesen Vergleich.}2122Reine Interpretierer lesen den Quelltext Anweisung für Anweisung und führen23diese direkt aus.2425\todo[inline]{Bild}2627Bei der \textit{Interpretation nach Vorübersetzung} wird der Quelltext analysiert28und in eine für den Interpretierer günstigere Form übersetzt. Das kann z.~B.29durch30\begin{itemize}31\item Zuordnung Bezeichnergebrauch - Vereinbarung\todo{?}32\item Transformation in Postfixbaum33\item Typcheck, wo statisch möglich34\end{itemize}35geschehen. Diese Vorübersetzung ist nicht unbedingt maschinennah.3637\todo[inline]{Bild}3839Die \textit{Just-in-time-Compiler}\xindex{Compiler!Just-in-time}\index{JIT|see{Just-in-time Compiler}} (kurz: JIT-Compiler) betreiben40Laufzeitübersetzung. Folgendes sind Vor- bzw. Nachteile von Just-in-time Compilern:41\begin{itemize}42\item schneller als reine Interpretierer43\item Speichergewinn: Quelle kompakter als Zielprogramm (vgl. \cref{bsp:code-kompaktheit})44\item Schnellerer Start des Programms45\item Langsamer (pro Funktion) als vollständige Übersetzung46\item kann dynamisch ermittelte Laufzeiteigenschaften berücksichtigen (dynamische Optimierung)47\end{itemize}4849\begin{beispiel}[Code-Kompaktheit]\label{bsp:code-kompaktheit}%50Man betrachte folgende Codestücke:5152\inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=Hello.java]{java}{scripts/java/Hello.java}53\inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=hello-world.c]{c}{scripts/c/hello-world.c}5455Nun zum Größenvergleich:5657\begin{itemize}58\item Der C-Code ist 83 Byte groß,59\item der Java-Codee ist 123 Byte groß,60\item der generierte Java Bytecode ist 416 Byte groß und61\item der erzeugt Maschinencode aus C ist 8565 Byte groß.62\end{itemize}63\end{beispiel}6465Moderne virtuelle Maschinen für Java und für .NET nutzen JIT-Compiler.6667Bei der \textit{vollständigen Übersetzung} wird der Quelltext vor der ersten68Ausführung des Programms $A$ in Maschinencode (z.~B. x86, SPARC) übersetzt.6970\todo[inline]{Bild}7172\section{Funktionsweise}73Üblicherweise führt ein Compiler folgende Schritte aus:74\begin{enumerate}75\item Lexikalische Analyse76\item Syntaktische Analyse77\item Semantische Analyse78\item Zwischencodeoptimierung79\item Codegenerierung80\item Assemblieren und Binden81\end{enumerate}8283\section{Lexikalische Analyse}\xindex{Analyse!lexikalische}%84In der lexikalischen Analyse wird der Quelltext als Sequenz von Zeichen betrachtet.85Sie soll bedeutungstragende Zeichengruppen, sog. \textit{Tokens}\xindex{Token},86erkennen und unwichtige Zeichen, wie z.~B. Kommentare überspringen. Außerdem87sollen Bezeichner identifiziert und in einer \textit{Stringtabelle}\xindex{Stringtabelle}88zusammengefasst werden.8990\begin{beispiel}91\todo[inline]{Beispiel erstellen}92\end{beispiel}9394\subsection{Reguläre Ausdrücke}\xindex{Ausdrücke!reguläre}95\begin{beispiel}[Regulärere Ausdrücke]96Folgender regulärer Ausdruck erkennt Float-Konstanten in C nach97ISO/IEC 9899:1999 §6.4.4.2:9899$((0|\dots|9)^*.(0|\dots|9)^+)|((0|\dots|9)^+.)$100\end{beispiel}101102\begin{satz}103Jede reguläre Sprache wird von einem (deterministischen) endlichen104Automaten akzeptiert.105\end{satz}106107TODO: Bild einfügen108109Zu jedem regulären Ausdruck im Sinne der theoretischen Informatik kann ein110nichtdeterministischer Automat generiert werden. Dieser kann mittels111Potenzmengenkonstruktion\footnote{\url{http://martin-thoma.com/potenzmengenkonstruktion/}}112in einen deterministischen Automaten überführen. Dieser kann dann mittels113Äquivalenzklassen minimiert werden.114115\todo[inline]{Alle Schritte beschreiben}116117\subsection{Lex}\index{Lex|(}\index{Flex|see{Lex}}118Lex ist ein Programm, das beim Übersetzerbau benutzt wird um Tokenizer für die119lexikalische Analyse zu erstellen. Flex ist eine Open-Source Variante davon.120121Eine Flex-Datei besteht aus 3 Teilen, die durch \texttt{\%\%} getrennt werden:122123\begin{verbatim}124Definitionen: Definiere Namen125%%126Regeln: Definiere reguläre Ausdrücke und127zugehörige Aktionen (= Code)128%%129Code: zusätzlicher Code130\end{verbatim}131132\subsubsection{Reguläre Ausdrücke in Flex}133\begin{table}134\begin{tabular}{ll}135x & Zeichen 'x' erkennen \\136"xy" & Zeichenkette 'xy' erkennen \\137\textbackslash & Zeichen 'x' erkennen (TODO) \\138$[xyz]$ & Zeichen $x$, $y$ oder $z$ erkennen \\139$[a-z]$ & Alle Kleinbuchstaben erkennen \\140$[\^a-z]$ & Alle Zeichen außer Kleinbuchstaben erkennen \\141$x|y$ & $x$ oder $y$ erkennen \\142(x) & x erkennen \\143x* & 0, 1 oder mehrere Vorkommen von x erkennen \\144x+ & 1 oder mehrere Vorkommen von x erkennen \\145x? & 0 oder 1 Vorkommen von x erkennen \\146\{Name\} & Expansion der Definition Name \\147\textbackslash t, \textbackslash n, \textbackslash rq & Tabulator, Zeilenumbruch, Wagenrücklauf erkennen \\148\end{tabular}149\end{table}150151\index{Lex|)}152153154\section{Syntaktische Analyse}\xindex{Analyse!syntaktische}%155In der syntaktischen Analyse wird überprüft, ob die Tokenfolge zur156kontextfreien Sprache\todo{Warum kontextfrei?} gehört. Außerdem soll die157hierarchische Struktur der Eingabe erkannt werden.\todo{Was ist gemeint?}158159Ausgegeben wird ein \textbf{abstrakter Syntaxbaum}\xindex{Syntaxbaum!abstrakter}.160161\begin{beispiel}[Abstrakter Syntaxbaum]162TODO163\end{beispiel}164165\subsection{Abstrakte Syntax}\xindex{Syntax!abstrakte}%166\begin{beispiel}[Abstrakte Syntax für reguläre Ausdrücke\footnotemark]167Die Grammatik $G = (\Set{\text{\texttt{char}}, (, ), \cup, \cdot, *, \varepsilon}, \Set{R}, P, R)$ mit den Produktionen168\[R \rightarrow \text{\texttt{char}} | \varepsilon | ( R \cup R ) | (R \cdot R) | (R)^*\]169erzeugt einfache reguläre Ausdrücke.170171Die zugehörige abstrakte Syntax ist172173\begin{align*}174\text{RegExp} &= \text{Char } | \text{ Epsilon } | \text{ Union } | \text{ Concatenation } | \text{ KleeneClosure }\\175\text{Union} &:: \text{RegExp RegExp}\\176\text{Concatenation} &:: \text{RegExp RegExp}\\177\text{KleeneClosure} &:: \text{RegExp}\\178\text{Char} &= \text{\texttt{char}}\\179\text{Epsilon} &= \varepsilon\\180\end{align*}181\end{beispiel}182\footnotetext{Klausur vom SS2012}183184\begin{beispiel}[Abstrakte Syntax für reguläre Ausdrücke\footnotemark]185Die Grammatik $G = (\Set{\text{\texttt{char}}, (, ), \cup, \cdot, *, \varepsilon}, \Set{R}, P, R)$ mit den Produktionen186\[R \rightarrow \text{\texttt{char}} | \varepsilon | ( R \cup R ) | (R \cdot R) | (R)^*\]187erzeugt einfache reguläre Ausdrücke.188189Die zugehörige abstrakte Syntax ist190191\begin{align*}192\text{RegExp} &= \text{Char } | \text{ Epsilon } | \text{ Union } | \text{ Concatenation } | \text{ KleeneClosure }\\193\text{Union} &:: \text{RegExp RegExp}\\194\text{Concatenation} &:: \text{RegExp RegExp}\\195\text{KleeneClosure} &:: \text{RegExp}\\196\text{Char} &= \text{\texttt{char}}\\197\text{Epsilon} &= \varepsilon\\198\end{align*}199\end{beispiel}200\footnotetext{Klausur vom SS2012}201202\section{Semantische Analyse}\xindex{Analyse!semantische}%203Die semantische Analyse arbeitet auf einem abstrakten Syntaxbaum und generiert204einen attributierten Syntaxbaum\xindex{Syntaxbaum!attributeriter}.205206Sie führt eine kontextsensitive Analyse durch. Dazu gehören:207\begin{itemize}208\item \textbf{Namensanalyse}: Beziehung zwischen Deklaration und Verwendung\todo{?}209\item \textbf{Typanalyse}: Bestimme und prüfe Typen von Variablen, Funktionen, \dots \todo{?}210\item \textbf{Konsistenzprüfung}: Wurden alle Einschränkungen der Programmiersprache eingehalten?\todo{?}211\end{itemize}212213\begin{beispiel}[Attributeriter Syntaxbaum]214TODO215\end{beispiel}216217\section{Zwischencodeoptimierung}218Hier wird der Code in eine sprach- und zielunabhängige Zwischensprache transformiert.219Dabei sind viele Optimierungen vorstellbar. Ein paar davon sind:220\begin{itemize}221\item \textbf{Konstantenfaltung}: Ersetze z.~B. $3+5$ durch $8$.222\item \textbf{Kopienfortschaffung}: Setze Werte von Variablen direkt ein223\item \textbf{Code verschieben}: Führe Befehle vor der Schleife aus, statt in der Schleife \todo{?}224\item \textbf{Gemeinsame Teilausdrücke entfernen}: Es sollen doppelte Berechnungen vermieden werden \todo{Beispiel?}225\item \textbf{Inlining}: Statt Methode aufzurufen, kann der Code der Methode an der Aufrufstelle eingebaut werden.226\end{itemize}227228\section{Codegenerierung}229Der letzte Schritt besteht darin, aus dem generiertem Zwischencode den230Maschinencode oder Assembler zu erstellen. Dabei muss folgendes beachtet werden:231\begin{itemize}232\item \textbf{Konventionen}: Wie werden z.~B. im Laufzeitsystem Methoden aufgerufen?233\item \textbf{Codeauswahl}: Welche Befehle kennt das Zielsystem?234\item \textbf{Scheduling}: In welcher Reihenfolge sollen die Befehle angeordnet werden?235\item \textbf{Registerallokation}: Welche Zwischenergebnisse sollen in welchen Prozessorregistern gehalten werden?236\item \textbf{Nachoptimierung}\todo{?}237\end{itemize}238239\section{Weiteres}240\subsection{First- und Follow}241\begin{definition}[$k$-Anfang]\xindex{k-Anfang@$k$-Anfang}\index{Anfang|see{$k$-Anfang}}\xindex{\# (Compilerbau)}%242Sei $G = (\Sigma, V, P, S)$ eine Grammatik, $k \in \mdn_{> 0}$ und243$x \in \Sigma^*$ mit244\[x = x_1 \dots x_m \text{ mit } x_i \in \Sigma \text{ wobei } i \in 1, \dots, m\]245246Dann heißt $\tilde{x} \in (\Sigma \cup \Set{\#})^+$ ein $k$-\textbf{Anfang} von $x$,247wenn gilt:248\[\tilde{x} =249\begin{cases}250x\# &\text{falls } x = x_1 \dots x_m \text{ und } m < k\\251x_1 \dots x_k &\text{sonst}252\end{cases}\]253wobei $\#$ das Ende der Eingabe bezeichnet. In diesem Fall schreibt man254\[ \tilde{x} = k\ :\ x\]255\end{definition}256257\begin{beispiel}[$k$-Anfang]258Sei $G = (\Set{A, B, C, S}, \Set{a, b, c}, P, S)$ mit259\begin{align*}260P = \{ &A \rightarrow aa | ab, \\261&B \rightarrow AC,\\262&C \rightarrow c,\\263&S \rightarrow ABC\}264\end{align*}265266Dann gilt:267\begin{multicols}{2}268\begin{bspenum}269\item $a = 1 : aaaa$270\item $a = 1 : a$271\item $a = 1 : aba$272\item $ab = 2 : aba$273\item $aba = 3 : aba$274\item $aba\# = 4 : aba$275\end{bspenum}276\end{multicols}277\end{beispiel}278279\begin{definition}[First- und Follow-Menge]\xindex{Firstkx@$\text{First}_k(x)$}\xindex{Followkx@$\text{Follow}_k(x)$}%280Sei $G = (\Sigma, V, P, S)$ eine Grammatik und $x \in (V \cup \Sigma)$.281282\begin{defenum}283\item $\begin{aligned}[t]\First_k (x) := \{u \in \Sigma^+ | \exists &y \in \Sigma^*:\\284&x \Rightarrow^* y\\285\land &u = k : y \}\end{aligned}$286\item $\First(x) := \First_1(x)$287\item $\begin{aligned}[t]\Follow_k(x) := \{u \in \Sigma^+ | \exists &m, y \in (V \cup \Sigma)^* :\\288&S \Rightarrow^* mxy\\289\land &u \in \First_k(y)\}\end{aligned}$290\item $\Follow(x) := \Follow_1(x)$291\end{defenum}292\end{definition}293294Die $\First_k(x)$-Menge beinhaltet also alle Terminalfolgen, die entweder $k$295Terminale lang sind oder kürzer sind und dafür mit \# enden und die zugleich296der Anfang von Ableitungen von $x$ sind.297298Die $\Follow_k(x)$-Menge hingegen hat alle Terminalfolgen der Länge $k$ oder kürzer299und dafür mit \# am Ende, die aus einer Ableitung des Startsymbols $S \Rightarrow^* mxy$300auf die Teilfolge $x$ folgen können.301302Mit der $\Follow_k(x)$-Menge kann man also zu jedem Zeitpunkt sagen, was momentan303folgen darf. Wenn der Parser etwas anderes liest, ist ein Fehler passiert.304305Da jede Terminalfolge, die sich aus $S$ folgern lässt mit \# endet, gilt immer:306\[\# \in \Follow_k(x)\]307308\begin{beispiel}[First- und Follow-Mengen\footnotemark]309Sei $G = (\Sigma, V, P, E)$ mit310\begin{align*}311\Sigma &= \Set{+, *, (, )}\\312V &= \Set{T, T', E, E', F}\\313P &= \{ E \rightarrow T E'\\314&\hphantom{= \{ } E' \rightarrow \varepsilon | +TE'\\315&\hphantom{= \{ } T \rightarrow FT'\\316&\hphantom{= \{ } T' \rightarrow \varepsilon | *FT'\\317&\hphantom{= \{ } F \rightarrow \id | (E)\}318\end{align*}319320Dann gilt:321\begin{bspenum}322\item $\First(E) = \First(T) = \First(F) = \Set{\id, (\ )}$323\item $\First(E') = \Set{\#, +}$324\item $\First(T') = \Set{\#, *}$325\item $\Follow(E) = \Follow(E') = \Set{\#, )}$326\item $\Follow(T) = \Follow(T') = \Set{\#, ), +}$327\item $\Follow(F) = \Set{\#, ), +, *}$328\end{bspenum}329\end{beispiel}330\footnotetext{Folie 348}331332\section{Literatur}333Ich kann das folgende Buch empfehlen:334335\textit{Compiler - Prinzipien, Techniken und Werkzeuge}. Alfred V. Aho, Monica S. Lam,336Ravi Sethi und Jeffry D. Ullman. Pearson Verlag, 2. Auflage, 2008. ISBN 978-3-8273-7097-6.337338Es ist mit über 1200 Seiten zwar etwas dick, aber dafür sehr einfach geschrieben.339340\index{Compilerbau|)}341342