Bug reports arrive as forty thousand lines. Nobody debugs that. You reduce it to something a maintainer can read in a minute, then write a test so it cannot come back.
The reducing is mechanical — llvm-reduce does it. The interestingness test
is the part people get wrong, and this lab is mostly about getting it wrong
twice on purpose, because that is how you learn what it has to assert.
Nothing to build. 03_reduce_and_pin.ll is one interesting function buried in
five noise functions, standing in for the large file.
#!/bin/sh grep -q "@the_one" "$1" # "is my function still mentioned?"
$ llvm-reduce --test=./interesting-loose.sh 03_reduce_and_pin.ll -o loose.ll
$ cat loose.ll
declare i32 @the_one()
A bare declaration. The reducer did exactly what you asked — it kept the name
and threw away everything else, because a declaration satisfies grep. This is
the single most common way a reduction wastes an afternoon.
The rule: the test must assert the BUG still reproduces, not that the code still looks familiar.
#!/bin/sh opt -passes=gvn -S "$1" -o - 2>/dev/null \ | sed -n '/@the_one/,/^}/p' | grep -q "add i32 %a, %a"
$ llvm-reduce --test=./interesting-good.sh 03_reduce_and_pin.ll -o good.ll
$ cat good.ll
define i32 @the_one(i32 %a) {
entry:
%sum = add i32 %a, %a
ret i32 %sum
}
Now it runs the pass and checks the output — much better, and still wrong. There
are no loads left. The reducer found a program whose output has the shape we
asked for without GVN doing anything at all: the input already said
add i32 %a, %a. We asserted a shape, not a transformation.
#!/bin/sh before=$(grep -c "load i32" "$1") [ "$before" -ge 2 ] || exit 1 after=$(opt -passes=gvn -S "$1" -o - 2>/dev/null | grep -c "load i32") [ "$after" -lt "$before" ]
$ llvm-reduce --test=./interesting-correct.sh 03_reduce_and_pin.ll -o correct.ll
$ cat correct.ll
define i32 @the_one() {
entry:
%a = load i32, ptr null, align 4
%b = load i32, ptr null, align 4
ret i32 %b
}
Four lines, and it still exercises the thing we care about: two loads go in, one comes out. This is what you attach to a bug report.
Note ptr null — the reducer replaced the parameter with the simplest value
that keeps the property. That is fine here because nothing dereferences it at
run time. When your property is a crash, it is not fine, and the test must
run the program rather than inspect its IR.
A reproducer that is not in the test suite will be reintroduced. FileCheck is how LLVM pins a transformation:
; RUN: opt -passes=gvn -S < %s | FileCheck %s define i32 @the_one(ptr %p) { ; CHECK-LABEL: @the_one ; CHECK: %a = load i32, ptr %p ; CHECK-NOT: load ; CHECK: ret entry: %a = load i32, ptr %p %b = load i32, ptr %p %sum = add i32 %a, %b ret i32 %sum }
$ opt -passes=gvn -S < 04_filecheck_test.ll | FileCheck 04_filecheck_test.ll && echo PASS
PASS
CHECK-NOT: load between two CHECKs is the load-bearing line: it asserts
there is no second load between the first one and the return. A test that only
checks the first load would pass even if GVN did nothing.
A test you have never seen fail is not a test.
$ opt -passes=gvn -S < 04_filecheck_test.ll | FileCheck 04_filecheck_test.ll && echo PASS
PASS
$ opt -S < 04_filecheck_test.ll | FileCheck 04_filecheck_test.ll && echo PASS || echo FAIL
FAIL
Without -passes=gvn the second load survives, CHECK-NOT fires, and the test
fails. Do this every time you write one.
"I reduce with llvm-reduce against an interestingness script, and the script
has to assert the transformation, not the shape of the output — otherwise the
reducer happily hands you a program that satisfies your grep and reproduces
nothing. Then I pin it with a lit/FileCheck test and confirm the test fails
without the fix."
That answer contains the two things people miss: what makes a reduction script correct, and that a test must be seen failing.
opt … ; [ $? -ne 0 ] — and
reduce something that asserts. Notice you must distinguish the crash you want
from every other crash, or you will reduce to unrelated malformed IR.; CHECK-SAME and ; CHECK-DAG variants and find out what each buys.llvm-reduce with --delta-passes= to limit which reductions it tries,
and watch the runtime change on a large input.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.