Your compiler executable should take two command line arguments, the first is the C-- code source file and the second is the name of the MIPS assembly code output file:
$ ./mycc foo.c-- foo.mipsYou can then run your MIPS assembly program using spim or xspim:
$ spim foo.mips $ xspim foo.mips
$ cd cs75/CS75_repositoryname/project $ cp /home/newhall/public/cs75/proj3_startingpt/setup3 .
$ ./setup3The setup3 script will copy over project 3 starting point files into the symtab, parser, and includes subdirectories, call svn add to add these files to your repository, and then call svn to commit the new files.
include/[codegen.h, symtab.h]: shell of header file for code generator and symbol table
codegen/codegen.c: starting point for code generator module, the codegen function
should take the AST and generate MIPS code to a codetable
codegen/main.c: the main program for the full C-- compiler, you may need to change
some of the function calls in here, but it is basically complete
codegen/Makefile: builds the mycc C-- compiler, modify this if you add more .c files
symtab/symtab.c: starting point for the symbol table module, you won't need
this until the next assignment
For this part of the code generator, you will ignore all variables and function calls. Assume that all C-- code will simply be a main program that deals directly with numbers.
The code table contains instruction entries. You should define a struct that can store all types of MIPS instructions, labels, and directives. Make sure to use enum or #defines for field values to make your code readable.
.data
_newline_:
.asciiz "\n"
.text
.globl main
_newline_ is used for writeln and the only global symbol is main.
Assume that the callee will always save all callee saved registers in the prolog. As an extra credit part of the next assignment, you can think about saving only the ones the callee actually needs to use.
Because you are not handling code generation for functions in this assignment, you should hard-code in calls to generate main's prolog before you handle generating code for the parts of C-- that you are handling, and then hard-code in a call to generate main's epilog after you are done generation code for the parts of C-- you are handling. In the next assignment you will remove these calls and handle generating code for main's prolog and epilog just like you handle for any other function definition in the AST.
An example C-- program that you should be able to compile is the following (you do not need to handle the variable declaration list part of a block for this assignment):
int main() {
if(4 != 5 || 6 == 8) {
write 1;
writeln;
write 2 + 6 * 3 - 'a';
writeln;
} else {
write 0;
writeln;
}
while(8 < 3) {
write 8;
}
}
int main() {
write 9;
write 9 + 10;
write 10 - 3;
}
Then try more and more complicated and longer arithmetic expressions
and test that your generated code is correct.
To generate code for expressions you need to use registers to store temporary results. For example, if the AST for a write stmt looks like:
write
|
+
/ \
9 *
/ \
10 3
The value of 10 needs to be loaded into a register, the value of 3 needs to be
loaded into a register, they need to be multiplied together and the result
stored in a register, then the value of 9 needs to be stored in a register
and added to the register storing 10*3 and that result needs to be stored
in a result register.
You should use the caller saved registers $t0-$t7 for evaluating expressions. Your code generator will need to implement a register allocation scheme for these registers so that when a temporary register is needed to store an operand or result, one can be found and when a temporary register is no longer needed it can be made available for generating other instructions. Make sure that you can handle expressions that have more operands then there are temporary registers:
write 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
Once you have stress tested arithmetic expressions, implement relational expressions:
write 9 > 13; // evaluates to 0 (false) write 9 <= 13; // evaluates to 1 (true) write 9 == 13; // evaluates to 0
Once you have thoroughly tested relational operators, then implement code generation for expressions with logical operators:
write !2; // evaluates to 0 write 0 || 1; // evaluates to 1 write 8 < 9 && 7 != 6 && 2 > 10; // evaluates to 0You must implement short-circuiting for (expr || expr) and ( expr && expr) To do this you need to implement unique labels to which the short-circuiting code will branch to skip over evaluating the rest of the expression once the final answer is known. You can use a simple label notation, like .Li where i increases for each unique label generated. sprintf may be a useful function for producing the label string.
... # instructions for the if part
...
.L0_else: # a unique label for the start of the else part
...
...
.L1_ifdone: # a unique label for the end of the if-else stmt