Skip to content

Chapter 12 · Passes, Pass Managers & Testing Compilers

Part 3 · Analysis Foundations & SSA · about 2–3 weeks · Previous: Ch 11 · Next: Ch 13

The problem

Every chapter from here to Chapter 24 writes passes: functions from programs to programs, run in a pipeline that a pass manager schedules, consulting analyses it caches and invalidates. The first half of this chapter asks how that machinery works and must work: given a pipeline \(p_1 \cdots p_k\) over IR units (modules, call-graph SCCs, functions, loops) and a set of analyses with dependencies, run each pass with coherent analysis results, compute each result as rarely as possible, let users add passes without rebuilding the compiler, and decide in which order passes should run. The second half asks how to know that a pass is right: given a compiler or pass \(C\), find inputs on which it crashes or miscompiles, turn them into small reproducible tests, locate the responsible pass or commit, and — where possible — prove that a particular run was correct. The inputs of this half are a compiler and a budget; the outputs are tests with oracles (FileCheck patterns, snapshots, reference executions, equivalent variants, refinement proofs), minimal failing inputs, and culprits. In pebblec, the chapter's passes live in the course plugin PebblePasses (pebble/lib/Passes/Basics/), and every later chapter tests its passes with the lit + FileCheck + lli methodology taught here.

What you will be able to do

  • Predict which analyses LLVM's new pass manager computes and invalidates for a pipeline, from the PreservedAnalyses of each pass and the dependencies of each analysis, and prove when caching is sound (drill pm-invalidation).
  • Compare LLVM's legacy and new pass managers, GCC's property/TODO manager and MLIR's nested operation-anchored manager, and read -debug-pass-manager, -fdump-passes and -mlir-print-ir-after-all output.
  • Write an analysis with a printer, a transformation that reports precise PreservedAnalyses, and an instrumentation pass, and register them in a plugin (pebble-stats, pebble-strength, pebble-bbcount); prove why sdiv x, 2^k needs a bias and when nsw survives.
  • Explain how -O1/-O2/-O3 pipelines are assembled and extended, and why no fixed phase order is optimal.
  • Predict FileCheck's verdict on a check file, including the greedy CHECK-NEXT/CHECK-DAG/CHECK-NOT surprises (drill filecheck-match); write lit tests with must-not-transform negatives and lli equivalence.
  • Test the same property as FileCheck, snapshot and differential tests and measure which planted bugs each detects; explain what Csmith, YARPGen, llvm-stress, grammar fuzzers, libFuzzer and EMI add.
  • Implement ddmin, prove 1-minimality and its test bound, trace it by hand (drill ddmin-trace), and compare it with C-Reduce and llvm-reduce; bisect a pipeline with -opt-bisect-limit and a history with git bisect.
  • State refinement with undef/poison, check rewrites by exhaustion or SMT (drill peephole-verify), and validate compiler runs with Alive2.

Prerequisites: Ch 9 (LLVM IR, poison and flags in Lesson 9.7), Ch 10 (the C++ API, IRBuilder, PreservedAnalyses in passing), Ch 2 (grammars, for Lesson 12.6). Chapter 11 is not needed.

Notation

Shared notation follows the house notation: §1 (sets, functions, logic), §3 (graphs), §5 (grammars, Lesson 12.6) and §8 (complexity). In this chapter:

