Quartz Architecture
Overview
Quartz is a self-hosting systems programming language that compiles to LLVM IR. The compiler is written in Quartz itself — true fixpoint achieved (gen2==gen3 byte-identical). The language is mature enough to write its own tools: formatter, linter, and build system (Quake) are all implemented in Quartz.
Current State
~186,888 lines of Quartz | ~4,710 functions | 464 QSpec test files | Self-hosting since January 2, 2026
Source (.qz) → Lexer → Parser → Desugar → TypeCheck → MIR → LLVM IR → Binary
Pipeline Stages
| Stage | Input | Output | Responsibility |
|---|---|---|---|
| Lexer | Source text | Token stream | Tokenize, track line/col, keywords |
| Parser | Tokens | Surface AST | Recursive descent, builds syntax tree |
| Desugar | Surface AST | HIR | Eliminate sugar (interpolation, for-in, compound assign) |
| TypeCheck | HIR | Typed HIR | Validate types, resolve names, check mutability, borrow checking |
| MIR Lower | Typed HIR | MIR | Reduce to intrinsics, structural dispatch, monomorphization |
| Codegen | MIR | LLVM IR | Mechanical translation to LLVM IR |
MIR Lowering Passes
| Pass | Purpose |
|---|---|
| PASS 0.55 | Register Drop types from impl blocks |
| PASS 0.9 | Register bounded generic functions (<T: Trait>) for monomorphization |
| PASS 0.95 | Register structurally-constrained functions (untyped params + UFCS) |
| PASS 0.96 | Transitive structural detection (fixpoint loop for chained calls) |
| PASS 1 | Lower all functions, skipping bounded generics and structural functions |
| PASS 1.5 | Register Drop types from impl blocks in lowered functions |
| PASS 2 | Emit pending specializations (cascading monomorphization) |
Key Invariants
| Boundary | Invariant |
|---|---|
| Post-Parse | Valid AST, source spans preserved |
| Post-Desugar | No surface syntax (interpolation, ranges, etc.) |
| Post-TypeCheck | All expressions typed, no unbound names, mutability checked |
| Post-MIR | Only intrinsics, explicit control flow |
Invariant violations in later phases are compiler bugs, not user errors.
Compilation Modes
Single-File Compilation (Default)
Compiles the entire compiler to a single .ll file, then to binary via llc + clang.
Separate Compilation (v5.8+)
Per-module .ll files → parallel llc → .o caching → link. Significantly faster for incremental builds.
./self-hosted/bin/quake build:separate
Incremental Compilation (v5.10+)
Three-tier caching strategy:
- Tier 0: Dependency graph — skip modules whose inputs haven’t changed
- Tier 1: Interface hash early cutoff — skip downstream if public API unchanged
- Tier 2: Object file caching — reuse
.ofiles when.llhasn’t changed
Directory Structure
quartz/
├── self-hosted/ # Quartz compiler written in Quartz
│ ├── quartz.qz # Entry point, CLI
│ ├── resolver.qz # Module resolution
│ ├── frontend/ # Lexer, parser, AST, macros, derive
│ ├── middle/ # Type checking, diagnostics, inference
│ ├── backend/ # MIR lowering, codegen, domtree
│ ├── shared/ # Build cache, interning
│ ├── error/ # Error explanations
│ └── bin/ # Compiled binaries
├── std/ # Standard library (Quartz)
│ ├── string.qz # String utilities
│ ├── json.qz # JSON parser
│ ├── colors.qz # Terminal colors
│ ├── http.qz # HTTP client
│ └── ... # Math, random, time, etc.
├── tools/ # Self-hosted tools (Quartz)
│ ├── fmt.qz # Formatter
│ ├── lint.qz # Linter
│ └── quake.qz # Quake build system launcher
├── Quakefile.qz # Build task definitions
├── spec/ # Test suites
│ ├── qspec/ # QSpec native tests (*_spec.qz) — primary
│ └── integration/ # RSpec integration tests (*_spec.rb) — residual
└── docs/ # Language documentation
Key Source Files
| File | Purpose |
|---|---|
frontend/lexer.qz | Tokenization, keywords, escape sequences |
frontend/parser.qz | Recursive descent parser (~5,900 lines) |
frontend/ast.qz | AST node types and constructors |
frontend/macro_expand.qz | Macro expansion |
frontend/derive.qz | @derive attribute processing |
middle/typecheck.qz | Bidirectional type checking, scope management |
middle/typecheck_walk.qz | AST walking: tc_expr/tc_stmt dispatchers |
middle/typecheck_helpers.qz | Pure validation helpers (binary/unary/let/call) |
middle/typecheck_generics.qz | Type parameter inference, struct/enum support |
middle/typecheck_match.qz | Pattern matching, exhaustiveness checking |
middle/typecheck_concurrency.qz | Send/Sync, spawn/await/select checks |
middle/typecheck_capture.qz | Closure capture analysis |
middle/typecheck_expr_handlers.qz | Expression handler functions (call, lambda, match, etc.) |
middle/typecheck_registry.qz | Type/trait/impl registry |
middle/typecheck_builtins.qz | Builtin function registration |
backend/mir.qz | MIR data structures, Drop support |
backend/mir_lower.qz | MIR lowering: dispatchers + orchestration |
backend/mir_lower_async_registry.qz | Async function tracking, suspendable leaves |
backend/mir_lower_gen.qz | Generator/async state machine lowering |
backend/mir_lower_expr_handlers.qz | Expression lowering handlers (call, binary, match, etc.) |
backend/mir_lower_stmt_handlers.qz | Statement lowering handlers (let, if, for, etc.) |
backend/mir_lower_iter.qz | Collection iteration lowering |
backend/codegen.qz | LLVM IR emission: entry points + function orchestration |
backend/codegen_instr.qz | LLVM IR instruction emission + dispatcher |
backend/codegen_separate.qz | Incremental + separate compilation modes |
backend/codegen_intrinsics.qz | Intrinsic dispatch registry (hub) |
backend/cg_intrinsic_*.qz | 7 intrinsic handler modules (collection, concurrency, core, data, memory, simd, string) |
resolver.qz | Module resolution, mutual imports, path handling |
shared/build_cache.qz | Incremental compilation cache |
Type System
Existential Type Model
Types exist at compile-time but vanish at runtime. Everything becomes i64:
Compile-time: Vec<Int>, Fn(Int): Int, Result<T, E>
Runtime: i64, i64, i64
Const-by-Default
x = 5 # Immutable binding
var y = 10 # Mutable binding
y = 20 # OK
x = 10 # ERROR: Cannot reassign immutable 'x'
Move Semantics & Drop
Types implementing Drop have linear/move semantics:
- Values are moved (not copied) on assignment or function call
Drop.drop()is called automatically at scope exitCopyandDropare mutually exclusive (QZ1215)- Nested fields implementing
Dropare dropped recursively
Length-Prefixed Strings
Quartz strings store length at ptr[-8] (8 bytes before the string data) and are null-terminated for C compatibility. String comparison (==) does content comparison.
Sugar vs Core
Surface syntax desugars to core forms:
| Sugar | Core |
|---|---|
"a#{x}b" | str_concat(str_concat("a", str_from_int(x)), "b") |
x += 1 | x = x + 1 |
for i in 0..n | while loop with counter |
x? | Early return if Err |
Debug Tools
./self-hosted/bin/quartz --dump-ast program.qz # Show AST
./self-hosted/bin/quartz --dump-mir program.qz # Show MIR
./self-hosted/bin/quartz --dump-ir program.qz # Show LLVM IR
./self-hosted/bin/quartz --format program.qz # Format source
Adding New Builtins
| Step | File | What to Do |
|---|---|---|
| 1 | typecheck_builtins.qz | Register builtin type signature |
| 2 | mir.qz | Add to intrinsic recognition |
| 3 | codegen_intrinsics.qz | Register in cg_init_intrinsic_registry() with category |
| 4 | cg_intrinsic_*.qz | Add handler in the appropriate category file |
All four steps required. Missing step 2 causes calls to become closures.
Self-Hosting Chain
v0.1.0 (C bootstrap) → ... → v5.x (self-hosting, fixpoint achieved)
The C bootstrap compiler has been retired (Feb 2026). The self-hosted compiler is now the sole compiler. Binary preservation ensures you cannot strand yourself — previous versions are always available as escape hatches.