Pipeline
The compiler pipeline transforms Flint source into machine code in a series of well-defined passes. Each pass has a clear input and output.
Source Input
The pipeline starts with a resolved set of .fl source files from the project’s src/ directory. The manifest (flint.toml) provides the target, board, edition, and build settings.
Pass 1: Lexing
flint-lexer tokenizes each source file into a flat token stream:
- Identifiers, keywords, literals (integers, floats, strings, chars)
- Operators and delimiters
- Comments and whitespace (preserved for the formatter; stripped for the parser)
- Spans: every token carries a
(file, start, end)location
The lexer is position-preserving. Every character in the source maps to a token, including whitespace. This matters for flint fmt, which works from the token stream.
Pass 2: Parsing and CST Construction
flint-parser builds a Concrete Syntax Tree (CST):
- The CST is lossless; every token is in the tree, including whitespace and comments.
- The parser performs error recovery: when it encounters a syntax error, it emits a diagnostic and attempts to continue parsing the rest of the file.
- A single parse run can report multiple errors.
The CST is useful for the formatter, which needs to preserve the original structure while normalizing whitespace and ordering.
Pass 3: AST Extraction
An Abstract Syntax Tree (AST) is extracted from the CST:
- Whitespace and comments are stripped.
- The AST represents only the semantic content: function declarations, types, expressions, statements.
- Spans are preserved so every AST node knows where it came from in source.
Pass 4: Symbol Resolution and Checking
flint-check performs:
- Name resolution: resolves all identifiers to their declarations. Reports unknown names, ambiguities, and import errors.
- Visibility checking: ensures
puband private items are accessed correctly. - Entry point validation: checks that
mainhas the correct signature (-> neveron MCU targets). - Recursion analysis: detects recursive named-call cycles. On MCU targets, these are reported as errors unless
allow-recursionis set. - Deprecation lints: emits warnings at use sites of
@deprecateditems.
Pass 5: Type Checking and HIR Lowering
flint-lower type-checks the program and produces a High-Level IR (HIR):
- Type inference resolves unsuffixed literal types from context.
- Parameter modes (
mut,owned) are checked against call sites. - Ownership rules are enforced: use-after-move, partial moves, mutable aliasing.
defersites are elaborated into explicit cleanup schedules.?propagation is expanded into explicit match arms.- Collection method calls are type-checked against official generic definitions.
HIR is a typed, ownership-annotated, desugared form of the source. It is still high-level and does not yet know about registers or machine layout.
Pass 6: FIR Lowering
flint-lower lowers HIR into FIR (Flint IR):
- FIR is a target-independent control-flow graph (CFG) based IR.
- Functions are broken into basic blocks with explicit edges.
- Ownership transfers are explicit in the IR (borrow edges, move edges, drop insertions).
- FIR does not use registers; values are still named IR values with types.
- FIR is the last IR shared across all targets.
Pass 7: Machine Lowering
flint-backend lowers FIR into target-specific machine IR:
- Instruction selection: FIR operations are mapped to machine instruction patterns.
- Register allocation: IR values are assigned to physical registers or stack slots.
- ABI lowering: function calls are expanded to follow the target ABI (calling convention, parameter passing, return values).
- Stack layout: frame setup and teardown are emitted.
For RP2040: this produces Thumb v6-M instruction sequences.
Pass 8: Code Generation
Machine IR is serialized to binary:
- Instruction encoding: each machine instruction is encoded to its binary representation.
- Relocation resolution: cross-function references and data references are resolved.
- Section layout:
.text,.rodata,.data,.bsssections are organized.
Pass 9: Image Packaging
flint-backend, flint-elf, and flint-uf2 produce the final output:
- ELF:
flint-elfconstructs a 32-bit ARM ELF executable with section headers, a load segment mapped to flash, and a symbol table. Used by probe-rs, OpenOCD, and GDB. - BIN: raw binary image stripped of ELF metadata.
- UF2: UF2-packaged image for drag-and-drop flashing, with the correct RP2040 family ID and boot2 second-stage bootloader.
Diagnostics
flint-diagnostics formats errors and warnings from any pass:
- Human-readable output with file, line, column, and source excerpt.
- JSON output (
--json) for editor integrations and CI. - Severity levels: error, warning, hint.