Quartz v5.25

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

StageInputOutputResponsibility
LexerSource textToken streamTokenize, track line/col, keywords
ParserTokensSurface ASTRecursive descent, builds syntax tree
DesugarSurface ASTHIREliminate sugar (interpolation, for-in, compound assign)
TypeCheckHIRTyped HIRValidate types, resolve names, check mutability, borrow checking
MIR LowerTyped HIRMIRReduce to intrinsics, structural dispatch, monomorphization
CodegenMIRLLVM IRMechanical translation to LLVM IR

MIR Lowering Passes

PassPurpose
PASS 0.55Register Drop types from impl blocks
PASS 0.9Register bounded generic functions (<T: Trait>) for monomorphization
PASS 0.95Register structurally-constrained functions (untyped params + UFCS)
PASS 0.96Transitive structural detection (fixpoint loop for chained calls)
PASS 1Lower all functions, skipping bounded generics and structural functions
PASS 1.5Register Drop types from impl blocks in lowered functions
PASS 2Emit pending specializations (cascading monomorphization)

Key Invariants

BoundaryInvariant
Post-ParseValid AST, source spans preserved
Post-DesugarNo surface syntax (interpolation, ranges, etc.)
Post-TypeCheckAll expressions typed, no unbound names, mutability checked
Post-MIROnly 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 .o files when .ll hasn’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

FilePurpose
frontend/lexer.qzTokenization, keywords, escape sequences
frontend/parser.qzRecursive descent parser (~5,900 lines)
frontend/ast.qzAST node types and constructors
frontend/macro_expand.qzMacro expansion
frontend/derive.qz@derive attribute processing
middle/typecheck.qzBidirectional type checking, scope management
middle/typecheck_walk.qzAST walking: tc_expr/tc_stmt dispatchers
middle/typecheck_helpers.qzPure validation helpers (binary/unary/let/call)
middle/typecheck_generics.qzType parameter inference, struct/enum support
middle/typecheck_match.qzPattern matching, exhaustiveness checking
middle/typecheck_concurrency.qzSend/Sync, spawn/await/select checks
middle/typecheck_capture.qzClosure capture analysis
middle/typecheck_expr_handlers.qzExpression handler functions (call, lambda, match, etc.)
middle/typecheck_registry.qzType/trait/impl registry
middle/typecheck_builtins.qzBuiltin function registration
backend/mir.qzMIR data structures, Drop support
backend/mir_lower.qzMIR lowering: dispatchers + orchestration
backend/mir_lower_async_registry.qzAsync function tracking, suspendable leaves
backend/mir_lower_gen.qzGenerator/async state machine lowering
backend/mir_lower_expr_handlers.qzExpression lowering handlers (call, binary, match, etc.)
backend/mir_lower_stmt_handlers.qzStatement lowering handlers (let, if, for, etc.)
backend/mir_lower_iter.qzCollection iteration lowering
backend/codegen.qzLLVM IR emission: entry points + function orchestration
backend/codegen_instr.qzLLVM IR instruction emission + dispatcher
backend/codegen_separate.qzIncremental + separate compilation modes
backend/codegen_intrinsics.qzIntrinsic dispatch registry (hub)
backend/cg_intrinsic_*.qz7 intrinsic handler modules (collection, concurrency, core, data, memory, simd, string)
resolver.qzModule resolution, mutual imports, path handling
shared/build_cache.qzIncremental 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 exit
  • Copy and Drop are mutually exclusive (QZ1215)
  • Nested fields implementing Drop are 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:

SugarCore
"a#{x}b"str_concat(str_concat("a", str_from_int(x)), "b")
x += 1x = x + 1
for i in 0..nwhile 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

StepFileWhat to Do
1typecheck_builtins.qzRegister builtin type signature
2mir.qzAdd to intrinsic recognition
3codegen_intrinsics.qzRegister in cg_init_intrinsic_registry() with category
4cg_intrinsic_*.qzAdd 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.