Compilerbau.tex 14 KB

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