[llvm] [BOLT] Add exact-name function skip options (PR #226731)
Adam Bzowski via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 05:09:24 PDT 2026
https://github.com/adam-bzowski-arm updated https://github.com/llvm/llvm-project/pull/226731
>From b5107ad9f9a78f96e9b21ec695a008e6c859a747 Mon Sep 17 00:00:00 2001
From: Adam Bzowski <Adam.Bzowski at arm.com>
Date: Sat, 26 Sep 2026 16:04:08 +0000
Subject: [PATCH 1/2] [BOLT] Add exact-name function skip options
Change-Id: I787c0bb946bbce29c17cbfd68e8fd65b12424435
---
bolt/docs/CommandLineArgumentReference.md | 12 ++-
bolt/include/bolt/Rewrite/RewriteInstance.h | 5 +-
bolt/lib/Rewrite/RewriteInstance.cpp | 75 ++++++++++++------
bolt/test/function-selection.test | 84 +++++++++++++++++++++
4 files changed, 151 insertions(+), 25 deletions(-)
create mode 100644 bolt/test/function-selection.test
diff --git a/bolt/docs/CommandLineArgumentReference.md b/bolt/docs/CommandLineArgumentReference.md
index f41b23ec598779..5a244f6bbc04d9 100644
--- a/bolt/docs/CommandLineArgumentReference.md
+++ b/bolt/docs/CommandLineArgumentReference.md
@@ -219,11 +219,11 @@
- `--funcs-file-no-regex=<string>`
- File with list of functions to optimize (non-regex)
+ File with list of functions to optimize (non-regex, ignoring BOLT-added name suffixes)
- `--funcs-no-regex=<func1,func2,func3,...>`
- Limit optimizations to functions from the list (non-regex)
+ Limit optimizations to functions from the list (non-regex, ignoring BOLT-added name suffixes)
- `--hot-data`
@@ -340,6 +340,14 @@
File with list of functions to skip
+- `--skip-funcs-file-no-regex=<string>`
+
+ File with list of functions to skip (non-regex, ignoring BOLT-added name suffixes)
+
+- `--skip-funcs-no-regex=<func1,func2,func3,...>`
+
+ List of functions to skip (non-regex, ignoring BOLT-added name suffixes)
+
- `--strict`
Trust the input to be from a well-formed source
diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h
index 4a253f579c6c22..b51dbff4378097 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -158,8 +158,9 @@ class RewriteInstance {
/// Collect functions that are specified to be bumped.
void selectFunctionsToPrint();
- /// Mark functions that are not meant for processing as ignored.
- void selectFunctionsToProcess();
+ /// Mark functions that are not meant for processing as ignored. Return an
+ /// error if incompatible function-selection options were specified.
+ Error selectFunctionsToProcess();
/// Read information from debug sections.
void readDebugInfo();
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 49472928233bb8..220df2e12c7e50 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -172,13 +172,15 @@ FunctionNamesFile("funcs-file",
static cl::list<std::string> ForceFunctionNamesNR(
"funcs-no-regex", cl::CommaSeparated,
- cl::desc("limit optimizations to functions from the list (non-regex)"),
+ cl::desc("limit optimizations to functions from the list (non-regex, "
+ "ignoring BOLT-added name suffixes)"),
cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory));
static cl::opt<std::string> FunctionNamesFileNR(
"funcs-file-no-regex",
- cl::desc("file with list of functions to optimize (non-regex)"), cl::Hidden,
- cl::cat(BoltCategory));
+ cl::desc("file with list of functions to optimize (non-regex, ignoring "
+ "BOLT-added name suffixes)"),
+ cl::Hidden, cl::cat(BoltCategory));
cl::opt<bool>
KeepTmp("keep-tmp",
@@ -261,6 +263,18 @@ SkipFunctionNamesFile("skip-funcs-file",
cl::Hidden,
cl::cat(BoltCategory));
+static cl::list<std::string> SkipFunctionNamesNR(
+ "skip-funcs-no-regex", cl::CommaSeparated,
+ cl::desc("list of functions to skip (non-regex, ignoring BOLT-added "
+ "name suffixes)"),
+ cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory));
+
+static cl::opt<std::string> SkipFunctionNamesFileNR(
+ "skip-funcs-file-no-regex",
+ cl::desc("file with list of functions to skip (non-regex, ignoring "
+ "BOLT-added name suffixes)"),
+ cl::Hidden, cl::cat(BoltCategory));
+
static cl::opt<bool> TrapOldCode(
"trap-old-code",
cl::desc("insert traps in old function bodies (relocation mode)"),
@@ -834,7 +848,8 @@ Error RewriteInstance::run() {
preprocessProfileData();
- selectFunctionsToProcess();
+ if (Error E = selectFunctionsToProcess())
+ return E;
readDebugInfo();
@@ -3678,24 +3693,29 @@ void RewriteInstance::selectFunctionsToPrint() {
populateFunctionNames(opts::PrintOnlyFile, opts::PrintOnly);
}
-void RewriteInstance::selectFunctionsToProcess() {
+Error RewriteInstance::selectFunctionsToProcess() {
// Extend the list of functions to process or skip from a file.
populateFunctionNames(opts::FunctionNamesFile, opts::ForceFunctionNames);
populateFunctionNames(opts::SkipFunctionNamesFile, opts::SkipFunctionNames);
populateFunctionNames(opts::FunctionNamesFileNR, opts::ForceFunctionNamesNR);
-
- // Make a set of functions to process to speed up lookups.
- std::unordered_set<std::string> ForceFunctionsNR(
- opts::ForceFunctionNamesNR.begin(), opts::ForceFunctionNamesNR.end());
+ populateFunctionNames(opts::SkipFunctionNamesFileNR,
+ opts::SkipFunctionNamesNR);
if ((!opts::ForceFunctionNames.empty() ||
!opts::ForceFunctionNamesNR.empty()) &&
- !opts::SkipFunctionNames.empty()) {
- BC->errs()
- << "BOLT-ERROR: cannot select functions to process and skip at the "
- "same time. Please use only one type of selection.\n";
- exit(1);
- }
+ (!opts::SkipFunctionNames.empty() || !opts::SkipFunctionNamesNR.empty()))
+ return createStringError(
+ errc::invalid_argument,
+ "cannot select functions to process and skip at the same time; "
+ "please use only one type of selection");
+
+ // Make sets of exact function names to speed up lookups.
+ StringSet<> ForceFunctionsNR;
+ for (const std::string &Name : opts::ForceFunctionNamesNR)
+ ForceFunctionsNR.insert(Name);
+ StringSet<> SkipFunctionsNR;
+ for (const std::string &Name : opts::SkipFunctionNamesNR)
+ SkipFunctionsNR.insert(Name);
uint64_t LiteThresholdExecCount = 0;
if (opts::LiteThresholdPct) {
@@ -3741,6 +3761,15 @@ void RewriteInstance::selectFunctionsToProcess() {
if (opts::MaxFunctions.getNumOccurrences() &&
NumFunctionsToProcess >= opts::MaxFunctions)
return true;
+ // Check explicit names first; checking regexes is costly.
+ // Check restored names too, so "foo" also skips BOLT-disambiguated names
+ // such as "foo/1" and "foo/2".
+ for (const StringRef Name : Function.getNames())
+ if (SkipFunctionsNR.contains(Name) ||
+ SkipFunctionsNR.contains(NameResolver::restore(Name)))
+ return true;
+
+ // Check regexes only after explicit names.
for (std::string &Name : opts::SkipFunctionNames)
if (Function.hasNameRegex(Name))
return true;
@@ -3759,16 +3788,19 @@ void RewriteInstance::selectFunctionsToProcess() {
// If the list is not empty, only process functions from the list.
if (!opts::ForceFunctionNames.empty() || !ForceFunctionsNR.empty()) {
+ // Check explicit names first; checking regexes is costly.
+ // Check restored names too, so "foo" also selects BOLT-disambiguated
+ // names such as "foo/1" and "foo/2".
+ for (const StringRef Name : Function.getNames())
+ if (ForceFunctionsNR.contains(Name) ||
+ ForceFunctionsNR.contains(NameResolver::restore(Name)))
+ return true;
+
// Regex check (-funcs and -funcs-file options).
for (std::string &Name : opts::ForceFunctionNames)
if (Function.hasNameRegex(Name))
return true;
- // Non-regex check (-funcs-no-regex and -funcs-file-no-regex).
- for (const StringRef Name : Function.getNames())
- if (ForceFunctionsNR.count(Name.str()))
- return true;
-
return false;
}
@@ -3826,7 +3858,7 @@ void RewriteInstance::selectFunctionsToProcess() {
}
if (!BC->HasSplitFunctions)
- return;
+ return Error::success();
// Fragment overrides:
// - If the fragment must be skipped, then the parent must be skipped as well.
@@ -3870,6 +3902,7 @@ void RewriteInstance::selectFunctionsToProcess() {
BC->outs() << "BOLT-INFO: processing ending on " << Function << '\n';
}
}
+ return Error::success();
}
void RewriteInstance::readDebugInfo() {
diff --git a/bolt/test/function-selection.test b/bolt/test/function-selection.test
new file mode 100644
index 00000000000000..5a14755aeb9f0a
--- /dev/null
+++ b/bolt/test/function-selection.test
@@ -0,0 +1,84 @@
+## Check regex and exact function-selection options, both on the command line
+## and through files.
+
+# REQUIRES: system-linux
+
+# RUN: split-file %s %t
+# RUN: %clang %cflags %t/input.c -o %t.exe -Wl,-q
+# RUN: echo '^foo$' > %t/regex
+# RUN: echo 'foo' > %t/exact
+
+## Regex skip options.
+## BOLT reports skipped functions, so check both regex matches without relying
+## on their order.
+# RUN: llvm-bolt %t.exe -o %t.skip-regex --lite=0 -v=1 \
+# RUN: --skip-funcs='foo.*' 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-REGEX
+# RUN: llvm-bolt %t.exe -o %t.skip-regex-file --lite=0 -v=1 \
+# RUN: --skip-funcs-file=%t/regex 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-FOO \
+# RUN: --implicit-check-not="skipping processing foobar per user request"
+
+## Exact skip options must not match a function for which the name is only a
+## prefix.
+# RUN: llvm-bolt %t.exe -o %t.skip-exact --lite=0 -v=1 \
+# RUN: --skip-funcs-no-regex=foo 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-FOO \
+# RUN: --implicit-check-not="skipping processing foobar per user request"
+# RUN: llvm-bolt %t.exe -o %t.skip-exact-file --lite=0 -v=1 \
+# RUN: --skip-funcs-file-no-regex=%t/exact 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-FOO \
+# RUN: --implicit-check-not="skipping processing foobar per user request"
+
+# SKIP-REGEX-DAG: BOLT-INFO: skipping processing foo per user request
+# SKIP-REGEX-DAG: BOLT-INFO: skipping processing foobar per user request
+# SKIP-FOO: BOLT-INFO: skipping processing foo per user request
+
+## Regex force options.
+## BOLT does not report functions selected for processing, so check that an
+## excluded function is skipped and that the selected function is not.
+# RUN: llvm-bolt %t.exe -o %t.force-regex --lite=0 -v=1 \
+# RUN: --funcs='^foo$' 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-FOO \
+# RUN: --implicit-check-not="skipping processing foo per user request"
+# RUN: llvm-bolt %t.exe -o %t.force-regex-file --lite=0 -v=1 \
+# RUN: --funcs-file=%t/regex 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-FOO \
+# RUN: --implicit-check-not="skipping processing foo per user request"
+
+## Exact force options.
+# RUN: llvm-bolt %t.exe -o %t.force-exact --lite=0 -v=1 \
+# RUN: --funcs-no-regex=foo 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-FOO \
+# RUN: --implicit-check-not="skipping processing foo per user request"
+# RUN: llvm-bolt %t.exe -o %t.force-exact-file --lite=0 -v=1 \
+# RUN: --funcs-file-no-regex=%t/exact 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-FOO \
+# RUN: --implicit-check-not="skipping processing foo per user request"
+# FORCE-FOO: BOLT-INFO: skipping processing foobar per user request
+
+## Exact matching ignores suffixes BOLT adds to disambiguate local symbols.
+# RUN: llvm-bolt %t.exe -o %t.skip-local --lite=0 -v=1 \
+# RUN: --skip-funcs-no-regex=localfoo 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-LOCAL
+# RUN: llvm-bolt %t.exe -o %t.force-local --lite=0 -v=1 \
+# RUN: --funcs-no-regex=localfoo 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-LOCAL \
+# RUN: --implicit-check-not="skipping processing localfoo/"
+# SKIP-LOCAL: BOLT-INFO: skipping processing localfoo/{{[0-9]+.*}} per user request
+# FORCE-LOCAL: BOLT-INFO: skipping processing foo per user request
+
+## Processing and skipping selectors cannot be combined.
+# RUN: not llvm-bolt %t.exe -o %t.error --lite=0 \
+# RUN: --funcs-no-regex=foo --skip-funcs-no-regex=foobar 2>&1 \
+# RUN: | FileCheck %s --check-prefix=ERROR
+# ERROR: cannot select functions to process and skip at the same time
+
+#--- input.c
+__attribute__((noinline)) int foo(void) { return 1; }
+__attribute__((noinline)) int foobar(void) { return 2; }
+// Local symbols receive BOLT's /N suffix, allowing the test to verify that
+// exact selectors also match the restored base name.
+static __attribute__((noinline, used)) int localfoo(void) { return 3; }
+
+int main(void) { return foo() + foobar() + localfoo(); }
>From bfdee74a8297d05ac7825535e4d6ecb7f4e1e478 Mon Sep 17 00:00:00 2001
From: Adam Bzowski <Adam.Bzowski at arm.com>
Date: Mon, 28 Sep 2026 12:05:22 +0000
Subject: [PATCH 2/2] [BOLT] Match exact function selectors literally
Change-Id: Ib9b7b5121dcbcc5a6ccd55ef13f62f6c0e3b5da3
---
bolt/docs/CommandLineArgumentReference.md | 8 +++----
bolt/lib/Rewrite/RewriteInstance.cpp | 28 ++++++++---------------
bolt/test/function-selection.test | 28 ++++++++++++++++-------
3 files changed, 33 insertions(+), 31 deletions(-)
diff --git a/bolt/docs/CommandLineArgumentReference.md b/bolt/docs/CommandLineArgumentReference.md
index 5a244f6bbc04d9..d45fc7c0e95ce2 100644
--- a/bolt/docs/CommandLineArgumentReference.md
+++ b/bolt/docs/CommandLineArgumentReference.md
@@ -219,11 +219,11 @@
- `--funcs-file-no-regex=<string>`
- File with list of functions to optimize (non-regex, ignoring BOLT-added name suffixes)
+ File with list of functions to optimize (non-regex)
- `--funcs-no-regex=<func1,func2,func3,...>`
- Limit optimizations to functions from the list (non-regex, ignoring BOLT-added name suffixes)
+ Limit optimizations to functions from the list (non-regex)
- `--hot-data`
@@ -342,11 +342,11 @@
- `--skip-funcs-file-no-regex=<string>`
- File with list of functions to skip (non-regex, ignoring BOLT-added name suffixes)
+ File with list of functions to skip (non-regex)
- `--skip-funcs-no-regex=<func1,func2,func3,...>`
- List of functions to skip (non-regex, ignoring BOLT-added name suffixes)
+ List of functions to skip (non-regex)
- `--strict`
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 220df2e12c7e50..fe0aa7c859daa8 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -172,15 +172,13 @@ FunctionNamesFile("funcs-file",
static cl::list<std::string> ForceFunctionNamesNR(
"funcs-no-regex", cl::CommaSeparated,
- cl::desc("limit optimizations to functions from the list (non-regex, "
- "ignoring BOLT-added name suffixes)"),
+ cl::desc("limit optimizations to functions from the list (non-regex)"),
cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory));
static cl::opt<std::string> FunctionNamesFileNR(
"funcs-file-no-regex",
- cl::desc("file with list of functions to optimize (non-regex, ignoring "
- "BOLT-added name suffixes)"),
- cl::Hidden, cl::cat(BoltCategory));
+ cl::desc("file with list of functions to optimize (non-regex)"), cl::Hidden,
+ cl::cat(BoltCategory));
cl::opt<bool>
KeepTmp("keep-tmp",
@@ -265,14 +263,12 @@ SkipFunctionNamesFile("skip-funcs-file",
static cl::list<std::string> SkipFunctionNamesNR(
"skip-funcs-no-regex", cl::CommaSeparated,
- cl::desc("list of functions to skip (non-regex, ignoring BOLT-added "
- "name suffixes)"),
+ cl::desc("list of functions to skip (non-regex)"),
cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory));
static cl::opt<std::string> SkipFunctionNamesFileNR(
"skip-funcs-file-no-regex",
- cl::desc("file with list of functions to skip (non-regex, ignoring "
- "BOLT-added name suffixes)"),
+ cl::desc("file with list of functions to skip (non-regex)"),
cl::Hidden, cl::cat(BoltCategory));
static cl::opt<bool> TrapOldCode(
@@ -3761,12 +3757,9 @@ Error RewriteInstance::selectFunctionsToProcess() {
if (opts::MaxFunctions.getNumOccurrences() &&
NumFunctionsToProcess >= opts::MaxFunctions)
return true;
- // Check explicit names first; checking regexes is costly.
- // Check restored names too, so "foo" also skips BOLT-disambiguated names
- // such as "foo/1" and "foo/2".
+ // Check explicit binary function names first; checking regexes is costly.
for (const StringRef Name : Function.getNames())
- if (SkipFunctionsNR.contains(Name) ||
- SkipFunctionsNR.contains(NameResolver::restore(Name)))
+ if (SkipFunctionsNR.contains(Name))
return true;
// Check regexes only after explicit names.
@@ -3788,12 +3781,9 @@ Error RewriteInstance::selectFunctionsToProcess() {
// If the list is not empty, only process functions from the list.
if (!opts::ForceFunctionNames.empty() || !ForceFunctionsNR.empty()) {
- // Check explicit names first; checking regexes is costly.
- // Check restored names too, so "foo" also selects BOLT-disambiguated
- // names such as "foo/1" and "foo/2".
+ // Check explicit binary function names first; checking regexes is costly.
for (const StringRef Name : Function.getNames())
- if (ForceFunctionsNR.contains(Name) ||
- ForceFunctionsNR.contains(NameResolver::restore(Name)))
+ if (ForceFunctionsNR.contains(Name))
return true;
// Regex check (-funcs and -funcs-file options).
diff --git a/bolt/test/function-selection.test b/bolt/test/function-selection.test
index 5a14755aeb9f0a..8db658e3cba01d 100644
--- a/bolt/test/function-selection.test
+++ b/bolt/test/function-selection.test
@@ -57,16 +57,28 @@
# RUN: --implicit-check-not="skipping processing foo per user request"
# FORCE-FOO: BOLT-INFO: skipping processing foobar per user request
-## Exact matching ignores suffixes BOLT adds to disambiguate local symbols.
-# RUN: llvm-bolt %t.exe -o %t.skip-local --lite=0 -v=1 \
+## Exact matching uses BOLT-disambiguated binary-function names literally.
+# RUN: llvm-bolt %t.exe -o %t.skip-local-base --lite=0 -v=1 \
# RUN: --skip-funcs-no-regex=localfoo 2>&1 \
-# RUN: | FileCheck %s --check-prefix=SKIP-LOCAL
-# RUN: llvm-bolt %t.exe -o %t.force-local --lite=0 -v=1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-LOCAL-BASE \
+# RUN: --implicit-check-not="skipping processing localfoo/"
+# SKIP-LOCAL-BASE: BOLT-INFO: writing section header for .note.bolt_info
+
+# RUN: llvm-bolt %t.exe -o %t.skip-local-suffix --lite=0 -v=1 \
+# RUN: --skip-funcs-no-regex=localfoo/1 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SKIP-LOCAL-SUFFIX
+# SKIP-LOCAL-SUFFIX: BOLT-INFO: skipping processing localfoo/1{{.*}} per user request
+
+# RUN: llvm-bolt %t.exe -o %t.force-local-base --lite=0 -v=1 \
# RUN: --funcs-no-regex=localfoo 2>&1 \
-# RUN: | FileCheck %s --check-prefix=FORCE-LOCAL \
+# RUN: | FileCheck %s --check-prefix=FORCE-LOCAL-BASE
+# FORCE-LOCAL-BASE: BOLT-INFO: skipping processing localfoo/1{{.*}} per user request
+
+# RUN: llvm-bolt %t.exe -o %t.force-local-suffix --lite=0 -v=1 \
+# RUN: --funcs-no-regex=localfoo/1 2>&1 \
+# RUN: | FileCheck %s --check-prefix=FORCE-LOCAL-SUFFIX \
# RUN: --implicit-check-not="skipping processing localfoo/"
-# SKIP-LOCAL: BOLT-INFO: skipping processing localfoo/{{[0-9]+.*}} per user request
-# FORCE-LOCAL: BOLT-INFO: skipping processing foo per user request
+# FORCE-LOCAL-SUFFIX: BOLT-INFO: skipping processing foo per user request
## Processing and skipping selectors cannot be combined.
# RUN: not llvm-bolt %t.exe -o %t.error --lite=0 \
@@ -78,7 +90,7 @@
__attribute__((noinline)) int foo(void) { return 1; }
__attribute__((noinline)) int foobar(void) { return 2; }
// Local symbols receive BOLT's /N suffix, allowing the test to verify that
-// exact selectors also match the restored base name.
+// exact selectors require the disambiguated binary-function name.
static __attribute__((noinline, used)) int localfoo(void) { return 3; }
int main(void) { return foo() + foobar() + localfoo(); }
More information about the llvm-commits
mailing list