[llvm] [BOLT] Add exact-name function skip options (PR #226731)
Adam Bzowski via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 13:46:53 PDT 2026
https://github.com/adam-bzowski-arm created https://github.com/llvm/llvm-project/pull/226731
## Summary
Add exact-name function skipping through:
- `--skip-funcs-no-regex`
- `--skip-funcs-file-no-regex`
These options avoid evaluating every function name against every regular expression. Exact matching also recognizes BOLT-disambiguated names: specifying `foo` matches `foo`, `foo/1`, `foo/2`, etc. Existing regex options retain their current behavior.
## Motivation
This was tested on a Chromium build containing **705,149 functions**, with **3,578 functions to skip**:
| Selection method | Entries | Time in `RewriteInstance::selectFunctionsToProcess()` |
|---|---:|---:|
| Per-function regexes, including optional `/N` suffixes | 3,502 | **54.9 minutes** |
| Manually compacted regexes | 228 | **3.11 minutes** |
| Exact-name selection from this patch | 3,502 | **297 ms** |
Exact-name selection is approximately 627 times faster than the compacted regex list and over 11,000 times faster than the original per-function regex list.
## Minor functional change
- Make `--funcs-no-regex=foo` select all BOLT-disambiguated `foo/N` variants.
## Non-functional improvements
- Use `StringSet<>` instead of `std::unordered_set<std::string>` for exact force-name lookups.
- Check exact force names before evaluating regular expressions.
- Validate incompatible force and skip options before processing functions.
- Return an LLVM `Error` instead of terminating with `exit(1)`.
- Update the command-line documentation.
## Testing
Added a target-independent LIT test covering:
- Regex and exact-name skipping.
- Regex and exact-name forcing.
- Command-line and file-based forms of every selector.
- Exact prefix behavior.
- BOLT `/N` suffix restoration.
- Conflicting force and skip options.
The existing `--skip-funcs` and `--skip-funcs-file` semantics remain unchanged.
>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] [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 f41b23ec59877..5a244f6bbc04d 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 4a253f579c6c2..b51dbff437809 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 49472928233bb..220df2e12c7e5 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 0000000000000..5a14755aeb9f0
--- /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(); }
More information about the llvm-commits
mailing list