A hands-on pathway to basic proficiency in LLVM and MLIR — enough to read IR fluently, run real passes, understand the architecture, and discuss these tools intelligently.
You work through numbered modules in order. Each module is a directory with:
- a
README.mdexplaining the concepts, - runnable
examples/, - exercises with answers you can check yourself using the tools.
You learn by running the real tools on real code and reading what comes out.
Everything in this repo hangs off a single picture. A compiler is a pipeline of lowerings — each stage takes a representation and rewrites it into a lower-level one, until you reach machine code.
C / C++ source
│ clang (frontend: lex, parse, type-check, emit IR)
▼
LLVM IR ◄──────────────┐ .ll (text) / .bc (bitcode)
│ opt (middle-end: target-independent optimization, IR → IR)
▼ │
LLVM IR (optimized) ────┘
│ llc (backend: instruction selection, register alloc, scheduling)
▼
Target assembly (arm64 .s)
│ assembler + linker
▼
Executable
LLVM IR is the waist of the hourglass. Many languages compile down to it; many targets are generated from it. Optimizations are written once, against the IR, and benefit every language and target.
MLIR widens the waist into a stack. Instead of one IR, MLIR lets you define many IRs ("dialects") at different abstraction levels and progressively lower between them — a high-level tensor op → loops → LLVM IR. That's the whole idea, and the second half of this pathway.
High-level dialect (e.g. tensor / linalg)
│ mlir-opt (passes & dialect conversions, IR → IR)
▼
Mid-level dialect (e.g. scf, affine)
│ mlir-opt
▼
llvm dialect
│ mlir-translate (MLIR → LLVM IR)
▼
LLVM IR ──► (rejoins the pipeline above)
| Tool | Does | First used in |
|---|---|---|
clang |
C/C++ frontend → LLVM IR or machine code | 01 |
clang++ |
compile C++ programs that use the LLVM libraries | 03 |
llvm-config |
prints build flags/libs for linking against LLVM | 03 |
opt |
Optimizer: LLVM IR → LLVM IR (runs passes) | 04 |
llc |
Backend: LLVM IR → target assembly | 06 |
lli |
Interpreter/JIT: runs LLVM IR directly | 06 |
llvm-as/-dis |
.ll ↔ .bc (assemble / disassemble bitcode) | 04 |
mlir-opt |
MLIR optimizer & dialect-conversion driver | 07 |
mlir-translate |
MLIR ↔ LLVM IR | 08 |
| # | Module | What you walk away able to do |
|---|---|---|
| 00 | Setup & the big picture | Install/verify the toolchain; explain the pipeline above |
| 01 | Reading LLVM IR | Read .ll: modules, functions, basic blocks, types, instructions, SSA |
| 02 | SSA & control flow | Understand phi nodes, branches, and how loops look in IR |
| 03 | C++ meets LLVM: IRBuilder |
Build IR from C++; compile against LLVM (llvm-config/CMake); core C++ API idioms |
| 04 | The optimizer | Run individual passes (mem2reg, instcombine, gvn…); read -O0 vs -O2 |
| 05 | Writing an LLVM pass in C++ | Write, build & load your own opt pass; walk IR in C++ |
| 06 | Backend & codegen | Lower IR to arm64 asm with llc; run IR with lli; target triples |
| 07 | MLIR foundations | Read MLIR; explain operations/dialects/regions and why MLIR exists |
| 08 | MLIR dialects & lowering | Lower scf/arith → llvm dialect → LLVM IR |
| 09 | Capstone | Drive a high-level MLIR program all the way to a running executable |
C++ enters at module 03 — as soon as you can read IR, you start building and transforming it with LLVM's C++ libraries. MLIR (07–08) stays command-line; its C++/TableGen dialect work is a natural follow-on module once these land.
Start with 00-setup — you install the toolchain there yourself (the tools are not pre-installed; setting them up is part of learning).
- Do the modules in order; each assumes the previous.
- Type the commands yourself — don't just read. The muscle memory matters.
- Every exercise has a "check yourself" command using the real tools.
- A C++-for-LLVM refresher lands in module 03, right before you need it; the C++ modules (03, 05) are real coding, not just reading.
Toolchain: LLVM/MLIR 22 via Homebrew, Apple Silicon (arm64). You install it in module 00.