[llvm] [BOLT] Preserve interprocedural fall-throughs (PR #219430)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 28 02:55:06 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-bolt
Author: wangjue (WangJee)
<details>
<summary>Changes</summary>
BOLT only represents fall-through edges within a BinaryFunction. Reordering a function that implicitly falls through into the next function can therefore silently change program behavior.
Detect this pattern before function post-processing and preserve both functions in the original text. Add RISC-V and X86 regression coverage.
---
Full diff: https://github.com/llvm/llvm-project/pull/219430.diff
3 Files Affected:
- (modified) bolt/lib/Rewrite/RewriteInstance.cpp (+50)
- (added) bolt/test/RISCV/interprocedural-fallthrough.s (+65)
- (added) bolt/test/X86/interprocedural-fallthrough.s (+66)
``````````diff
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 4486efe926d01..bf4efdd31489e 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -4098,12 +4098,62 @@ void RewriteInstance::buildFunctionsCFG() {
BC->postProcessSymbolTable();
}
+namespace {
+
+BinaryFunction *getInterproceduralFallThroughTarget(BinaryContext &BC,
+ BinaryFunction &Function) {
+ if (!Function.hasCFG() || Function.getLayout().block_empty())
+ return nullptr;
+
+ const BinaryBasicBlock *LastBB = Function.getLayout().block_back();
+ const MCInst *LastInst = LastBB->getLastNonPseudoInstr();
+ if (!LastInst || LastBB->succ_size() != 0 ||
+ BC.MIB->isTerminator(*LastInst) || BC.MIB->isCall(*LastInst))
+ return nullptr;
+
+ BinaryFunction *Target = BC.getBinaryFunctionAtAddress(Function.getAddress() +
+ Function.getMaxSize());
+ if (!Target || Target == &Function || Target->isPseudo() ||
+ Target->isFragment() ||
+ Target->getOriginSection() != Function.getOriginSection())
+ return nullptr;
+
+ return Target;
+}
+
+} // namespace
+
void RewriteInstance::postProcessFunctions() {
// We mark fragments as non-simple here, not during disassembly,
// So we can build their CFGs.
BC->skipMarkedFragments();
BC->clearFragmentsToSkip();
+ // Function boundaries are not represented by CFG edges. If a function
+ // reaches the end of its body without a terminator, execution continues in
+ // the next function in the input layout. Preserve both functions in place,
+ // since moving either one independently would change program semantics.
+ //
+ // setIgnored() cannot be used after disassembly in process-all-functions
+ // mode. That mode is intended to emit every function, so leave its existing
+ // behavior unchanged.
+ if (!opts::processAllFunctions()) {
+ for (auto &BFI : BC->getBinaryFunctions()) {
+ BinaryFunction &Function = BFI.second;
+ BinaryFunction *Target =
+ getInterproceduralFallThroughTarget(*BC, Function);
+ if (!Target)
+ continue;
+
+ BC->errs() << "BOLT-WARNING: interprocedural fall-through detected "
+ "from "
+ << Function.getPrintName() << " to " << Target->getPrintName()
+ << "; preserving both functions in the original text\n";
+ Function.setIgnored();
+ Target->setIgnored();
+ }
+ }
+
BC->TotalScore = 0;
BC->SumExecutionCount = 0;
for (auto &BFI : BC->getBinaryFunctions()) {
diff --git a/bolt/test/RISCV/interprocedural-fallthrough.s b/bolt/test/RISCV/interprocedural-fallthrough.s
new file mode 100644
index 0000000000000..0bd1db9580649
--- /dev/null
+++ b/bolt/test/RISCV/interprocedural-fallthrough.s
@@ -0,0 +1,65 @@
+// Check that BOLT preserves functions connected by an implicit fall-through.
+// Moving fallthrough_source away from fallthrough_target changes the exit code
+// from zero to one because it starts falling through into poison instead.
+
+// RUN: split-file %s %t.dir
+// RUN: llvm-mc -triple=riscv64 -mattr=+c -filetype=obj \
+// RUN: -o %t.dir/input.o %t.dir/input.s
+// RUN: ld.lld -q -e _start -o %t.dir/input.exe %t.dir/input.o
+// RUN: llvm-bolt %t.dir/input.exe -o %t.dir/output.exe \
+// RUN: --reorder-functions=user --function-order=%t.dir/order.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WARNING
+// RUN: llvm-objdump -d --no-show-raw-insn %t.dir/output.exe \
+// RUN: | FileCheck %s --check-prefix=DISASM
+
+// WARNING: BOLT-WARNING: interprocedural fall-through detected from fallthrough_source to fallthrough_target; preserving both functions in the original text
+
+// DISASM-LABEL: <fallthrough_source>:
+// DISASM: li a0, 0x0
+// DISASM-NEXT: nop
+// DISASM-LABEL: <fallthrough_target>:
+// DISASM-NEXT: ret
+
+//--- input.s
+ .text
+ .option rvc
+
+ .globl _start
+ .type _start, @function
+ .p2align 2
+_start:
+ call fallthrough_source
+ li a7, 93
+ ecall
+ j .
+ .size _start, .-_start
+
+ .globl fallthrough_source
+ .type fallthrough_source, @function
+ .p2align 2
+fallthrough_source:
+ li a0, 0
+ .size fallthrough_source, .-fallthrough_source
+
+ // Keep this alignment padding outside fallthrough_source's symbol size.
+ // Execution intentionally crosses it to reach fallthrough_target.
+ .p2align 2
+ .globl fallthrough_target
+ .type fallthrough_target, @function
+fallthrough_target:
+ ret
+ .size fallthrough_target, .-fallthrough_target
+
+ .globl poison
+ .type poison, @function
+ .p2align 2
+poison:
+ li a0, 1
+ ret
+ .size poison, .-poison
+
+//--- order.txt
+_start
+fallthrough_source
+poison
+fallthrough_target
diff --git a/bolt/test/X86/interprocedural-fallthrough.s b/bolt/test/X86/interprocedural-fallthrough.s
new file mode 100644
index 0000000000000..edce242342629
--- /dev/null
+++ b/bolt/test/X86/interprocedural-fallthrough.s
@@ -0,0 +1,66 @@
+# REQUIRES: x86_64-linux
+
+## Check that the generic BOLT rewrite pipeline preserves functions connected
+## by an implicit fall-through. If fallthrough_source is moved away from
+## fallthrough_target, it falls through into poison and changes the exit code
+## from zero to one.
+
+# RUN: split-file %s %t.dir
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux-gnu \
+# RUN: %t.dir/input.s -o %t.dir/input.o
+# RUN: %clang %cflags -no-pie %t.dir/input.o -o %t.dir/input.exe \
+# RUN: -Wl,-q -Wl,-e,_start
+# RUN: %t.dir/input.exe
+# RUN: llvm-bolt %t.dir/input.exe -o %t.dir/output.exe \
+# RUN: --reorder-functions=user --function-order=%t.dir/order.txt 2>&1 \
+# RUN: | FileCheck %s --check-prefix=WARNING
+# RUN: %t.dir/output.exe
+# RUN: llvm-nm -n %t.dir/output.exe | FileCheck %s --check-prefix=NM
+
+# WARNING: BOLT-WARNING: interprocedural fall-through detected from fallthrough_source to fallthrough_target; preserving both functions in the original text
+
+# NM: T fallthrough_source
+# NM-NEXT: T fallthrough_target
+
+#--- input.s
+ .text
+
+ .globl _start
+ .type _start, @function
+ .p2align 4
+_start:
+ callq fallthrough_source
+ movq $60, %rax
+ syscall
+ ud2
+ .size _start, .-_start
+
+ .globl fallthrough_source
+ .type fallthrough_source, @function
+ .p2align 4
+fallthrough_source:
+ xorl %edi, %edi
+ .size fallthrough_source, .-fallthrough_source
+
+ # Keep this alignment padding outside fallthrough_source's symbol size.
+ # Execution intentionally crosses it to reach fallthrough_target.
+ .p2align 4
+ .globl fallthrough_target
+ .type fallthrough_target, @function
+fallthrough_target:
+ retq
+ .size fallthrough_target, .-fallthrough_target
+
+ .globl poison
+ .type poison, @function
+ .p2align 4
+poison:
+ movl $1, %edi
+ retq
+ .size poison, .-poison
+
+#--- order.txt
+_start
+fallthrough_source
+poison
+fallthrough_target
``````````
</details>
https://github.com/llvm/llvm-project/pull/219430
More information about the llvm-commits
mailing list