Symbol Meaning
\(\mathcal{P}\), \(P\), \(u\) the set of programs, a program, an IR unit (module, SCC, function, loop) (Definition 12.1.1)
\(A(P, u)\), \(A \to B\) analysis result on a unit; \(A\) depends on \(B\) (Definition 12.1.2)
\(\mathrm{PA} = (K, S, X)\), \(\mathrm{all}\), \(\mathrm{none}\) preservation report: preserved analyses, preserved sets, abandoned analyses (Definition 12.1.3)
\(\mathrm{inv}_A\), \(\mathrm{Inv}\) invalidation predicate of \(A\); the memoizing invalidator (Definition 12.1.4)
\(\Phi\) GCC's set of IR properties (Proposition 12.1.20)
\(\Sigma\), \(w \in \Sigma^{*}\), \(w(P)\), \(m\) passes, a pass sequence, its result on \(P\), a cost (Definition 12.2.6)
iN, \(\lfloor x \rfloor_u\), \(\lfloor x \rfloor_s\) an \(N\)-bit integer type and a bit string read unsigned or signed (Definition 12.3.1)
\(G^{+}\), \(\mathrm{cnt}(u \to v)\), \(e - n + 1\) extended CFG with the exit-to-entry edge, edge counts, the number of counters needed (Definition 12.3.5, Theorem 12.3.12)
\(\mathit{in}\), \([s, e)\), \(\mathrm{first}(p, a, b)\) FileCheck input, a match interval, the leftmost match of pattern \(p\) in \([a, b)\) (Definition 12.4.1)
\(O(C, x)\) a test oracle (Definition 12.5.1)
\(\llbracket P \rrbracket(i)\) the (unique) behavior of a well-defined program on input \(i\) (Definition 12.5.4)
\(q\), \(N\), \(\delta\) per-program detection probability, number of programs, failure probability (Theorem 12.5.11)
\(\pi_A\), \(\xi\), \(m\), \(f(s)\) production probabilities, offspring count, mean offspring, generating function (Definition 12.6.1)
\(\Phi(x)\), \(\mathcal{C}\) features of an execution, a fuzzing corpus (Definition 12.6.3)
\(\mathrm{cov}_I(P)\) statements executed on input \(I\) (Definition 12.6.5)
\(c\), \(n\), \(\Delta_i\), \(\nabla_i\), \(\mathsf{fail}/\mathsf{pass}/\mathsf{unres}\) a configuration, the granularity, the \(i\)-th subset and complement, test outcomes (Definitions 12.7.1–12.7.2)
\(v_0..v_n\), \(\mathrm{bad}\) a history and a monotone predicate for bisection (Definition 12.7.6)
\(t \sqsupseteq s\), \(\mathsf{UB}\), poison target refines source; immediate undefined behavior; the poison value (Definitions 12.8.1–12.8.2)

Numbered statements are N.k.m (chapter, lesson, counter), as in NOTATION.md §9.

Technique map

Family Techniques (origin) Lesson
Pass-manager architectures LLVM legacy pass manager (AnalysisUsage, static schedule; LLVM 1.x, documented in [LLVM-WLP]); LLVM new pass manager (Carruth, 2014–; default since LLVM 13 [LLVM-NPM]); GCC's pass manager (passes.def, properties, TODO flags [GCC-Int]); MLIR's nested, operation-anchored pass manager ([MLIR-PM]) 12.1
Pipelines and extensibility Pass plugins and extension points (PassPlugin, PassBuilder callbacks [LLVM-PassPlugin, LLVM-PB]); -O1/-O2/-O3/-Os pipeline assembly ([LLVM-PBP]); the phase-ordering problem (Cooper, Schielke and Subramanian 1999 [CSS99]; Kulkarni et al. 2006 [KWTD06]) 12.2
Writing passes Analysis passes and printers ([LLVM-WNPM]); strength reduction by powers of two (Allen, Cocke and Kennedy 1981 [ACK81]; Granlund and Montgomery 1994 [GM94]; [HD2]); counter-based instrumentation (Knuth; Ball and Larus 1994 [BL94]) 12.3
Oracle tests lit ([LLVM-lit]); FileCheck ([LLVM-FileCheck]); automatic check generation, update_test_checks.py ([LLVM-UTC]) 12.4
Snapshot, differential and random testing Snapshot/golden testing (Turnt [Turnt], insta [Insta]); differential testing (McKeeman 1998 [McK98]); Csmith (Yang et al. 2011 [YCER11]); YARPGen (Livinskii, Babokin and Regehr 2020 [LBR20]); llvm-stress ([LLVM-Stress]) 12.5
Fuzzing and metamorphic testing Grammar-based fuzzing (Hanford 1970 [Han70]; Purdom 1972 [Pur72]; [FB-Grammar]); coverage-guided fuzzing with libFuzzer ([LLVM-LibFuzzer]; Serebryany 2016 [Ser16]); equivalence modulo inputs (Le, Afshari and Su 2014 [LAS14]) 12.6
Reduction and bisection Delta debugging, ddmin (Zeller and Hildebrandt 2002 [ZH02]); C-Reduce (Regehr et al. 2012 [RCC+12]); llvm-reduce and bugpoint ([LLVM-Reduce, LLVM-BugpointRedesign]); bisection: -opt-bisect-limit, -print-changed, git bisect ([LLVM-OptBisect, GitBisect]) 12.7
Translation validation Translation validation (Pnueli, Siegel and Singerman 1998 [PSS98]); Necula's validator (2000 [Nec00]); Alive (Lopes et al. 2015 [LMNR15]); Alive2 (Lopes et al. 2021 [LLH+21]) 12.8
flowchart LR
  L[Legacy PM<br/>declared usage] -->|lazy caching, per-run<br/>PreservedAnalyses| N[New PM]
  G[GCC PM<br/>properties + TODOs] -.->|same problem| N
  N -->|nest along IR,<br/>parallel| M[MLIR PM]
  N --> P[Plugins, extension points,<br/>O-pipelines, phase ordering]
  P --> W[Your passes:<br/>printer, strength, counters]
  W --> T1[lit + FileCheck<br/>update_test_checks]
  T1 -->|check everything| S[Snapshots]
  T1 -->|no expected output| D[Differential testing]
  D --> R[Csmith, YARPGen,<br/>llvm-stress]
  D -->|one compiler| E[EMI]
  R -->|grammar only| GF[Grammar fuzzing]
  GF -->|coverage feedback| CF[libFuzzer]
  D --> DD[ddmin]
  DD -->|C-aware passes| CR[C-Reduce]
  DD -->|IR-aware deltas| LR[llvm-reduce]
  D --> B[Bisection]
  D -->|prove the run| TV[Translation validation]
  TV --> NE[Necula]
  TV --> AL[Alive] --> A2[Alive2]

