[llvm] [OptBisect] Add function filtering and interval disable support to opt-bisect (PR #222123)
Daniel Donenfeld via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 8 13:01:52 PDT 2026
https://github.com/daniel-donenfeld created https://github.com/llvm/llvm-project/pull/222123
Add two additional control to opt-bisect. The first extends `-opt-disable` to accept pass-number intervals, including -1 to disable all passes. The second restricts the behavior of opt-bisect to only the functions passed to `-opt-bisect-funcs`. Added a lit test to check the verbose output using the new flags.
>From d1fe09515c84b2558e8051a9e146a8be61371429 Mon Sep 17 00:00:00 2001
From: Daniel Donenfeld <ddonenfeld at nvidia.com>
Date: Wed, 2 Sep 2026 19:36:25 +0000
Subject: [PATCH] Add two additional control to opt-bisect. The first extends
-opt-disable to accept pass-number intervals, including -1 to disable all
passes. The second restricts the behavior of opt-bisect to only the functions
passed to -opt-bisect-funcs. Added a lit test to check the verbose output
using the new flags.
---
llvm/include/llvm/IR/OptBisect.h | 40 ++++++-
llvm/lib/Analysis/LoopPass.cpp | 6 +-
llvm/lib/Analysis/RegionPass.cpp | 6 +-
llvm/lib/IR/OptBisect.cpp | 52 ++++++++-
llvm/lib/IR/Pass.cpp | 4 +-
llvm/lib/Passes/StandardInstrumentations.cpp | 6 +-
llvm/test/Other/opt-bisect-skip-and-filter.ll | 107 ++++++++++++++++++
7 files changed, 208 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/Other/opt-bisect-skip-and-filter.ll
diff --git a/llvm/include/llvm/IR/OptBisect.h b/llvm/include/llvm/IR/OptBisect.h
index 2cfb612020ae7..7198daf507dfd 100644
--- a/llvm/include/llvm/IR/OptBisect.h
+++ b/llvm/include/llvm/IR/OptBisect.h
@@ -14,6 +14,7 @@
#ifndef LLVM_IR_OPTBISECT_H
#define LLVM_IR_OPTBISECT_H
+#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Compiler.h"
@@ -31,6 +32,11 @@ class OptPassGate {
/// over.
virtual bool shouldRunPass(StringRef PassName,
StringRef IRDescription) const {
+ return shouldRunPass(PassName, IRDescription, "");
+ }
+
+ virtual bool shouldRunPass(StringRef PassName, StringRef IRDescription,
+ StringRef FuncName) const {
return true;
}
@@ -68,17 +74,20 @@ class LLVM_ABI OptBisect : public OptPassGate {
/// Most passes should not call this routine directly. Instead, it is called
/// through helper routines provided by the base classes of the pass. For
/// instance, function passes should call FunctionPass::skipFunction().
- bool shouldRunPass(StringRef PassName,
- StringRef IRDescription) const override;
+ bool shouldRunPass(StringRef PassName, StringRef IRDescription,
+ StringRef FuncName) const override;
/// isEnabled() should return true before calling shouldRunPass().
bool isEnabled() const override {
- return !BisectIntervals.empty() || !DisabledPasses.empty();
+ return !BisectIntervals.empty() || !DisabledPasses.empty() ||
+ !DisabledIntervals.empty();
}
void reset() override {
clearIntervals();
DisabledPasses.clear();
+ clearDisabledIntervals();
+ clearEnabledFuncs();
}
/// Set intervals directly from an IntervalList.
@@ -96,11 +105,36 @@ class LLVM_ABI OptBisect : public OptPassGate {
/// to be disabled. Multiple pass names can be provided with comma separation.
void setDisabled(StringRef Pass) { DisabledPasses.insert(Pass); }
+ /// Set intervals directly from an IntervalList.
+ void
+ setDisabledIntervals(IntegerInclusiveIntervalUtils::IntervalList Intervals) {
+ DisabledIntervals.append(Intervals);
+ }
+
+ /// Clear all disabled intervals.
+ void clearDisabledIntervals() {
+ DisabledIntervals.clear();
+ LastDisableNum = 0;
+ }
+
+ /// Add a function name to the set of functions enabled for opt bisect.
+ void setEnabledFunc(StringRef FuncName) {
+ OptBisectFuncNames.insert(FuncName);
+ }
+
+ /// Clear the set of functions enabled for opt bisect, this reenables all
+ /// functions.
+ void clearEnabledFuncs() { OptBisectFuncNames.clear(); }
+
private:
mutable int LastBisectNum = 0;
IntegerInclusiveIntervalUtils::IntervalList BisectIntervals;
StringSet<> DisabledPasses = {};
+ IntegerInclusiveIntervalUtils::IntervalList DisabledIntervals;
+ mutable int LastDisableNum = 0;
+
+ StringSet<> OptBisectFuncNames = {};
};
/// Singleton instance of the OptPassGate class, so multiple pass managers don't
diff --git a/llvm/lib/Analysis/LoopPass.cpp b/llvm/lib/Analysis/LoopPass.cpp
index 85c15c5721cd6..4e5d0e4a03a0a 100644
--- a/llvm/lib/Analysis/LoopPass.cpp
+++ b/llvm/lib/Analysis/LoopPass.cpp
@@ -372,9 +372,13 @@ bool LoopPass::skipLoop(const Loop *L) const {
return false;
// Check the opt bisect limit.
const OptPassGate &Gate = F->getContext().getOptPassGate();
+
if (Gate.isEnabled() &&
- !Gate.shouldRunPass(this->getPassName(), getDescription(*L)))
+ !Gate.shouldRunPass(this->getPassName(), getDescription(*L),
+ F->getName())) {
return true;
+ }
+
// Check for the OptimizeNone attribute.
if (F->hasOptNone()) {
// FIXME: Report this to dbgs() only once per function.
diff --git a/llvm/lib/Analysis/RegionPass.cpp b/llvm/lib/Analysis/RegionPass.cpp
index ae1d84659de86..6ebd1ef2cf261 100644
--- a/llvm/lib/Analysis/RegionPass.cpp
+++ b/llvm/lib/Analysis/RegionPass.cpp
@@ -283,9 +283,11 @@ static std::string getDescription(const Region &R) {
bool RegionPass::skipRegion(Region &R) const {
Function &F = *R.getEntry()->getParent();
const OptPassGate &Gate = F.getContext().getOptPassGate();
- if (Gate.isEnabled() &&
- !Gate.shouldRunPass(this->getPassName(), getDescription(R)))
+
+ if (Gate.isEnabled() && !Gate.shouldRunPass(this->getPassName(),
+ getDescription(R), F.getName())) {
return true;
+ }
if (F.hasOptNone()) {
// Report this only once per function.
diff --git a/llvm/lib/IR/OptBisect.cpp b/llvm/lib/IR/OptBisect.cpp
index 39562664e609c..3c44a411ff21c 100644
--- a/llvm/lib/IR/OptBisect.cpp
+++ b/llvm/lib/IR/OptBisect.cpp
@@ -79,13 +79,45 @@ static cl::opt<bool> OptBisectVerbose(
"Show verbose output when opt-bisect-limit and/or opt-disable are set"),
cl::Hidden, cl::init(true), cl::Optional);
+// Augment -opt-disable to support intervals in addition to pass names
static cl::list<std::string> OptDisablePasses(
- "opt-disable", cl::Hidden, cl::CommaSeparated, cl::Optional,
- cl::cb<void, std::string>([](const std::string &Pass) {
- getOptBisector().setDisabled(Pass);
+ "opt-disable", cl::Hidden, cl::Optional,
+ cl::cb<void, std::string>([](const std::string &PassOrIntervalStr) {
+ if (PassOrIntervalStr == "-1") {
+ // -1 means disable all passes.
+ getOptBisector().setDisabledIntervals(
+ {{1, std::numeric_limits<int>::max()}});
+ return;
+ }
+
+ // decide whether to parse this as an interval string or pass name
+ if (isdigit(PassOrIntervalStr[0])) {
+ auto Intervals =
+ IntegerInclusiveIntervalUtils::parseIntervals(PassOrIntervalStr);
+ if (!Intervals) {
+ handleAllErrors(Intervals.takeError(), [&](const StringError &E) {
+ errs() << "Error: Invalid interval specification for -opt-disable: "
+ << PassOrIntervalStr << " (" << E.getMessage() << ")\n";
+ });
+ exit(1);
+ }
+ getOptBisector().setDisabledIntervals(std::move(*Intervals));
+ } else {
+ for (StringRef PassName : llvm::split(PassOrIntervalStr, ','))
+ getOptBisector().setDisabled(PassName);
+ }
}),
cl::desc("Optimization pass(es) to disable (comma-separated list)"));
+static cl::list<std::string> OptBisectFuncsList(
+ "opt-bisect-funcs", cl::value_desc("function names"), cl::CommaSeparated,
+ cl::cb<void, std::string>([](const std::string &FuncName) {
+ getOptBisector().setEnabledFunc(FuncName);
+ }),
+ cl::Hidden,
+ cl::desc("Only perform opt bisect for functions that are included in this "
+ "list and if empty, apply to all functions."));
+
static void printPassMessage(StringRef Name, int PassNum, StringRef TargetDesc,
bool Running) {
StringRef Status = Running ? "" : "NOT ";
@@ -93,8 +125,8 @@ static void printPassMessage(StringRef Name, int PassNum, StringRef TargetDesc,
<< " on " << TargetDesc << '\n';
}
-bool OptBisect::shouldRunPass(StringRef PassName,
- StringRef IRDescription) const {
+bool OptBisect::shouldRunPass(StringRef PassName, StringRef IRDescription,
+ StringRef FuncName) const {
assert(isEnabled());
int CurBisectNum = ++LastBisectNum;
@@ -108,6 +140,16 @@ bool OptBisect::shouldRunPass(StringRef PassName,
// Also check if the pass is disabled via -opt-disable.
ShouldRun = ShouldRun && !DisabledPasses.contains(PassName);
+ // Also check if the pass is disabled by interval.
+ const int CurDisableNum = ++LastDisableNum;
+ ShouldRun = ShouldRun && !IntegerInclusiveIntervalUtils::contains(
+ DisabledIntervals, CurDisableNum);
+
+ // If passed a function name, check if the function is enabled for bisection
+ // via opt-bisect-funcs
+ bool SkipGate = !FuncName.empty() && !OptBisectFuncNames.empty() &&
+ !OptBisectFuncNames.contains(FuncName);
+ ShouldRun = ShouldRun && !SkipGate;
if (OptBisectVerbose)
printPassMessage(PassName, CurBisectNum, IRDescription, ShouldRun);
diff --git a/llvm/lib/IR/Pass.cpp b/llvm/lib/IR/Pass.cpp
index 08d58379b7e69..c1ce3aef10b57 100644
--- a/llvm/lib/IR/Pass.cpp
+++ b/llvm/lib/IR/Pass.cpp
@@ -197,8 +197,10 @@ bool FunctionPass::skipFunction(const Function &F) const {
if (PassName.empty())
PassName = this->getPassName();
- if (Gate.isEnabled() && !Gate.shouldRunPass(PassName, getDescription(F)))
+ if (Gate.isEnabled() &&
+ !Gate.shouldRunPass(PassName, getDescription(F), F.getName())) {
return true;
+ }
if (F.hasOptNone()) {
LLVM_DEBUG(dbgs() << "Skipping pass '" << getPassName() << "' on function "
diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 9a2225807145a..f0e05c345de02 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -1053,8 +1053,12 @@ bool OptPassGateInstrumentation::shouldRun(StringRef PassName, IRUnitRef IR) {
if (isIgnored(PassName))
return true;
+ StringRef FuncName;
+ if (const auto *F = dyn_cast<Function>(IR))
+ FuncName = F->getName();
+
bool ShouldRun =
- Context.getOptPassGate().shouldRunPass(PassName, getIRName(IR));
+ Context.getOptPassGate().shouldRunPass(PassName, getIRName(IR), FuncName);
if (!ShouldRun && !this->HasWrittenIR && !OptBisectPrintIRPath.empty()) {
// FIXME: print IR if limit is higher than number of opt-bisect
// invocations
diff --git a/llvm/test/Other/opt-bisect-skip-and-filter.ll b/llvm/test/Other/opt-bisect-skip-and-filter.ll
new file mode 100644
index 0000000000000..76486da6d3e43
--- /dev/null
+++ b/llvm/test/Other/opt-bisect-skip-and-filter.ll
@@ -0,0 +1,107 @@
+; Tests that verify functionality for -opt-disable and -opt-bisect-funcs
+
+; If enabling bisect function filtering, do not skip Module Passes.
+; RUN: opt -disable-output -disable-verify -passes=inferattrs \
+; RUN: -opt-bisect-funcs=f1 -opt-bisect-limit=-1 -opt-bisect-verbose=true %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=CHECK-MODULE-PASS-FILTER
+; CHECK-MODULE-PASS-FILTER: BISECT: running pass (1) inferattrs on [module]
+; CHECK-MODULE-PASS-FILTER-NOT: BISECT: NOT running
+
+; RUN: opt -disable-output -disable-verify \
+; RUN: -passes=early-cse -opt-bisect-limit=-1 -opt-bisect-funcs=f1 -opt-bisect-verbose=true %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=CHECK-FUNCTION-PASS-FILTER
+; CHECK-FUNCTION-PASS-FILTER: BISECT: running pass (1) early-cse on f1
+; CHECK-FUNCTION-PASS-FILTER: BISECT: NOT running pass (2) early-cse on f2
+; CHECK-FUNCTION-PASS-FILTER: BISECT: NOT running pass (3) early-cse on f3
+; CHECK-FUNCTION-PASS-FILTER: BISECT: NOT running pass (4) early-cse on f4
+
+; RUN: opt -disable-output -disable-verify \
+; RUN: -passes=early-cse -opt-bisect-limit=2 -opt-bisect-funcs=f1,f2,f3 -opt-bisect-verbose=true %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=CHECK-LIMIT-FUNCTION-PASS-FILTER
+; CHECK-LIMIT-FUNCTION-PASS-FILTER: BISECT: running pass (1) early-cse on f1
+; CHECK-LIMIT-FUNCTION-PASS-FILTER: BISECT: running pass (2) early-cse on f2
+; CHECK-LIMIT-FUNCTION-PASS-FILTER: BISECT: NOT running pass (3) early-cse on f3
+; CHECK-LIMIT-FUNCTION-PASS-FILTER: BISECT: NOT running pass (4) early-cse on f4
+
+; RUN: opt -disable-output -disable-verify \
+; RUN: -passes=function-attrs -opt-bisect-limit=-1 -opt-bisect-funcs=f2 -opt-bisect-verbose=true %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=CHECK-CGSCC-PASS-FILTER
+; CHECK-CGSCC-PASS-FILTER: BISECT: running pass (1) function-attrs on (f1)
+; CHECK-CGSCC-PASS-FILTER-NOT: BISECT: Skip bisecting pass 'function-attrs' on
+; CHECK-CGSCC-PASS-FILTER: BISECT: running pass (2) function-attrs on (f2)
+; CHECK-CGSCC-PASS-FILTER: BISECT: running pass (3) function-attrs on (f3)
+; CHECK-CGSCC-PASS-FILTER: BISECT: running pass (4) function-attrs on (f4)
+
+
+; RUN: opt -disable-output -disable-verify -opt-disable=3,7 \
+; RUN: -passes='inferattrs,cgscc(function-attrs,function(early-cse))' -opt-bisect-verbose=true %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=CHECK-DISABLE-PASS
+; CHECK-DISABLE-PASS: BISECT: running pass (1) inferattrs on [module]
+; CHECK-DISABLE-PASS: BISECT: running pass (2) function-attrs on (f1)
+; CHECK-DISABLE-PASS: BISECT: NOT running pass (3) early-cse on f1
+; CHECK-DISABLE-PASS: BISECT: running pass (4) function-attrs on (f2)
+; CHECK-DISABLE-PASS: BISECT: running pass (5) early-cse on f2
+; CHECK-DISABLE-PASS: BISECT: running pass (6) function-attrs on (f3)
+; CHECK-DISABLE-PASS: BISECT: NOT running pass (7) early-cse on f3
+; CHECK-DISABLE-PASS: BISECT: running pass (8) function-attrs on (f4)
+; CHECK-DISABLE-PASS: BISECT: running pass (9) early-cse on f4
+
+declare i32 @g()
+
+define void @f1() {
+entry:
+ br label %loop.0
+loop.0:
+ br i1 undef, label %loop.0.0, label %loop.1
+loop.0.0:
+ br i1 undef, label %loop.0.0, label %loop.0.1
+loop.0.1:
+ br i1 undef, label %loop.0.1, label %loop.0
+loop.1:
+ br i1 undef, label %loop.1, label %loop.1.bb1
+loop.1.bb1:
+ br i1 undef, label %loop.1, label %loop.1.bb2
+loop.1.bb2:
+ br i1 undef, label %end, label %loop.1.0
+loop.1.0:
+ br i1 undef, label %loop.1.0, label %loop.1
+end:
+ ret void
+}
+
+define i32 @f2() {
+entry:
+ ret i32 0
+}
+
+define i32 @f3() {
+entry:
+ %temp = call i32 @g()
+ %icmp = icmp ugt i32 %temp, 2
+ br i1 %icmp, label %bb.true, label %bb.false
+bb.true:
+ %temp2 = call i32 @f2()
+ ret i32 %temp2
+bb.false:
+ ret i32 0
+}
+
+; This function is here to verify that opt-bisect can skip all passes for
+; functions that contain lifetime intrinsics.
+define void @f4() {
+entry:
+ %i = alloca i32, align 4
+ call void @llvm.lifetime.start(i64 4, ptr %i)
+ br label %for.cond
+
+for.cond:
+ br i1 undef, label %for.body, label %for.end
+
+for.body:
+ br label %for.cond
+
+for.end:
+ ret void
+}
+
+declare void @llvm.lifetime.start(i64, ptr nocapture)
More information about the llvm-commits
mailing list