Assembly Generation (emit.ml)

At last, we reached the final phase: assembly generation. Since we have already finished the most complex part (namely, register allocation), we can just output SparcAsm.t as real SPARC assembly with no particular difficulty.

Of course, however, we must implement virtual instructions. Conditional expressions are implemented by comparisons and branches. Save and Restore are implemented with store and load by calculating the set stackset of already saved variables and the list stackmap of stack variables' locations. Function calls are easy, maybe except the (somewhat tricky) function Emit.shuffle to arrange arguments in the order of registers.

The only non-trivial, important process in this module is tail call optimization. It implements function calls (tail calls) that have nothing afterward and do not return, by mere jump instructions instead of calls. Thanks to this optimization, recursive functions such as gcd are compiled just into the same code as loops. For this purpose, function Emit.g which generates assembly for instruction sequences, as well as function Emit.g' which generates assembly for each instruction, takes a data type value Emit.dest that represents whether we are in a tail position. If dest is Tail, we tail-call another function by a jump instruction, or set the result of computation to the first register and return by the ret instruction of SPARC. If dest is NonTail(x), we set the computation result on x.

Finally, we implement the main routine stub.c which allocates the heap and function call stack, provide external functions libmincaml.S that are necessary for test programs, and then get a MinCaml compiler that works!