Our compiler implements a Stack-Based Model targeting the x86-64 System V ABI. It is the exact same strategy GCC uses with Zero Optimization (gcc -O0).
Whenever a function is called, your code generator executes a standard prologue:
pushq %rbp # Save caller's base pointer movq %rsp, %rbp # Set current base pointer subq $1024, %rsp # Allocate memory for locals
Instead of calculating exact byte sizes, we pre-allocate a massive, flat block of memory. This guarantees we never accidentally overwrite another function's memory.
Every variable gets an 8-byte slot as an offset from the Base Pointer (%rbp). Because the CPU cannot add two RAM addresses directly, registers (%rax, %rbx) are used strictly as temporary delivery trucks. They fetch variables from RAM, do the math, drop the result back in RAM, and immediately forget it.
Modern CPU hardware instructions (like movaps used in C-libraries like printf) demand that the memory address it accesses is a perfect multiple of 16. If it isn't, the CPU throws a Segmentation Fault.
To prevent crashing, we wrap every C-library call in a bitwise mask (andq $-16, %rsp) which acts like a cookie-cutter to force the stack to round down to 16. Watch how the Failsafe adds "Padding" to the stack!
How does a function call itself without overwriting its own variables? The answer is Activation Records (Frames). Every time call is executed, the CPU jumps to the top of the function and builds a brand new, isolated sandbox on top of the old one.
It looks like magic when the pointers instantly jump back to the old frame, but the secret is in the Saved %rbp slot. The leave instruction is actually a shortcut for two hardware commands executed back-to-back:
movq %rbp, %rsp (The Collapse): Instantly moves the stack pointer up to the base pointer, abandoning all local variables in the current sandbox.popq %rbp (The Restore): The CPU reads the value stored at the new `%rsp` (which is the old `%rbp` address saved during the prologue), puts it into the `%rbp` register, and moves `%rsp` up 8 bytes.Finally, ret pops the Return Address into the instruction pointer (%rip) so the CPU knows what line of code to resume.
Watch how executing factorial(2) pushes isolated memory frames, calculates the result, and safely dismantles the stack frame using the sub-instructions of leave to hand the output back to main!
The CPU only knows how to execute instructions sequentially (top-to-bottom). To create if/else statements or while loops, the compiler generates Compare (cmp) and Conditional Jump (jCC) instructions.
This trace follows the complex if / else if / else logic from our Binary Search example. We are searching for key = 12, but the current arr[mid] = 30. Watch how the CPU compares the values in the RAM Stack and physically jumps over the blocks of code it shouldn't execute!
The most highly-optimized compilers (like GCC with -O3) use complex algorithms like Graph Coloring to keep variables permanently inside CPU registers rather than writing to RAM.
However, we explicitly chose not to do this. Here is why: