In this component, you will implement a bottom-up local register allocator based on the algorithm in Ch. 13.3.2 of Engineering a Compiler. Your program will takes as input a sequence of branch-free operations, as specified in Component 1, and an integer k ≥ 3. Your program will produce as output an equivalent sequence of ILOC operations that uses at most k registers.

Do not underestimate this component. This is the hardest single assignment I give in any of my courses.

Specification

For the purposes of this component, an output block is equivalent to an input block when, for every input, they print same values in the same order; and each data-memory location defined in the execution of the input block has the same value in execution of the output block. It is permissible for output block to define additional memory locations that hold spilled values.

Your allocator MUST only perform register allocation. No other optimization is allowed. While you could execute some blocks and replace them with a series of output and store operations, this is forbidden. The output block MUST contain all of the arithmetic operations, in the same sequence, as the input block. The goal is to focus on the problems that arise in a local register allocator, not to capitalize on the simplified form of the ILOC test blocks. You MUST NOT use hashtables for this component.

Before submitting your program, test it thoroughly on all provided blocks.

Core Component: Naïve Allocator

You MUST extend tipc to support register allocation. As before, your input is a ingle basic block, consisting of instructions from the same subset of ILOC as The Lexer/Parser Component. You SHOULD use the lexer, parser, and intermediate representation you built in that component, so  be sure to fix any parsing or lexing errors: they will count as failures for this component. You SHOULD update your intermediate representation to use a struct or class for each operand, so you can store the different registers allocated at different stages of the process. You SHOULD store the original index of each instruction, which MAY be the line number in the source file or MAY simply be a count of instructions in the source file.

The new component of tipc is the bottom-up local register allocator, described below and drawn from Figure 13.1, page 687, of Engineering a Compiler. This proceeds in two steps.

You SHOULD use different fields to store the source register, virtual register, and allocated physical register of each operand, along with all necessary annotations like next use. The algorithm here use the fields SR, VR, PR, and NU for the source register, virtual register, physical register, and next use value. These algorithms assume each instruction has three operands. Modifying them to work with the full range of instructions is left as an exercise for the implementer. 

As a reminder, a store r1 => r3 instruction does not define a register. The second operand, r3, is a use, even though it occurs on the right of a =>. You MUST to treat both operands as uses. You SHOULD store the second operand in the second column of your intermediate representation.

You MAY add annotations to your intermediate representation to note the original index, if an instruction is a spill or restore instruction, or other useful debugging information. If printed during pretty-printing, these annotations MUST be printed as comments.