[llvm] [BOLT] Retain certain local symbols (PR #184074)

YongKang Zhu via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 2 00:17:40 PST 2026


https://github.com/yozhu created https://github.com/llvm/llvm-project/pull/184074

BOLT currently strips all STT_NOTYPE STB_LOCAL zero-sized symbols
that fall inside function bodies. Certain such symbols are named
labels (loop markers and subroutine entry points) or local function
symbols in hand-written assembly. We now keep them in local symbol
table in BOLT processed binaries for better symbolication.

>From ebc6e2751392a857ade18d3280c8f7b7b8d4dac9 Mon Sep 17 00:00:00 2001
From: YongKang Zhu <yongzhu at fb.com>
Date: Wed, 18 Feb 2026 22:40:49 -0800
Subject: [PATCH] [BOLT] Retain certain local symbols

BOLT currently strips all STT_NOTYPE STB_LOCAL zero-sized symbols
that fall inside function bodies. Certain such symbols are named
labels (loop markers and subroutine entry points) or local function
symbols in hand-written assembly. We now keep them in local symbol
table in BOLT processed binaries for better symbolication.
---
 bolt/lib/Rewrite/RewriteInstance.cpp          | 43 +++++++++++++------
 .../AArch64/compare-and-branch-inversion.S    |  8 ++++
 .../compare-and-branch-reorder-blocks.S       |  2 +
 bolt/test/AArch64/retain-local-symbols.s      | 30 +++++++++++++
 bolt/test/X86/avx512-trap.test                |  3 +-
 bolt/test/X86/dynamic-relocs-on-entry.s       |  5 ++-
 6 files changed, 74 insertions(+), 17 deletions(-)
 create mode 100644 bolt/test/AArch64/retain-local-symbols.s

diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index e9bb5d81c80f9..f8bd956c3a8fe 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -5352,13 +5352,27 @@ void RewriteInstance::updateELFSymbolTable(
     } else {
       // Check if the function symbol matches address inside a function, i.e.
       // it marks a secondary entry point.
+      // Also look up local NOTYPE symbols inside functions so we can
+      // update their addresses to reflect the output layout.
+      // Skip AArch64 marker symbols ($d, $x) inside functions —
+      // BOLT generates its own via addExtraSymbols.
+      auto IsAArch64MarkerSymbol = [&]() {
+        return BC->isAArch64() &&
+               (SymbolName->starts_with("$d") || SymbolName->starts_with("$x"));
+      };
+      const bool IsLocalLabel = Symbol.getType() == ELF::STT_NOTYPE &&
+                                Symbol.getBinding() == ELF::STB_LOCAL &&
+                                Symbol.st_size == 0 && !IsAArch64MarkerSymbol();
       Function =
-          (Symbol.getType() == ELF::STT_FUNC)
+          (Symbol.getType() == ELF::STT_FUNC || IsLocalLabel)
               ? BC->getBinaryFunctionContainingAddress(Symbol.st_value,
                                                        /*CheckPastEnd=*/false,
                                                        /*UseMaxSize=*/true)
               : nullptr;
 
+      assert((!Function || !IsLocalLabel || !Function->isFolded()) &&
+             "Local label inside ICF-folded function");
+
       if (Function && Function->isEmitted()) {
         assert(Function->getLayout().isHotColdSplit() &&
                "Adding symbols based on cold fragment when there are more than "
@@ -5366,6 +5380,12 @@ void RewriteInstance::updateELFSymbolTable(
         const uint64_t OutputAddress =
             Function->translateInputToOutputAddress(Symbol.st_value);
 
+        // Remove symbols that cannot be mapped to the output, e.g.
+        // data-in-code labels (jump tables) whose addresses BOLT
+        // does not track.
+        if (!OutputAddress)
+          continue;
+
         NewSymbol.st_value = OutputAddress;
         // Force secondary entry points to have zero size.
         NewSymbol.st_size = 0;
@@ -5414,19 +5434,14 @@ void RewriteInstance::updateELFSymbolTable(
             NewSymbol.st_shndx = getNewSectionIndex(Symbol.st_shndx);
         }
 
-        // Detect local syms in the text section that we didn't update
-        // and that were preserved by the linker to support relocations against
-        // .text. Remove them from the symtab.
-        if (Symbol.getType() == ELF::STT_NOTYPE &&
-            Symbol.getBinding() == ELF::STB_LOCAL && Symbol.st_size == 0) {
-          if (BC->getBinaryFunctionContainingAddress(Symbol.st_value,
-                                                     /*CheckPastEnd=*/false,
-                                                     /*UseMaxSize=*/true)) {
-            // Can only delete the symbol if not patching. Such symbols should
-            // not exist in the dynamic symbol table.
-            assert(!IsDynSym && "cannot delete symbol");
-            continue;
-          }
+        // Drop AArch64 marker symbols ($d, $x) inside functions —
+        // BOLT generates its own via addExtraSymbols.
+        if (IsAArch64MarkerSymbol() &&
+            BC->getBinaryFunctionContainingAddress(Symbol.st_value,
+                                                   /*CheckPastEnd=*/false,
+                                                   /*UseMaxSize=*/true)) {
+          assert(!IsDynSym && "cannot delete symbol");
+          continue;
         }
       }
     }
diff --git a/bolt/test/AArch64/compare-and-branch-inversion.S b/bolt/test/AArch64/compare-and-branch-inversion.S
index f5903b2b5d25d..479775a2bbd64 100644
--- a/bolt/test/AArch64/compare-and-branch-inversion.S
+++ b/bolt/test/AArch64/compare-and-branch-inversion.S
@@ -30,8 +30,10 @@ immediate_increment:
 
 # CHECK: <immediate_increment>:
 # CHECK-NEXT:            {{.*}} cblt x0, #0x1, 0x[[ADDR0:[0-9a-f]+]] <{{.*}}>
+# CHECK:      <.exit0>:
 # CHECK-NEXT:            {{.*}} mov  x0, #0x2 // =2
 # CHECK-NEXT:            {{.*}} ret
+# CHECK:      <.cold0>:
 # CHECK-NEXT: [[ADDR0]]: {{.*}} mov x0, #0x1 // =1
 # CHECK-NEXT:            {{.*}} ret
 
@@ -52,8 +54,10 @@ immediate_decrement:
 
 # CHECK: <immediate_decrement>:
 # CHECK-NEXT:            {{.*}} cbhi x0, #0x0, 0x[[ADDR1:[0-9a-f]+]] <{{.*}}>
+# CHECK:      <.exit1>:
 # CHECK-NEXT:            {{.*}} mov  x0, #0x2 // =2
 # CHECK-NEXT:            {{.*}} ret
+# CHECK:      <.cold1>:
 # CHECK-NEXT: [[ADDR1]]: {{.*}} mov x0, #0x1 // =1
 # CHECK-NEXT:            {{.*}} ret
 
@@ -74,8 +78,10 @@ register_swap:
 
 # CHECK: <register_swap>:
 # CHECK-NEXT:            {{.*}} cbgt x1, x0, 0x[[ADDR2:[0-9a-f]+]] <{{.*}}>
+# CHECK:      <.exit2>:
 # CHECK-NEXT:            {{.*}} mov  x0, #0x2 // =2
 # CHECK-NEXT:            {{.*}} ret
+# CHECK:      <.cold2>:
 # CHECK-NEXT: [[ADDR2]]: {{.*}} mov x0, #0x1 // =1
 # CHECK-NEXT:            {{.*}} ret
 
@@ -99,8 +105,10 @@ irreversible:
 # CHECK: <irreversible>:
 # CHECK-NEXT:            {{.*}} cbgt x0, #0x3f, 0x[[ADDR3:[0-9a-f]+]] <{{.*}}>
 # CHECK-NEXT:            {{.*}} b               0x[[ADDR4:[0-9a-f]+]] <{{.*}}>
+# CHECK:      <.exit3>:
 # CHECK-NEXT: [[ADDR3]]: {{.*}} mov  x0, #0x2 // =2
 # CHECK-NEXT:            {{.*}} ret
+# CHECK:      <.cold3>:
 # CHECK-NEXT: [[ADDR4]]: {{.*}} mov x0, #0x1 // =1
 # CHECK-NEXT:            {{.*}} ret
 
diff --git a/bolt/test/AArch64/compare-and-branch-reorder-blocks.S b/bolt/test/AArch64/compare-and-branch-reorder-blocks.S
index 464243d788c1f..0a5e75e9a4375 100644
--- a/bolt/test/AArch64/compare-and-branch-reorder-blocks.S
+++ b/bolt/test/AArch64/compare-and-branch-reorder-blocks.S
@@ -41,8 +41,10 @@ reorder_blocks:
 
 # CHECK: <reorder_blocks>:
 # CHECK-NEXT:           {{.*}} cbgt x0, #0x0, 0x[[ADDR:[0-9a-f]+]] <{{.*}}>
+# CHECK:      <.hot_exit>:
 # CHECK-NEXT:           {{.*}} mov  x0, #0x2 // =2
 # CHECK-NEXT:           {{.*}} ret
+# CHECK:      <.cold_exit>:
 # CHECK-NEXT: [[ADDR]]: {{.*}} mov  x0, #0x1 // =1
 # CHECK-NEXT:           {{.*}} ret
 
diff --git a/bolt/test/AArch64/retain-local-symbols.s b/bolt/test/AArch64/retain-local-symbols.s
new file mode 100644
index 0000000000000..b7c8e48d9e203
--- /dev/null
+++ b/bolt/test/AArch64/retain-local-symbols.s
@@ -0,0 +1,30 @@
+## Check that BOLT retains named local symbols (assembly labels) inside
+## functions and updates their addresses to reflect the output layout.
+
+# RUN: %clang %cflags %s -o %t.exe -Wl,-q
+# RUN: llvm-bolt %t.exe -o %t.bolt -lite=false
+# RUN: llvm-nm -n %t.bolt | FileCheck %s
+
+# CHECK: T _start
+# CHECK: t loop_start
+# CHECK: t loop_end
+# CHECK: t helper
+
+  .text
+  .global _start
+  .type _start, %function
+_start:
+  mov x0, #10
+  bl helper
+loop_start:
+  sub x0, x0, #1
+  cbnz x0, loop_start
+loop_end:
+  ret
+
+helper:
+  add x0, x0, #1
+  ret
+
+## Force relocation mode.
+  .reloc 0, R_AARCH64_NONE
diff --git a/bolt/test/X86/avx512-trap.test b/bolt/test/X86/avx512-trap.test
index 93b02f4397cc8..8c98fa3016d57 100644
--- a/bolt/test/X86/avx512-trap.test
+++ b/bolt/test/X86/avx512-trap.test
@@ -8,7 +8,7 @@ RUN: llvm-objdump -d --disassemble-symbols=use_avx512 %t | \
 RUN:   FileCheck %s --check-prefix=CHECK-DIS-NO-TRAP
 
 RUN: llvm-bolt %t --trap-avx512=1 -o %t.bolt --lite=0 2>&1 | FileCheck %s
-RUN: llvm-objdump -d --disassemble-symbols=use_avx512 %t.bolt | \
+RUN: llvm-objdump -d --disassemble-symbols=use_avx512,secondary_entry %t.bolt | \
 RUN:   FileCheck %s --check-prefix=CHECK-DIS
 
 RUN: llvm-bolt %t --trap-avx512=0 -o %t.bolt --lite=0
@@ -20,6 +20,7 @@ CHECK: BOLT-WARNING: 1 function will trap on entry
 ## Check that we have two ud2 instructions - one per entry.
 CHECK-DIS:      use_avx512
 CHECK-DIS-NEXT:    ud2
+CHECK-DIS:      <secondary_entry>:
 CHECK-DIS-NEXT:    ud2
 
 ## Check that we generate correct AVX-512
diff --git a/bolt/test/X86/dynamic-relocs-on-entry.s b/bolt/test/X86/dynamic-relocs-on-entry.s
index 4ec8ba4ad4460..477aece70f19e 100644
--- a/bolt/test/X86/dynamic-relocs-on-entry.s
+++ b/bolt/test/X86/dynamic-relocs-on-entry.s
@@ -5,13 +5,14 @@
 # RUN: %clang %cflags -fPIC -pie %s -o %t.exe -nostdlib -Wl,-q
 # RUN: llvm-bolt %t.exe -o %t.bolt > %t.out.txt
 # RUN: llvm-readelf -r %t.bolt >> %t.out.txt
-# RUN: llvm-objdump --disassemble-symbols=chain %t.bolt >> %t.out.txt
+# RUN: llvm-objdump --disassemble-symbols=chain,Label %t.bolt >> %t.out.txt
 # RUN: FileCheck %s --input-file=%t.out.txt
 
 ## Check if the new address in `chain` is correctly updated by BOLT
 # CHECK: Relocation section '.rela.dyn' at offset 0x{{.*}} contains 1 entries:
 # CHECK: {{.*}} R_X86_64_RELATIVE [[#%x,ADDR:]]
-# CHECK: [[#ADDR]]: c3 retq
+# CHECK: <Label>:
+# CHECK-NEXT: [[#ADDR]]: c3 retq
 	.text
 	.type   chain, @function
 chain:



More information about the llvm-commits mailing list