Compilation as a whole is: source code -> parse tree -> intermediate representation -> machine representation The parse tree, as we well know, is simply what the source code gets parsed into, and where any simple source level meta-programming like macros happens Once that's taken care of, it gets translated into the IR The IR is a machine-independent and fairly low-level-but-not-quite representation of the parse tree. It provides various simple operations that are more or less machine instruction equivalents. Like various integer operations, floating point ops, memory loads/stores, etc. It also provides various higher-level instruction mixed in like method invocation, closure instantiation, method definition, etc. As a whole, control flow is tree-structured, sort of like the parse tree However, the control flow nodes do not directly correspond to the control flow represented in the parse tree. Where the control flow is mostly documented in my previously written IR notes! However, at the leaves, you still have basic blocks. These still hold instructions and the IR still deals with variables, closures, etc. This is where the modes come in. Take for example: ["A"] whileTrue: ["B"] Normally, this gets interpreted as a message invocation by the IR generator. So it turns into a method invocation instruction, invoked upon 2 closure objects as argument. But then we have a problem... how do we write the nefarious meta-level code that implements notions like method invocation in the first place? There needs to be some kind of escape to write more primitive "machine level" code. Other people have chosen to use C for this, but we wanted to keep this in-house! So you need another language for doing this... but what language? The path of least resistance is to use the /same/ language, in so far as syntax is concerned, but to just interpret it differently when it comes to the IR generator in a more primitive mode. The example above would instead be translated into a loop node, with some basic blocks to handle the code in blocks A and B, and test the condition value, etc. So that the normal source-level messages instead of being treated as method invocations are now treated effectively as macros that generate various "low level" IR code. In other words, it's basically a Slate assembly language of sorts. Which is in fact exactly how messages are handled in the "primitive mode": they're IR code-generation macros. Constants and variable references are also handled slightly differently. An eye was also kept out for making the modes interoperable: you can embed standard mode code within the primitive mode and vice versa, by just switching the mode within some expression. This is performed with a simple macro: it just generates a parse tree node (a mode node!). All code held within the mode node is interpreted in that mode. `primitiveMode and `standardMode are the mode macros which basically just stuff the expression into a mode node. The macros themselves are 3 liners! Currently, there isn't a motivation for other modes. There could be in the future, but right now the only function modes need to provide is to allow you an escape for generating lower-level IR constructs, and one extra mode is sufficient for this. Optimization ------------ Higher-level constructs like closures are lowered into equivalent but lower level constructs like functions, environment records, etc. Once that's done, the code is almost at the MR level. However, now the control flow structure gets "flattened out": the tree structure is translated entirely into the equivalent control flow graph of basic blocks. Instruction selection is done to map the low-level IR instructions into specific machine instructions are equivalent. Machine instructions here are specific to whatever machine we're targetting code generation for. Register allocation and whatever scheduling needs to be done then happen to make the code executable. From there, we have the option of outputting it as assembly code for some assembler like GAS or NASM to assemble, or to assemble it online eventually and make the system self-contained, leading the way for runtime compilation and other things.