Compilation Pipeline

Mojo compiles through MLIR (Multi-Level Intermediate Representation) before reaching LLVM IR and finally machine code. This multi-stage pipeline is what enables Mojo to optimize at different abstraction levels — from high-level tensor operations down to register allocation.

The Pipeline

Mojo Source Code
    ↓ Parse & Type Check
Mojo AST
    ↓ Lower to MLIR
MLIR (high-level dialects)
    ↓ Transformation passes (tiling, vectorization, fusion)
MLIR (low-level dialects)
    ↓ Lower to LLVM IR
LLVM IR
    ↓ LLVM optimization passes
Machine Code (x86, ARM, etc.)

Why MLIR?

LLVM operates at a low level — it sees loads, stores, and arithmetic. It cannot reason about tensor operations or loop tiling. MLIR adds higher-level dialects that understand these concepts, enabling optimizations that LLVM alone cannot perform:

  • Affine dialect: Loop transformations (tiling, unrolling, interchange)
  • Linalg dialect: Linear algebra operations (matmul, conv)
  • Vector dialect: SIMD operations across targets
  • GPU dialect: GPU kernel generation

Transformation Passes

Each pass transforms the IR at one level before lowering to the next. A matmul might go through: tiling → vectorization → register allocation → instruction selection.

Constraint

Write a simple function with a loop. Reason about what the compiler can infer: loop bounds, types, memory access patterns. Identify which optimizations (unrolling, vectorization, inlining) are possible given the information available.

Why It Matters

Understanding the pipeline explains why certain code patterns are fast and others aren't. If you write code the compiler can't analyze (dynamic bounds, pointer aliasing, indirect calls), it can't optimize. Every decision in Phases 1-3 either helps or blocks the compilation pipeline.