TRACK: debugging | DIFFICULTY: 2 | NEEDS: mlir-opt (an assertions build for -debug-only) | TOOL: tools/who_did_it.sh
~/debug_lab/passwork bisected LLVM's 201 passes with -opt-bisect-limit.
MLIR has no bisect limit, but it has something better: it can write the IR
after every pass to a directory tree, one file per pass, in order:
mlir-opt IN.mlir --pass-pipeline=... --mlir-print-ir-after-all --mlir-print-ir-tree-dir=DIR
tools/who_did_it.sh REGEX <mlir-opt command> does that and prints, in
pipeline order, how many lines match REGEX after each pass. The first row
where the count changes names the pass. One run, no bisection, any property
you can grep.
memref.alloca inside the SVE k loop? (lab 05_aarch64/02, failure 3)$ ../tools/who_did_it.sh 'memref.alloca' mlir-opt ../05_aarch64/out/sve.t.mlir --pass-pipeline="builtin.module(func.func(convert-linalg-to-loops), canonicalize, cse, convert-vector-to-scf, convert-scf-to-cf)" order after pass matches 0 (input) 0 1 func_func_matmul/0_0_convert-linalg-to-loops.mlir 0 2 builtin_module_no-symbol-name/1_canonicalize.mlir 0 3 builtin_module_no-symbol-name/2_cse.mlir 0 4 builtin_module_no-symbol-name/3_convert-vector-to-scf.mlir 7 5 builtin_module_no-symbol-name/4_convert-scf-to-cf.mlir 7
0 → 7 at convert-vector-to-scf. Then read its options
(mlir-opt --help | grep -A5 -- '--convert-vector-to-scf '), find
full-unroll, and the fix follows. Nested func.func(...) passes write N_M_ files and restart their
numbering, so the tool orders rows by write time (true execution order).
memref.copy? (lab 04_optimization/02)$ ../tools/who_did_it.sh 'memref.copy' mlir-opt ../04_optimization/fusion.mlir --pass-pipeline='builtin.module(linalg-fuse-elementwise-ops,one-shot-bufferize{bufferize-function-boundaries function-boundary-type-conversion=identity-layout-map},canonicalize)' order after pass matches 0 (input) 0 1 builtin_module_no-symbol-name/0_linalg-fuse-elementwise-ops.mlir 0 2 builtin_module_no-symbol-name/1_one-shot-bufferize.mlir 1 3 builtin_module_no-symbol-name/2_canonicalize.mlir 1
one-shot-bufferize. It made the copy because it couldn't prove in-place
was safe. The follow-up question is why, which lab 03_linalg/02's
test-analysis-only print-conflicts answers.
| flag | use it when |
|---|---|
--mlir-print-ir-after-all | small input, you want to read everything (stderr, huge) |
--mlir-print-ir-after-change | combine with the above: skip passes that changed nothing |
--mlir-print-ir-before=PASS / --mlir-print-ir-after=PASS | you already know the suspect |
--mlir-print-ir-after-failure | a pass fails; print the IR it left behind |
--mlir-print-ir-tree-dir=DIR | route all of the above to per-pass files (what who_did_it.sh uses) |
--mlir-print-ir-module-scope | a func-level dump isn't enough; show the whole module |
--mlir-elide-elementsattrs-if-larger=16 | weights/constants make dumps unreadable |
--mlir-print-debuginfo | map an op back to its source loc(...) (Triton → Python line) |
--mlir-print-op-generic | a custom printer hides something (lab 01_basics/01) |
For Triton, the equivalent switch is MLIR_ENABLE_DUMP=1 (a vendor MLIR backend
developer guide), which dumps before every pass of Triton's own pipelines.
-debug-onlymlir-opt x.mlir -debug-only=dialect-conversion ... # mlir-opt: Unknown command line argument '-debug-only=dialect-conversion'.
-debug-only=<DEBUG_TYPE> prints a pass's internal reasoning (every pattern
tried in a conversion, every fusion candidate rejected). It needs an LLVM
built with -DLLVM_ENABLE_ASSERTIONS=ON. Both local builds
(build, build-full) have it OFF. For compiler development on a production MLIR backend,
make your dev build an assertions build: you get -debug-only, and verifier
checks inside passes that catch bugs at the pass instead of three passes
later. Keep a Release build for timing.
vector<8x16xf32> into vector<16xf32> in lab 04's
D variant? who_did_it.sh 'vector<8x16xf32>' mlir-opt ... on the
pre-lowering file with $LOWER_STD.unrealized_conversion_cast in $LOWER_STD?--mlir-print-ir-after-change with --mlir-print-ir-after-all on
$LOWER_STD and count how many passes actually changed lab 01/04's program.A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.