Who uses what

System Technique Notes
LLVM 23 New pass manager for the optimizer, legacy manager in llc; PassBuilder pipelines and plugins; lit + FileCheck + update_test_checks.py; -opt-bisect-limit; llvm-reduce, llvm-stress, libFuzzer-based *-fuzzer tools Lessons 12.1–12.4, 12.7
GCC 15 passes.def tree with properties and TODO flags; plugins via PLUGIN_PASS_MANAGER_SETUP; -fdump-passes; DejaGnu test suite; --coverage/gcov Lessons 12.1, 12.2
MLIR (Flang, CIRCT, IREE, Triton) Nested operation-anchored pass managers, dynamic pipelines, crash reproducers, mlir-reduce Lesson 12.1
Clang -fpass-plugin, -fsanitize=fuzzer, -fprofile-generate, -fcoverage-mapping Lessons 12.2, 12.3, 12.6
Compiler-testing research tools Csmith, YARPGen (differential testing), Orion (EMI), C-Reduce (reduction), Alive/Alive2 (translation validation) Lessons 12.5–12.8
Cornell Bril, Rust ecosystem Turnt and insta snapshot testing Lesson 12.5
pebblec One plugin PebblePasses with self-registering passes; lit/FileCheck/lli tests for every pass; course -O1 = registered steps exercises.md

Comparison

One row per technique, the same rows as the lessons' §8 tables (each lesson has the "Choose it when…" paragraphs).

