Compilation Pipeline
Leo’s compilation process follows a well-defined pipeline:Pipeline Stages
- Lexical Analysis: The
leo-parser-rowancrate uses thelogoslibrary to tokenize source code - Parsing: A Rowan-based parser constructs an untyped syntax tree from tokens
- AST Construction: The
leo-parsercrate converts the Rowan parse tree into a typed Abstract Syntax Tree - Compiler Passes: The
leo-passescrate applies approximately 25 sequential transformations - Code Generation: The final pass generates Aleo bytecode instructions
The Leo compiler uses a red-green tree approach with Rowan, enabling incremental parsing and better error recovery than traditional parser generators.
Crate Architecture
The Leo compiler is organized into a set of interdependent crates, each with a specific responsibility:Foundation Crates
leo-span
Provides source location tracking for error reporting.
fxhash, indexmap, serde
Key Features:
- Fast hash-based span lookups
- Deterministic ordering with IndexMap
- Serializable for AST snapshots
leo-errors
Centralized error handling for all compiler stages.
Error Code Format: E{PREFIX}037{CODE}
EPAR037XXXX: Parser errors (0-999)EAST037XXXX: AST errors (2000-2999)ECMP037XXXX: Compiler errors (6000-6999)
AST and Parsing
leo-ast
Defines all Abstract Syntax Tree node types.
Core Requirements:
- Every node implements the
Nodetrait (usingsimple_node_impl!macro) - Every node contains
SpanandNodeIDfor error reporting and traversal - Uses
IndexMapfor deterministic ordering (neverHashMap) - Large enum variants must be boxed to control memory layout
leo-parser-rowan
Lexer and untyped parser built on the Rowan library.
Architecture:
- Grammar defined in
grammar.rs - Tokenization via
logoscrate - Produces a lossless syntax tree (includes whitespace and comments)
- Error recovery built into the parser
leo-parser
Converts the Rowan parse tree into typed AST nodes.
Responsibilities:
- Type-safe AST construction
- Initial semantic validation
- Span preservation from source to AST
Compiler Passes
leo-passes
Implements all compiler transformations and optimizations.
Pass Trait:
CompilerState in leo-compiler/src/compiler.rs:186-247:
Pass ordering is critical. Each pass depends on invariants established by previous passes. For example, SSA form must be established before flattening.
leo-compiler
Orchestrates parsing and all compiler passes.
Compiler Structure (leo-compiler/src/compiler.rs:58-73):
leo-passes/src/pass.rs:26-52):
Supporting Crates
leo-abi / leo-abi-types
Generate Application Binary Interface definitions.
Generated After Monomorphization: ABIs are captured immediately after the monomorphization pass to ensure all const generic types are resolved (leo-compiler/src/compiler.rs:213-215).
leo-fmt
Leo source code formatter (uses leo-parser-rowan).
leo-disassembler
Converts Aleo bytecode back to human-readable format.
leo-package
Parses and manages Leo project structure (program.json, etc.).
leo-test-framework
Test harness for .leo test files.
Test Structure:
- Tests in
tests/tests/{category}/ - Expectations in
tests/expectations/{category}/ - Use
UPDATE_EXPECT=1to regenerate expectations
Data Flow Through Compiler
1. Source to AST
2. Pass Execution
Each pass is wrapped indo_pass which handles AST snapshots:
3. Code Generation
The final pass generates Aleo bytecode (leo-compiler/src/compiler.rs:300):
Memory and Performance
Design Principles
- Pre-allocation: Use
with_capacitywhen final size is known - Avoid Cloning: Prefer references and
into_iter()over.clone()anditer().cloned() - Iterator Chains: Avoid intermediate vectors and unnecessary
.collect() - Deterministic Ordering: Always use
IndexMaporIndexSet, neverHashMaporHashSet
Hot Path Optimizations
The compiler applies several optimizations in performance-critical paths:- Symbol Interning: Identifiers are interned to reduce string allocation
- Arena Allocation: Node IDs reference arena-allocated nodes
- Copy-on-Write: AST nodes are modified in-place when possible
Security Guarantees
Unsafe Code Prohibition
The following crates forbid unsafe code:leo-spanleo-passesleo-compilerleo-errorsleo-package
#![forbid(unsafe_code)] at the crate root.
Input Validation
- All external input is validated at trust boundaries
- Parser rejects malformed syntax with descriptive errors
- Type checker enforces type safety
- Bounds checking on all array accesses
Fail-Closed Design
The compiler follows a fail-closed approach: when uncertain, reject the program rather than making assumptions.Debugging and Testing
AST Snapshots
Enable AST snapshots to see transformations:program_name.initial.json- AST after parsingprogram_name.TypeChecking.json- AST after type checkingprogram_name.Flattening.json- AST after flattening- etc.
Running Tests
Related Resources
- Compiler Passes - Details on each compilation pass
- Zero-Knowledge Concepts - How Leo compiles to ZK proofs
- Optimization - Performance optimization techniques