9 Compiler 3: Register Allocation (Corrected)
9.1 Assignment Summary
The goal of this assignment is to introduce (1) "optimizing" compilation (2) register allocation, perhaps the most important "optimization". In this assignment, you will replace the assign-homes pass with assign-homes-opt, a version that tries hard to put variables into registers instead of in the frame.
This assignment is due Friday January 31, 2020 at 11:59pm.
Assignment Checklist
You should find a new repository in your https://github.students.cs.ubc.ca account named a3_<team ids> with a code skeleton and a support library. You should complete the assignment in that git repository and push it to the GitHub Students instance.
You should first copy your solution to Compiler 2: Values-lang to x64 into the starter code provided. You can start from the code base of any of your team members.
If we patch the code skeleton or library after the release, the most recent version will be available here at a3-skeleton.rkt and a3-compiler-lib.rkt, and the functional graph library a3-graph-lib.rkt. The name of the skeleton is a3-skeleton.rkt to avoid accidentally overwriting your files, but your file in the Git repository should be named a3.rkt.
Design and implement assign-registers, a compiler from Conflict-lang to Loc-assigned-lang.
Design and implement conflict-analysis, a compiler from Undead-loc-lang to Conflict-lang.
Design and implement undead-analysis, a compiler from Loc-locals-lang to Undead-loc-lang.
Design and implement assign-homes-opt, a compiler from Loc-locals-lang to Loc-assigned-lang.
Write a paragraph comparing two versions of your compiler: one with assign-homes and one with assign-homes-opt.
9.2 Language Diagram
9.3 Register Allocation
To assign abstract locations (variables) to physical locations efficiently, we need to know when any two variables are in conflict, i.e., cannot be assigned to the same physical location because they might both be used at the same time.
We start by designing a language, Conflict-lang, in which we assume someone has told us all the variables that are in use and which other variables they are in conflict with. This is all the information we need to complete register allocation in one step, so we can make progress by assuming we have it, and figuring out how to get it later. In Conflict-lang, the program is annotated with: (1) a list of local variables declarations, like in Loc-locals-lang, and (2) a conflict graph, represented as an association list from a variable to the set of variables with which it conflicts. The language is essentially the same as Loc-locals-lang except for the addition of the conflict graph to the info field.
p | ::= | (begin info s ...) | |||||
info | ::= |
| |||||
s | ::= | (set! aloc int64) | |||||
| | (set! aloc aloc) | ||||||
| | (set! aloc (binop aloc aloc)) | ||||||
| | (halt aloc) | ||||||
binop | ::= | * | |||||
| | + |
The (conflicts ((aloc (aloc ...)) ...)) form in the info field represents a conflict graph. Interpreted as an undirected graph, the variables as represented as nodes (also known as vertexes), and conflicts between variables are represented as an edge the variable to each of the variables in the associated set. If there is an edge between any two nodes, then they are in conflict. Interpreted as a dictionary, this form maps each variable to a set of its conflicts.
As in the previous assignment, the info field also contains a declaration of the (locals (aloc ...)) abstract locations that may be used in the program, and (as usual) possibly other non-required but useful information.
The register assignment algorithm takes a Conflict-lang program and produces a Loc-assigned-lang program, annotating the program with an assignment of variables to physical locations in the info field.
If the list of variables is empty, return the empty assignment.
Choose a low-degree variable from the input list of variables, if one exists. Otherwise, pick an arbtrary variable (such as the first one in the list).
A low-degree variable is one with fewer than k conflicts. We pick k to be the number of registers available for use during register allocation.
Recur with the chosen variable removed from the input list and the conflict graph. The recursive call should return an assignment for all the remaining variables.
- Attempt to select a register for the chosen variable. You cannot select registers to which conflicting variables were assigned by the recursive call. This attempt succeeds if a low-degree variable was chosen, and might fail otherwise (but it depends on which registers got allocated in the recursive call).
If you succeed in assigning a register, then add the assignment for the chosen variable to the result of the recursive call.
Otherwise, we cannot assign the choosen variable to a register. Instead, we spill it, i.e., we allocate a fresh frame location using a displacement mode operand.
We can simplify the implementation of this algorithm by separting it into two parts: first, sort all variables in degree order, then assign each register in sorted order. See sort.
Exercise 1: Design and implement the parameter? current-assignable-registers, which stores the list of k registers available to the register allocator.
The default should be all registers except the following. You may not use the register rax, since it is used to patch instructions, or rbp, since it contains the frame pointer.
Exercise 2: Design and implement the function assign-registers, which performs graph-coloring register allocation. The source language is Conflict-lang and the target language is Loc-assigned-lang. The pass should attempt to fit each of the variables declared in (locals (aloc ...)) into a free register, and if one cannot be found, should allocate the variable a fresh frame location.
We provide a functional graph library, a3-graph-lib.rkt, for working with the conflict graph.
This algorithm is somewhat tedious to write purely functionally. You may use a single mutable variable in your implementation. Use box, unbox, and set-box! for an idomatic way of using mutable variables in Racket.
It will be difficult to keep enough variables live at one time to test spilling. You should use parameterize and current-assignable-registers to test spilling.
> (assign-registers '(begin ((locals (x.1)) (conflicts ((x.1 ())))) (set! x.1 42) (halt x.1))) '(begin ((assignment ((x.1 r15)))) (set! x.1 42) (halt x.1))
> (parameterize ([current-assignable-registers '(r9)]) (assign-registers '(begin ((locals (x.1)) (conflicts ((x.1 ())))) (set! x.1 42) (halt x.1)))) '(begin ((assignment ((x.1 r9)))) (set! x.1 42) (halt x.1))
> (parameterize ([current-assignable-registers '()]) (assign-registers '(begin ((locals (x.1)) (conflicts ((x.1 ())))) (set! x.1 42) (halt x.1)))) '(begin ((assignment ((x.1 (rbp - 8))))) (set! x.1 42) (halt x.1))
> (assign-registers '(begin ((locals (a.1 b.2 c.3 d.4 e.5)) (conflicts ((a.1 (b.2 c.3 d.4 e.5)) (b.2 (a.1 c.3 e.5)) (c.3 (a.1 b.2 e.5)) (d.4 (a.1 e.5)) (e.5 (a.1 b.2 c.3 d.4))))) (set! a.1 1) (set! c.3 2) (set! b.2 a.1) (set! b.2 (+ a.1 c.3)) (set! d.4 (* b.2 c.3)) (halt d.4)))
'(begin
((assignment ((a.1 r12) (b.2 r13) (c.3 r14) (d.4 r14) (e.5 r15))))
(set! a.1 1)
(set! c.3 2)
(set! b.2 a.1)
(set! b.2 (+ a.1 c.3))
(set! d.4 (* b.2 c.3))
(halt d.4))
> (parameterize ([current-assignable-registers '(rbx rcx r9 r10)]) (assign-registers '(begin ((locals (a.1 b.2 c.3 d.4 e.5)) (conflicts ((a.1 (b.2 c.3 d.4 e.5)) (b.2 (a.1 c.3 e.5)) (c.3 (a.1 b.2 e.5)) (d.4 (a.1 e.5)) (e.5 (a.1 b.2 c.3 d.4))))) (set! a.1 1) (set! c.3 2) (set! b.2 a.1) (set! b.2 (+ a.1 c.3)) (set! d.4 (* b.2 c.3)) (halt d.4))))
'(begin
((assignment ((b.2 rbx) (a.1 rcx) (c.3 r9) (d.4 r9) (e.5 r10))))
(set! a.1 1)
(set! c.3 2)
(set! b.2 a.1)
(set! b.2 (+ a.1 c.3))
(set! d.4 (* b.2 c.3))
(halt d.4))
> (assign-registers '(begin ((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1)) (conflicts ((x.3 (z.5 p.1 y.4 v.1 w.2)) (w.2 (z.5 p.1 y.4 v.1 x.3)) (v.1 (w.2 x.3)) (y.4 (t.6 z.5 p.1 w.2 x.3)) (p.1 (t.6 z.5 y.4 w.2 x.3)) (z.5 (t.6 p.1 y.4 w.2 x.3)) (t.6 (z.5 p.1 y.4))))) (set! v.1 1) (set! w.2 46) (set! x.3 v.1) (set! p.1 7) (set! x.3 (+ x.3 p.1)) (set! y.4 x.3) (set! p.1 4) (set! y.4 (+ y.4 p.1)) (set! z.5 x.3) (set! z.5 (+ z.5 w.2)) (set! t.6 y.4) (set! p.1 -1) (set! t.6 (* t.6 p.1)) (set! z.5 (+ z.5 t.6)) (halt z.5)))
'(begin
((assignment
((v.1 r15) (w.2 r11) (x.3 r14) (y.4 r12) (z.5 r13) (t.6 r14) (p.1 r15))))
(set! v.1 1)
(set! w.2 46)
(set! x.3 v.1)
(set! p.1 7)
(set! x.3 (+ x.3 p.1))
(set! y.4 x.3)
(set! p.1 4)
(set! y.4 (+ y.4 p.1))
(set! z.5 x.3)
(set! z.5 (+ z.5 w.2))
(set! t.6 y.4)
(set! p.1 -1)
(set! t.6 (* t.6 p.1))
(set! z.5 (+ z.5 t.6))
(halt z.5))
When debugging your register allocator, you might try comparing your allocator to the graph coloring algorithm provided by Racket Generic Graph Library. This provides an implementation of graph coloring, although it does not perform spilling so it will not work in general. You cannot use this library in the implementation of your compiler; it may only be used for testing. Unfortunately, it uses a different representation of graphs, so you will need to write conversion procedures.
p | ::= | (begin info s ... (halt aloc)) | ||||||
info | ::= |
| ||||||
s | ::= | ; Same as Loc-assigned-lang |
> (check-assignment '(begin ((locals (x.1 y.1)) (conflicts ((x.1 (y.1)) (y.1 (x.1)))) (assignment ((x.1 r9) (y.1 r9)))) (set! x.1 42) (set! y.1 42) (set! x.1 (+ x.1 y.1)) (halt x.1))) check-assignment: Produced bad assignment:
y.1 and x.1 both assigned to r9
> (check-assignment '(begin ((locals (x.1 y.1)) (conflicts ((x.1 (y.1)) (y.1 (x.1)))) (assignment ((x.1 r9)))) (set! x.1 42) (set! y.1 42) (set! x.1 (+ x.1 y.1)) (halt x.1))) check-assignment: Some locals not assigned homes:
homeless locals: y.1
9.4 Undead and Conflict Analysis
For the next two passes, we switch perspectives and work top-down, as it’s difficult to explain them bottom-up.
We have just assumed that we know which variables are in conflict. This allowed us to make progress, but now now we need to discharge that assumption and build conflict graphs.
True Definition of Conflict Any variable that gets a new value during an instruction is in conflict with every variable that (1) has a different value and (2) will still be used after that instruction.
Loc-locals-lang is a simple enough language that we can tell whether a variable is dead or alive. Later, when we add new instructions, we will modify the undead analysis and find variables that aren’t necessarily live or dead, and must be assumed to be undead. This is also necessary if we want to handle linking, or separate compilation.
Instead, we assume we have information about which variables might be used after an instruction. We ignore their values, and approximate conflicts. We compute a conflict graph by creating a conflict between every variable that gets a new value and every variable that might be live and might have a different value.
To figure out conflicts, we’ll need to figure out which variables might be live, or, are not definitely dead.
9.4.1 Undeadness Analysis
A variable (with a particular value) that definitely will be used is considered live, and variable (with a particular value) that definitely won’t be used is considered dead. Due to Rice’s Theorem, we know it’s generally impossible to tell whether a variable is dead or alive. This means that when writing an analysis, we must assume partial knowledge. We ignore liveness entirely.
We also cannot know the values of variables. Instead, we focus on assignments to variables, which change the values of variables.
We assume that any variable that gets used, or might get used, might not
be dead, i.e., we assume it is undead, and consider a variable
dead only when we have conclusive proof—
We collect the undead variables into sets. The undead-out set of an instruction is a set of (aloc ...). The set represents the variables that, after executing the instruction associated with the set, are undead.
To determine whether a variable is in the undead-out set for an instruction, we analyze the program by looping over the instruction sequence backwards, starting with the last statement. We don’t need to analyze the program backwards, but it’s faster. The loop takes an instruction and its undead-out set. We analyze each instruction with its undead-out set and compute an undead-in set for the instruction, which is the same as the undead-out set of the preceding instruction. That is, the undead-in set for an instruction s_i is the undead-out set for the instruction s_{i-1} in the program.
Each iteration of the loop performs the following analysis on a particular
instruction.
We start making by assuming the undead-in set is the same as the
undead-out set, then update it depending on what happens in the
instruction.
If a variable is used in the instruction, it ought to be
live—
To start the loop, we need a default undead-out set for the last instruction; the default undead-out set for Loc-locals-lang is empty. In general, the default set may not be empty, because we may have to assume that some values are live after the program. For example, in Paren-x64, we assume rax is live out.
The analysis collects the undead-out sets for each instructions so that later passes can associate each instruction with its undead-out set. There are many ways to associate the undead-out sets with instructions. A simple way is to create a data structure that maps each set to an instruction.
(define (fn-for-ss-and-undead-outs ss undead-outs) (match (cons ss undead-outs) [(cons '() '()) (... case-for-empty ...)] [(cons `(,s ,rest-ss ...) `(,undead-out ,rest-undead-outs ...)) (... (fn-for-s-and-undead-out s undead-out) (fn-for-ss-and-undead-outs rest-ss rest-undead-outs))]))
Most compilers call these live-out or live-after sets. This suggests that variables are definitely alive, and that the analysis is computing sets of variables that are definitely alive, neither of which is true. Undead is not exactly the same as not-definitely-dead, except in horror movies and video games, but it’s more suggestive of reality for compilers.
We collect the undead info in a new info field. Below, we design Undead-loc-lang. The language is essentially the same as Conflict-lang except for the info field.
p | ::= | (begin info s ...) | |||||
info | ::= |
| |||||
s | ::= | (set! aloc int64) | |||||
| | (set! aloc aloc) | ||||||
| | (set! aloc (binop aloc aloc)) | ||||||
| | (halt aloc) | ||||||
binop | ::= | * | |||||
| | + |
Exercise 3: Design and implement the function undead-analysis, which decorates a program with its list of undead-in sets; only the info field of the program is modified. The source language is Loc-locals-lang and the target language is Undead-loc-lang.
> (undead-analysis '(begin ((locals (x.1))) (set! x.1 42) (halt x.1))) '(begin ((locals (x.1)) (undead-out ((x.1) ()))) (set! x.1 42) (halt x.1))
> (undead-analysis '(begin ((locals (a.1 b.2 c.3 d.4 e.5))) (set! a.1 1) (set! c.3 2) (set! b.2 a.1) (set! b.2 (+ a.1 c.3)) (set! d.4 (* b.2 c.3)) (halt d.4)))
'(begin
((locals (a.1 b.2 c.3 d.4 e.5))
(undead-out ((a.1) (a.1 c.3) (a.1 c.3) (c.3 b.2) (d.4) ())))
(set! a.1 1)
(set! c.3 2)
(set! b.2 a.1)
(set! b.2 (+ a.1 c.3))
(set! d.4 (* b.2 c.3))
(halt d.4))
> (undead-analysis '(begin ((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1))) (set! v.1 1) (set! w.2 46) (set! x.3 v.1) (set! p.1 7) (set! x.3 (+ x.3 p.1)) (set! y.4 x.3) (set! p.1 4) (set! y.4 (+ y.4 p.1)) (set! z.5 x.3) (set! z.5 (+ z.5 w.2)) (set! t.6 y.4) (set! p.1 -1) (set! t.6 (* t.6 p.1)) (set! z.5 (+ z.5 t.6)) (halt z.5)))
'(begin
((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1))
(undead-out
((v.1)
(v.1 w.2)
(x.3 w.2)
(p.1 x.3 w.2)
(x.3 w.2)
(y.4 x.3 w.2)
(p.1 y.4 x.3 w.2)
(x.3 w.2 y.4)
(w.2 z.5 y.4)
(y.4 z.5)
(t.6 z.5)
(p.1 t.6 z.5)
(t.6 z.5)
(z.5)
())))
(set! v.1 1)
(set! w.2 46)
(set! x.3 v.1)
(set! p.1 7)
(set! x.3 (+ x.3 p.1))
(set! y.4 x.3)
(set! p.1 4)
(set! y.4 (+ y.4 p.1))
(set! z.5 x.3)
(set! z.5 (+ z.5 w.2))
(set! t.6 y.4)
(set! p.1 -1)
(set! t.6 (* t.6 p.1))
(set! z.5 (+ z.5 t.6))
(halt z.5))
> (undead-analysis '(begin ((locals (x.1 y.1))) (set! y.1 42) (set! x.1 5) (halt x.1)))
'(begin
((locals (x.1 y.1)) (undead-out (() (x.1) ())))
(set! y.1 42)
(set! x.1 5)
(halt x.1))
> (undead-analysis '(begin ((locals (x.1 y.1))) (set! x.1 5) (set! y.1 42) (halt x.1)))
'(begin
((locals (x.1 y.1)) (undead-out ((x.1) (x.1) ())))
(set! x.1 5)
(set! y.1 42)
(halt x.1))
In a realistic compiler, unused variables should be removed be an optimization.
Challenge 1: Design and implement the function bury-dead, which removes assignments to unused abstract locations. The source language is Undead-loc-lang and the target language is Undead-loc-lang. (Optimizations often have the same source and target language.)
An assignment to a variable is dead if it is not in the undead-out set for the instruction.
9.4.2 Conflict Analysis
Now, we can figure out which variables are in conflict.
We create a conflict graph from the undead-out sets as follows. Any variable that is defined during an instruction is in conflict with every variable (except itself) in the undead-out set associated with that instruction. For example, the variable x.1 is defined in (set! x.1 (+ x.1 x.2)), while the variables x.2 and x.1 are referenced. No variable can conflict with itself, since it always has the same value as itself. We must assume that all variables in the undead-out set have different values. We know that x.1 cannot be in the same physical location as any variables in the undead-out set. If x.2 is undead-out at this instruction, and we try to put x.1 and in the same physical location as x.2, then the value of x.2 would have been overwritten by the value of (+ x.1 x.2).
We can do slightly better, as one instruction does tell us that two values will be the same. A move instruction is an instruction that simply defines the value of one variables to be that of another, such as (set! x.1 x.2). We do not consider these two variables in conflicts. Even if x.2 is in the undead-out set, it’s value has not changed.
Any variable defined during a non-move instruction is in conflict with every variable (except itself) in the undead-out set associated with the instruction.
Any variable defined during a move instruction is in conflict with every variable in the undead-out set associated with the instruction, except itself and the variable referenced in the move.
Exercise 4: Design and implement the function conflict-analysis, which decorates a program with its conflict graph. The source language is Undead-loc-lang and the target language is Conflict-lang program.
For working with sets, you may want to use Sets.
> (conflict-analysis '(begin ((locals (x.1)) (undead-out ((x.1) ()))) (set! x.1 42) (halt x.1))) '(begin ((locals (x.1)) (conflicts ((x.1 ())))) (set! x.1 42) (halt x.1))
> (conflict-analysis '(begin ((locals (a.1 b.2 c.3 d.4 e.5)) (undead-out ((a.1) (c.3 a.1) (c.3 a.1) (b.2 c.3) (d.4) ()))) (set! a.1 1) (set! c.3 2) (set! b.2 a.1) (set! b.2 (+ a.1 c.3)) (set! d.4 (* b.2 c.3)) (halt d.4)))
'(begin
((locals (a.1 b.2 c.3 d.4 e.5))
(conflicts ((e.5 ()) (d.4 ()) (c.3 (a.1 b.2)) (b.2 (c.3)) (a.1 (c.3)))))
(set! a.1 1)
(set! c.3 2)
(set! b.2 a.1)
(set! b.2 (+ a.1 c.3))
(set! d.4 (* b.2 c.3))
(halt d.4))
> (conflict-analysis '(begin ((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1)) (undead-out ((v.1) (v.1 w.2) (w.2 x.3) (p.1 w.2 x.3) (w.2 x.3) (y.4 w.2 x.3) (p.1 y.4 w.2 x.3) (y.4 w.2 x.3) (z.5 y.4 w.2) (z.5 y.4) (t.6 z.5) (t.6 z.5 p.1) (t.6 z.5) (z.5) ()))) (set! v.1 1) (set! w.2 46) (set! x.3 v.1) (set! p.1 7) (set! x.3 (+ x.3 p.1)) (set! y.4 x.3) (set! p.1 4) (set! y.4 (+ y.4 p.1)) (set! z.5 x.3) (set! z.5 (+ z.5 w.2)) (set! t.6 y.4) (set! p.1 -1) (set! t.6 (* t.6 p.1)) (set! z.5 (+ z.5 t.6)) (halt z.5)))
'(begin
((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1))
(conflicts
((p.1 (t.6 z.5 y.4 w.2 x.3))
(t.6 (z.5 p.1))
(z.5 (t.6 p.1 y.4 w.2))
(y.4 (z.5 p.1 w.2 x.3))
(x.3 (p.1 y.4 w.2))
(w.2 (z.5 p.1 y.4 v.1 x.3))
(v.1 (w.2)))))
(set! v.1 1)
(set! w.2 46)
(set! x.3 v.1)
(set! p.1 7)
(set! x.3 (+ x.3 p.1))
(set! y.4 x.3)
(set! p.1 4)
(set! y.4 (+ y.4 p.1))
(set! z.5 x.3)
(set! z.5 (+ z.5 w.2))
(set! t.6 y.4)
(set! p.1 -1)
(set! t.6 (* t.6 p.1))
(set! z.5 (+ z.5 t.6))
(halt z.5))
9.5 Optimizing assign-homes
We can now assign registers for all Loc-locals-lang programs from last week. By assigning variables to registers, patch-instructions will be able to produce much faster code because it will not need to move values to and from memory on every single instruction.
Exercise 5: Define assign-homes-opt, which can be used as a drop-in replacement for assign-homes. The interface is the same as assign-home: the source language is Loc-locals-lang and the target language is Loc-assigned-lang.
Run your test suite and ensure you get the same outputs whether you use assign-homes or assign-homes-opt.
> (define p '(begin ((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1))) (set! v.1 1) (set! w.2 46) (set! x.3 v.1) (set! p.1 7) (set! x.3 (+ x.3 p.1)) (set! y.4 x.3) (set! p.1 4) (set! y.4 (+ y.4 p.1)) (set! z.5 x.3) (set! z.5 (+ z.5 w.2)) (set! t.6 y.4) (set! p.1 -1) (set! t.6 (* t.6 p.1)) (set! z.5 (+ z.5 t.6)) (halt z.5))) > (assign-homes-opt p)
'(begin
((assignment
((v.1 r15) (w.2 r12) (x.3 r13) (y.4 r14) (z.5 r13) (t.6 r14) (p.1 r15))))
(set! v.1 1)
(set! w.2 46)
(set! x.3 v.1)
(set! p.1 7)
(set! x.3 (+ x.3 p.1))
(set! y.4 x.3)
(set! p.1 4)
(set! y.4 (+ y.4 p.1))
(set! z.5 x.3)
(set! z.5 (+ z.5 w.2))
(set! t.6 y.4)
(set! p.1 -1)
(set! t.6 (* t.6 p.1))
(set! z.5 (+ z.5 t.6))
(halt z.5))
> (assign-homes p)
'(begin
((locals (v.1 w.2 x.3 y.4 z.5 t.6 p.1))
(assignment
((v.1 (rbp - 8))
(w.2 (rbp - 16))
(x.3 (rbp - 24))
(y.4 (rbp - 32))
(z.5 (rbp - 40))
(t.6 (rbp - 48))
(p.1 (rbp - 56)))))
(set! v.1 1)
(set! w.2 46)
(set! x.3 v.1)
(set! p.1 7)
(set! x.3 (+ x.3 p.1))
(set! y.4 x.3)
(set! p.1 4)
(set! y.4 (+ y.4 p.1))
(set! z.5 x.3)
(set! z.5 (+ z.5 w.2))
(set! t.6 y.4)
(set! p.1 -1)
(set! t.6 (* t.6 p.1))
(set! z.5 (+ z.5 t.6))
(halt z.5))
> (define (loc-assigned-execute f) (lambda (p) (parameterize ([current-pass-list (list f replace-locations patch-instructions check-paren-x64 generate-x64 wrap-x64-boilerplate wrap-x64-run-time)]) (execute p)))) > (define loc-lang-execute-opt (loc-assigned-execute assign-homes-opt)) > (define loc-lang-execute (loc-assigned-execute assign-homes)) > (loc-lang-execute-opt p) 42
> (loc-lang-execute p) 42
Exercise 6: Create two versions of your compiler by defining two functions, compiler-a2 and compiler-a3. The source language should be Values-lang and the target should be x64-linux, represented as a string. compiler-a2 should use assign-homes, while compiler-a3 should use assign-home-opt instead. You should use parameterize and current-pass-list.
Write a paragraph comparing the two compilers. Consider the following questions:
What are benefits and the disadvantages of each?
Is one "optimal", and if so, in what sense?
Try running the same programs compiled with each compiler. Are there any surprises?