[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