ATLAS / TECHNICAL REFERENCE

Architecture

A walkthrough of every stage in the compiler pipeline.

sections: 10  ·  src: ~11 files  ·  impl: Rust

§01 PREPROCESSOR src/preprocessor/

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
  • #include resolution — recursive with cycle detection; searches the source file's directory first, then system paths discovered at runtime
  • Conditional compilation#ifdef, #ifndef, #if, #elif, #else, #endif backed by a full expression evaluator supporting defined(), 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%, walks Program Files (x86)/Windows Kits/10/Include/<latest>/ucrt|shared|um|winrt and Visual Studio MSVC include paths; #include <windows.h> resolves without any configuration
  • Line continuation — backslash-newline splicing before tokenization
§02 LEXER src/lexer/

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
§03 PARSER src/parser/

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
  • static and inline function 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/default
  • break, 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.

§04 SEMA src/sema.rs

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
  • switch expression required to be integer-typed
  • Duplicate parameters detected
  • Errors reported as type error at line:col: message
§05 IR src/ir/

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 pack overrides
  • 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; extern globals become extern IR functions
  • break/continue resolved 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
}
§06 CODEGEN src/codegen/codegen.rs

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 copies RDX to RAX
  • 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_start intrinsic: spills the four argument registers (RCX/RDX/R8/R9) into the caller shadow space area so va_arg can walk them sequentially
§07 PEEPHOLE src/codegen/opt.rs

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, r no-op self-moves
  • Invalidates the slot cache on labels, jumps, and calls
  • Calls invalidate_reg when a register is clobbered, removing all cache entries that reference it
  • Runs the peephole pass in a loop until the instruction list stops changing (fixed point)
§08 ENCODER src/codegen/encode.rs

Emits raw x86-64 bytes from the machine instruction list. No textual assembly intermediate anywhere in the pipeline.

Encoding details

  • REX prefixesREX.W for 64-bit operands; REX.R/REX.B for R8–R15; 8-bit operations check needs_rex_8 for 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 is 0x24 (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

§09 OBJECT FORMAT src/linker/object.rs

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_extern flag

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_symbols performs BFS from main over 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
§10 LINKER src/linker/pe.rs
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+) — ImageBase 0x140000000, section alignment 0x1000, file alignment 0x200, 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/Name RVAs
  • 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 .lib files 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