Hello! In our previous lesson, we established a crucial workflow for bug triage: starting from a large C++ project, we systematically isolated a single crashing compiler pass and the LLVM IR that triggers it. We ended by introducing llvm-reduce as the tool to automate the final, and often most tedious, step of minimization.
Today, we will take a deep dive into llvm-reduce, moving from a basic understanding to a proficient one. This lesson is designed to help you master this essential tool, fulfilling the learning outcome: Use the llvm-reduce tool to automatically minimize an LLVM IR test case. We will explore how to write more robust "interestingness tests" for different kinds of bugs, how to control the tool's inner workings, and how to navigate common pitfalls you'll encounter in real-world bug reduction.
The Interestingness Test: Your Guide for llvm-reduce
As we touched on last time, llvm-reduce operates on a simple principle: you provide it with a script, called an interestingness test, that determines if a given IR file is still "interesting"—that is, if it still triggers the bug. The script must exit with a status code of 0 if the bug is present, and a non-zero code otherwise.
The simplest script for a crash looks like this, where ! inverts the exit code of the crashing command:
#!/bin/bash
# interesting-crash.sh
# Run the crashing command on the input file ($1) provided by llvm-reduce.
# Redirect stderr to /dev/null to keep the output clean.
# '!' makes the script exit 0 (interesting) if opt crashes (non-zero exit code).
! /path/to/your/build/bin/opt -passes=gvn -disable-output "$1" 2>/dev/null
However, this simple approach has a weakness. Sometimes, a reduction can simplify the code in a way that triggers a different bug in the same pass. llvm-reduce will happily follow this new path, leaving you with a minimal reproducer for a bug you weren't trying to fix.
Making Your Test More Specific
To avoid this, you should make your interestingness test as specific as possible. The best way to do this is to check for the unique assertion message or error output associated with your bug.
#!/bin/bash
# interesting-specific-crash.sh
# Run the command, pipe its stderr (2>&1) to stdout, and grep for the specific message.
# grep exits 0 if the pattern is found, which is exactly what llvm-reduce wants.
/path/to/your/build/bin/opt -passes=gvn -disable-output "$1" 2>&1 | grep -q "Value *not* dominated by definition"
The -q flag tells grep to be quiet and simply exit with the appropriate status code. This is the most common and robust way to test for crashes.
[PDF] How to Reduce an LLVM Bug
The presentation "How to Reduce an LLVM Bug" provides excellent visual examples of this evolution from a simple crash test to a more robust, specific one.
Please review slides 25-27. Pay close attention to the slide Filtering Error Messages, which shows how to use grep and even FileCheck to ensure you are tracking the correct bug throughout the reduction process.
Handling Compiler Hangs
What if the bug isn't a crash, but an infinite loop or a task with extreme computational complexity that causes the compiler to hang? You can still use llvm-reduce by creating an interestingness test based on a timeout.
How to reduce LLVM crashes - nikic's Blog
Nikita Popov's blog post details this technique concisely.
In the post "How to reduce LLVM crashes," find the end of the "Special cases" section and read the paragraph starting from the note on compiler hangs. It explains how to use the timeout command in your script.
The script would look like this:
#!/bin/bash
# interesting-hang.sh
# The 'timeout' command runs a program for a maximum duration.
# If the command exceeds the time limit (e.g., 5 seconds), timeout kills it
# and exits with a special status code (often 124).
# We want to treat a timeout as "interesting", so we check for this.
timeout 5 /path/to/your/build/bin/opt -passes=some-slow-pass -disable-output "$1" &>/dev/null
# If the process timed out (exit code 124), it's interesting (exit 0).
# Otherwise, it's not interesting (exit 1).
if [ $? -eq 124 ]; then
exit 0
else
exit 1
fi
This script defines "interesting" as any IR file that causes the pass to run for more than 5 seconds. Be warned: this method can be slow, as every successful reduction step will take at least 5 seconds.
Peeking Under the Hood: Delta Passes
llvm-reduce is not just randomly deleting lines. It applies a series of structured transformations, called delta passes or "deltas," to minimize the IR. Each delta knows how to perform a specific kind of reduction, such as:
- Deleting a function.
- Removing instructions.
- Replacing a global variable with a constant.
- Simplifying function signatures.
- Merging basic blocks.
You can see the list of all available delta passes by running:llvm-reduce --print-delta-passes
Understanding deltas is key to troubleshooting tricky reductions.
A Critical Pitfall: llvm-reduce Crashing Itself
Internally, llvm-reduce uses a small set of LLVM passes (function(sroa,instcombine,gvn,simplifycfg,...)) to clean up the IR after applying a delta. This can be a problem. If you are trying to reduce a bug in, say, instcombine, then llvm-reduce itself will crash when it tries to use instcombine for its internal cleanup.
The solution is to tell llvm-reduce not to use its internal IR passes.
How to reduce LLVM crashes - nikic's Blog
Nikita Popov's blog post clearly explains this common gotcha and its solution.
In the section "IR reduction," read the paragraphs that describe this problem, starting from the common gotcha. This section introduces the --skip-delta-passes=ir-passes flag, which is essential to know. Also, quickly scan the llvm-reduce options in the final cheatsheet at the end.
The command to disable these internal passes is:llvm-reduce --test=interesting.sh --skip-delta-passes=ir-passes my_bug.ll
You can also be more selective and run only a specific set of deltas with --delta-passes=... or skip specific ones with --skip-delta-passes=.... This gives you fine-grained control over the reduction process, which can be useful if a particular delta is being counterproductive.
[PDF] How to Reduce an LLVM Bug
The "How to Reduce an LLVM Bug" slides visualize these controls.
Review slides 28 and 29, which cover controlling reductions. This will reinforce the concepts of --delta-passes and --skip-delta-passes.
Example Walkthrough
Let's consolidate this with a practical example. Imagine opt -passes=instcombine is crashing on a large file bug.ll with the assertion message "Assertion II->hasOneUse() failed."
-
Create the Interestingness Script (
test.sh): We make it specific bygrep-ing for the assertion.#!/bin/bash /path/to/llvm/bin/opt -passes=instcombine -disable-output "$1" 2>&1 | grep -q "Assertion \`II->hasOneUse()\` failed"Make it executable:
chmod +x test.sh. -
Run
llvm-reduce: We start the reduction.llvm-reduce --test=./test.sh bug.ll -o reduced.ll -
Troubleshoot:
llvm-reducecrashes! The bug is ininstcombine, andllvm-reduceusesinstcombineinternally. This is the classic pitfall. -
Refine the Command: We run it again, but this time we tell
llvm-reducenot to use its internal IR passes.llvm-reduce --test=./test.sh --skip-delta-passes=ir-passes bug.ll -o reduced.ll -
Success:
llvm-reduceruns to completion and producesreduced.ll, a minimal file that triggers the assertion. -
Final Cleanup (Optional but Recommended): The resulting IR might have ugly names (
%123). We can clean it up for the final bug report and test case.opt -S -passes=strip,metarenamer reduced.ll -o final_test.llThe
strippass removes debug metadata, andmetarenamergives everything clean, canonical names (%0,%1, etc.), making the test case much more readable.
Conclusion
You have now moved beyond a surface-level understanding of llvm-reduce and can wield it as a powerful, precise tool for bug isolation. You know not only how to use it but also how its internal mechanisms work and how to control them when things go wrong.
Key Takeaways:
- An interestingness test is a script that exits with code 0 if a file triggers a bug.
- Make your test specific by using
greporFileCheckon the exact error message to avoid reducing to a different bug. - Compiler hangs can be reduced by using the
timeoutcommand in your test script. llvm-reduceuses delta passes to perform structured reductions. You can see them with--print-delta-passes.- If
llvm-reducecrashes, it's likely because the bug is in a pass thatllvm-reduceitself uses. The fix is to use--skip-delta-passes=ir-passes. - Clean up your final minimized IR with
opt -passes=strip,metarenamerto create a professional test case.
In our next lesson, we'll address another critical part of the bug-fixing lifecycle. Now that you have a minimal, reproducible test case, the next question is: when did this bug appear? We will learn to use git bisect to automatically search the project's history and pinpoint the exact commit that introduced a regression.