Draft function|call|return VM instr.s (TODO debug)
x1phosura x1phosura@x1phosura.zone
Tue, 05 Dec 2023 02:47:08 -0800
3 files changed,
195 insertions(+),
21 deletions(-)
M
projects/08/src/codewriter.h
→
projects/08/src/codewriter.h
@@ -97,11 +97,12 @@ "0;JMP\n" // return
"\n" "(__comp_funcs_end)\n"; -char vm_init[] = "@256\n" // starting address of stack (nothing pushed yet) - "D=A\n" // D = 256 - "@SP\n" // A = <constant representing address of SP> - "M=D\n" // <memory pointed to by SP> = 256 - "\n%s\n"; // <- comp_vm_funcs +char vm_init[] = "@256\n" // start address of stack (nothing pushed yet) + "D=A\n" // D = 256 + "@SP\n" // A = <constant representing address of SP> + "M=D\n" // <memory pointed to by SP> = 256 + "@Sys.init\n" // jump to Sys.init function (not call since + "0;JMP\n\n"; // we don't need to set up the stack) // TODO: add initializers for argument, local, static, constant, this, that // TODO only output vm_stop when Main.main@@ -109,10 +110,10 @@ char vm_stop[] = "(END)\n" // starting address of stack (nothing pushed yet)
"@END\n" // D = 256 "0;JMP\n"; // A = <constant representing address of SP> - void write_vm_init(FILE *fp) { - fprintf(fp, vm_init, comp_vm_funcs); + fprintf(fp, "%s", vm_init); // can (un)comment to toggle init behavior + fprintf(fp, "%s\n", comp_vm_funcs); } void write_vm_stop(FILE *fp)@@ -360,18 +361,75 @@ fprintf(fp, if_asm, vm_instr->arg1);
return true; } +////////// +// +// The Elements of Computing Systems, 2nd edition, pg. 214 +// +// CALLING CONVENTIONS STACK DIAGRAM +// =================================== +// Low memory addresses +// +// ... similar blocks above ... +// |--------------------| \ +// | some value | | Local variables/working stack of _caller_ +// |--------------------| | +// | some value | | +// |--------------------| < -------- BEGIN FUNCTION CALL -------- +// ARG -> | argument 0 | | Argument segment of _callee_: +// |--------------------| | Pushed by caller before calling +// | argument 1 | | callee using 'call' command. +// |--------------------| | +// | ... | | +// |--------------------| < +// LCL-5 -> | return address | | Saved frame of _caller_: +// |--------------------| | Pushed by the VM when handling +// LCL-4 -> | saved LCL | | the 'call' command. +// |--------------------| | When handling 'return', the VM +// LCL-3 -> | saved ARG | | pops these values and uses them +// |--------------------| | for restoring the memory segments +// LCL-2 -> | saved THIS | | of, and returning to, the caller's +// |--------------------| | code. +// LCL-1 -> | saved THAT | | +// |--------------------| < +// LCL -> | local 0 | | The local segment of _callee_: +// |--------------------| | Initialized by the VM when handling +// | local 1 | | the 'function' command. +// |--------------------| | +// | working stack... | | Working stack of _callee_ +// |--------------------| < +// SP -> | < undefined > | | +// +--------------------+ | +// _||_ | +// direction stack \ / / +// grows (up) \/ +// +// High memory addresses +// + static bool write_function(struct vm_instruction_t *vm_instr, FILE *fp) { - /* - * (functionName) // injects function entry label into the code - * repeat nVars times: // nVars = number of local variables - * push 0 // initializes local variable to 0 - */ - print_vm_instruction(vm_instr, fp); - return true; // STUB TODO implement + size_t i, num_locals; + num_locals = vm_instr->arg2; + char set_local_boilerplate[] = "M=0\n" + "A=A+1\n"; + char set_sp[] = "D=A\n" // D = number of locals + "@SP\n" // get address of SP + "M=D\n"; // set SP to new value + + // (functionName) // injects function entry label into the code + // repeat nVars times: // nVars = number of local variables + // push 0 // initializes local variable to 0 + fprintf(fp, "(%s)\n", vm_instr->arg1); // write function label + fprintf(fp, "@SP\n"); // get SP + for (i = 0; i < num_locals; ++i) { + fprintf(fp, "%s", set_local_boilerplate); // write 'push 0' + } + fprintf(fp, "%s", set_sp); + + return true; } -static bool write_return(struct vm_instruction_t *vm_instr, FILE *fp) +static bool write_return(FILE *fp) { /* * frame = LCL // frame is a temporary variable@@ -384,12 +442,62 @@ * ARG = *(frame - 3) // restores ARG for the caller
* LCL = *(frame - 4) // restores LCL for the caller * goto retAddr // jump to where return address points */ - print_vm_instruction(vm_instr, fp); - return true; // STUB TODO implement + char ret_boilerplate[] = "// *ARG = pop()\n" + "@SP\n" + "AM=M-1\n" // decrement A and SP + "D=M\n" // "pop" (read) return value into D + "@ARG\n" + "A=M\n" // A points to argument segment + "M=D\n" // *ARG = D // (D = pop()) + "// SP = ARG+1\n" + "D=A+1\n" // A still contains arg pointer + "@SP\n" + "M=D\n" + "// frame = LCL\n" + "// THAT = *(frame - 1)\n" + "@LCL\n" + "AM=M-1\n" // decrement A and LCL + "D=M\n" // D = *(frame - 1) + "@THAT\n" + "M=D\n" // THAT = D + "// THIS = *(frame - 2)\n" + "@LCL\n" + "AM=M-1\n" // decrement A and LCL + "D=M\n" // D = *(frame - 2) + "@THIS\n" + "M=D\n" + "// ARG = *(frame - 3)\n" + "@LCL\n" + "AM=M-1\n" // decrement A and LCL + "D=M\n" // D = *(frame - 3) + "@ARG\n" + "M=D\n" + "// R13 = retAddr = *(frame - 5)\n" + "@LCL\n" + "AM=M-1\n" // decrement A and LCL + // now LCL = LCL - 4 + "D=M-1\n" // D = *(frame - 4 - 1) + "@R13\n" + "M=D\n" // R13 = D (retAddr) + "// LCL = *(frame - 4)\n" + "@LCL\n" + "A=M\n" // put pointer in address reg. + "D=M\n" // D = prev. LCL value + "@LCL\n" + "M=D\n" // write prev LCL value to LCL + "// goto retAddr\n" + "@R13\n" // location of retAddr + "A=M\n" // A = retAddr + "0;JMP\n" // return + "\n"; + + fprintf(fp, "%s", ret_boilerplate); + return true; } static bool write_call(struct vm_instruction_t *vm_instr, FILE *fp) { + uint16_t new_arg; /* * push retAddr // generates a label and pushes it to the stack * push LCL // saves LCL of caller@@ -401,8 +509,72 @@ * LCL = SP // repositions LCL
* goto functionName // transfers control to the callee * (retAddr) // injects the return address label into the code */ - print_vm_instruction(vm_instr, fp); - return true; // STUB TODO implement + // Note: I use a different convention for generating return address + // labels. The book does FilenameFunctionname$ret.<0-n> for n returns, + // but the extra bookkeeping will annoy me. So I'll just use the current + // line number instead. This works because the function name also + // contains the file name, and the file name combined with a line number + // provides a uniqueness guarantee, so return addresses won't collide. + char call_boilerplate[] = "// push retAddr\n" + "@%s.%hu\n" // will format to retAddr + "D=A\n" // D = retAddr as constant + "@SP\n" + "M=M+1\n" // RAM[SP]++ // inc SP + "A=M-1\n" // A = RAM[SP] - 1 // prev top + "M=D\n" // RAM[SP] = local pointer + "// push LCL\n" + "@LCL\n" + "D=M\n" // D = current local pointer + "@SP\n" + "M=M+1\n" // RAM[SP]++ // inc SP + "A=M-1\n" // A = RAM[SP] - 1 // prev top + "M=D\n" // RAM[SP] = arg pointer + "// push ARG\n" + "@ARG\n" + "D=M\n" // D = current arg pointer + "@SP\n" + "M=M+1\n" // RAM[SP]++ // inc SP + "A=M-1\n" // A = RAM[SP] - 1 // prev top + "M=D\n" // RAM[SP] = arg + "// push THIS\n" + "@THIS\n" + "D=M\n" // D = current this + "@SP\n" + "M=M+1\n" // RAM[SP]++ // inc SP + "A=M-1\n" // A = RAM[SP] - 1 // prev top + "M=D\n" // RAM[SP] = this + "// push THAT\n" + "@THAT\n" + "D=M\n" // D = current that + "@SP\n" + "M=M+1\n" // RAM[SP]++ // inc SP + "A=M-1\n" // A = RAM[SP] - 1 // prev top + "M=D\n" // RAM[SP] = that + "// ARG = SP - 5 - nArgs\n" + "@SP\n" + "D=M\n" // D = SP + "@%hu\n" // will format to (5 + nArgs) + "D=D-A\n" // D = SP - 5 - nArgs + "@ARG\n" + "M=D\n" + "// LCL = SP\n" + "@SP\n" + "D=M\n" // D = SP + "@LCL\n" + "M=D\n" // LCL = SP + "// goto functionName\n" + "@%s\n" + "0;JMP\n" + "// (retAddr)\n" + "(%s.%hu)\n" + "\n"; + + //print_vm_instruction(vm_instr, fp); // TODO remove me + new_arg = 5 + vm_instr->arg2; // probably breaks if arg2 > 32K lmfao + fprintf(fp, call_boilerplate, vm_instr->arg1, file_line_no, + new_arg, vm_instr->arg1, + vm_instr->arg1, file_line_no); + return true; } bool write_instruction(struct vm_instruction_t *vm_instr, FILE *fp)@@ -425,7 +597,7 @@ return write_if(vm_instr, fp);
case C_FUNCTION: return write_function(vm_instr, fp); case C_RETURN: - return write_return(vm_instr, fp); + return write_return(fp); case C_CALL: return write_call(vm_instr, fp); default:
M
projects/08/src/parser.h
→
projects/08/src/parser.h
@@ -75,7 +75,7 @@ }
void print_vm_instruction(struct vm_instruction_t *vm_instr, FILE *fp) { - fprintf(fp, "{\n\tcmd: %d,\n\targ1: \"%s\",\n\targ2: %hu,\n}\n", + fprintf(fp, "//{\n\t//cmd: %d,\n\t//arg1: \"%s\",\n\t//arg2: %hu,\n//}\n", vm_instr->cmd, vm_instr->arg1, vm_instr->arg2); }
M
projects/08/src/vmtranslator.c
→
projects/08/src/vmtranslator.c
@@ -63,6 +63,8 @@ return filepath;
} // sets symbol name (used for statc vars) to base name of file before extension +// (note: this is basically just the filename w/o the extension) +// TODO: rename this function to something clearer static bool set_static_symbol_name(char *filename) { size_t i, filename_len, base_name_len; char *tmp, *base_name;