Technique Power / precision Speed Output / error quality Implementation effort Typical use
LLVM legacy PM Static, declared preservation: must assume the worst case per pass Recomputes after every declared non-preserving pass, even when nothing changed -debug-pass=Structure shows the whole schedule up front Low per pass (declare usage); complex manager LLVM codegen (llc) in LLVM 23; LLVM optimizer before LLVM 13
LLVM new PM Dynamic, per-run PreservedAnalyses; dependency-aware invalidation (Theorem 12.1.17) Lazy; recomputes only what a run invalidated; \(O(\lvert \mathrm{cache} \rvert + d)\) bookkeeping per pass -debug-pass-manager, -print-changed, pass instrumentation Moderate per pass (truthful reports, invalidate for dependent analyses) LLVM 23 optimizer, opt, clang, the course plugin
GCC PM Coarse: IR properties and TODO flags instead of per-analysis caching Static tree; repairs (update SSA, cleanup CFG) on request -fdump-passes, per-pass dumps -fdump-tree-<pass> Low per pass (fill in pass_data) GCC's GIMPLE, RTL and IPA pipelines
MLIR PM Per-operation analysis managers; nesting follows the IR; op-agnostic pipelines Parallel over isolated operations (Proposition 12.1.21) -mlir-print-ir-after-all, -mlir-timing, crash reproducers Moderate: declare anchors, keep passes local MLIR-based compilers (Flang, CIRCT, IREE, Triton)
Pass plugins and extension points Any pass, at named points of the default pipelines or by name in textual pipelines dlopen + one callback per name; no LLVM rebuild "unknown pass name" if unregistered; print<pebble-passes> lists registrations Low: one registration line per pass (course registry) Research passes, out-of-tree tools (Polly, Enzyme, the course plugin)
Optimization pipelines (-O1/-O2/-O3/-Os) Hand-designed, balanced for code in general; not tuned per program O1: 100 elements; O2: 119; O3: 122 (§7) -print-pipeline-passes shows the exact pipeline, re-runnable with -passes= High, but done once by compiler developers Every clang -O<n> compilation
Phase ordering (search) Optimal for the searched program, metric and length bound (Theorem 12.2.13) Exponential in \(L\) in the worst case; pruned by distinct outcomes Per-program best sequence Medium (a driver around the compiler) Research, auto-tuning, embedded code size; informs fixed pipelines
Analysis passes and printers Exact facts about the current IR; cached per unit \(\Theta(I + e)\) per computation, reused until invalidated A documented, FileCheck-able format Low (analysis + printer + registration) Every LLVM analysis; the course's print<pebble-x> contracts
Strength reduction by powers of two Exact (Theorems 12.3.9–12.3.10); only power-of-two constants \(\Theta(I)\); replaces a multi-cycle hardware divide by 1–4 single-cycle instructions Wrong if done naively (sdiv → ashr, nsw kept at \(k = N-1\)) Low, but the proofs matter InstCombine (mul/udiv/urem/exact sdiv), DAGCombiner (sdiv), pebble-strength
Counter-based instrumentation Exact counts of the training runs; spanning tree: minimum counters (Theorem 12.3.12) One increment per executed block (naive) or per instrumented edge (MST) Per-block counts for PGO; needs a runtime Low (naive) to medium (MST + reconstruction) -fprofile-generate, -fprofile-arcs, gcov, pebble-bbcount
lit Runs any shell pipeline; features select configurations; XFAIL for known bugs Parallel; per-test cost is the tools PASS/FAIL/XFAIL/XPASS/UNSUPPORTED with the failing command Low per test (RUN lines); one lit.cfg.py per suite LLVM, Clang, MLIR, this course
FileCheck Ordered patterns with NEXT/SAME/NOT/DAG/LABEL, captures, numeric expressions; greedy, sound but incomplete (Theorems 12.4.9, 12.4.10, Prop. 12.4.11) \(O(m\,n\,\lvert p \rvert)\); fast Precise error location and an annotated input dump Low to write, easy to get subtly wrong (NOT ranges, DAG/NOT ordering) Checking the relevant part of compiler output
Automatic check generation Checks everything up to consistent renaming (Theorem 12.4.14) One run per update Diffs of generated checks in review Very low per test; one generator per output kind InstCombine and most IR transformation tests
Snapshot testing Detects every output change (Prop. 12.5.9); false alarm on every benign change One run + diff per input A diff to review; accepting is one command Very low per test Printers, diagnostics, small IR outputs; update_test_checks
Differential testing Sound only for well-defined programs (Thm. 12.5.10); misses shared and unobservable bugs (Prop. 12.5.12) \(k\) compilations + runs per program; \(1/q\) programs to a bug A disagreeing program (large: reduce it) Low (a harness) given a generator Csmith/YARPGen campaigns, -O0 vs -O2, before/after a pass
Csmith UB-free C with pointers, structs, unions, volatile; checksum oracle Seconds per program Thousands of lines per failing program High (a large C++ generator with its own pointer and effect analyses) Finding miscompilations in C compilers
YARPGen UB-free C/C++ with loops and arrays, policies for optimizations Sub-second generation Two files per test; hashes of results High Loop and vectorizer bugs in GCC/LLVM/ICC
llvm-stress Random verifier-clean IR; crash oracle only Milliseconds Small IR functions Low (one LLVM tool) Crash testing passes and back ends
Grammar-based fuzzing Syntactically valid inputs; no semantic validity; terminates with cost-aware expansion (Thm. 12.6.7) Microseconds per input Readable inputs; crash oracle unless combined with differential testing Low (a grammar + Algorithm 12.6.2) Parser and front-end robustness
Coverage-guided fuzzing (libFuzzer) Finds inputs that pass deep checks without knowing the format (Thm. 12.6.8); crash/sanitizer oracle \(10^5\)–\(10^6\) executions/s in process A minimal-ish crashing input (-minimize_crash) Low: one entry point + -fsanitize=fuzzer Parsers, decoders, LLVM's own *-fuzzer tools
Equivalence modulo inputs Detects miscompilations with one compiler and no expected output (Thm. 12.6.9) A coverage run + one compile/run per variant A pair of programs that differ only in dead code Medium (coverage + a source rewriter) Miscompilation hunting in GCC/LLVM (Orion, Athena, Hermes)
Delta debugging (ddmin) 1-minimal (Thm. 12.7.8), format-agnostic; stuck on structure (paired lines) \(O(\log N)\) best, \(\le 3N^2 + 3N\) tests worst; lab: 195 tests for 53 lines Readable only at the chosen granularity Low (≈100 lines; the lab) Byte inputs, change sets, line-level first cuts
C-Reduce Fixpoint of C-aware transformations (Thm. 12.7.11); far smaller than ddmin Minutes to hours; lab: ~100 s Tiny, idiomatic C (may contain UB) High (a tool) — use it Csmith and user reports of C/C++ compiler bugs
llvm-reduce (bugpoint) Fixpoint over IR entities; output always verifies Seconds to minutes; lab: 8.5 s Minimal IR tests ready for llvm/test Low to use Any LLVM crash or IR-level miscompile with a script
Bisection Exact first bad state for monotone histories (Thm. 12.7.12) \(\lceil \log_2 n \rceil\) tests One pass execution or one commit Very low Localizing a pass (-opt-bisect-limit) or a commit (git bisect)
Translation validation Proves each run correct (sound, Theorem 12.8.11); says nothing about unvalidated programs Solver time per run; ms for peepholes A concrete counterexample input Medium: a semantics + an encoding (the course's is ~250 lines) Checking a pass on its test corpus, bug hunting
Necula's validator Handles control-flow-changing optimizations via inferred simulation relations; incomplete (unknown) Seconds per function (2000-era GCC) The failing verification condition High GCC's optimizer in [Nec00]; ideas reused in later validators
Alive Proves rewrite rules for all feasible types, with undef/poison; infers flags Seconds per rule Counterexample with types and values Low per rule (the DSL) InstCombine rewrites, flag questions
Alive2 Whole functions, memory, undef/poison, bounded loops (Prop. 12.8.12) ms to minutes; timeouts Counterexample, or "failed to prove" Very low to use Validating LLVM passes and tests daily; reviewing patches

Comparison-lab results (reference solution in the course's Linux container; reproduce with ctest --test-dir build/<preset> -R ch12.lab after L1 and L2, and the commands in Lesson 12.7 §7):

  • Three oracles against five planted bugs (Part A, ch12.lab.matrix): FileCheck detects bugs 1, 2, 3, 5 and correctly passes the harmless bug 4; snapshots detect all five, including a false alarm on bug 4; the differential tester detects 1, 2, 3, passes 4, and misses the poison-only bug 5 (which translation validation catches, Lesson 12.8).
  • Reducers on the planted crash (Part B, 53-line C file): line-level ddmin keeps 10 lines after 195 tests (≈12 s); token-level ddmin keeps 119 of 205 tokens after 1 653 tests (≈65 s); C-Reduce reaches 4 lines (≈100 s); llvm-reduce on the 220-line IR reaches a 3-instruction function (≈8.5 s).

Route through this chapter

Step What Techniques How it is exercised
1 Lesson 12.1 legacy PM, new PM, GCC PM, MLIR PM drill pm-invalidation; Pebble implements pebble-stats (E1); quiz; flashcards
2 Lesson 12.2 plugins and extension points, O-pipelines, phase ordering E2 joins the course -O1; quiz
3 Lesson 12.3 analysis printers, strength reduction, counter instrumentation Pebble implements E1–E3; drill peephole-verify
4 Lesson 12.4 lit, FileCheck, update_test_checks drill filecheck-match; tests/ch12/lit; lab L1
5 Lesson 12.5 snapshots, differential testing, Csmith, YARPGen, llvm-stress lab Part A (L1): three oracles vs planted bugs
6 Lesson 12.6 grammar fuzzing, libFuzzer, EMI quiz; real-world boxes; ★ E4 (libFuzzer on your parser); Chapter 24's fuzzer
7 Lesson 12.7 ddmin, C-Reduce, llvm-reduce, bisection drill ddmin-trace; lab Part B (L2): your ddmin vs llvm-reduce
8 Lesson 12.8 translation validation, Necula, Alive, Alive2 drill peephole-verify; examples/tv.py
9 Exercises Pebble uses the new pass manager through the PebblePasses registry ./course test 12
10 Comparison lab labs/ch12-testing/ FileCheck vs snapshot vs differential; ddmin vs llvm-reduce (vs ★ C-Reduce) lab tests + measurements
11 Theory test all ./course quiz 12 (≥ 80 % to finish)

Practice and check

./course drill pm-invalidation --difficulty easy   # which analyses are computed and invalidated
./course drill filecheck-match                     # FileCheck's verdict and match lines
./course drill ddmin-trace                         # ddmin rounds, result, test count
./course drill peephole-verify                     # is this rewrite correct? (exhaustive i4)
./course flash 12                                  # daily, a few minutes
./course quiz 12                                   # after the lessons
./course test 12                                   # after the exercises and the lab
./course status                                    # done = quiz ≥ 80 % and tests pass

Tools outside the course toolchain

Most real-world boxes need only the course toolchain (clang, opt, llc, lli, llvm-reduce, llvm-stress, FileCheck, lit 23.1.2) and the reference build of ./course test 12 --solution (SOL=$PWD/build/ci-solutions-linux, or build/ci-solutions-macos). The others need these tools; the boxes' outputs came from the versions listed.

Tool (boxes) macOS Linux
compiler-rt 23.1.2: profile, fuzzer and sanitizer runtimes (Lessons 12.3, 12.6) Homebrew's llvm ships them; omit -resource-dir="$RD" sudo apt install libclang-rt-23-dev with apt.llvm.org's clang-23 (omit -resource-dir), or with the course's conda-forge toolchain build a resource directory $RD as below
mlir-opt 23.1.2 (Lesson 12.1) Homebrew's llvm ($(brew --prefix llvm)/bin/mlir-opt) apt.llvm.org: sudo apt install mlir-23-tools; or conda-forge mlir=23.1.2, run with its lib/ on LD_LIBRARY_PATH
gcc 14.2.0 (Lessons 12.1, 12.5) brew install gcc@14 (driver gcc-14) Ubuntu 24.04: sudo apt install gcc-14
Csmith 2.3.0 (Lesson 12.5) brew install csmith; headers in $(brew --prefix csmith)/include/csmith-2.3.0 Ubuntu 24.04: sudo apt install csmith libcsmith-dev; headers in /usr/include/csmith
YARPGen 2.0 at e2a0512 (Lesson 12.5) build from source as below build from source as below
C-Reduce (Lesson 12.7) brew install creduce (2.10.0) Ubuntu 24.04: sudo apt install creduce (2.11.0, used here)
Alive2 at 01a5ec45: alive, alive-tv (Lesson 12.8) build from source as below, with brew install z3 re2c ninja build from source as below, with sudo apt install libz3-dev re2c ninja-build
Turnt, fuzzingbook, z3-solver (Lessons 12.5, 12.6, 12.8) uvx turnt, uvx --from fuzzingbook, uv run --with z3-solver same

The resource directory with compiler-rt for the conda-forge toolchain (Linux x86-64):

curl -sSLO https://conda.anaconda.org/conda-forge/noarch/compiler-rt23_linux-64-23.1.2-h0e38de2_0.conda
mkdir -p crt && (cd crt && unzip -q ../compiler-rt23_linux-64-23.1.2-h0e38de2_0.conda && tar --zstd -xf pkg-*.tar.zst)
RD=$PWD/rd && mkdir -p "$RD" && cp -r "$(clang-23 -print-resource-dir)"/. "$RD" && cp -r crt/lib/clang/23/lib "$RD"/

YARPGen and Alive2 from source (any platform; LLVM_DIR is your LLVM 23.1.2's lib/cmake/llvm, e.g. $(brew --prefix llvm)/lib/cmake/llvm or /opt/llvm-23/lib/cmake/llvm):

git clone https://github.com/intel/yarpgen && cd yarpgen && git checkout e2a0512
sed -i.bak 's/ -Werror>/>/' src/CMakeLists.txt            # newer clang warns where YARPGen's -Werror stops the build
cmake -B build -DCMAKE_CXX_COMPILER=clang++ && cmake --build build   # -> build/yarpgen
cd .. && git clone https://github.com/AliveToolkit/alive2 && cd alive2 && git checkout 01a5ec45
cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TV=1 -DLLVM_DIR="$LLVM_DIR" && cmake --build build   # -> build/alive, build/alive-tv

Alive2 is pinned because it tracks LLVM's development branch: a newer commit may not build against LLVM 23.1.2.

References

The chapter's annotated bibliography — papers, textbook sections, pinned source files and docs — is in references.md. Start with: [LLVM-NPM] (the pass manager you use), [LLVM-FileCheck] and [LLVM-TestingGuide] (how every later test is written), [YCER11] and [McK98] (random differential testing), [ZH02] (delta debugging), and [LLH+21] (Alive2).