2014-02-20 10:42:44 +01:00
%!TEX root = Programmierparadigmen.tex
2014-02-07 12:04:12 +01:00
\chapter { Compilerbau}
\index { Compilerbau|(}
2014-02-14 09:23:58 +01:00
Wenn man über Compiler redet, meint man üblicherweise \enquote { vollständige Übersetzer} :
2014-02-07 12:04:12 +01:00
2014-02-14 09:23:58 +01:00
\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}
2014-02-07 12:04:12 +01:00
2014-02-14 09:23:58 +01:00
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
2014-04-08 15:03:38 +02:00
\item Speichergewinn: Quelle kompakter als Zielprogramm (vgl. \cref { bsp:code-kompaktheit} )
2014-02-14 09:23:58 +01:00
\item Schnellerer Start des Programms
\item Langsamer (pro Funktion) als vollständige Übersetzung
\item kann dynamisch ermittelte Laufzeiteigenschaften berücksichtigen (dynamische Optimierung)
\end { itemize}
2014-04-08 15:03:38 +02:00
\begin { beispiel} [Code-Kompaktheit]\label { bsp:code-kompaktheit} %
Man betrachte folgende Codestücke:
\inputminted [linenos, numbersep=5pt, tabsize=4, frame=lines, label=Hello.java] { java} { scripts/java/Hello.java}
\inputminted [linenos, numbersep=5pt, tabsize=4, frame=lines, label=hello-world.c] { c} { scripts/c/hello-world.c}
Nun zum Größenvergleich:
\begin { itemize}
\item Der C-Code ist 83 Byte groß,
\item der Java-Codee ist 123 Byte groß,
\item der generierte Java Bytecode ist 416 Byte groß und
\item der erzeugt Maschinencode aus C ist 8565 Byte groß.
\end { itemize}
\end { beispiel}
2014-02-14 09:23:58 +01:00
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}
2014-02-20 10:42:44 +01:00
\section { Lexikalische Analyse} \xindex { Analyse!lexikalische} %
2014-02-14 09:23:58 +01:00
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}
2014-02-20 10:42:44 +01:00
\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|)}
2014-02-14 09:23:58 +01:00
\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}
2014-04-02 19:08:06 +02:00
\subsection { Abstrakte Syntax} \xindex { Syntax!abstrakte} %
\begin { beispiel} [Abstrakte Syntax für reguläre Ausdrücke\footnotemark ]
Die Grammatik $ G = ( \Set { \text { \texttt { char } } , ( , ) , \cup , \cdot , * , \varepsilon } , \Set { R } , P, R ) $ mit den Produktionen
\[ R \rightarrow \text { \texttt { char } } | \varepsilon | ( R \cup R ) | ( R \cdot R ) | ( R ) ^ * \]
erzeugt einfache reguläre Ausdrücke.
Die zugehörige abstrakte Syntax ist
\begin { align*}
\text { RegExp} & = \text { Char } | \text { Epsilon } | \text { Union } | \text { Concatenation } | \text { KleeneClosure } \\
\text { Union} & :: \text { RegExp RegExp} \\
\text { Concatenation} & :: \text { RegExp RegExp} \\
\text { KleeneClosure} & :: \text { RegExp} \\
\text { Char} & = \text { \texttt { char} } \\
\text { Epsilon} & = \varepsilon \\
\end { align*}
\end { beispiel}
\footnotetext { Klausur vom SS2012}
\begin { beispiel} [Abstrakte Syntax für reguläre Ausdrücke\footnotemark ]
Die Grammatik $ G = ( \Set { \text { \texttt { char } } , ( , ) , \cup , \cdot , * , \varepsilon } , \Set { R } , P, R ) $ mit den Produktionen
\[ R \rightarrow \text { \texttt { char } } | \varepsilon | ( R \cup R ) | ( R \cdot R ) | ( R ) ^ * \]
erzeugt einfache reguläre Ausdrücke.
Die zugehörige abstrakte Syntax ist
\begin { align*}
\text { RegExp} & = \text { Char } | \text { Epsilon } | \text { Union } | \text { Concatenation } | \text { KleeneClosure } \\
\text { Union} & :: \text { RegExp RegExp} \\
\text { Concatenation} & :: \text { RegExp RegExp} \\
\text { KleeneClosure} & :: \text { RegExp} \\
\text { Char} & = \text { \texttt { char} } \\
\text { Epsilon} & = \varepsilon \\
\end { align*}
\end { beispiel}
\footnotetext { Klausur vom SS2012}
2014-02-14 09:23:58 +01:00
\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}
2014-04-01 14:44:24 +02:00
\section { Weiteres}
\subsection { First- und Follow}
2014-04-01 18:05:52 +02:00
\begin { definition} [$ k $ -Anfang]\xindex { k-Anfang@$ k $ -Anfang} \index { Anfang|see{ $ k $ -Anfang} } \xindex { \# (Compilerbau)} %
2014-04-01 14:44:24 +02:00
Sei $ G = ( \Sigma , V, P, S ) $ eine Grammatik, $ k \in \mdn _ { > 0 } $ und
2014-04-01 18:05:52 +02:00
$ x \in \Sigma ^ * $ mit
\[ x = x _ 1 \dots x _ m \text { mit } x _ i \in \Sigma \text { wobei } i \in 1 , \dots , m \]
2014-04-01 14:44:24 +02:00
2014-04-04 13:07:12 +02:00
Dann heißt $ \tilde { x } \in ( \Sigma \cup \Set { \# } ) ^ + $ ein $ k $ -\textbf { Anfang} von $ x $ ,
2014-04-01 14:44:24 +02:00
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}
2014-04-01 18:05:52 +02:00
\begin { beispiel} [$ k $ -Anfang]
2014-04-01 14:44:24 +02:00
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:
2014-04-04 13:07:12 +02:00
\begin { multicols} { 2}
2014-04-01 14:44:24 +02:00
\begin { bspenum}
2014-04-01 18:05:52 +02:00
\item $ a = 1 : aaaa $
\item $ a = 1 : a $
\item $ a = 1 : aba $
\item $ ab = 2 : aba $
\item $ aba = 3 : aba $
\item $ aba \# = 4 : aba $
2014-04-01 14:44:24 +02:00
\end { bspenum}
2014-04-04 13:07:12 +02:00
\end { multicols}
2014-04-01 14:44:24 +02:00
\end { beispiel}
\begin { definition} [First- und Follow-Menge]\xindex { Firstkx@$ \text { First } _ k ( x ) $ } \xindex { Followkx@$ \text { Follow } _ k ( x ) $ } %
2014-04-04 13:07:12 +02:00
Sei $ G = ( \Sigma , V, P, S ) $ eine Grammatik und $ x \in ( V \cup \Sigma ) $ .
2014-04-01 14:44:24 +02:00
\begin { defenum}
2014-04-04 13:07:12 +02:00
\item $ \begin { aligned } [ t ] \First _ k ( x ) : = \{ u \in \Sigma ^ + | \exists & y \in \Sigma ^ * : \\
2014-04-01 14:44:24 +02:00
& x \Rightarrow ^ * y\\
\land & u = k : y \} \end { aligned} $
2014-04-04 13:07:12 +02:00
\item $ \First ( x ) : = \First _ 1 ( x ) $
\item $ \begin { aligned } [ t ] \Follow _ k ( x ) : = \{ u \in \Sigma ^ + | \exists & m, y \in ( V \cup \Sigma ) ^ * : \\
2014-04-01 14:44:24 +02:00
& S \Rightarrow ^ * mxy\\
\land & u \in \First _ k(y)\} \end { aligned} $
2014-04-04 13:07:12 +02:00
\item $ \Follow ( x ) : = \Follow _ 1 ( x ) $
2014-04-01 14:44:24 +02:00
\end { defenum}
\end { definition}
2014-04-04 13:07:12 +02:00
Die $ \First _ k ( x ) $ -Menge beinhaltet also alle Terminalfolgen, die entweder $ k $
Terminale lang sind oder kürzer sind und dafür mit \# enden und die zugleich
der Anfang von Ableitungen von $ x $ sind.
Die $ \Follow _ k ( x ) $ -Menge hingegen hat alle Terminalfolgen der Länge $ k $ oder kürzer
und dafür mit \# am Ende, die aus einer Ableitung des Startsymbols $ S \Rightarrow ^ * mxy $
auf die Teilfolge $ x $ folgen können.
Mit der $ \Follow _ k ( x ) $ -Menge kann man also zu jedem Zeitpunkt sagen, was momentan
folgen darf. Wenn der Parser etwas anderes liest, ist ein Fehler passiert.
Da jede Terminalfolge, die sich aus $ S $ folgern lässt mit \# endet, gilt immer:
\[ \# \in \Follow _ k ( x ) \]
2014-04-01 14:44:24 +02:00
\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}
2014-03-23 19:28:44 +01:00
\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.
2014-02-14 09:23:58 +01:00
\index { Compilerbau|)}