@@ -18,6 +18,169 @@ In addition the source code for your project, you must write a 2-page
1818document which gives a summary of your work and describes how your
1919project is implemented.
2020
21+ @section{a86 optimizer}
22+
23+ Our compiler is designed to be simple and easy to maintain. That
24+ comes at the cost of emitting code that often does needless work.
25+ Write an a86 optimizer, i.e., a program that takes in a list of a86
26+ instructions and produces an alternative list of instructions that
27+ have the same behavior, but will execute more efficiently.
28+
29+ This is a fairly open-ended project, which means you can take a simple
30+ approach, or you can do a deep-dive on assembly code optimization and
31+ try to do something very sophisticated.
32+
33+ For a maximum of 95% of the possible points, your optimizer should
34+ work on any a86 instructions produced by the
35+ @seclink["Iniquity " ]{Iniquity} compiler. For 100%, your optimizer
36+ should work on any a86 instructions produced by the
37+ @seclink["Loot " ]{Loot} compiler.
38+
39+ The most important aspect of the optimizer is it must preserve the
40+ meaning of the original source program. If running a program with or
41+ without optimization can produce different results, you will lose
42+ significant points.
43+
44+ The second important aspect of the optimizer is that it produces more
45+ efficient code (but this should never come at the expense of
46+ correctness---otherwise it's trivial to optimize every program!). You
47+ should design some experiments demonstrating the impact of your
48+ optimizations and measure the performance improvement of your optimizer.
49+
50+ Here are some ideas for what you can optimize:
51+
52+ @itemlist[
53+
54+ @item{Avoid stack references where possible.
55+
56+ For example, you might push something and immediately reference it:
57+ @racket[(seq (Push _r1) (Mov _r2 (Offset rsp 0 )))], which is
58+ equivalent to @racket[(seq (Push _r1) (Mov _r2 _r1))]. The
59+ register-to-register move will be faster than accessing the memory on
60+ the stack.}
61+
62+ @item{Avoid stack pushes where possible.
63+
64+ In the previous example, it may be tempting to delete the
65+ @racket[Push], but that is only valid if that stack element is not
66+ referenced later before being popped. And even if the element is not
67+ referenced, we have to be careful about how the element is popped.
68+
69+ But if you know where the pop occurs and there's no intervening
70+ references in to the stack or other stack changes, then you can
71+ improve the code further, e.g. @racket[(seq (Push _r1) (Mov _r2
72+ (Offset rsp 0 )) (Add rsp 8 ))] can become @racket[(seq (Mov _r2 _r1))].
73+ }
74+
75+ @item{Statically compute.
76+
77+ Sometimes the compiler emits code for computing something at run-time
78+ which can instead be computed at compile time. For example, the
79+ compiler might emit @racket[(seq (Mov _r 42 ) (Add _r 12 ))], but this
80+ can be simplified to @racket[(seq (Mov _r 54 ))].}
81+
82+ ]
83+
84+ There are many, many other kinds of optimizations you might consider.
85+ To get a sense of the opportunities for optimization, try compiling
86+ small examples and looking at the assembly code produces. Try
87+ hand-optimizing the code, then try to abstract what you did by hand
88+ and do it programmatically.
89+
90+ @section{Source optimizer}
91+
92+ Another complimentary approach to making programs compute more
93+ efficiently is to optimize them at the level of source code. Write a
94+ source code optimizer, i.e. a program that takes in a program AST and
95+ produces an alternative AST that has the same behavior, but will
96+ execute more efficiently.
97+
98+ This is another fairly open-ended project, which means you can take a
99+ simple approach, or you can do a deep-dive on source code optimization
100+ and try to do something very sophisticated.
101+
102+ For a maximum of 95% of the possible points, your optimizer should
103+ work for the @seclink["Iniquity " ]{Iniquity} language. For 100%, your
104+ optimizer should work for the @seclink["Loot " ]{Loot} language (or later).
105+
106+ The most important aspect of the optimizer is it must preserve the
107+ meaning of the original source program. If running a program with or
108+ without optimization can produce different results, you will lose
109+ significant points.
110+
111+ The second important aspect of the optimizer is that it produces more
112+ efficient code (but this should never come at the expense of
113+ correctness—otherwise it’s trivial to optimize every program!). You
114+ should design some experiments demonstrating the impact of your
115+ optimizations and measure the performance improvement of your
116+ optimizer.
117+
118+ Here are some ideas for where you can optimize:
119+
120+ @itemlist[
121+
122+ @item{Avoid variable bindings where possible.
123+
124+ Sometimes a program may bind a variable to a value, but then use the
125+ variable only once, e.g. @racket[(let ((x (add1 7 ))) (add1 x))]. We
126+ can instead replace the variable occurrence with it's definition to
127+ get: @racket[(add1 (add1 7 ))]. Note that can must be taken to
128+ @emph{not} do this optimization if it changes the order in which
129+ effects may happen. For example, consider
130+
131+ @racketblock[
132+ (let ((x (read-byte)))
133+ (begin (read-byte)
134+ (add1 x)))
135+ ]
136+
137+ This is not the same as:
138+
139+ @racketblock[
140+ (begin (read-byte)
141+ (add1 (read-byte)))
142+ ]
143+
144+ because the latter adds one to the second byte of the input stream rather than the first.}
145+
146+ @item{Statically compute.
147+
148+ Sometimes parts of a program can be computed at compile-time rather
149+ than run-time. For example, @racket[(add1 41 )] can be replaced with
150+ @racket[42 ]. Likewise, expressions like @racket[(if #f _e1 _e2)] can
151+ be replaced by @racket[_e2].}
152+
153+ @item{Inline function calls.
154+
155+ Suppose you have:
156+
157+ @racketblock[
158+ (define (f x) (add1 x))
159+ (if (zero? (f 5 )) _e1 _e2)
160+ ]
161+
162+ Since the expression @racket[(f 5 )] is calling a known function, you
163+ should be able to transform this call into @racket[(let ((x 5 )) (add1
164+ x))]. Using the previously described optimization, you can further
165+ optimize this to @racket[(add1 5 )], which in turn can be simiplified
166+ to @racket[6 ]. You can keep going and notice that @racket[(zero? 6 )]
167+ is just @racket[#f ], so the whole program can be simplified to:
168+
169+ @racketblock[
170+ (define (f x) (add1 x))
171+ _e2
172+ ]
173+ }
174+
175+ ]
176+
177+ Note that the last example can get considerably more complicated in a
178+ language with first-class functions since it may not be possible to
179+ know statically which function is being called.
180+
181+ There are many other optimziations you might consider. Think about
182+ the kinds of expressions you might write and how they can be
183+ simplified, then figure out how to do it programmatically.
21184
22185@section{Multiple return values}
23186
@@ -79,7 +242,29 @@ what the surrounding context expects, an error should be signalled.
79242
80243]
81244
245+ The top-level expression may produce any number of values and the
246+ run-time system should print each of them out, followed by a newline:
247+
248+ @ex[
249+ (values 1 2 3 )
250+ ]
82251
252+ Note there is some symmetry here between function arity checking where
253+ we make sure the number of arguments matches the number of parameters
254+ of the function being called and the ``result arity'' checking that is
255+ required to implement this feature. This suggests a similiar approach
256+ to implementing this feature, namely designating a register to
257+ communicate the arity of the result, which should be checked by the
258+ surrounding context.
259+
260+ You will also need to design an alternative mechanism for
261+ communicating return values. Using a single register (@racket['rax ])
262+ works when every expression produces a single result, but now
263+ expressions may produce an arbitrary number of results and using
264+ registers will no longer suffice. (Although you may want to continue
265+ to use @racket['rax ] for the common case of a single result.) The
266+ solution for this problem with function parameters was to use the
267+ stack and a similar approach can work for results too.
83268
84269@section{Exceptions and Exception Handling}
85270
@@ -360,4 +545,11 @@ Pushing your local repository to github ``submits'' your work. We
360545will grade the latest submission that occurs before the deadline.
361546}
362547
363- }
548+ }
549+
550+ @section{Design your own}
551+
552+ You may also design your own project, however, you will need to submit
553+ a one-page write-up that documents what you plan to do and how you
554+ will evaluate whether it is successful. You must submit this document
555+ and have it approved by the instructor by Dec 7.
0 commit comments