nand2tetris/vm/CodeWriter.php

606 lines
13 KiB
PHP

<?php
namespace captn3m0\NandToTetris;
class CodeWriter {
function __construct($outputFile) {
$this->ic = 0;
$this->sourceLine = 0;
$this->file = fopen($outputFile, "w");
// We aren't inside a function by default
$this->fn = null;
// Write the preamble for the assembler
$this->writeInit();
}
public function writeReturn() {
$this->write([
"@SP",
"A=M-1",
"D=M",// Popped value to D
// And then write it to *ARG = pop()
"@ARG",
"A=M",
"M=D",
// SP=ARG+1
"@ARG",
"D=M+1",
"@SP",
"M=D // @SP = ARG+1",
"@LCL",
"D=M",
"@R13",
"M=D // Save LCL to R13",
// now we go restoring THAT, THIS, ARG, LCL
"A=D-1 // A=*LCL-1",
"D=M // D=*(*LCL-1)",
"@THAT // A=THAT",
"M=D // *that = *(*lcl-1)",
// now we restore THIS
"@R13",
"A=M-1",
"A=A-1 // A=*LCL-2",
"D=M // D=*(*LCL-2)",
"@THIS // A=THIS",
"M=D // *THIS = *(*lcl-2)",
// now we restore ARG
"@R13",
"A=M-1",
"A=A-1",
"A=A-1 // A=*LCL-3",
"D=M // D=*(*LCL-3)",
"@ARG // A=ARG",
"M=D // *ARG = *(*lcl-3)",
// now we restore LCL
"@R13",
"A=M-1",
"A=A-1",
"A=A-1",
"A=A-1 // A=*LCL-4",
"D=M // D=*(*LCL-4)",
"@LCL // A=LCL",
"M=D // *LCL = *(*lcl-4)",
// Now we hyperjump
"@R13",
"A=M-1",
"A=A-1",
"A=A-1",
"A=A-1",
"A=A-1 // A=*LCL-5",
"A=M // A=*(*LCL-5)",
"0;JMP // Jump to *(LCL-5)",
]);
}
/**
* Translates the "function" start.
* function $name $args
*/
public function writeFunction($name, $numArgs) {
// This is used for labels within the current function
$this->fn = $name;
$this->write([
"($name) // function $name $numArgs",
]);
// We write this once at the top
// And for subsequent loops, we can re-use
// the @SP call at the end where we bump the stack by 1
if ($numArgs > 0) {
$this->write([
// This is only required for the first argument
"@SP", "A=M"
]);
}
// For every argument in the function
// push a zero to the stack
for($i=0;$i<$numArgs;$i++) {
$this->write([
"M=0",
"@SP",
"AM=M+1",
]);
}
}
/**
* Function call executed, save state and JUMP
*/
public function writeCall(String $functionName, $numArgs) {
$label = bin2hex(random_bytes(16));
// push the label to top of the stack
$this->write([
"@$label",
"D=A",
"@SP",
"A=M",
"M=D",
"@SP",
"M=M+1"
]);
$pushes = [
"@LCL",
"@ARG",
"@THIS",
"@THAT",
];
// TODO: optimize this by saving LCL, ARG
// then doing the SP-n-5 calculation (since that will be much faster)
// and then updating ARG
foreach ($pushes as $lookupRegister) {
$this->write([
"$lookupRegister // Read $lookupRegister to A",
"D=M // Put $lookupRegister to D",
"@SP",
"A=M",
"M=D // Save $lookupRegister to SP",
"@SP",
"M=M+1",
]);
}
// Load current stackpointer to D
$this->write([
"@SP",
"D=M",
]);
// Write current stackpointer to LCL
$this->write([
"@LCL",
"M=D",
]);
// Reduce D height times = numArgs+5
$height = $numArgs + 5;
for ($i=0; $i < $height; $i++) {
$this->write([
"D=D-1",
]);
}
// now D = SP-n-5
// now we need to write D to ARG
$this->write([
"@ARG",
"M=D",
"@$functionName // Jump to $functionName",
"0;JMP",
"($label) // return back from function here",
]);
}
/**
* Writes the preable to initialize the VM
*/
private function writeInit() {
$this->write([
"@256 // init starts",
"D=A",
"@SP",
"M=D // initialized SP to 256",
"@300",
"D=A",
"@LCL",
"M=D // initialized @LCL to 300",
"@400",
"D=A",
"@ARG",
"M=D // initialized @ARG to 400, init ends",
// We jump to Sys.init
"@Sys.init",
"0;JMP"
]);
}
function setInputFileName($inputFileName) {
$this->vm = basename($inputFileName, ".vm");
}
function nextSourceLine() {
$this->sourceLine += 1;
}
function __destruct() {
fclose($this->file);
}
/**
* Puts the closing non-terminating
* loop
*/
function close() {
$endJump = $this->ic+1;
$this->write([
"@$endJump",
"0;JMP"
]);
}
/**
* Writes label X as (fnlabel)
*/
function writeLabel(String $label) {
$globalLabel = $this->resolveLabel($label);
$this->write([
"($globalLabel) // end label $label (L{$this->sourceLine})",
]);
}
/**
* Generates a unique global label
* by using the current function name
*/
private function resolveLabel(String $label) {
if($this->fn === null)
return "__GLOBAL__.$label";
return $this->fn . $label;
}
/**
* Writes a unconditional jump statement
*/
public function writeGoto(String $label) {
$globalLabel = $this->resolveLabel($label);
$this->write([
"@$globalLabel",
"0;JMP // end goto $label (L{$this->sourceLine})",
]);
}
/**
* Writes corresponding code for if-goto
* if value == true, goto X
* else keep executing
*/
function writeIf(String $label) {
$globalLabel = $this->resolveLabel($label);
$this->write([
// Read top of the stack to D
'@SP',
'AM=M-1',
'D=M',
"@$globalLabel",
"D;JNE // end if-goto $label (L{$this->sourceLine})",
]);
}
function writeArithmetic(String $command) {
$stackDecrease=true;
// Read top of stack to D
$this->write([
"@SP // ==== $command ====",
"A=M-1",
"D=M"
]);
switch ($command) {
// TODO: Combine all the binary math commands into one
// And the unary math commands into one
case 'sub':
$this->write([
'A=A-1',
"M=M-D",
]);
break;
case 'add':
// But add it to previous D this time
$this->write([
'A=A-1',
'M=D+M'
]);
break;
case 'neg':
$this->write([
"M=-M // end $command (L{$this->sourceLine})",
]);
$stackDecrease = false;
break;
case 'not':
$this->write([
"M=!M // end $command (L{$this->sourceLine})",
]);
$stackDecrease = false;
break;
case 'and':
$this->write([
'A=A-1',
'M=D&M',
]);
break;
case 'or':
$this->write([
'A=A-1',
'M=D|M',
]);
break;
// TODO: Combine all the boolean commands
case 'lt':
$jumpPointer = $this->ic+10;
$this->write([
'A=A-1',
'D=M-D',
'M=0',
'M=M-1',
"@$jumpPointer",
"D;JLT",
"@SP",
"A=M-1",
"A=A-1",
"M=0",
]);
break;
case 'gt':
$jumpPointer = $this->ic+10;
$this->write([
'A=A-1',
'D=M-D',
'M=0',
'M=M-1',
"@$jumpPointer",
"D;JGT",
"@SP",
"A=M-1",
"A=A-1",
"M=0",
]);
break;
case 'eq':
$jumpPointer = $this->ic+10;
$this->write([
'A=A-1',
'D=M-D',
'M=0',
'M=M-1',
"@{$jumpPointer}",
'D;JEQ',
"@SP",
"A=M-1",
"A=A-1",
"M=0",
]);
break;
default:
throw new \Exception("$command not Implemented", 1);
}
if ($stackDecrease) {
$this->write([
'@SP',
"M=M-1 // end $command (L{$this->sourceLine})"
]);
}
}
private function write(Array $lines) {
foreach ($lines as $line) {
if (substr($line, 0, 2) !== "//") {
$this->ic += 1;
}
fwrite($this->file, "$line\n");
}
}
private function resolveTemp(Int $index) {
// Temp section starts from R5;
$tempBase = 5;
$ramAddress = $tempBase + $index;
return "@R$ramAddress";
}
function writePush(String $segment, Int $index) {
switch ($segment) {
case 'constant':
$this->write([
// Take the constant
"@$index // push $segment $index",
// Write it to D
"D=A",
]);
break;
case 'argument':
case 'local':
case 'this':
case 'that':
$register = $this->resolveSegmentToRegister($segment);
if ($index !== 0) {
$this->resolveSegmentToR13($segment, $index);
$register = "@R13";
}
$this->write([
$register,
"A=M",
"D=M",
]);
break;
case 'static':
$symbol = $this->resolveStatic($index);
$this->write([
$symbol,
"D=M"
]);
break;
case 'pointer':
$register = $this->resolvePointer($index);
$this->write([
"$register // pointer $index",
"D=M"
]);
break;
case 'temp':
$register = $this->resolveTemp($index);
$this->write([
"$register // temp $index",
"D=M"
]);
break;
default:
throw new \Exception("Not Implemented $segment", 1);
break;
}
$this->write([
// A=SP
"@SP",
"A=M",
// Write D to SP
"M=D",
// Bump Stack Pointer
"@SP",
"M=M+1 // end push $segment $index (L{$this->sourceLine})",
]);
}
/**
* Resolves a given segment to a register
*/
private function resolveSegmentToRegister(String $segment) {
return [
'local' => '@LCL',
'argument' => '@ARG',
'this' => '@THIS',
'that' => '@THAT',
][$segment];
}
/**
* For cases where we need calculations on both LHS and RHS, we temporarily
* store the resolved address of the memory segment to R13. This is the code
* that does that
*/
private function resolveSegmentToR13(string $segment, Int $index) {
$register = $this->resolveSegmentToRegister($segment);
$this->write([
"$register // $segment $index" ,
"D=M",
"@$index // write $index to A",
"D=D+A // D = segment+index",
"@R13 // save it to R13",
"M=D // write $register+$index to R13",
]);
}
/**
* Static variables are just the same labels repeated again
* They are unique across a file
*/
private function resolveStatic(Int $index) {
return "@{$this->vm}.$index";
}
private function writePop(String $segment, Int $index) {
switch ($segment) {
// The address is given by LCL+INDEX
case 'local':
case 'argument':
case 'this':
case 'that':
if($index !== 0) {
$this->resolveSegmentToR13($segment, $index);
$lookupRegister = '@R13';
} else{
$lookupRegister = $this->resolveSegmentToRegister($segment);
}
$this->write([
"@SP // pop",
"AM=M-1",
"D=M",
$lookupRegister,
"A=M // Read $lookupRegister to A (for $segment $index)",
"M=D // end pop $segment $index (L{$this->sourceLine})",
]);
break;
case 'static':
$symbol = $this->resolveStatic($index);
$this->write([
"@SP //pop $segment $index",
"AM=M-1",
"D=M",
$symbol,
"M=D // end pop $segment $index (L{$this->sourceLine})"
]);
break;
case 'pointer':
// pointer points to a 2 register segment holding [this, that]
$register = $this->resolvePointer($index);
$this->write([
"@SP // pop",
"AM=M-1",
"D=M",
$register,
"M=D // (L{$this->sourceLine})"
]);
break;
case 'temp':
$tempRegister = $this->resolveTemp($index);
$this->write([
"@SP",
"AM=M-1",
"D=M",
"$tempRegister",
"M=D // end pop temp $index (L{$this->sourceLine})"
]);
break;
default:
throw new \Exception("Not implemented pop $segment");
break;
}
}
/**
* Resolves pointer [0|1] to the this|that register
*/
private function resolvePointer(Int $index) {
return $index == 0 ? '@THIS' : '@THAT';
}
// Keeping this because book asked me to
function writePushPop(Int $command, String $segment , Int $index) {
switch ($command) {
case CommandType::PUSH:
$this->writePush($segment, $index);
break;
case CommandType::POP:
$this->writePop($segment, $index);
break;
default:
throw new Exception("Invalid Command Type", 1);
break;
}
}
}