$ opt -O2 -opt-bisect-limit=-1 01_bisect.ll -o /dev/null 2>&1 | wc -l
201
-1 means "run everything and narrate it". Two hundred and one passes for
one twelve-line function. Reading that list top to bottom is not a plan."It works at -O0 and breaks at -O2" is the most common compiler bug report there is, and the useless response is to read all 201 passes. The useful response is a binary search. -opt-bisect-limit=N runs the first N passes and skips the rest, so you can bisect on any observable property of the output and land on the exact pass in about eight runs. Here the property is benign — does the IR contain vector types — but the method is identical when the property is "does the program print the wrong answer". That is the whole point: practise the loop on something safe so you can run it under pressure on something that is not.
$ git clone https://github.com/adit4443ya/debug_labs.git && cd debug_labs
$ cd passwork && ./build.sh
$ opt -O2 -opt-bisect-limit=-1 01_bisect.ll -o /dev/null 2>&1 | wc -l
201
-1 means "run everything and narrate it". Two hundred and one passes for
one twelve-line function. Reading that list top to bottom is not a plan.$ opt -O2 01_bisect.ll -S -o - | grep -c '<4 x i32>'
16Sixteen vector-typed values in the final IR. At limit 0 there will be none. Somewhere between is the pass that introduced them. A good property is one word of output and no judgement: a count, or an exit status.
$ for n in 200 100 150 125 137 142 144 143; do \
printf "limit=%-4s vectors=%s\n" "$n" \
"$(opt -O2 -opt-bisect-limit=$n 01_bisect.ll -S -o - 2>/dev/null | grep -c '<4 x i32>')"; \
done
limit=200 vectors=16
limit=100 vectors=0
limit=150 vectors=11
limit=125 vectors=0
limit=137 vectors=0
limit=142 vectors=0
limit=144 vectors=11
limit=143 vectors=0Eight runs to go from 201 candidates to one. The change happens when the limit reaches 144 and not at 143, so pass 144 is the one.
$ opt -O2 -opt-bisect-limit=-1 01_bisect.ll -o /dev/null 2>&1 | sed -n '143,145p'
BISECT: running pass (143) InjectTLIMappings on sum_squares
BISECT: running pass (144) LoopVectorizePass on sum_squares
BISECT: running pass (145) InferAlignmentPass on sum_squares
LoopVectorizePass. Now you have a pass name, and every later question —
why did it fire, was it legal, what does it cost — has somewhere to start.$ opt -O2 01_bisect.ll -S -o - | grep -c '<4 x i32>'
16$ opt -O2 -disable-loop-vectorization 01_bisect.ll -S -o - | grep -c '<4 x i32>'
0A bisect gives you a suspect; disabling the pass gives you a confession. Always do the second step — an off-by-one in the bisect is easy and the confirmation costs one command.
Eleven at the boundary, sixteen at the end. Later passes widen and clean up what the vectorizer produced. That is normal, and it is why you bisect on "did the property appear" rather than on an exact number.
Same loop, different test. Write a script that exits 0 when the program is correct and 1 when it is not, then bisect on the exit status:
$ opt -O2 -opt-bisect-limit=$n bug.ll -o t.bc && clang t.bc -o t && ./t
That script is also exactly what llvm-reduce wants (lab 03), so writing it
once serves both.1. Bisect for a different property: when does `sum_squares` stop being a call and get inlined into main? Grep the IR for `call.*sum_squares`. 2. Run with `-print-after=loop-vectorize` and read the IR it produced. 3. Compare the pass count at -O1, -O2 and -O3. The difference between -O2 and -O3 is smaller than most people assume — check before you believe anyone who says "just use -O3". ============================================================
#include <stdio.h>
#include <stdlib.h>
int __attribute__((noinline)) sum_squares(const int *a, int n) {
int total = 0;
for (int i = 0; i < n; i++)
total += a[i] * a[i];
return total;
}
int main(int argc, char **argv) {
int n = argc > 1 ? atoi(argv[1]) : 8;
int *a = malloc(n * sizeof *a);
for (int i = 0; i < n; i++) a[i] = i;
printf("%d\n", sum_squares(a, n));
free(a);
return 0;
}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.