Now we switch to making the shunting yard code into a very basic compiler. To do this for the most part we emit (output) assembly code to do in assembly what the C program would normally do itself, at least with respect to the evaluation of the expression.
We switch to using the hardware stack for our numbers/values. For this version of our program we use the push_num() only to push constant values onto the hardware stack, however the pop instruction must pop the value off the stack into a register, so we pass to the pop_num() function the register name to pop the value into.
push_num()
pop_num()
// Emit code to push an immediate value onto the stack: void push_num(int n) { printf("\tpush %d\n", n); } // Emit code to pop a number off stack into a specific register: void pop_num(char *reg) { printf("\tpop %s\n", reg); }
Almost all the work of converting the program into a compiler is actually done in the action function. In this function we still pop the operator in the normal fashion, but the pop_num() functions will emit code to pop values off the hardware stack into the rbx (the right side of the expression) and rax (the left side) unless the operator is unary in which case code to move a 0 into rax is emitted.
Then the particular operation is emitted in assembly. Using rax as the left side makes it convenient to use for the mul and div instructions. Finally push rax back onto the stack as that holds the result of the operation.
void action(void) { struct op op; // Get the operator: op = pop_op(); // Right side is at the top of the stack, left would be underneath it, // unless it's a unary op pop_num("rbx"); if (op.unary == FALSE) pop_num("rax"); else printf("\tmov rax, 0\n"); if (uprec[op.op] == -1 && op.unary) die("Malformed unary expression in action."); // Perform the operation: switch(op.op) { case T_PLUS: printf("\tadd rax,rbx\n"); break; case T_MINUS: printf("\tsub rax,rbx\n"); break; case T_MULT: printf("\tmul rbx\n"); break; case T_DIV: printf("\tcqo\n\tdiv rbx\n"); break; default: die("Illegal operation."); } // Push the result onto the stack: printf("\tpush rax\n"); }
Before we enter the main-loop, we emit code for the assembly code to include our library and any needed external functions that we'll use for our assembly code and then setup the text section and _start function.
_start
printf("%%include \"lib/lib.h\"\n"); printf("extern printnum, putc, exit\n\n"); printf("SECTION .text\n\n"); printf("GLOBAL _start\n_start:\n");
Finally after the main loop (which requires no modification,) we emit code to pop the result off the stack and print it, then exit the program.
pop_num("rax"); printf("\tcall printnum\n"); printf("%s", "\tmov al, `\\n`\n" "\tcall putc\n"); printf("%s", "\tmov rdi, 0\n" "\tcall exit\n");