Implement key algorithms to perform register allocation on OAT programs. You will take a program like this:
Which would have ordinarily turned into something like this:
But which should now look like this:
This package depends on the files from Project 6.
Remember that you may change the provided code so long as you keep the interfaces the same (so that we can use everyone's testers on everyone's assignments).
This project adds four new modules to the OAT compiler. Together these form an optimizing backend. They are:
This is a group project. Groups of size 1 or 2 are acceptable.
You must first implement the functions dominators and dominance_frontier in Fullildom. We suggest that you use the algorithm due to Cooper, Harvey and Kennedy that was discussed in class. The relevant paper is online.
We provide the code that makes the initial SSA transformation (using your dominance algorithms). For reference, here is what the above program looks like in fully-expanded form:
There are several things to note:
Complete the function get_liveness in Fullilssa. This function calculates the live-in and live-out sets for CFG nodes in a particular procedure. You should use the dataflow analysis algorithm that was presented in class, though your implementation will differ because the program representation allows for more than one (non-branch) instruction in a basic block.
Handling phi-functions in get_liveness can be tricky. If you look at get_defs_uses, you will find that phi-functions are defined inside the blocks to which they are attached. Unlike a regular instruction, however, a phi-function's right-hand side does not add any uses to the def-use table. If this were the case, then it's easy to see that the liveness algorithm would float all of the phi-function's arguments to the top node of the program! To solve this problem, you should add to the live-out sets of the immediate predecessors of nodes containing phi-functions. For example, the live-in and live-out sets for the code above are:
Notice that bk1 does not have 10:1 on its live-out set while bk2 does not have 10:2; also note that bk0 has neither 10:1 nor 10:2 on its in-set.
Once you have the live-in and live-out sets, you'll want to be able to query them to see whether a particular doperand is live at a given block:instruction pair. Complete the functions is_live_at_block and is_live_at.
Now that you can query for liveness, you can build up a graph of those doperands that interfere with one another. We provide code for doing so in build_interference_graph. The graph keeps track of doperands that cannot coexist in a register as well as doperands that are move-related. These are operands that may be simultaneously live but are only copies of each other. (Assuming that we can store both in the same place works due to SSA's guarantee that each assignment gets a unique name.)
You can see an example (colored) interference graph here. Thick edges are actual interferences and mark nodes that cannot be stored to the same physical register at the same time. Dotted edges mark nodes that are move-related and therefore may be stored in the same register simultaneously.
Complete the function color_stack, which takes a color map (a hash table with doperand keys and igraphcolor values) and a stack of graph nodes and attempts to give all nodes in the stack a color. It may be the case that you are not able to find a coloring for a particular node; you should then raise an exception with the node that you failed to color. This is caught by the spilling code, which breaks up live ranges when necessary.
You must also provide the function pick_spill_candidate, which takes an interference graph and chooses the best (for some reasonable definition of best) node to spill. Some nodes should never be spilled; you can check this by using the is_heavy_node function with the node's ig_symbol. If no spillable nodes remain, raise No_spill_candidate.
Often it is the case that the left-hand side of a phi-function is mapped to the same register as all of the possible operands on its right-hand side. In certain cases, though, the sides may make inconsistent assumptions. It is sometimes necessary to insert a small amount of bookkeeping code that shuffles operands around in registers between a predecessor node and its phi-function bearing successor. This process is made slightly difficult by the fact that these thunk routines have no scratch space.
Complete the function make_thunk_code, which takes a list of (dest-opnd,source-opnd) mappings and produces thunk code to be run on the edge between two nodes dest and source where dest contains one or more phi-functions that use nonhomogeneous registers. You will find the new instruction Il.Ext (Swap (a,b)) very useful.
Edit the included readme.txt file, making sure to fill in your name(s) and email address(es).
You should also spend some time to ensure that your code is readable and well-commented. We have some suggestions and requirements for your programming style. You will definitely lose points for the file submission requirements (code must compile, 80 column limit, no tabs); however, barring any particularly egregious cases, you may take the remaining rules as suggestions.
Part of your grade will be based on testing. Some example tests and utility functions are provided in fullilssa_test.ml and fullildom_test.ml. You must use the testing framework provided and explicitly note any modifications in your readme.txt file. We ask that you name your test cases regularly: choose a username from those in your group. All test names and files should contain that username followed by an underscore as a prefix. (For example, if your username is cat and you have written a test case called alive, that test should be called cat_alive.)
The grading breakdown for this project is as follows:
In addition, you can receive extra credit for using information about move-related operands to drive graph coloring (+5%) and for improving the provided code (either by making it generate better IL/X86 or by improving the speed of the compiler itself). Contact us to see how much a change is worth and make note of it in your readme.txt.
Remember that your raw score will be scaled— that is, more difficult assignments will be worth more than less difficult assignments, even if the more difficult assignment has fewer available points.
Running ralloc.run --help will give you a comprehensive documented list of supported flags. Remember that the optimizing backend will not run without the -O flag being provided.
First, make sure to clean your project with omake clean. All of the files you've written should be in projects/ralloc. Submit your assignment on ENIAC. Your project must run on ENIAC, including all test cases. Be sure to submit from ENIAC and not from some other Linux machine; otherwise you will get an error.
Switch to the projects directory and type: