Reduce a Reproducible LLVM IR Failure with llvm-reduce-20

llvm-reduce-20 turns a huge, ugly LLVM IR file that crashes a compiler into a small one that still crashes it, so you can actually read the bug. That makes a compiler crash, assertion or miscompilation far easier to inspect. This guide uses the locally installed llvm-reduce-20 from package llvm-20, version 20.1.8. Allow 10 to 20 minutes for a small input; large reductions can run much longer.

You need an LLVM IR file, a command that demonstrates the problem, and a shell script that returns success only when the problem is still present. The reducer can read a named file or standard input. It writes its reduced testcase as reduced.ll in the current directory unless you use --in-place.

1. Preserve the original testcase

Work in a new directory and copy the input there. Replace the placeholder path with your real file:

$ mkdir -p "$HOME/llvm-reduction"
$ cp /path/to/failing-test.ll "$HOME/llvm-reduction/input.ll"
$ cd "$HOME/llvm-reduction"
$ llvm-reduce-20 --version

On the system covered here, the last command reports Ubuntu LLVM version 20.1.8. Keep input.ll unchanged: the reduction is a search through candidates, not a reversible edit history.

Checkpoint: confirm the original is present before running the reducer.

$ test -s input.ll && echo "original input is ready"
original input is ready

2. Turn the failure into an interestingness test

The test receives the candidate filename as its first argument. It must exit with status 0 when the candidate remains interesting, and non-zero when it does not. For a compiler assertion, run the same command that originally failed and search its diagnostic:

#!/bin/sh
set -eu
candidate=$1
/path/to/opt -O2 -disable-output "$candidate" 2>&1 \
  | grep -Fq "Assertion failed at line 1234"

Save this as interesting.sh, then make it executable:

$ chmod +x interesting.sh
$ ./interesting.sh input.ll
$ printf 'test status: %s\n' "$?"
test status: 0

The exact diagnostic and tool invocation above are placeholders: replace both with the failure you can actually reproduce.

3. Run a non-destructive reduction

Run from the working directory with the test and input named explicitly:

$ llvm-reduce-20 --test="$PWD/interesting.sh" "$PWD/input.ll"
*** Reducing Functions...
...
Done reducing! Reduced testcase: reduced.ll

The full progress log has many delta-pass headings and may take a while. A successful run returns status 0 and leaves reduced.ll behind. No root privileges needed.

Warning: do not add --in-place to this first run. The manpage warns that it replaces the input file, which removes your easy recovery path.

Checkpoint: inspect the result and rerun the test against it.

$ sed -n '1,160p' reduced.ll
$ ./interesting.sh reduced.ll
$ printf 'reduced test status: %s\n' "$?"
reduced test status: 0

The reduced file may still be valid but not minimal. The default maximum is five full passes. For a quicker first attempt, use --max-pass-iterations=1; for a more thorough attempt, increase that value once you have confirmed the test is stable. More iterations cost time and do not guarantee a particular size.

4. Control the reduction when it is slow or noisy

Use -j 1 when the interestingness test is not safe to run concurrently, or when reproducibility matters more than speed:

$ llvm-reduce-20 -j 1 --test="$PWD/interesting.sh" "$PWD/input.ll"
Done reducing! Reduced testcase: reduced.ll

By default, chunks run in parallel. Parallel tests must not share a fixed output file, mutate shared state or depend on test order. Give each test its own temporary output, or use -j 1.

For IR that needs a target context, pass the target triple used by the original failure:

$ llvm-reduce-20 --mtriple=x86_64-pc-linux-gnu \
    --test="$PWD/interesting.sh" "$PWD/input.ll"

Use -x=mir for Machine IR rather than ordinary LLVM IR; the installed command accepts both -x=ir and -x=mir. If you need a particular optimisation pipeline, --ir-passes=<pipeline> takes the textual description used with opt -passes.

5. Handle invalid candidates and failed tests

Some candidate reductions may produce invalid IR. If that is unacceptable for your investigation, add --abort-on-invalid-reduction so the run stops the moment an invalid reduction occurs:

$ llvm-reduce-20 --abort-on-invalid-reduction \
    --test="$PWD/interesting.sh" "$PWD/input.ll"
$ status=$?
$ printf 'reducer status: %s\n' "$status"

A non-zero status means the reducer hit an error; it is not proof the original testcase was reduced successfully.

If crash debugging is part of the investigation, --preserve-debug-environment stops the reducer disabling crash reports, llvm-symbolizer and core dumps.

6. Keep or undo the result

Once the test passes on reduced.ll, copy it to a deliberately named file and leave the original untouched:

$ cp -- reduced.ll failing-test-min.ll
$ ./interesting.sh failing-test-min.ll
$ cmp -s input.ll failing-test-min.ll; echo "different input: status $?"
different input: status 1

The final cmp status of 1 is expected here: the files differ. If you do not want the result, remove only the generated file with rm -- reduced.ll, or remove the whole temporary working directory after checking its path. No elevated privileges are required.

Recovery: if you used --in-place by mistake, recover input.ll from your copy or version control; llvm-reduce cannot reconstruct the original for you.

Done means