Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 pub and private items are accessed correctly.
  • Entry point validation: checks that main has the correct signature (-> never on MCU targets).
  • Recursion analysis: detects recursive named-call cycles. On MCU targets, these are reported as errors unless allow-recursion is set.
  • Deprecation lints: emits warnings at use sites of @deprecated items.

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.
  • defer sites 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, .bss sections are organized.

Pass 9: Image Packaging

flint-backend, flint-elf, and flint-uf2 produce the final output:

  • ELF: flint-elf constructs 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.