If you ever wanted to improve your understanding of a computer system or write a compiler then this post may convince you to take the course or read the book.
TLDR
Cons
- Hack platform is an educational platform, so learnings aren’t directly applicable in “real world” (see Pros tho)
- provided software is somewhat rough at times with unclear and hard to debug errors
- Jack OS is not what many people may expect, it’s a mix of Jack standard library (
Array
,String
) and HW drivers (Memory
,Keyboard
,Output
,Screen
)
Pros
- the only course(AFAIK) with such a depth: from NAND gates to the OS(requires part I)
- learning are transferrable to the “real world” platforms to some degree
- introduces concept of the Stack Machines
- introduces Virtual Machines and VM instructions
- introduces concepts of high-level programming languages
- introduces to Syntax and Formal Grammars
- teaches how to implement Hack assembler
- teaches how to implement Jack Lexer
- teaches how to implement Jack Parser
- teaches how to implement Jack Compiler
- teaches how to program in Jack
- teaches how to implement memory allocator as part of Jack OS
- teaches how to implement Strings and Arrays
- teaches how to deal with IO and build Jack OS
Week 1: Machine language and Stack Arithmetic
Is a warmup week and a refresher on the Hack HW platform and Assembly in the first half.
Some notes about the platform and assembly:
- Hack is a 16 bit platform meaning its memory is an array of words(16bit) and CPU operates with instructions that are 16bit values
- Hack Assembly contains only 2 kinds of instructions:
A
andC
A
instructions are used to load a value and to address the memory via theM
registerC
instructions are used to perform the operations
Stack Machines
The second part introduces:
- Hack’s VM language and general ideas about VMs and what role they serve as a HW abstraction
- Hack’s VM implementation using Stack Machine
Turns out that Stack Machine is an extended variant of a Reverse Polish Notation
For example, the infix expression (1 + 2) * 3
is equal to 1 2 + 3 *
, in postfix notation, and translates to the following VM instructions:
push const 1
push const 2
add
// `add`s implementation pushes result back on stack
push 3
call Math.multiply 2
// `Math.multiply` s `return` pushes the final result back on stack
The assignment requires to implement VM language translator for the arithmetic/logic and memory access VM instructions: given the VM instructions - generate the corresponding assembly.
The VM topic deserves a separate blog post.
Hack VM language
- Arithmetic/Logic:
add
,sub
,neg
,eq
,gt
,lt
,and
,or
,not
- Memory segment:
push/pop <segment> <value>
- Branching:
goto <label>
,label <label>
,if-goto <label>
- Function cmds:
call <fname> <nvars>
,function <fname> <nvars>
,return
nvars
- defines how manylocal
variables the function declaresnargs
- defines how manyargument
variables function accepts (lastnargs
values on stack)
Week 2: VM language part II
Last part of the VM instructions translation implementation.
- branching with
goto
,if-goto
,label
VM instructions - function “invocation” with
call
,return
VM instructions and all the mechanics that comes with it: call frame, stack and memory allocation, handling recursive calls
The call
and return
mechanics deserves a separate blog because their implementations are significantly more complex than the rest of instructions and the ideas translate well to other platforms.
This chapter was quite challenging and educational.
Week 3: Jack language
Introduced Jack language, syntax and programming. Jack feels like mix of Java and C. For educational purposes and simplification, Jack syntax is somewhat verbose and clunky.
/** Hello World program. */
// must be in Main.jack
class Main {
function void main() {
var String s;
let s = "world";
/* Prints some text using the standard library. */
do Output.printString("Hello ");
do Output.printString(s);
do Output.println(); // New line
return;
}
}
Some notes:
- to simplify parsing, every statement is explicitly prefixed with a keyword thus -
var
,let
,do
, etc - declaration and initialization are separate
- arithmetic expressions are evaluated sequentially left-to-right disregarding the mathematical precedence, ie
1 + 2 * 3 = (1 + 2) * 3
- operators
<=
and>=
are absent so instead I was using negated variants ie:~(a < b)
instead ofa >= b
- lack of modulo operator. In special case of
x mod 16
I was usingx & 15
- 3 simple types:
int
,char
,boolean
void
is not a real type(cannot declare avoid
variable) rather a way to “mark” afunction
/method
that doesn’t return any value- few built-in data types
Array
,String
- custom types defined via
class
Compilation could be done via provided Jack compiler:
./nand2tetris/tools/JackCompiler.sh Main.jack
Compiling /nand2tetris/Main.jack
And then running via /nand2tetris/tools/VMEmulator.sh
the produces the Main.vm
file.
Week 4: Compiler I, Parsing and parse-tree traversal
This chapter introduces few concepts required to implement a compiler:
- Tokenizing - the process of consuming input bytes and classifying them into tokens
- Grammars - high-level formal definition of a language’s structure and terminals(tokens)
- Parsing - a process of consuming input tokens and inferring the structure based on the language’s grammar
- Parse trees - a tree-like byproduct of the parsing/traversal process
- touched on other concepts like parser-generators and other variants of parsers
The week’s assignment is to build a parser that, given the Jack source code, outputs XML encoded parse-tree representation. Next week’s VM code generation builds upon the implementation.
Week 5: Compiler II, VM code generation
The second part of the compiler implementation.
Implementation from the week 5’s assignment has to be modified to emit VM instructions for the corresponding Jack source code. During the week we went through:
- handling variables by defining them in symbol tables and mapping onto VM memory segments
- handling expressions and translating Jack’s infix notation into VM’s bytecode as postfix notation
- handling flow of control and translating Jack’s
while
,if
,else
statements into VM’sgoto
,if-goto
,label
commands - handling Objects and translating Jack’s class
constructor
/method
/function
notation into VM’s procedural notation - handling Arrays with memory allocation and access
Worth noting that Week 6’s assignment(the next one) uncovered many issues with my freshly implemented compiler while implementing Jack OS which was challenging to debug and fix.
Week 6: Jack OS.
Was likely my least favorite week since I’m not too fond of Jack programming and it was 100% of it. Yet some great nuggets are there like implementing:
Math
module: simple implementations of multiplication, division, and square rootMemory
module: memory allocatorScreen
module: implementing graphical routines likedrawLine
,drawCircle
,drawRectangle
Output
module: implementation of textual interface likemoveCursor
,printChar
,printString
, font glyphsKeyboard
module: implementing reading from keyboard likereadChar
,readLine
, etcString
module: allocation and basic manipulations:new
,appendChar
,charAt
, etcArray
module: and how array variable is just a pointer to its first element similarly to CSys module: the entry point for the Jack applications application along with
sleep,
halt` routines
Week 7: Future
There are plans to continue the saga and create part III with some exciting ideas like implementing Hack platform in real hardware.
Looking forward to the part III.
Slides and the Book
Most of the slides (and some chapters of the book) are freely available on the Nand2Tetris website and provide great overview of the course.