123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385 |
- \documentclass[11pt]{book}
- \usepackage[T1]{fontenc}
- \usepackage[utf8]{inputenc}
- \usepackage{lmodern}
- \usepackage{hyperref}
- \usepackage{graphicx}
- \usepackage[english]{babel}
- \usepackage{listings}
- \usepackage{amsmath}
- \usepackage{amsthm}
- \usepackage{amssymb}
- \usepackage{natbib}
- \usepackage{stmaryrd}
- \usepackage{xypic}
- \usepackage{semantic}
- \usepackage{wrapfig}
- %% For pictures
- \usepackage{tikz}
- \usetikzlibrary{arrows.meta}
- \tikzset{baseline=(current bounding box.center), >/.tip={Triangle[scale=1.4]}}
- % Computer Modern is already the default. -Jeremy
- %\renewcommand{\ttdefault}{cmtt}
- \lstset{%
- language=Lisp,
- basicstyle=\ttfamily\small,
- escapechar=|,
- columns=flexible
- }
- \newtheorem{theorem}{Theorem}
- \newtheorem{lemma}[theorem]{Lemma}
- \newtheorem{corollary}[theorem]{Corollary}
- \newtheorem{proposition}[theorem]{Proposition}
- \newtheorem{constraint}[theorem]{Constraint}
- \newtheorem{definition}[theorem]{Definition}
- \newtheorem{exercise}[theorem]{Exercise}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- % 'dedication' environment: To add a dedication paragraph at the start of book %
- % Source: http://www.tug.org/pipermail/texhax/2010-June/015184.html %
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \newenvironment{dedication}
- {
- \cleardoublepage
- \thispagestyle{empty}
- \vspace*{\stretch{1}}
- \hfill\begin{minipage}[t]{0.66\textwidth}
- \raggedright
- }
- {
- \end{minipage}
- \vspace*{\stretch{3}}
- \clearpage
- }
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- % Chapter quote at the start of chapter %
- % Source: http://tex.stackexchange.com/a/53380 %
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \makeatletter
- \renewcommand{\@chapapp}{}% Not necessary...
- \newenvironment{chapquote}[2][2em]
- {\setlength{\@tempdima}{#1}%
- \def\chapquote@author{#2}%
- \parshape 1 \@tempdima \dimexpr\textwidth-2\@tempdima\relax%
- \itshape}
- {\par\normalfont\hfill--\ \chapquote@author\hspace*{\@tempdima}\par\bigskip}
- \makeatother
- \input{defs}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \title{\Huge \textbf{Essentials of Compilation} \\
- \huge An Incremental Approach}
- \author{\textsc{Jeremy G. Siek} \\
- %\thanks{\url{http://homes.soic.indiana.edu/jsiek/}} \\
- Indiana University \\
- \\
- with contributions from: \\
- Carl Factora \\
- Cameron Swords
- }
- \begin{document}
- \frontmatter
- \maketitle
- \begin{dedication}
- This book is dedicated to the programming language wonks at Indiana
- University.
- \end{dedication}
- \tableofcontents
- %\listoffigures
- %\listoftables
- \mainmatter
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter*{Preface}
- The tradition of compiler writing at Indiana University goes back to
- programming language research and courses taught by Daniel Friedman in
- the 1970's and 1980's. Dan had conducted research on lazy evaluation
- in the context of Lisp~\citep{McCarthy:1960dz} and then studied
- continuations and macros in the context of the
- Scheme~\citep{Sussman:1975ab}, a dialect of Lisp. One of students of
- those courses, Kent Dybvig, went on to build Chez
- Scheme~\citep{Dybvig:2006aa}, a production-quality and efficient
- compiler for Scheme. After completing his Ph.D. at the University of
- North Carolina, Kent returned to teach at Indiana University.
- Throughout the 1990's and early 2000's, Kent continued development of
- Chez Scheme and rotated with Dan in teaching the compiler course.
- Thanks to this collaboration between Dan and Kent, the compiler course
- evolved to incorporate novel pedagogical ideas while also including
- elements of effective real-world compilers. One of Dan's ideas was to
- split the compiler into many small passes over the input program and
- subsequent intermediate representations, so that the code for each
- pass would be easy to understood in isolation. (In contrast, most
- compilers of the time were organized into only a few monolithic passes
- for reasons of compile-time efficiency.) Kent and his students,
- Dipanwita Sarkar and Andrew Keep, developed infrastructure to support
- this approach and evolved the course, first to use micro-sized passes
- and then into even smaller nano
- passes~\citep{Sarkar:2004fk,Keep:2012aa}. I took this compiler course
- in the early 2000's, as part of my Ph.D. studies at Indiana
- University. Needless to say, I enjoyed the course immensely.
- One of my classmates, Abdulaziz Ghuloum, observed that the
- front-to-back organization of the course made it difficult for
- students to understand the rationale for the compiler
- design. Abdulaziz proposed an incremental approach in which the
- students build the compiler in stages; they start by implementing a
- complete compiler for a very small subset of the input language, then
- in each subsequent stage they add a feature to the input language and
- add or modify passes to handle the new feature~\citep{Ghuloum:2006bh}.
- In this way, the students see how the language features motivate
- aspects of the compiler design.
- After graduating from Indiana University in 2005, I went on to teach
- at the University of Colorado. I adapted the nano pass and incremental
- approaches to compiling a subset of the Python
- language~\citep{Siek:2012ab}. Python and Scheme are quite different
- on the surface but there is a large overlap in the compiler techniques
- required for the two languages. Thus, I was able to teach much of the
- same content from the Indiana compiler course. I very much enjoyed
- teaching the course organized in this way, and even better, many of
- the students learned a lot and got excited about compilers. (No, I
- didn't do a quantitative study to support this claim.)
- It is now 2016 and I too have returned to teach at Indiana University.
- In my absence the compiler course had switched from the front-to-back
- organization to a back-to-front organization. Seeing how well the
- incremental approach worked at Colorado, I found this rather
- unsatisfactory and have proceeded to reorganize the course, porting
- and adapting the structure of the Colorado course back into the land
- of Scheme. Of course, in the meantime Scheme has been superseded by
- Racket (at least in Indiana), so the course is now about implementing,
- in Racket~\citep{plt-tr}, a subset of Racket.
- This is the textbook for the incremental version of the compiler
- course at Indiana University (Spring 2016) and it is the first attempt
- to create a textbook for the Indiana compiler course. With this book
- I hope to make the Indiana compiler course available to people that
- have not had the chance to study here in person. Many of the compiler
- design decisions in this book are drawn from the assignment
- descriptions of \cite{Dybvig:2010aa}. I have captured what I think are
- the most important topics from \cite{Dybvig:2010aa} but have omitted
- topics that I think are less interesting conceptually and I have made
- simplifications to reduce complexity. In this way, this book leans
- more towards pedagogy than towards absolute efficiency. Also, the book
- differs in places where I saw the opportunity to make the topics more
- fun, such as in relating register allocation to Sudoku
- (Chapter~\ref{ch:register-allocation}).
- \section*{Prerequisites}
- This material in this book is challenging but rewarding. It is meant
- to prepare students for a lifelong career in programming languages. I
- do not recommend this book for students who only want to dabble in
- programming languages. The book uses the Racket language both for the
- implementation of the compiler and for the language that is
- compiled. Thus, a student should be proficient with Racket (or Scheme)
- prior to reading this book. There are many other excellent resources
- for learning Racket and
- Scheme~\citep{Dybvig:1987aa,Abelson:1996uq,Friedman:1996aa,Felleisen:2001aa,Felleisen:2013aa,Flatt:2014aa}. It
- is helpful but not necessary for the student to have prior exposure to
- x86 (or x86-64) assembly language, as one might obtain from a computer
- systems course~\citep{Bryant:2005aa,Bryant:2010aa}. This book
- introduces the parts of x86-64 assembly language that are needed.
- %\section*{Structure of book}
- % You might want to add short description about each chapter in this book.
- %\section*{About the companion website}
- %The website\footnote{\url{https://github.com/amberj/latex-book-template}} for %this file contains:
- %\begin{itemize}
- % \item A link to (freely downlodable) latest version of this document.
- % \item Link to download LaTeX source for this document.
- % \item Miscellaneous material (e.g. suggested readings etc).
- %\end{itemize}
- \section*{Acknowledgments}
- Need to give thanks to
- \begin{itemize}
- \item Bor-Yuh Evan Chang
- \item Kent Dybvig
- \item Daniel P. Friedman
- \item Ronald Garcia
- \item Abdulaziz Ghuloum
- \item Ryan Newton
- \item Dipanwita Sarkar
- \item Andrew Keep
- \item Oscar Waddell
- \end{itemize}
- \mbox{}\\
- \noindent Jeremy G. Siek \\
- \noindent \url{http://homes.soic.indiana.edu/jsiek} \\
- \noindent Spring 2016
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Preliminaries}
- \label{ch:trees-recur}
- In this chapter, we review the basic tools that are needed for
- implementing a compiler. We use abstract syntax trees (ASTs) in the
- form of S-expressions to represent programs (Section~\ref{sec:ast})
- and pattern matching to inspect individual nodes in an AST
- (Section~\ref{sec:pattern-matching}). We use recursion to construct
- and deconstruct entire ASTs (Section~\ref{sec:recursion}).
- \section{Abstract Syntax Trees}
- \label{sec:ast}
- The primary data structure that is commonly used for representing
- programs is the \emph{abstract syntax tree} (AST). When considering
- some part of a program, a compiler needs to ask what kind of part it
- is and what sub-parts it has. For example, the program on the left is
- represented by the AST on the right.
- \begin{center}
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (+ (read) (- 8))
- \end{lstlisting}
- \end{minipage}
- \begin{minipage}{0.4\textwidth}
- \begin{equation}
- \begin{tikzpicture}
- \node[draw, circle] (plus) at (0 , 0) {\key{+}};
- \node[draw, circle] (read) at (-1, -1.5) {{\footnotesize\key{read}}};
- \node[draw, circle] (minus) at (1 , -1.5) {$\key{-}$};
- \node[draw, circle] (8) at (1 , -3) {\key{8}};
- \draw[->] (plus) to (read);
- \draw[->] (plus) to (minus);
- \draw[->] (minus) to (8);
- \end{tikzpicture}
- \label{eq:arith-prog}
- \end{equation}
- \end{minipage}
- \end{center}
- We shall use the standard terminology for trees: each circle above is
- called a \emph{node}. The arrows connect a node to its \emph{children}
- (which are also nodes). The top-most node is the \emph{root}. Every
- node except for the root has a \emph{parent} (the node it is the child
- of). If a node has no children, it is a \emph{leaf} node. Otherwise
- it is an \emph{internal} node.
- When deciding how to compile the above program, we need to know that
- the root node operation is addition and that it has two children:
- \texttt{read} and a negation. The abstract syntax tree data structure
- directly supports these queries and hence is a good choice. In this
- book, we will often write down the textual representation of a program
- even when we really have in mind the AST because the textual
- representation is more concise. We recommend that, in your mind, you
- alway interpret programs as abstract syntax trees.
- \section{Grammars}
- \label{sec:grammar}
- A programming language can be thought of as a \emph{set} of programs.
- The set is typically infinite (one can always create larger and larger
- programs), so one cannot simply describe a language by listing all of
- the programs in the language. Instead we write down a set of rules, a
- \emph{grammar}, for building programs. We shall write our rules in a
- variant of Backus-Naur Form (BNF)~\citep{Backus:1960aa,Knuth:1964aa}.
- As an example, we describe a small language, named $R_0$, of
- integers and arithmetic operations. The first rule says that any
- integer is in the language:
- \begin{equation}
- R_0 ::= \Int \label{eq:arith-int}
- \end{equation}
- Each rule has a left-hand-side and a right-hand-side. The way to read
- a rule is that if you have all the program parts on the
- right-hand-side, then you can create and AST node and categorize it
- according to the left-hand-side. (We do not define $\Int$ because the
- reader already knows what an integer is.) We make the simplifying
- design decision that all of the languages in this book only handle
- machine-representable integers (those representable with 64-bits,
- i.e., the range $-2^{63}$ to $2^{63}$) which corresponds to the
- \texttt{fixnum} datatype in Racket. A name such as $R_0$ that is
- defined by the grammar rules is a \emph{non-terminal}.
- The second rule for the $R_0$ language is the \texttt{read}
- operation that receives an input integer from the user of the program.
- \begin{equation}
- R_0 ::= (\key{read}) \label{eq:arith-read}
- \end{equation}
- The third rule says that, given an $R_0$ node, you can build another
- $R_0$ node by negating it.
- \begin{equation}
- R_0 ::= (\key{-} \; R_0) \label{eq:arith-neg}
- \end{equation}
- Symbols such as \key{-} in typewriter font are \emph{terminal} symbols
- and must literally appear in the program for the rule to be
- applicable.
- We can apply the rules to build ASTs in the $R_0$
- language. For example, by rule \eqref{eq:arith-int}, \texttt{8} is an
- $R_0$, then by rule \eqref{eq:arith-neg}, the following AST is
- an $R_0$.
- \begin{center}
- \begin{minipage}{0.25\textwidth}
- \begin{lstlisting}
- (- 8)
- \end{lstlisting}
- \end{minipage}
- \begin{minipage}{0.25\textwidth}
- \begin{equation}
- \begin{tikzpicture}
- \node[draw, circle] (minus) at (0, 0) {$\text{--}$};
- \node[draw, circle] (8) at (0, -1.2) {$8$};
- \draw[->] (minus) to (8);
- \end{tikzpicture}
- \label{eq:arith-neg8}
- \end{equation}
- \end{minipage}
- \end{center}
- The last rule for the $R_0$ language is for addition:
- \begin{equation}
- R_0 ::= (\key{+} \; R_0 \; R_0) \label{eq:arith-add}
- \end{equation}
- Now we can see that the AST \eqref{eq:arith-prog} is in $R_0$.
- We know that \lstinline{(read)} is in $R_0$ by rule
- \eqref{eq:arith-read} and we have shown that \texttt{(- 8)} is in
- $R_0$, so we can apply rule \eqref{eq:arith-add} to show that
- \texttt{(+ (read) (- 8))} is in the $R_0$ language.
- If you have an AST for which the above four rules do not apply, then
- the AST is not in $R_0$. For example, the AST \texttt{(-
- (read) (+ 8))} is not in $R_0$ because there are no rules
- for \key{+} with only one argument, nor for \key{-} with two
- arguments. Whenever we define a language with a grammar, we
- implicitly mean for the language to be the smallest set of programs
- that are justified by the rules. That is, the language only includes
- those programs that the rules allow.
- It is common to have many rules with the same left-hand side, so there
- is a vertical bar notation for gathering several rules, as shown in
- Figure~\ref{fig:r0-syntax}. Each clause between a vertical bar is
- called an ``alternative''.
- \begin{figure}[tbp]
- \fbox{
- \begin{minipage}{\textwidth}
- \[
- R_0 ::= \Int \mid ({\tt \key{read}}) \mid (\key{-} \; R_0) \mid
- (\key{+} \; R_0 \; R_0)
- \]
- \end{minipage}
- }
- \caption{The syntax of the $R_0$ language.}
- \label{fig:r0-syntax}
- \end{figure}
- \section{S-Expressions}
- \label{sec:s-expr}
- Racket, as a descendant of Lisp, has
- convenient support for creating and manipulating abstract syntax trees
- with its \emph{symbolic expression} feature, or S-expression for
- short. We can create an S-expression simply by writing a backquote
- followed by the textual representation of the AST. (Technically
- speaking, this is called a \emph{quasiquote} in Racket.) For example,
- an S-expression to represent the AST \eqref{eq:arith-prog} is created
- by the following Racket expression:
- \begin{center}
- \texttt{`(+ (read) (- 8))}
- \end{center}
- To build larger S-expressions one often needs to splice together
- several smaller S-expressions. Racket provides the comma operator to
- splice an S-expression into a larger one. For example, instead of
- creating the S-expression for AST \eqref{eq:arith-prog} all at once,
- we could have first created an S-expression for AST
- \eqref{eq:arith-neg8} and then spliced that into the addition
- S-expression.
- \begin{lstlisting}
- (define ast1.4 `(- 8))
- (define ast1.1 `(+ (read) ,ast1.4))
- \end{lstlisting}
- In general, the Racket expression that follows the comma (splice)
- can be any expression that computes an S-expression.
- \section{Pattern Matching}
- \label{sec:pattern-matching}
- As mentioned above, one of the operations that a compiler needs to
- perform on an AST is to access the children of a node. Racket
- provides the \texttt{match} form to access the parts of an
- S-expression. Consider the following example and the output on the
- right.
- \begin{center}
- \begin{minipage}{0.5\textwidth}
- \begin{lstlisting}
- (match ast1.1
- [`(,op ,child1 ,child2)
- (print op) (newline)
- (print child1) (newline)
- (print child2)])
- \end{lstlisting}
- \end{minipage}
- \vrule
- \begin{minipage}{0.25\textwidth}
- \begin{lstlisting}
- '+
- '(read)
- '(- 8)
- \end{lstlisting}
- \end{minipage}
- \end{center}
- The \texttt{match} form takes AST \eqref{eq:arith-prog} and binds its
- parts to the three variables \texttt{op}, \texttt{child1}, and
- \texttt{child2}. In general, a match clause consists of a
- \emph{pattern} and a \emph{body}. The pattern is a quoted S-expression
- that may contain pattern-variables (preceded by a comma). The body
- may contain any Racket code.
- A \texttt{match} form may contain several clauses, as in the following
- function \texttt{leaf?} that recognizes when an $R_0$ node is
- a leaf. The \texttt{match} proceeds through the clauses in order,
- checking whether the pattern can match the input S-expression. The
- body of the first clause that matches is executed. The output of
- \texttt{leaf?} for several S-expressions is shown on the right. In the
- below \texttt{match}, we see another form of pattern: the \texttt{(?
- fixnum?)} applies the predicate \texttt{fixnum?} to the input
- S-expression to see if it is a machine-representable integer.
- \begin{center}
- \begin{minipage}{0.5\textwidth}
- \begin{lstlisting}
- (define (leaf? arith)
- (match arith
- [(? fixnum?) #t]
- [`(read) #t]
- [`(- ,c1) #f]
- [`(+ ,c1 ,c2) #f]))
- (leaf? `(read))
- (leaf? `(- 8))
- (leaf? `(+ (read) (- 8)))
- \end{lstlisting}
- \end{minipage}
- \vrule
- \begin{minipage}{0.25\textwidth}
- \begin{lstlisting}
- #t
- #f
- #f
- \end{lstlisting}
- \end{minipage}
- \end{center}
- \section{Recursion}
- \label{sec:recursion}
- Programs are inherently recursive in that an $R_0$ AST is made
- up of smaller $R_0$ ASTs. Thus, the natural way to process in
- entire program is with a recursive function. As a first example of
- such a function, we define \texttt{arith?} below, which takes an
- arbitrary S-expression, {\tt sexp}, and determines whether or not {\tt
- sexp} is in {\tt arith}. Note that each match clause corresponds to
- one grammar rule for $R_0$ and the body of each clause makes a
- recursive call for each child node. This pattern of recursive function
- is so common that it has a name, \emph{structural recursion}. In
- general, when a recursive function is defined using a sequence of
- match clauses that correspond to a grammar, and each clause body makes
- a recursive call on each child node, then we say the function is
- defined by structural recursion.
- \begin{center}
- \begin{minipage}{0.7\textwidth}
- \begin{lstlisting}
- (define (arith? sexp)
- (match sexp
- [(? fixnum?) #t]
- [`(read) #t]
- [`(- ,e) (arith? e)]
- [`(+ ,e1 ,e2)
- (and (arith? e1) (arith? e2))]
- [else #f]))
- (arith? `(+ (read) (- 8)))
- (arith? `(- (read) (+ 8)))
- \end{lstlisting}
- \end{minipage}
- \vrule
- \begin{minipage}{0.25\textwidth}
- \begin{lstlisting}
- #t
- #f
- \end{lstlisting}
- \end{minipage}
- \end{center}
- \section{Interpreters}
- \label{sec:interp-R0}
- The meaning, or semantics, of a program is typically defined in the
- specification of the language. For example, the Scheme language is
- defined in the report by \cite{SPERBER:2009aa}. The Racket language is
- defined in its reference manual~\citep{plt-tr}. In this book we use an
- interpreter to define the meaning of each language that we consider,
- following Reynold's advice in this
- regard~\citep{reynolds72:_def_interp}. Here we will warm up by writing
- an interpreter for the $R_0$ language, which will also serve
- as a second example of structural recursion. The \texttt{interp-R0}
- function is defined in Figure~\ref{fig:interp-R0}. The body of the
- function is a match on the input expression \texttt{e} and there is
- one clause per grammar rule for $R_0$. The clauses for
- internal AST nodes make recursive calls to \texttt{interp-R0} on
- each child node.
- \begin{figure}[tbp]
- \begin{lstlisting}
- (define (interp-R0 e)
- (match e
- [(? fixnum?) e]
- [`(read)
- (define r (read))
- (cond [(fixnum? r) r]
- [else (error 'interp-R0 "expected an integer" r)])]
- [`(- ,e)
- (fx- 0 (interp-R0 e))]
- [`(+ ,e1 ,e2)
- (fx+ (interp-R0 e1) (interp-R0 e2))]
- ))
- \end{lstlisting}
- \caption{Interpreter for the $R_0$ language.}
- \label{fig:interp-R0}
- \end{figure}
- Let us consider the result of interpreting some example $R_0$
- programs. The following program simply adds two integers.
- \begin{lstlisting}
- (+ 10 32)
- \end{lstlisting}
- The result is \key{42}, as you might expected.
- %
- The next example demonstrates that expressions may be nested within
- each other, in this case nesting several additions and negations.
- \begin{lstlisting}
- (+ 10 (- (+ 12 20)))
- \end{lstlisting}
- What is the result of the above program?
- If we interpret the AST \eqref{eq:arith-prog} and give it the input
- \texttt{50}
- \begin{lstlisting}
- (interp-R0 ast1.1)
- \end{lstlisting}
- we get the answer to life, the universe, and everything:
- \begin{lstlisting}
- 42
- \end{lstlisting}
- Moving on, the \key{read} operation prompts the user of the program
- for an integer. Given an input of \key{10}, the following program
- produces \key{42}.
- \begin{lstlisting}
- (+ (read) 32)
- \end{lstlisting}
- We include the \key{read} operation in $R_1$ so that a compiler for
- $R_1$ cannot be implemented simply by running the interpreter at
- compilation time to obtain the output and then generating the trivial
- code to return the output. (A clever student at Colorado did this the
- first time I taught the course.)
- %% The behavior of the following program is somewhat subtle because
- %% Racket does not specify an evaluation order for arguments of an
- %% operator such as $-$.
- %% \marginpar{\scriptsize This is not true of Racket. \\ --Jeremy}
- %% \[
- %% \BINOP{+}{\READ}{\UNIOP{-}{\READ}}
- %% \]
- %% Given the input $42$ then $10$, the above program can result in either
- %% $42$ or $-42$, depending on the whims of the Racket implementation.
- The job of a compiler is to translate a program in one language into a
- program in another language so that the output program behaves the
- same way as the input program. This idea is depicted in the following
- diagram. Suppose we have two languages, $\mathcal{L}_1$ and
- $\mathcal{L}_2$, and an interpreter for each language. Suppose that
- the compiler translates program $P_1$ in language $\mathcal{L}_1$ into
- program $P_2$ in language $\mathcal{L}_2$. Then interpreting $P_1$
- and $P_2$ on their respective interpreters with input $i$ should yield
- the same output $o$.
- \begin{equation} \label{eq:compile-correct}
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (p1) at (0, 0) {$P_1$};
- \node (p2) at (3, 0) {$P_2$};
- \node (o) at (3, -2.5) {$o$};
- \path[->] (p1) edge [above] node {compile} (p2);
- \path[->] (p2) edge [right] node {interp-$\mathcal{L}_2$($i$)} (o);
- \path[->] (p1) edge [left] node {interp-$\mathcal{L}_1$($i$)} (o);
- \end{tikzpicture}
- \end{equation}
- In the next section we see our first example of a compiler, which is
- another example of structural recursion.
- \section{Partial Evaluation}
- \label{sec:partial-evaluation}
- In this section we consider a compiler that translates $R_0$
- programs into $R_0$ programs that are more efficient, that is,
- this compiler is an optimizer. Our optimizer will accomplish this by
- trying to eagerly compute the parts of the program that do not depend
- on any inputs. For example, given the following program
- \begin{lstlisting}
- (+ (read) (- (+ 5 3)))
- \end{lstlisting}
- our compiler will translate it into the program
- \begin{lstlisting}
- (+ (read) -8)
- \end{lstlisting}
- Figure~\ref{fig:pe-arith} gives the code for a simple partial
- evaluator for the $R_0$ language. The output of the partial
- evaluator is an $R_0$ program, which we build up using a
- combination of quasiquotes and commas. (Though no quasiquote is
- necessary for integers.) In Figure~\ref{fig:pe-arith}, the normal
- structural recursion is captured in the main \texttt{pe-arith}
- function whereas the code for partially evaluating negation and
- addition is factored out the into two separate helper functions:
- \texttt{pe-neg} and \texttt{pe-add}. The input to these helper
- functions is the output of partially evaluating the children nodes.
- \begin{figure}[tbp]
- \begin{lstlisting}
- (define (pe-neg r)
- (match r
- [(? fixnum?) (fx- 0 r)]
- [else `(- ,r)]))
- (define (pe-add r1 r2)
- (match (list r1 r2)
- [`(,n1 ,n2) #:when (and (fixnum? n1) (fixnum? n2))
- (fx+ r1 r2)]
- [else `(+ ,r1 ,r2)]))
- (define (pe-arith e)
- (match e
- [(? fixnum?) e]
- [`(read) `(read)]
- [`(- ,e1) (pe-neg (pe-arith e1))]
- [`(+ ,e1 ,e2) (pe-add (pe-arith e1) (pe-arith e2))]))
- \end{lstlisting}
- \caption{A partial evaluator for the $R_0$ language.}
- \label{fig:pe-arith}
- \end{figure}
- Our code for \texttt{pe-neg} and \texttt{pe-add} implements the simple
- idea of checking whether the inputs are integers and if they are, to
- go ahead perform the arithmetic. Otherwise, we use quasiquote to
- create an AST node for the appropriate operation (either negation or
- addition) and use comma to splice in the child nodes.
- To gain some confidence that the partial evaluator is correct, we can
- test whether it produces programs that get the same result as the
- input program. That is, we can test whether it satisfies Diagram
- \eqref{eq:compile-correct}. The following code runs the partial
- evaluator on several examples and tests the output program. The
- \texttt{assert} function is defined in Appendix~\ref{appendix:utilities}.
- \begin{lstlisting}
- (define (test-pe pe p)
- (assert "testing pe-arith"
- (equal? (interp-R0 p) (interp-R0 (pe-arith p)))))
- (test-pe `(+ (read) (- (+ 5 3))))
- (test-pe `(+ 1 (+ (read) 1)))
- (test-pe `(- (+ (read) (- 5))))
- \end{lstlisting}
- \begin{exercise}
- \normalfont % I don't like the italics for exercises. -Jeremy
- We challenge the reader to improve on the simple partial evaluator in
- Figure~\ref{fig:pe-arith} by replacing the \texttt{pe-neg} and
- \texttt{pe-add} helper functions with functions that know more about
- arithmetic. For example, your partial evaluator should translate
- \begin{lstlisting}
- (+ 1 (+ (read) 1))
- \end{lstlisting}
- into
- \begin{lstlisting}
- (+ 2 (read))
- \end{lstlisting}
- To accomplish this, we recommend that your partial evaluator produce
- output that takes the form of the $\itm{residual}$ non-terminal in the
- following grammar.
- \[
- \begin{array}{lcl}
- \Exp &::=& (\key{read}) \mid (\key{-} \;(\key{read})) \mid (\key{+} \; \Exp \; \Exp)\\
- \itm{residual} &::=& \Int \mid (\key{+}\; \Int\; \Exp) \mid \Exp
- \end{array}
- \]
- \end{exercise}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Integers and Variables}
- \label{ch:int-exp}
- This chapter concerns the challenge of compiling a subset of Racket,
- which we name $R_1$, to x86-64 assembly code~\citep{Matz:2013aa}. The
- chapter begins with a description of the $R_1$ language
- (Section~\ref{sec:s0}) and then a description of x86-64
- (Section~\ref{sec:x86-64}). The x86-64 assembly language is quite
- large, so we only discuss what is needed for compiling $R_1$. We
- introduce more of x86-64 in later chapters. Once we have introduced
- $R_1$ and x86-64, we reflect on their differences and come up with a
- plan breaking down the translation from $R_1$ to x86-64 into a handful
- of steps (Section~\ref{sec:plan-s0-x86}). The rest of the sections in
- this Chapter give detailed hints regarding each step
- (Sections~\ref{sec:uniquify-s0} through \ref{sec:patch-s0}). We hope
- to give enough hints that the well-prepared reader can implement a
- compiler from $R_1$ to x86-64 while at the same time leaving room for
- some fun and creativity.
- \section{The $R_1$ Language}
- \label{sec:s0}
- The $R_1$ language extends the $R_0$ language
- (Figure~\ref{fig:r0-syntax}) with variable definitions. The syntax of
- the $R_1$ language is defined by the grammar in
- Figure~\ref{fig:r1-syntax}. In addition to variable definitions, the
- $R_1$ language includes the \key{program} form to mark the top of the
- program, which is helpful in some of the compiler passes. The $R_1$
- language is rich enough to exhibit several compilation techniques but
- simple enough so that the reader can implement a compiler for it in a
- couple weeks of part-time work. To give the reader a feeling for the
- scale of this first compiler, the instructor solution for the $R_1$
- compiler consists of 6 recursive functions and a few small helper
- functions that together span 256 lines of code.
- \begin{figure}[btp]
- \centering
- \fbox{
- \begin{minipage}{\textwidth}
- \begin{align*}
- \Exp &::= \Int \mid ({\tt \key{read}}) \mid (\key{-} \; \Exp) \mid
- (\key{+} \; \Exp \; \Exp) \mid \Var \mid \LET{\Var}{\Exp}{\Exp} \\
- R_1 &::= (\key{program} \; () \; \Exp)
- \end{align*}
- \end{minipage}
- }
- \caption{The syntax of the $R_1$ language.
- The non-terminal \Var{} may be any Racket identifier.}
- \label{fig:r1-syntax}
- \end{figure}
- The \key{let} construct defines a variable for use within its body
- and initializes the variable with the value of an expression. So the
- following program initializes \code{x} to \code{32} and then evaluates
- the body \code{(+ 10 x)}, producing \code{42}.
- \begin{lstlisting}
- (program ()
- (let ([x (+ 12 20)]) (+ 10 x)))
- \end{lstlisting}
- When there are multiple \key{let}'s for the same variable, the closest
- enclosing \key{let} is used. That is, variable definitions overshadow
- prior definitions. Consider the following program with two \key{let}'s
- that define variables named \code{x}. Can you figure out the result?
- \begin{lstlisting}
- (program ()
- (let ([x 32]) (+ (let ([x 10]) x) x)))
- \end{lstlisting}
- For the purposes of showing which variable uses correspond to which
- definitions, the following shows the \code{x}'s annotated with subscripts
- to distinguish them. Double check that your answer for the above is
- the same as your answer for this annotated version of the program.
- \begin{lstlisting}
- (program ()
- (let ([x|$_1$| 32]) (+ (let ([x|$_2$| 10]) x|$_2$|) x|$_1$|)))
- \end{lstlisting}
- The initializing expression is always evaluated before the body of the
- \key{let}, so in the following, the \key{read} for \code{x} is
- performed before the \key{read} for \code{y}. Given the input
- \code{52} then \code{10}, the following produces \code{42} (and not
- \code{-42}).
- \begin{lstlisting}
- (program ()
- (let ([x (read)]) (let ([y (read)]) (- x y))))
- \end{lstlisting}
- Figure~\ref{fig:interp-R1} shows the interpreter for the $R_1$
- language. It extends the interpreter for $R_0$ with two new
- \key{match} clauses for variables and for \key{let}. For \key{let},
- we will need a way to communicate the initializing value of a variable
- to all the uses of a variable. To accomplish this, we maintain a
- mapping from variables to values, which is traditionally called an
- \emph{environment}. For simplicity, here we use an association list to
- represent the environment. The \code{interp-R1} function takes the
- current environment, \code{env}, as an extra parameter. When the
- interpreter encounters a variable, it finds the corresponding value
- using the \code{lookup} function (Appendix~\ref{appendix:utilities}).
- When the interpreter encounters a \key{let}, it evaluates the
- initializing expression, extends the environment with the result bound
- to the variable, then evaluates the body of the \key{let}.
- \begin{figure}[tbp]
- \begin{lstlisting}
- (define (interp-R1 env e)
- (match e
- [(? symbol?) (lookup e env)]
- [`(let ([,x ,e]) ,body)
- (define v (interp-R1 env e))
- (define new-env (cons (cons x v) env))
- (interp-R1 new-env body)]
- [(? fixnum?) e]
- [`(read)
- (define r (read))
- (cond [(fixnum? r) r]
- [else (error 'interp-R1 "expected an integer" r)])]
- [`(- ,e)
- (fx- 0 (interp-R1 env e))]
- [`(+ ,e1 ,e2)
- (fx+ (interp-R1 env e1) (interp-R1 env e2))]
- [`(program () ,e) (interp-R1 '() e)]
- ))
- \end{lstlisting}
- \caption{Interpreter for the $R_1$ language.}
- \label{fig:interp-R1}
- \end{figure}
- The goal for this chapter is to implement a compiler that translates
- any program $P_1$ in the $R_1$ language into an x86-64 assembly
- program $P_2$ such that $P_2$ exhibits the same behavior on an x86
- computer as the $R_1$ program running in a Racket implementation.
- That is, they both output the same integer $n$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (p1) at (0, 0) {$P_1$};
- \node (p2) at (4, 0) {$P_2$};
- \node (o) at (4, -2) {$n$};
- \path[->] (p1) edge [above] node {\footnotesize compile} (p2);
- \path[->] (p1) edge [left] node {\footnotesize interp-$R_1$} (o);
- \path[->] (p2) edge [right] node {\footnotesize interp-x86} (o);
- \end{tikzpicture}
- \]
- In the next section we introduce enough of the x86-64 assembly
- language to compile $R_1$.
- \section{The x86-64 Assembly Language}
- \label{sec:x86-64}
- An x86-64 program is a sequence of instructions. The instructions may
- refer to integer constants (called \emph{immediate values}), variables
- called \emph{registers}, and instructions may load and store values
- into \emph{memory}. Memory is a mapping of 64-bit addresses to 64-bit
- values. Figure~\ref{fig:x86-a} defines the syntax for the subset of
- the x86-64 assembly language needed for this chapter. (We use the
- AT\&T syntax that is expected by the GNU assembler inside \key{gcc}.)
- An immediate value is written using the notation \key{\$}$n$ where $n$
- is an integer.
- %
- A register is written with a \key{\%} followed by the register name,
- such as \key{\%rax}.
- %
- An access to memory is specified using the syntax $n(\key{\%}r)$,
- which reads register $r$, obtaining address $a$, and then offsets the
- address by $n$ bytes (8 bits), producing the address $a + n$. The
- address is then used to either load or store to memory depending on
- whether it occurs as a source or destination argument of an
- instruction.
- An arithmetic instruction, such as $\key{addq}\,s\,d$, reads from the
- source $s$ and destination $d$, applies the arithmetic operation, then
- write the result in $d$. So the \key{addq} instruction computes $d
- \gets d + s$.
- %
- The move instruction, $\key{movq}\,s\,d$ reads from $s$ and stores the
- result in $d$.
- %
- The $\key{callq}\,\mathit{label}$ instruction executes the procedure
- specified by the label, which we shall use to implement
- \key{read}.
- \begin{figure}[tbp]
- \fbox{
- \begin{minipage}{0.96\textwidth}
- \[
- \begin{array}{lcl}
- \Reg &::=& \key{rsp} \mid \key{rbp} \mid \key{rax} \mid \key{rbx} \mid \key{rcx}
- \mid \key{rdx} \mid \key{rsi} \mid \key{rdi} \mid \\
- && \key{r8} \mid \key{r9} \mid \key{r10}
- \mid \key{r11} \mid \key{r12} \mid \key{r13}
- \mid \key{r14} \mid \key{r15} \\
- \Arg &::=& \key{\$}\Int \mid \key{\%}\Reg \mid \Int(\key{\%}\Reg) \\
- \Instr &::=& \key{addq} \; \Arg, \Arg \mid
- \key{subq} \; \Arg, \Arg \mid
- % \key{imulq} \; \Arg,\Arg \mid
- \key{negq} \; \Arg \mid \key{movq} \; \Arg, \Arg \mid \\
- && \key{callq} \; \mathit{label} \mid
- \key{pushq}\;\Arg \mid \key{popq}\;\Arg \mid \key{retq} \\
- \Prog &::= & \key{.globl \_main}\\
- & & \key{\_main:} \; \Instr^{+}
- \end{array}
- \]
- \end{minipage}
- }
- \caption{A subset of the x86-64 assembly language (AT\&T syntax).}
- \label{fig:x86-a}
- \end{figure}
- \begin{wrapfigure}{r}{2.25in}
- \begin{lstlisting}
- .globl _main
- _main:
- movq $10, %rax
- addq $32, %rax
- retq
- \end{lstlisting}
- \caption{An x86-64 program equivalent to $\BINOP{+}{10}{32}$.}
- \label{fig:p0-x86}
- \end{wrapfigure}
- Figure~\ref{fig:p0-x86} depicts an x86-64 program that is equivalent
- to \code{(+ 10 32)}. The \key{globl} directive says that the
- \key{\_main} procedure is externally visible, which is necessary so
- that the operating system can call it. The label \key{\_main:}
- indicates the beginning of the \key{\_main} procedure which is where
- the operating system starting executing this program. The instruction
- \lstinline{movq $10, %rax} puts $10$ into register \key{rax}. The
- following instruction \lstinline{addq $32, %rax} adds $32$ to the
- $10$ in \key{rax} and puts the result, $42$, back into
- \key{rax}. The instruction \key{retq} finishes the \key{\_main}
- function by returning the integer in the \key{rax} register to the
- operating system.
- \begin{wrapfigure}{r}{2.25in}
- \begin{lstlisting}
- .globl _main
- _main:
- pushq %rbp
- movq %rsp, %rbp
- subq $16, %rsp
- movq $10, -8(%rbp)
- negq -8(%rbp)
- movq $52, %rax
- addq -8(%rbp), %rax
- addq $16, %rsp
- popq %rbp
- retq
- \end{lstlisting}
- \caption{An x86-64 program equivalent to $\BINOP{+}{52}{\UNIOP{-}{10} }$.}
- \label{fig:p1-x86}
- \end{wrapfigure}
- The next example exhibits the use of memory. Figure~\ref{fig:p1-x86}
- lists an x86-64 program that is equivalent to $\BINOP{+}{52}{
- \UNIOP{-}{10} }$. To understand how this x86-64 program works, we
- need to explain a region of memory called called the \emph{procedure
- call stack} (or \emph{stack} for short). The stack consists of a
- separate \emph{frame} for each procedure call. The memory layout for
- an individual frame is shown in Figure~\ref{fig:frame}. The register
- \key{rsp} is called the \emph{stack pointer} and points to the item at
- the top of the stack. The stack grows downward in memory, so we
- increase the size of the stack by subtracting from the stack
- pointer. The frame size is required to be a multiple of 16 bytes. The
- register \key{rbp} is the \emph{base pointer} which serves two
- purposes: 1) it saves the location of the stack pointer for the
- procedure that called the current one and 2) it is used to access
- variables associated with the current procedure. We number the
- variables from $1$ to $n$. Variable $1$ is stored at address
- $-8\key{(\%rbp)}$, variable $2$ at $-16\key{(\%rbp)}$, etc.
- \begin{figure}[tbp]
- \centering
- \begin{tabular}{|r|l|} \hline
- Position & Contents \\ \hline
- 8(\key{\%rbp}) & return address \\
- 0(\key{\%rbp}) & old \key{rbp} \\
- -8(\key{\%rbp}) & variable $1$ \\
- -16(\key{\%rbp}) & variable $2$ \\
- \ldots & \ldots \\
- 0(\key{\%rsp}) & variable $n$\\ \hline
- \end{tabular}
- \caption{Memory layout of a frame.}
- \label{fig:frame}
- \end{figure}
- Getting back to the program in Figure~\ref{fig:p1-x86}, the first
- three instructions are the typical prelude for a procedure. The
- instruction \key{pushq \%rbp} saves the base pointer for the procedure
- that called the current one onto the stack and subtracts $8$ from the
- stack pointer. The second instruction \key{movq \%rsp, \%rbp} changes
- the base pointer to the top of the stack. The instruction \key{subq
- \$16, \%rsp} moves the stack pointer down to make enough room for
- storing variables. This program just needs one variable ($8$ bytes)
- but because the frame size is required to be a multiple of 16 bytes,
- it rounds to 16 bytes.
- The next four instructions carry out the work of computing
- $\BINOP{+}{52}{\UNIOP{-}{10} }$. The first instruction \key{movq \$10,
- -8(\%rbp)} stores $10$ in variable $1$. The instruction \key{negq
- -8(\%rbp)} changes variable $1$ to $-10$. The \key{movq \$52, \%rax}
- places $52$ in the register \key{rax} and \key{addq -8(\%rbp), \%rax}
- adds the contents of variable $1$ to \key{rax}, at which point
- \key{rax} contains $42$.
- The last three instructions are the typical \emph{conclusion} of a
- procedure. These instructions are necessary to get the state of the
- machine back to where it was before the current procedure was called.
- The \key{addq \$16, \%rsp} instruction moves the stack pointer back to
- point at the old base pointer. The amount added here needs to match
- the amount that was subtracted in the prelude of the procedure. Then
- \key{popq \%rbp} returns the old base pointer to \key{rbp} and adds
- $8$ to the stack pointer. The \key{retq} instruction jumps back to
- the procedure that called this one and subtracts 8 from the stack
- pointer.
- The compiler will need a convenient representation for manipulating
- x86 programs, so we define an abstract syntax for x86 in
- Figure~\ref{fig:x86-ast-a}. The \itm{info} field of the \key{program}
- AST node is for storing auxilliary information that needs to be
- communicated from one step of the compiler to the next.
- \begin{figure}[tbp]
- \fbox{
- \begin{minipage}{\textwidth}
- \[
- \begin{array}{lcl}
- \Arg &::=& \INT{\Int} \mid \REG{\itm{register}}
- \mid \STACKLOC{\Int} \\
- \Instr &::=& (\key{addq} \; \Arg\; \Arg) \mid
- (\key{subq} \; \Arg\; \Arg) \mid
- % (\key{imulq} \; \Arg\;\Arg) \mid
- (\key{negq} \; \Arg) \mid (\key{movq} \; \Arg\; \Arg) \\
- &\mid& (\key{call} \; \mathit{label}) \mid
- (\key{pushq}\;\Arg) \mid
- (\key{popq}\;\Arg) \mid
- (\key{retq}) \\
- \Prog &::= & (\key{program} \;\itm{info} \; \Instr^{+})
- \end{array}
- \]
- \end{minipage}
- }
- \caption{Abstract syntax for x86-64 assembly.}
- \label{fig:x86-ast-a}
- \end{figure}
- \section{Planning the trip from $R_1$ to x86-64}
- \label{sec:plan-s0-x86}
- To compile one language to another it helps to focus on the
- differences between the two languages. It is these differences that
- the compiler will need to bridge. What are the differences between
- $R_1$ and x86-64 assembly? Here we list some of the most important the
- differences.
- \begin{enumerate}
- \item x86-64 arithmetic instructions typically take two arguments and
- update the second argument in place. In contrast, $R_1$ arithmetic
- operations only read their arguments and produce a new value.
- \item An argument to an $R_1$ operator can be any expression, whereas
- x86-64 instructions restrict their arguments to integers, registers,
- and memory locations.
- \item An $R_1$ program can have any number of variables whereas x86-64
- has only 16 registers.
- \item Variables in $R_1$ can overshadow other variables with the same
- name. The registers and memory locations of x86-64 all have unique
- names.
- \end{enumerate}
- We ease the challenge of compiling from $R_1$ to x86 by breaking down
- the problem into several steps, dealing with the above differences one
- at a time. The main question then becomes: in what order do we tackle
- these differences? This is often one of the most challenging questions
- that a compiler writer must answer because some orderings may be much
- more difficult to implement than others. It is difficult to know ahead
- of time which orders will be better so often some trial-and-error is
- involved. However, we can try to plan ahead and choose the orderings
- based on this planning.
- For example, to handle difference \#2 (nested expressions), we shall
- introduce new variables and pull apart the nested expressions into a
- sequence of assignment statements. To deal with difference \#3 we
- will be replacing variables with registers and/or stack
- locations. Thus, it makes sense to deal with \#2 before \#3 so that
- \#3 can replace both the original variables and the new ones. Next,
- consider where \#1 should fit in. Because it has to do with the format
- of x86 instructions, it makes more sense after we have flattened the
- nested expressions (\#2). Finally, when should we deal with \#4
- (variable overshadowing)? We shall solve this problem by renaming
- variables to make sure they have unique names. Recall that our plan
- for \#2 involves moving nested expressions, which could be problematic
- if it changes the shadowing of variables. However, if we deal with \#4
- first, then it will not be an issue. Thus, we arrive at the following
- ordering.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \foreach \i/\p in {4/1,2/2,1/3,3/4}
- {
- \node (\i) at (\p*1.5,0) {$\i$};
- }
- \foreach \x/\y in {4/2,2/1,1/3}
- {
- \draw[->] (\x) to (\y);
- }
- \end{tikzpicture}
- \]
- We further simplify the translation from $R_1$ to x86 by identifying
- an intermediate language named $C_0$, roughly half-way between $R_1$
- and x86, to provide a rest stop along the way. We name the language
- $C_0$ because it is vaguely similar to the $C$
- language~\citep{Kernighan:1988nx}. The differences \#4 and \#1,
- regarding variables and nested expressions, will be handled by two
- steps, \key{uniquify} and \key{flatten}, which bring us to
- $C_0$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \foreach \i/\p in {R_1/1,R_1/2,C_0/3}
- {
- \node (\p) at (\p*3,0) {\large $\i$};
- }
- \foreach \x/\y/\lbl in {1/2/uniquify,2/3/flatten}
- {
- \path[->,bend left=15] (\x) edge [above] node {\ttfamily\footnotesize \lbl} (\y);
- }
- \end{tikzpicture}
- \]
- Each of these steps in the compiler is implemented by a function,
- typically a structurally recursive function that translates an input
- AST into an output AST. We refer to such a function as a \emph{pass}
- because it makes a pass over, i.e. traverses, the entire AST.
- The syntax for $C_0$ is defined in Figure~\ref{fig:c0-syntax}. The
- $C_0$ language supports the same operators as $R_1$ but the arguments
- of operators are now restricted to just variables and integers. The
- \key{let} construct of $R_1$ is replaced by an assignment statement
- and there is a \key{return} construct to specify the return value of
- the program. A program consists of a sequence of statements that
- include at least one \key{return} statement. Each program is also
- annotated with a list of variables. At the start of the program, these
- variables are uninitialized (they contain garbage) and each variable
- becomes initialized on its first assignment. All of the variables used
- in the program must be present in this list.
- \begin{figure}[tbp]
- \fbox{
- \begin{minipage}{0.96\textwidth}
- \[
- \begin{array}{lcl}
- \Arg &::=& \Int \mid \Var \\
- \Exp &::=& \Arg \mid (\Op \; \Arg^{*})\\
- \Stmt &::=& \ASSIGN{\Var}{\Exp} \mid \RETURN{\Arg} \\
- \Prog & ::= & (\key{program}\;(\Var^{*})\;\Stmt^{+})
- \end{array}
- \]
- \end{minipage}
- }
- \caption{The $C_0$ intermediate language.}
- \label{fig:c0-syntax}
- \end{figure}
- To get from $C_0$ to x86-64 assembly it remains for us to handle
- difference \#1 (the format of instructions) and difference \#3
- (variables versus registers). These two differences are intertwined,
- creating a bit of a Gordian Knot. To handle difference \#3, we need to
- map some variables to registers (there are only 16 registers) and the
- remaining variables to locations on the stack (which is unbounded). To
- make good decisions regarding this mapping, we need the program to be
- close to its final form (in x86-64 assembly) so we know exactly when
- which variables are used. After all, variables that are used in
- disjoint parts of the program can be assigned to the same register.
- However, our choice of x86-64 instructions depends on whether the
- variables are mapped to registers or stack locations, so we have a
- circular dependency. We cut this knot by doing an optimistic selection
- of instructions in the \key{select-instructions} pass, followed by the
- \key{assign-homes} pass to map variables to registers or stack
- locations, and conclude by finalizing the instruction selection in the
- \key{patch-instructions} pass.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (1) at (0,0) {\large $C_0$};
- \node (2) at (3,0) {\large $\text{x86}^{*}$};
- \node (3) at (6,0) {\large $\text{x86}^{*}$};
- \node (4) at (9,0) {\large $\text{x86}$};
- \path[->,bend left=15] (1) edge [above] node {\ttfamily\footnotesize select-instr.} (2);
- \path[->,bend left=15] (2) edge [above] node {\ttfamily\footnotesize assign-homes} (3);
- \path[->,bend left=15] (3) edge [above] node {\ttfamily\footnotesize patch-instr.} (4);
- \end{tikzpicture}
- \]
- The \key{select-instructions} pass is optimistic in the sense that it
- treats variables as if they were all mapped to registers. The
- \key{select-instructions} pass generates a program that consists of
- x86-64 instructions but that still use variables, so it is an
- intermediate language that is technically different than x86-64, which
- explains the astericks in the diagram above.
- In this Chapter we shall take the easy road to implementing
- \key{assign-homes} and simply map all variables to stack locations.
- The topic of Chapter~\ref{ch:register-allocation} is implementing a
- smarter approach in which we make a best-effort to map variables to
- registers, resorting to the stack only when necessary.
- %% \marginpar{\scriptsize I'm confused: shouldn't `select instructions' do this?
- %% After all, that selects the x86-64 instructions. Even if it is separate,
- %% if we perform `patching' before register allocation, we aren't forced to rely on
- %% \key{rax} as much. This can ultimately make a more-performant result. --
- %% Cam}
- Once variables have been assigned to their homes, we can finalize the
- instruction selection by dealing with an indiosycracy of x86
- assembly. Many x86 instructions have two arguments but only one of the
- arguments may be a memory reference (and the stack is a part of
- memory). Because some variables may get mapped to stack locations,
- some of our generated instructions may violate this restriction. The
- purpose of the \key{patch-instructions} pass is to fix this problem by
- replacing every violating instruction with a short sequence of
- instructions that use the \key{rax} register. Once we have implemented
- a good register allocator (Chapter~\ref{ch:register-allocation}), the
- need to patch instructions will be relatively rare.
- \section{Uniquify Variables}
- \label{sec:uniquify-s0}
- The purpose of this pass is to make sure that each \key{let} uses a
- unique variable name. For example, the \code{uniquify} pass should
- translate the program on the left into the program on the right. \\
- \begin{tabular}{lll}
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (program ()
- (let ([x 32])
- (+ (let ([x 10]) x) x)))
- \end{lstlisting}
- \end{minipage}
- &
- $\Rightarrow$
- &
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (program ()
- (let ([x.1 32])
- (+ (let ([x.2 10]) x.2) x.1)))
- \end{lstlisting}
- \end{minipage}
- \end{tabular} \\
- %
- The following is another example translation, this time of a program
- with a \key{let} nested inside the initializing expression of another
- \key{let}.\\
- \begin{tabular}{lll}
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (program ()
- (let ([x (let ([x 4])
- (+ x 1))])
- (+ x 2)))
- \end{lstlisting}
- \end{minipage}
- &
- $\Rightarrow$
- &
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (program ()
- (let ([x.2 (let ([x.1 4])
- (+ x.1 1))])
- (+ x.2 2)))
- \end{lstlisting}
- \end{minipage}
- \end{tabular}
- We recommend implementing \code{uniquify} as a structurally recursive
- function that mostly copies the input program. However, when
- encountering a \key{let}, it should generate a unique name for the
- variable (the Racket function \code{gensym} is handy for this) and
- associate the old name with the new unique name in an association
- list. The \code{uniquify} function will need to access this
- association list when it gets to a variable reference, so we add
- another parameter to \code{uniquify} for the association list. It is
- quite common for a compiler pass to need a map to store extra
- information about variables. Such maps are often called \emph{symbol
- tables}.
- The skeleton of the \code{uniquify} function is shown in
- Figure~\ref{fig:uniquify-s0}. The function is curried so that it is
- convenient to partially apply it to an association list and then apply
- it to different expressions, as in the last clause for primitive
- operations in Figure~\ref{fig:uniquify-s0}. In the last \key{match}
- clause for the primitive operators, note the use of the comma-@
- operator to splice a list of S-expressions into an enclosing
- S-expression.
- \begin{exercise}
- \normalfont % I don't like the italics for exercises. -Jeremy
- Complete the \code{uniquify} pass by filling in the blanks, that is,
- implement the clauses for variables and for the \key{let} construct.
- \end{exercise}
- \begin{figure}[tbp]
- \begin{lstlisting}
- (define uniquify
- (lambda (alist)
- (lambda (e)
- (match e
- [(? symbol?) ___]
- [(? integer?) e]
- [`(let ([,x ,e]) ,body) ___]
- [`(program ,info ,e)
- `(program ,info ,((uniquify alist) e))]
- [`(,op ,es ...)
- `(,op ,@(map (uniquify alist) es))]
- ))))
- \end{lstlisting}
- \caption{Skeleton for the \key{uniquify} pass.}
- \label{fig:uniquify-s0}
- \end{figure}
- \begin{exercise}
- \normalfont % I don't like the italics for exercises. -Jeremy
- Test your \key{uniquify} pass by creating five example $R_1$ programs
- and checking whether the output programs produce the same result as
- the input programs. The $R_1$ programs should be designed to test the
- most interesting parts of the \key{uniquify} pass, that is, the
- programs should include \key{let} constructs, variables, and variables
- that overshadow each other. The five programs should be in a
- subdirectory named \key{tests} and they should have the same file name
- except for a different integer at the end of the name, followed by the
- ending \key{.scm}. Use the \key{interp-tests} function
- (Appendix~\ref{appendix:utilities}) from \key{utilities.rkt} to test
- your \key{uniquify} pass on the example programs.
- %% You can use the interpreter \key{interpret-S0} defined in the
- %% \key{interp.rkt} file. The entire sequence of tests should be a short
- %% Racket program so you can re-run all the tests by running the Racket
- %% program. We refer to this as the \emph{regression test} program.
- \end{exercise}
- \section{Flatten Expressions}
- \label{sec:flatten-s0}
- The \key{flatten} pass will transform $R_1$ programs into $C_0$
- programs. In particular, the purpose of the \key{flatten} pass is to
- get rid of nested expressions, such as the $\UNIOP{-}{10}$ in the
- following program.
- \begin{lstlisting}
- (program ()
- (+ 52 (- 10)))
- \end{lstlisting}
- This can be accomplished by introducing a new variable, assigning the
- nested expression to the new variable, and then using the new variable
- in place of the nested expressions. For example, the above program is
- translated to the following one.
- \begin{lstlisting}
- (program (x y)
- (assign x (- 10))
- (assign y (+ 52 x))
- (return y))
- \end{lstlisting}
- We recommend implementing \key{flatten} as a structurally recursive
- function that returns two things, 1) the newly flattened expression,
- and 2) a list of assignment statements, one for each of the new
- variables introduced while flattening the expression. You can return
- multiple things from a function using the \key{values} form and you
- can receive multiple things from a function call using the
- \key{define-values} form. If you are not familiar with these
- constructs, the Racket documentation will be of help.
- Take special care for programs such as the following that initialize
- variables with integers or other variables. It should be translated
- to the program on the right \\
- \begin{tabular}{lll}
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (let ([a 42])
- (let ([b a])
- b))
- \end{lstlisting}
- \end{minipage}
- &
- $\Rightarrow$
- &
- \begin{minipage}{0.4\textwidth}
- \begin{lstlisting}
- (program (a b)
- (assign a 42)
- (assign b a)
- (return b))
- \end{lstlisting}
- \end{minipage}
- \end{tabular} \\
- and not to the following, which could result from a naive
- implementation of \key{flatten}.
- \begin{lstlisting}
- (program (x.1 a x.2 b)
- (assign x.1 42)
- (assign a x.1)
- (assign x.2 a)
- (assign b x.2)
- (return b))
- \end{lstlisting}
- \begin{exercise}
- \normalfont
- Implement the \key{flatten} pass and test it on all of the example
- programs that you created to test the \key{uniquify} pass and create
- three new example programs that are designed to exercise all of the
- interesting code in the \key{flatten} pass. Use the \key{interp-tests}
- function (Appendix~\ref{appendix:utilities}) from \key{utilities.rkt} to
- test your passes on the example programs.
- \end{exercise}
- \section{Select Instructions}
- \label{sec:select-s0}
- In the \key{select-instructions} pass we begin the work of
- translating from $C_0$ to x86. The target language of this pass is a
- pseudo-x86 language that still uses variables, so we add an AST node
- of the form $\VAR{\itm{var}}$ to the x86 abstract syntax. The
- \key{select-instructions} pass deals with the differing format of
- arithmetic operations. For example, in $C_0$ an addition operation
- could take the following form:
- \[
- \ASSIGN{x}{ \BINOP{+}{10}{32} }
- \]
- To translate to x86, we need to express this addition using the
- \key{addq} instruction that does an inplace update. So we first move
- $10$ to $x$ then perform the \key{addq}.
- \[
- (\key{mov}\,\INT{10}\, \VAR{x})\; (\key{addq} \;\INT{32}\; \VAR{x})
- \]
- There are some cases that require special care to avoid generating
- needlessly complicated code. If one of the arguments is the same as
- the left-hand side of the assignment, then there is no need for the
- extra move instruction. For example, the following
- \[
- \ASSIGN{x}{ \BINOP{+}{10}{x} }
- \quad\text{should translate to}\quad
- (\key{addq} \; \INT{10}\; \VAR{x})
- \]
- Regarding the \RETURN{e} statement of $C_0$, we recommend treating it
- as an assignment to the \key{rax} register and let the procedure
- conclusion handle the transfer of control back to the calling
- procedure.
- \section{Assign Homes}
- \label{sec:assign-s0}
- As discussed in Section~\ref{sec:plan-s0-x86}, the
- \key{assign-homes} pass places all of the variables on the stack.
- Consider again the example $R_1$ program $\BINOP{+}{52}{ \UNIOP{-}{10} }$,
- which after \key{select-instructions} looks like the following.
- \[
- \begin{array}{l}
- (\key{movq}\;\INT{10}\; \VAR{x})\\
- (\key{negq}\; \VAR{x})\\
- (\key{movq}\; \INT{52}\; \REG{\itm{rax}})\\
- (\key{addq}\; \VAR{x} \REG{\itm{rax}})
- \end{array}
- \]
- The one and only variable $x$ is assigned to stack location
- \key{-8(\%rbp)}, so the \key{assign-homes} pass translates the
- above to
- \[
- \begin{array}{l}
- (\key{movq}\;\INT{10}\; \STACKLOC{{-}8})\\
- (\key{negq}\; \STACKLOC{{-}8})\\
- (\key{movq}\; \INT{52}\; \REG{\itm{rax}})\\
- (\key{addq}\; \STACKLOC{{-}8}\; \REG{\itm{rax}})
- \end{array}
- \]
- In the process of assigning stack locations to variables, it is
- convenient to compute and store the size of the frame which will be
- needed later to generate the procedure conclusion.
- \section{Patch Instructions}
- \label{sec:patch-s0}
- The purpose of this pass is to make sure that each instruction adheres
- to the restrictions regarding which arguments can be memory
- references. For most instructions, the rule is that at most one
- argument may be a memory reference.
- Consider again the following example.
- \[
- \LET{a}{42}{ \LET{b}{a}{ b }}
- \]
- After \key{assign-homes} pass, the above has been translated to
- \[
- \begin{array}{l}
- (\key{movq} \;\INT{42}\; \STACKLOC{{-}8})\\
- (\key{movq}\;\STACKLOC{{-}8}\; \STACKLOC{{-}16})\\
- (\key{movq}\;\STACKLOC{{-}16}\; \REG{\itm{rax}})
- \end{array}
- \]
- The second \key{movq} instruction is problematic because both arguments
- are stack locations. We suggest fixing this problem by moving from the
- source to \key{rax} and then from \key{rax} to the destination, as
- follows.
- \[
- \begin{array}{l}
- (\key{movq} \;\INT{42}\; \STACKLOC{{-}8})\\
- (\key{movq}\;\STACKLOC{{-}8}\; \REG{\itm{rax}})\\
- (\key{movq}\;\REG{\itm{rax}}\; \STACKLOC{{-}16})\\
- (\key{movq}\;\STACKLOC{{-}16}\; \REG{\itm{rax}})
- \end{array}
- \]
- %% The \key{imulq} instruction is a special case because the destination
- %% argument must be a register.
- \section{Print x86-64}
- \label{sec:print-x86}
- The last step of the compiler from $R_1$ to x86-64 is to convert the
- x86-64 AST (defined in Figure~\ref{fig:x86-ast-a}) to the string
- representation (defined in Figure~\ref{fig:x86-a}). The Racket
- \key{format} and \key{string-append} functions are useful in this
- regard. The main work that this step needs to perform is to create the
- \key{\_main} function and the standard instructions for its prelude
- and conclusion, as described in Section~\ref{sec:x86-64}. You need to
- know the number of stack-allocated variables, which is convenient to
- compute in the \key{assign-homes} pass (Section~\ref{sec:assign-s0})
- and then store in the $\itm{info}$ field of the \key{program}.
- %% \section{Testing with Interpreters}
- %% The typical way to test a compiler is to run the generated assembly
- %% code on a diverse set of programs and check whether they behave as
- %% expected. However, when a compiler is structured as our is, with many
- %% passes, when there is an error in the generated assembly code it can
- %% be hard to determine which pass contains the source of the error. A
- %% good way to isolate the error is to not only test the generated
- %% assembly code but to also test the output of every pass. This requires
- %% having interpreters for all the intermediate languages. Indeed, the
- %% file \key{interp.rkt} in the supplemental code provides interpreters
- %% for all the intermediate languages described in this book, starting
- %% with interpreters for $R_1$, $C_0$, and x86 (in abstract syntax).
- %% The file \key{run-tests.rkt} automates the process of running the
- %% interpreters on the output programs of each pass and checking their
- %% result.
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Register Allocation}
- \label{ch:register-allocation}
- In Chapter~\ref{ch:int-exp} we simplified the generation of x86-64
- assembly by placing all variables on the stack. We can improve the
- performance of the generated code considerably if we instead try to
- place as many variables as possible into registers. The CPU can
- access a register in a single cycle, whereas accessing the stack can
- take from several cycles (to go to cache) to hundreds of cycles (to go
- to main memory). Figure~\ref{fig:reg-eg} shows a program with four
- variables that serves as a running example. We show the source program
- and also the output of instruction selection. At that point the
- program is almost x86-64 assembly but not quite; it still contains
- variables instead of stack locations or registers.
- \begin{figure}
- \begin{minipage}{0.45\textwidth}
- Source program:
- \begin{lstlisting}
- (let ([v 1])
- (let ([w 46])
- (let ([x (+ v 7)])
- (let ([y (+ 4 x)])
- (let ([z (+ x w)])
- (- z y))))))
- \end{lstlisting}
- \end{minipage}
- \begin{minipage}{0.45\textwidth}
- After instruction selection:
- \begin{lstlisting}
- (program (v w x y z)
- (movq (int 1) (var v))
- (movq (int 46) (var w))
- (movq (var v) (var x))
- (addq (int 7) (var x))
- (movq (var x) (var y))
- (addq (int 4) (var y))
- (movq (var x) (var z))
- (addq (var w) (var z))
- (movq (var z) (reg rax))
- (subq (var y) (reg rax)))
- \end{lstlisting}
- \end{minipage}
- \caption{Running example for this chapter.}
- \label{fig:reg-eg}
- \end{figure}
- The goal of register allocation is to fit as many variables into
- registers as possible. It is often the case that we have more
- variables than registers, so we can't naively map each variable to a
- register. Fortunately, it is also common for different variables to be
- needed during different periods of time, and in such cases the
- variables can be mapped to the same register. Consider variables $x$
- and $y$ in Figure~\ref{fig:reg-eg}. After the variable $x$ is moved
- to $z$ it is no longer needed. Variable $y$, on the other hand, is
- used only after this point, so $x$ and $y$ could share the same
- register. The topic of the next section is how we compute where a
- variable is needed.
- \section{Liveness Analysis}
- A variable is \emph{live} if the variable is used at some later point
- in the program and there is not an intervening assignment to the
- variable.
- %
- To understand the latter condition, consider the following code
- fragment in which there are two writes to $b$. Are $a$ and
- $b$ both live at the same time?
- \begin{lstlisting}[numbers=left,numberstyle=\tiny]
- (movq (int 5) (var a)) ; |$a \gets 5$|
- (movq (int 30) (var b)) ; |$b \gets 30$|
- (movq (var a) (var c)) ; |$c \gets x$|
- (movq (int 10) (var b)) ; |$b \gets 10$|
- (addq (var b) (var c)) ; |$c \gets c + b$|
- \end{lstlisting}
- The answer is no because the value $30$ written to $b$ on line 2 is
- never used. The variable $b$ is read on line 5 and there is an
- intervening write to $b$ on line 4, so the read on line 5 receives the
- value written on line 4, not line 2.
- The live variables can be computed by traversing the instruction
- sequence back to front (i.e., backwards in execution order). Let
- $I_1,\ldots, I_n$ be the instruction sequence. We write
- $L_{\mathsf{after}}(k)$ for the set of live variables after
- instruction $I_k$ and $L_{\mathsf{before}}(k)$ for the set of live
- variables before instruction $I_k$. The live variables after an
- instruction are always the same as the live variables before the next
- instruction.
- \begin{equation*}
- L_{\mathsf{after}}(k) = L_{\mathsf{before}}(k+1)
- \end{equation*}
- To start things off, there are no live variables after the last
- instruction, so
- \begin{equation*}
- L_{\mathsf{after}}(n) = \emptyset
- \end{equation*}
- We then apply the following rule repeatedly, traversing the
- instruction sequence back to front.
- \begin{equation*}
- L_{\mathtt{before}}(k) = (L_{\mathtt{after}}(k) - W(k)) \cup R(k),
- \end{equation*}
- where $W(k)$ are the variables written to by instruction $I_k$ and
- $R(k)$ are the variables read by instruction $I_k$.
- Figure~\ref{fig:live-eg} shows the results of live variables analysis
- for the running example. Next to each instruction we write its
- $L_{\mathtt{after}}$ set.
- \begin{figure}[tbp]
- \begin{lstlisting}
- (program (v w x y z)
- (movq (int 1) (var v)) |$\{ v \}$|
- (movq (int 46) (var w)) |$\{ v, w \}$|
- (movq (var v) (var x)) |$\{ w, x \}$|
- (addq (int 7) (var x)) |$\{ w, x \}$|
- (movq (var x) (var y)) |$\{ w, x, y\}$|
- (addq (int 4) (var y)) |$\{ w, x, y \}$|
- (movq (var x) (var z)) |$\{ w, y, z \}$|
- (addq (var w) (var z)) |$\{ y, z \}$|
- (movq (var z) (reg rax)) |$\{ y \}$|
- (subq (var y) (reg rax))) |$\{\}$|
- \end{lstlisting}
- \caption{Running example program annotated with live-after sets.}
- \label{fig:live-eg}
- \end{figure}
- \section{Building the Interference Graph}
- Based on the liveness analysis, we know the program regions where each
- variable is needed. However, during register allocation, we need to
- answer questions of the specific form: are variables $u$ and $v$ ever
- live at the same time? (And therefore cannot be assigned to the same
- register.) To make this question easier to answer, we create an
- explicit data structure, an \emph{interference graph}. An
- interference graph is an undirected graph that has an edge between two
- variables if they are live at the same time, that is, if they
- interfere with each other.
- The most obvious way to compute the interference graph is to look at
- the set of live variables between each statement in the program, and
- add an edge to the graph for every pair of variables in the same set.
- This approach is less than ideal for two reasons. First, it can be
- rather expensive because it takes $O(n^2)$ time to look at every pair
- in a set of $n$ live variables. Second, there is a special case in
- which two variables that are live at the same time do not actually
- interfere with each other: when they both contain the same value
- because we have assigned one to the other.
- A better way to compute the edges of the intereference graph is given
- by the following rules.
- \begin{itemize}
- \item If instruction $I_k$ is a move: (\key{movq} $s$\, $d$), then add
- the edge $(d,v)$ for every $v \in L_{\mathsf{after}}(k)$ unless $v =
- d$ or $v = s$.
- \item If instruction $I_k$ is not a move but some other arithmetic
- instruction such as (\key{addq} $s$\, $d$), then add the edge $(d,v)$
- for every $v \in L_{\mathsf{after}}(k)$ unless $v = d$.
-
- \item If instruction $I_k$ is of the form (\key{call}
- $\mathit{label}$), then add an edge $(r,v)$ for every caller-save
- register $r$ and every variable $v \in L_{\mathsf{after}}(k)$.
- \end{itemize}
- Working from the top to bottom of Figure~\ref{fig:live-eg}, $z$
- interferes with $x$, $y$ interferes with $z$, and $w$ interferes with
- $y$ and $z$. The resulting interference graph is shown in
- Figure~\ref{fig:interfere}.
- \begin{figure}[tbp]
- \large
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v$};
- \node (w) at (2,0) {$w$};
- \node (x) at (4,0) {$x$};
- \node (y) at (2,-2) {$y$};
- \node (z) at (4,-2) {$z$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- \caption{Interference graph for the running example.}
- \label{fig:interfere}
- \end{figure}
- \section{Graph Coloring via Sudoku}
- We now come to the main event, mapping variables to registers (or to
- stack locations in the event that we run out of registers). We need
- to make sure not to map two variables to the same register if the two
- variables interfere with each other. In terms of the interference
- graph, this means we cannot map adjacent nodes to the same register.
- If we think of registers as colors, the register allocation problem
- becomes the widely-studied graph coloring
- problem~\citep{Balakrishnan:1996ve,Rosen:2002bh}.
- The reader may be more familar with the graph coloring problem then he
- or she realizes; the popular game of Sudoku is an instance of the
- graph coloring problem. The following describes how to build a graph
- out of a Sudoku board.
- \begin{itemize}
- \item There is one node in the graph for each Sudoku square.
- \item There is an edge between two nodes if the corresponding squares
- are in the same row or column, or if the squares are in the same
- $3\times 3$ region.
- \item Choose nine colors to correspond to the numbers $1$ to $9$.
- \item Based on the initial assignment of numbers to squares in the
- Sudoku board, assign the corresponding colors to the corresponding
- nodes in the graph.
- \end{itemize}
- If you can color the remaining nodes in the graph with the nine
- colors, then you've also solved the corresponding game of Sudoku.
- Given that Sudoku is graph coloring, one can use Sudoku strategies to
- come up with an algorithm for allocating registers. For example, one
- of the basic techniques for Sudoku is Pencil Marks. The idea is that
- you use a process of elimination to determine what numbers still make
- sense for a square, and write down those numbers in the square
- (writing very small). At first, each number might be a
- possibility, but as the board fills up, more and more of the
- possibilities are crossed off (or erased). For example, if the number
- $1$ is assigned to a square, then by process of elimination, you can
- cross off the $1$ pencil mark from all the squares in the same row,
- column, and region. Many Sudoku computer games provide automatic
- support for Pencil Marks. This heuristic also reduces the degree of
- branching in the search tree.
- The Pencil Marks technique corresponds to the notion of color
- \emph{saturation} due to \cite{Brelaz:1979eu}. The
- saturation of a node, in Sudoku terms, is the number of possibilities
- that have been crossed off using the process of elimination mentioned
- above. In graph terminology, we have the following definition:
- \begin{equation*}
- \mathrm{saturation}(u) = |\{ c \;|\; \exists v. v \in \mathrm{Adj}(u)
- \text{ and } \mathrm{color}(v) = c \}|
- \end{equation*}
- where $\mathrm{Adj}(u)$ is the set of nodes adjacent to $u$ and
- the notation $|S|$ stands for the size of the set $S$.
- Using the Pencil Marks technique leads to a simple strategy for
- filling in numbers: if there is a square with only one possible number
- left, then write down that number! But what if there are no squares
- with only one possibility left? One brute-force approach is to just
- make a guess. If that guess ultimately leads to a solution, great. If
- not, backtrack to the guess and make a different guess. Of course,
- this is horribly time consuming. One standard way to reduce the amount
- of backtracking is to use the most-constrained-first heuristic. That
- is, when making a guess, always choose a square with the fewest
- possibilities left (the node with the highest saturation). The idea
- is that choosing highly constrained squares earlier rather than later
- is better because later there may not be any possibilities left.
- In some sense, register allocation is easier than Sudoku because we
- can always cheat and add more numbers by spilling variables to the
- stack. Also, we'd like to minimize the time needed to color the graph,
- and backtracking is expensive. Thus, it makes sense to keep the
- most-constrained-first heuristic but drop the backtracking in favor of
- greedy search (guess and just keep going).
- Figure~\ref{fig:satur-algo} gives the pseudo-code for this simple
- greedy algorithm for register allocation based on saturation and the
- most-constrained-first heuristic, which is roughly equivalent to the
- DSATUR algorithm of \cite{Brelaz:1979eu} (also known as
- saturation degree ordering
- (SDO)~\citep{Gebremedhin:1999fk,Omari:2006uq}). Just as in Sudoku,
- the algorithm represents colors with integers, with the first $k$
- colors corresponding to the $k$ registers in a given machine and the
- rest of the integers corresponding to stack locations.
- \begin{figure}[btp]
- \centering
- \begin{lstlisting}[basicstyle=\rmfamily,deletekeywords={for,from,with,is,not,in,find},morekeywords={while},columns=fullflexible]
- Algorithm: DSATUR
- Input: a graph |$G$|
- Output: an assignment |$\mathrm{color}[v]$| for each node |$v \in G$|
- |$W \gets \mathit{vertices}(G)$|
- while |$W \neq \emptyset$| do
- pick a node |$u$| from |$W$| with the highest saturation,
- breaking ties randomly
- find the lowest color |$c$| that is not in |$\{ \mathrm{color}[v] \;:\; v \in \mathrm{Adj}(v)\}$|
- |$\mathrm{color}[u] \gets c$|
- |$W \gets W - \{u\}$|
- \end{lstlisting}
- \caption{Saturation-based greedy graph coloring algorithm.}
- \label{fig:satur-algo}
- \end{figure}
- With this algorithm in hand, let us return to the running example and
- consider how to color the interference graph in
- Figure~\ref{fig:interfere}. Initially, all of the nodes are not yet
- colored and they are unsaturated, so we annotate each of them with a
- dash for their color and an empty set for the saturation.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:-,\{\}$};
- \node (w) at (3,0) {$w:-,\{\}$};
- \node (x) at (6,0) {$x:-,\{\}$};
- \node (y) at (3,-1.5) {$y:-,\{\}$};
- \node (z) at (6,-1.5) {$z:-,\{\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- We select a maximally saturated node and color it $0$. In this case we
- have a 5-way tie, so we arbitrarily pick $y$. The color $0$ is no
- longer available for $w$, $x$, and $z$ because they interfere with
- $y$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:-,\{\}$};
- \node (w) at (3,0) {$w:-,\{0\}$};
- \node (x) at (6,0) {$x:-,\{0\}$};
- \node (y) at (3,-1.5) {$y:0,\{\}$};
- \node (z) at (6,-1.5) {$z:-,\{0\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- Now we repeat the process, selecting another maximally saturated node.
- This time there is a three-way tie between $w$, $x$, and $z$. We color
- $w$ with $1$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:-,\{1\}$};
- \node (w) at (3,0) {$w:1,\{0\}$};
- \node (x) at (6,0) {$x:-,\{0,1\}$};
- \node (y) at (3,-1.5) {$y:0,\{1\}$};
- \node (z) at (6,-1.5) {$z:-,\{0,1\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- The most saturated nodes are now $x$ and $z$. We color $x$ with the
- next available color which is $2$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:-,\{1\}$};
- \node (w) at (3,0) {$w:1,\{0,2\}$};
- \node (x) at (6,0) {$x:2,\{0,1\}$};
- \node (y) at (3,-1.5) {$y:0,\{1,2\}$};
- \node (z) at (6,-1.5) {$z:-,\{0,1\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- We have only two nodes left to color, $v$ and $z$, but $z$ is
- more highly saturated, so we color $z$ with $2$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:-,\{1\}$};
- \node (w) at (3,0) {$w:1,\{0,2\}$};
- \node (x) at (6,0) {$x:2,\{0,1\}$};
- \node (y) at (3,-1.5) {$y:0,\{1,2\}$};
- \node (z) at (6,-1.5) {$z:2,\{0,1\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- The last iteration of the coloring algorithm assigns color $0$ to $v$.
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (v) at (0,0) {$v:0,\{1\}$};
- \node (w) at (3,0) {$w:1,\{0,2\}$};
- \node (x) at (6,0) {$x:2,\{0,1\}$};
- \node (y) at (3,-1.5) {$y:0,\{1,2\}$};
- \node (z) at (6,-1.5) {$z:2,\{0,1\}$};
- \draw (v) to (w);
- \foreach \i in {w,x,y}
- {
- \foreach \j in {w,x,y}
- {
- \draw (\i) to (\j);
- }
- }
- \draw (z) to (w);
- \draw (z) to (y);
- \end{tikzpicture}
- \]
- With the coloring complete, we can finalize assignment of variables to
- registers and stack locations. Recall that if we have $k$ registers,
- we map the first $k$ colors to registers and the rest to stack
- locations.
- Suppose for the moment that we just have one extra register
- to use for register allocation, just \key{rbx}. Then the following is
- the mapping of colors to registers and stack allocations.
- \[
- \{ 0 \mapsto \key{\%rbx}, \; 1 \mapsto \key{-8(\%rbp)}, \; 2 \mapsto \key{-16(\%rbp)}, \ldots \}
- \]
- Putting this together with the above coloring of the variables, we
- arrive at the following assignment.
- \[
- \{ v \mapsto \key{\%rbx}, \;
- w \mapsto \key{-8(\%rbp)}, \;
- x \mapsto \key{-16(\%rbp)}, \;
- y \mapsto \key{\%rbx}, \;
- z\mapsto \key{-16(\%rbp)} \}
- \]
- Applying this assignment to our running example
- (Figure~\ref{fig:reg-eg}) yields the following program.
- % why frame size of 32? -JGS
- \begin{lstlisting}
- (program 32
- (movq (int 1) (reg rbx))
- (movq (int 46) (stack-loc -8))
- (movq (reg rbx) (stack-loc -16))
- (addq (int 7) (stack-loc -16))
- (movq (stack-loc 16) (reg rbx))
- (addq (int 4) (reg rbx))
- (movq (stack-loc -16) (stack-loc -16))
- (addq (stack-loc -8) (stack-loc -16))
- (movq (stack-loc -16) (reg rax))
- (subq (reg rbx) (reg rax)))
- \end{lstlisting}
- This program is almost an x86-64 program. The remaining step is to apply
- the patch instructions pass. In this example, the trivial move of
- \key{-16(\%rbp)} to itself is deleted and the addition of
- \key{-8(\%rbp)} to \key{-16(\%rbp)} is fixed by going through
- \key{\%rax}. The following shows the portion of the program that
- changed.
- \begin{lstlisting}
- (addq (int 4) (reg rbx))
- (movq (stack-loc -8) (reg rax)
- (addq (reg rax) (stack-loc -16))
- \end{lstlisting}
- An overview of all of the passes involved in register allocation is
- shown in Figure~\ref{fig:reg-alloc-passes}.
- \begin{figure}[tbp]
- \[
- \begin{tikzpicture}[baseline=(current bounding box.center)]
- \node (1) at (-3.5,0) {$C_0$};
- \node (2) at (0,0) {$\text{x86-64}^{*}$};
- \node (3) at (0,-1.5) {$\text{x86-64}^{*}$};
- \node (4) at (0,-3) {$\text{x86-64}^{*}$};
- \node (5) at (0,-4.5) {$\text{x86-64}^{*}$};
- \node (6) at (3.5,-4.5) {$\text{x86-64}$};
- \path[->] (1) edge [above] node {\ttfamily\scriptsize select-instr.} (2);
- \path[->] (2) edge [right] node {\ttfamily\scriptsize uncover-live} (3);
- \path[->] (3) edge [right] node {\ttfamily\scriptsize build-interference} (4);
- \path[->] (4) edge [left] node {\ttfamily\scriptsize allocate-registers} (5);
- \path[->] (5) edge [above] node {\ttfamily\scriptsize patch-instr.} (6);
- \end{tikzpicture}
- \]
- \caption{Diagram of the passes for register allocation.}
- \label{fig:reg-alloc-passes}
- \end{figure}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Booleans, Type Checking, and Control Flow}
- \label{ch:bool-types}
- \section{The $R_2$ Language}
- \begin{figure}[htbp]
- \centering
- \fbox{
- \begin{minipage}{0.85\textwidth}
- \[
- \begin{array}{lcl}
- \Op &::=& \ldots \mid \key{and} \mid \key{or} \mid \key{not} \mid \key{eq?} \\
- \Exp &::=& \ldots \mid \key{\#t} \mid \key{\#f} \mid
- \IF{\Exp}{\Exp}{\Exp}
- \end{array}
- \]
- \end{minipage}
- }
- \caption{The $R_2$ language, an extension of $R_1$
- (Figure~\ref{fig:s0-syntax}).}
- \label{fig:s2-syntax}
- \end{figure}
- \section{Type Checking $R_2$ Programs}
- \marginpar{\scriptsize Type checking is a difficult thing to cover, I think, without having 522 as a prerequisite for this course. -- Cam}
- % T ::= Integer | Boolean
- It is common practice to specify a type system by writing rules for
- each kind of AST node. For example, the rule for \key{if} is:
- \begin{quote}
- For any expressions $e_1, e_2, e_3$ and any type $T$, if $e_1$ has
- type \key{bool}, $e_2$ has type $T$, and $e_3$ has type $T$, then
- $\IF{e_1}{e_2}{e_3}$ has type $T$.
- \end{quote}
- It is also common practice to write rules using a horizontal line,
- with the conditions written above the line and the conclusion written
- below the line.
- \begin{equation*}
- \inference{e_1 \text{ has type } \key{bool} &
- e_2 \text{ has type } T & e_3 \text{ has type } T}
- {\IF{e_1}{e_2}{e_3} \text{ has type } T}
- \end{equation*}
- Because the phrase ``has type'' is repeated so often in these type
- checking rules, it is abbreviated to just a colon. So the above rule
- is abbreviated to the following.
- \begin{equation*}
- \inference{e_1 : \key{bool} & e_2 : T & e_3 : T}
- {\IF{e_1}{e_2}{e_3} : T}
- \end{equation*}
- The $\LET{x}{e_1}{e_2}$ construct poses an interesting challenge. The
- variable $x$ is assigned the value of $e_1$ and then $x$ can be used
- inside $e_2$. When we get to an occurrence of $x$ inside $e_2$, how do
- we know what type the variable should be? The answer is that we need
- a way to map from variable names to types. Such a mapping is called a
- \emph{type environment} (aka. \emph{symbol table}). The capital Greek
- letter gamma, written $\Gamma$, is used for referring to type
- environments environments. The notation $\Gamma, x : T$ stands for
- making a copy of the environment $\Gamma$ and then associating $T$
- with the variable $x$ in the new environment. We write $\Gamma(x)$ to
- lookup the associated type for $x$. The type checking rules for
- \key{let} and variables are as follows.
- \begin{equation*}
- \inference{e_1 : T_1 \text{ in } \Gamma &
- e_2 : T_2 \text{ in } \Gamma,x:T_1}
- {\LET{x}{e_1}{e_2} : T_2 \text{ in } \Gamma}
- \qquad
- \inference{\Gamma(x) = T}
- {x : T \text{ in } \Gamma}
- \end{equation*}
- Type checking has roots in logic, and logicians have a tradition of
- writing the environment on the left-hand side and separating it from
- the expression with a turn-stile ($\vdash$). The turn-stile does not
- have any intrinsic meaning per se. It is punctuation that separates
- the environment $\Gamma$ from the expression $e$. So the above typing
- rules are written as follows.
- \begin{equation*}
- \inference{\Gamma \vdash e_1 : T_1 &
- \Gamma,x:T_1 \vdash e_2 : T_2}
- {\Gamma \vdash \LET{x}{e_1}{e_2} : T_2}
- \qquad
- \inference{\Gamma(x) = T}
- {\Gamma \vdash x : T}
- \end{equation*}
- Overall, the statement $\Gamma \vdash e : T$ is an example of what is
- called a \emph{judgment}. In particular, this judgment says, ``In
- environment $\Gamma$, expression $e$ has type $T$.''
- Figure~\ref{fig:S1-type-system} shows the type checking rules for
- $R_2$.
- \begin{figure}
- \begin{gather*}
- \inference{\Gamma(x) = T}
- {\Gamma \vdash x : T}
- \qquad
- \inference{\Gamma \vdash e_1 : T_1 &
- \Gamma,x:T_1 \vdash e_2 : T_2}
- {\Gamma \vdash \LET{x}{e_1}{e_2} : T_2}
- \\[2ex]
- \inference{}{\Gamma \vdash n : \key{Integer}}
- \quad
- \inference{\Gamma \vdash e_i : T_i \ ^{\forall i \in 1\ldots n} & \Delta(\Op,T_1,\ldots,T_n) = T}
- {\Gamma \vdash (\Op \; e_1 \ldots e_n) : T}
- \\[2ex]
- \inference{}{\Gamma \vdash \key{\#t} : \key{Boolean}}
- \quad
- \inference{}{\Gamma \vdash \key{\#f} : \key{Boolean}}
- \quad
- \inference{\Gamma \vdash e_1 : \key{bool} \\
- \Gamma \vdash e_2 : T &
- \Gamma \vdash e_3 : T}
- {\Gamma \vdash \IF{e_1}{e_2}{e_3} : T}
- \end{gather*}
- \caption{Type System for $R_2$.}
- \label{fig:S1-type-system}
- \end{figure}
- \begin{figure}
- \begin{align*}
- \Delta(\key{+},\key{Integer},\key{Integer}) &= \key{Integer} \\
- \Delta(\key{-},\key{Integer},\key{Integer}) &= \key{Integer} \\
- \Delta(\key{-},\key{Integer}) &= \key{Integer} \\
- \Delta(\key{*},\key{Integer},\key{Integer}) &= \key{Integer} \\
- \Delta(\key{read}) &= \key{Integer} \\
- \Delta(\key{and},\key{Boolean},\key{Boolean}) &= \key{Boolean} \\
- \Delta(\key{or},\key{Boolean},\key{Boolean}) &= \key{Boolean} \\
- \Delta(\key{not},\key{Boolean}) &= \key{Boolean} \\
- \Delta(\key{eq?},\key{Integer},\key{Integer}) &= \key{Boolean} \\
- \Delta(\key{eq?},\key{Boolean},\key{Boolean}) &= \key{Boolean}
- \end{align*}
- \caption{Types for the primitives operators.}
- \end{figure}
- \section{The $C_1$ Language}
- \begin{figure}[htbp]
- \[
- \begin{array}{lcl}
- \Arg &::=& \ldots \mid \key{\#t} \mid \key{\#f} \\
- \Stmt &::=& \ldots \mid \IF{\Exp}{\Stmt^{*}}{\Stmt^{*}}
- \end{array}
- \]
- \caption{The $C_1$ intermediate language, an extension of $C_0$
- (Figure~\ref{fig:c0-syntax}).}
- \label{fig:c1-syntax}
- \end{figure}
- \section{Flatten Expressions}
- \section{Select Instructions}
- \section{Register Allocation}
- \section{Patch Instructions}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Tuples and Heap Allocation}
- \label{ch:tuples}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Garbage Collection}
- \label{ch:gc}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Functions}
- \label{ch:functions}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Lexically Scoped Functions}
- \label{ch:lambdas}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Mutable Data}
- \label{ch:mutable-data}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{The Dynamic Type}
- \label{ch:type-dynamic}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Parametric Polymorphism}
- \label{ch:parametric-polymorphism}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{High-level Optimization}
- \label{ch:high-level-optimization}
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \chapter{Appendix}
- \section{Interpreters}
- \label{appendix:interp}
- We provide several interpreters in the \key{interp.rkt} file. The
- \key{interp-scheme} function takes an AST in one of the Racket-like
- languages considered in this book ($R_1, R_2, \ldots$) and interprets
- the program, returning the result value. The \key{interp-C} function
- interprets an AST for a program in one of the C-like languages ($C_0,
- C_1, \ldots$), and the \key{interp-x86} function interprets an AST for
- an x86-64 program.
- \section{Utility Functions}
- \label{appendix:utilities}
- The utility function described in this section can be found in the
- \key{utilities.rkt} file.
- The \key{assert} function displays the error message \key{msg} if the
- Boolean \key{bool} is false.
- \begin{lstlisting}
- (define (assert msg bool) ...)
- \end{lstlisting}
- The \key{lookup} function ...
- The \key{interp-tests} function takes a compiler name (a string) a
- description of the passes a test family name (a string), and a list of
- test numbers, and runs the compiler passes and the interpreters to
- check whether the passes correct. The description of the passes is a
- list with one entry per pass. An entry is a list with three things: a
- string giving the name of the pass, the function that implements the
- pass (a translator from AST to AST), and a function that implements
- the interpreter (a function from AST to result value). The
- interpreters from Appendix~\ref{appendix:interp} make a good choice.
- The \key{interp-tests} function assumes that the subdirectory
- \key{tests} has a bunch of Scheme programs whose names all start with
- the family name, followed by an underscore and then the test number,
- ending in \key{.scm}. Also, for each Scheme program there is a file
- with the same number except that it ends with \key{.in} that provides
- the input for the Scheme program.
- \begin{lstlisting}
- (define (interp-tests name passes test-family test-nums) ...
- \end{lstlisting}
- The compiler-tests function takes a compiler name (a string) a
- description of the passes (see the comment for \key{interp-tests}) a
- test family name (a string), and a list of test numbers (see the
- comment for interp-tests), and runs the compiler to generate x86-64 (a
- \key{.s} file) and then runs gcc to generate machine code. It runs
- the machine code and checks that the output is 42.
- \begin{lstlisting}
- (define (compiler-tests name passes test-family test-nums) ...)
- \end{lstlisting}
- The compile-file function takes a description of the compiler passes
- (see the comment for \key{interp-tests}) and returns a function that,
- given a program file name (a string ending in \key{.scm}), applies all
- of the passes and writes the output to a file whose name is the same
- as the proram file name but with \key{.scm} replaced with \key{.s}.
- \begin{lstlisting}
- (define (compile-file passes)
- (lambda (prog-file-name) ...))
- \end{lstlisting}
- \bibliographystyle{plainnat}
- \bibliography{all}
- \end{document}
- %% LocalWords: Dybvig Waddell Abdulaziz Ghuloum Dipanwita
- %% LocalWords: Sarkar lcl Matz aa representable
|