ATLAS / TECHNICAL REFERENCE
Architecture
A walkthrough of every stage in the compiler pipeline.
sections: 10 · src: ~11 files · impl: Rust
Runs before the lexer and produces a clean token stream with all directives resolved. The preprocessor is a complete implementation — not a stub.
- Macro expansion — object-like and function-like macros with correct rescanning, argument substitution, and expansion guards to prevent infinite recursion
- Variadic macros —
__VA_ARGS__with correct expansion and stringification - Token pasting and stringification —
##and#inside macro bodies #includeresolution — recursive with cycle detection; searches the source file's directory first, then system paths discovered at runtime- Conditional compilation —
#ifdef,#ifndef,#if,#elif,#else,#endifbacked by a full expression evaluator supportingdefined(), arithmetic, bitwise, and logical operators #pragma pack— push/pop stack; emits sentinel tokens (__pragma_pack_push_N,__pragma_pack_pop) consumed by the parser- Predefined macros —
_WIN32,_WIN64,_MSC_VER(1930),_M_AMD64,__STDC__,__cdecl,__declspec(x),NULL, and others Windows SDK headers depend on - SDK/MSVC discovery — reads
%INCLUDE%, walksProgram Files (x86)/Windows Kits/10/Include/<latest>/ucrt|shared|um|winrtand Visual Studio MSVC include paths;#include <windows.h>resolves without any configuration - Line continuation — backslash-newline splicing before tokenization
Hand-written tokenizer with full line/column tracking for error messages. Handles all C tokens:
- Hex literals (
0x...), integer/float suffixes (ULL,f,L) - All escape sequences in string and character literals
- Full set of compound-assignment operators, bitwise operators,
->,... - Every span records a line and column, propagated through parsing to semantic errors
Recursive-descent parser that builds a typed AST. Typedef-name tracking resolves the classic type-vs-identifier ambiguity at parse time.
Declarations
- Functions, local and global variables, structs, unions, enums, typedefs, forward declarations
- Storage classes:
extern,static,auto,register staticandinlinefunction definitions are treated as declarations — bodies are parsed but discarded. This allows SDK headers containing inline helpers to parse cleanly.
Statements
if/else,while,do-while,for,switch/case/defaultbreak,continue,goto, labeled statements,return
Expressions
- Correct precedence via precedence climbing
- Binary, unary, ternary, casts,
sizeof,alignof - Address-of, dereference, struct member (
.and->), array index, function call - All assignment operators:
=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=
MSVC extensions
__declspec,__stdcall,__cdecl,__fastcall,__int8/16/32/64__forceinline,__pragma,__attribute__,__unaligned- Enum constant evaluation with compile-time integer expression folding
Precompiled headers
Binary serialization (COMPPCH\x01) of the full parsed AST, all active macro definitions,
typedef names, and enum constants. --make-pch writes, --use-pch loads. Eliminates
repeated parsing of heavy header files.
Dedicated type-checking pass that runs after parsing and before IR lowering.
Collection pass
- Typedefs resolved first, then struct field maps, then function signatures and globals
- Typedef cycles detected and reported
Check pass
- All function bodies checked with a stack of scopes for correct variable resolution
- Binary/unary expression types validated (arithmetic, pointer, logical)
- Assignment compatibility: same-type, arithmetic-to-arithmetic, pointer-to-pointer, integer-to-pointer
- Lvalue requirements enforced on assignment LHS and address-of operands
- Argument counts checked against function signatures; variadic requires at least the declared number
switchexpression required to be integer-typed- Duplicate parameters detected
- Errors reported as
type error at line:col: message
Typed, flat intermediate representation with explicit basic blocks.
Types
void i8 i16 i32
i64 f32 f64 ptr(T)
array(T, N)
Instructions
alloca load store binop
unaryop call gep cast
copy
Terminators
ret br condbr unreachable
Lowering
- Struct field offsets and sizes computed with correct natural alignment and
#pragma packoverrides - Union fields all map to offset 0; struct size padded to the largest alignment, then rounded up
- Anonymous struct/union fields flattened into the parent namespace
- Struct definitions processed in dependency order to handle forward references
- String literals pooled with automatic null termination
- Globals with constant initializers emitted to
.data;externglobals become extern IR functions break/continueresolved through loop exit/condition block stacks
IR dump (example)
fn @add(%0: i32, %1: i32) -> i32 {
bb0:
%2 = alloca i32
%3 = alloca i32
store i32 %0, ptr %2
store i32 %1, ptr %3
%4 = load i32, ptr %2
%5 = load i32, ptr %3
%6 = Add i32 %4, %5
ret %6
}
Translates IR into machine instructions following the Microsoft x64 ABI.
Calling convention
- First four integer/pointer arguments in
RCX,RDX,R8,R9; arguments 5+ spilled to stack at[RSP + 8*i] - 32-byte shadow space allocated before every
call - Return value in
RAX - Stack kept 16-byte aligned before every
call
Frame layout
- Every virtual register gets a fixed
[RBP - N]slot — spill-everything, no register allocator - Frame size computed as:
round_up(vreg_slots + alloca_bytes + out_arg_bytes, 16)
Sized memory operations
- 1-byte:
MOV8(store),MOVZX8(load with zero-extend) - 2-byte:
MOV16,MOVZX16 - 4-byte:
MOV32 - 8-byte:
MOV - Selected by
ty.size_bytes()from the IR load/store instruction
Notable patterns
- Comparisons:
CMP+SETcc+MOVZX→ clean 0/1 integer result - Division/modulo:
CQO+IDIV; modulo copiesRDXtoRAX - External DLL calls:
FF 15 rel32(RIP-relative indirect through IAT slot) - Internal calls:
E8 rel32(direct relative call) - Function pointer calls:
CALL RAX __va_startintrinsic: spills the four argument registers (RCX/RDX/R8/R9) into the caller shadow space area sova_argcan walk them sequentially
Runs after codegen, before encoding. Operates on the flat machine instruction list.
- Tracks a
HashMap<i32, Operand>of known values for each[RBP+N]stack slot - Eliminates
mov reg, [rbp+N]when the slot was just written with that register's value - Removes
mov r, rno-op self-moves - Invalidates the slot cache on labels, jumps, and calls
- Calls
invalidate_regwhen a register is clobbered, removing all cache entries that reference it - Runs the
peepholepass in a loop until the instruction list stops changing (fixed point)
Emits raw x86-64 bytes from the machine instruction list. No textual assembly intermediate anywhere in the pipeline.
Encoding details
- REX prefixes —
REX.Wfor 64-bit operands;REX.R/REX.Bfor R8–R15; 8-bit operations checkneeds_rex_8for registers requiring a REX byte (SPL, BPL, SIL, DIL) - ModR/M — register-register (
mod=11), register-memory with base register, RIP-relative (mod=00 rm=101) - SIB — emitted when base register is
RSP(rm=100); byte is0x24(no index, base=RSP) - Displacement — 8-bit when the value fits in a signed byte; 32-bit otherwise
- RIP-relative relocations —
(offset, symbol)pairs recorded per function; patched by the PE writer once section RVAs are finalized - Label fixups — branch targets resolved within the encoder after all instructions are emitted; back-patches the displacement bytes
Instruction coverage
MOV MOV8 MOV16 MOV32
MOVZX8 MOVZX16
LEA ADD SUB IMUL
AND OR XOR
SHL SAR
NEG NOT
CQO IDIV
CMP SETcc
PUSH POP
CALL JMP Jcc RET
Custom binary format rather than COFF. Magic: CCOBJ001.
Contents per .obj file
- Encoded function bytes and their names
- Relocation records:
(function_name, [(offset, symbol)]) - String literal table:
(name, bytes) - Global variable table: name, IR type, optional initializer value,
is_externflag
Merge and link
- String literal names scoped with a per-object prefix (
.Lobj{idx}_) to prevent collisions - Duplicate symbol definitions rejected with a diagnostic
retain_reachable_symbolsperforms BFS frommainover the relocation graph; unreachable functions, strings, and globals are dropped before writing the PE- Pruned counts reported:
link: removed N functions, N strings, N globals
src/linker/import_lib.rs
PE32+ writer
Constructs a valid Windows executable from scratch. Layout:
- DOS stub — 64-byte minimal header with correct
e_lfanew - PE signature, COFF header — machine type
0x8664(AMD64), section count, timestamp - Optional header (PE32+) —
ImageBase0x140000000, section alignment0x1000, file alignment0x200, console subsystem, data directory entries for import table and IAT - Sections:
.text(RX),.rdata(R),.data(RW),.idata(R) - Import Directory Table — one entry per DLL with
OriginalFirstThunk/FirstThunk/NameRVAs - IAT — hint/name entries for each imported symbol; slots referenced via
FF 15 rel32 - RIP-relative relocations patched after all section RVAs are computed
Entry trampoline
and rsp, -16 ; Windows entry RSP ≡ 8 (mod 16) — align before first call call main mov rcx, rax ; pass return code to ExitProcess sub rsp, 32 ; shadow space for ExitProcess call [rip+ExitProcess]
Import resolver
- Searches
%LIB%,Program Files (x86)/Windows Kits/10/lib, Visual Studio MSVC lib paths - Opens
.libfiles as COFF archives; parses archive member headers - Identifies COFF Short Import Objects by
Sig1=0x0000, Sig2=0xFFFF; extracts symbol name and DLL name - Registers both raw symbol and
__imp_-prefixed variant - Unresolved externs warn:
warning: 'foo' not found — use --import DLL:foo