[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