[llvm] [FunctionnSpecializer] Do not mark function dead if any unexecutable call site exists (PR #154668)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 20 21:35:05 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-function-specialization

Author: XChy (XChy)

<details>
<summary>Changes</summary>

Fixes #<!-- -->153295.
For test case below:
```llvm
define i32 @<!-- -->caller() {
entry:
  %call1 = call i32 @<!-- -->callee(i32 1)
  %call2 = call i32 @<!-- -->callee(i32 0)
  %cond = icmp eq i32 %call2, 0
  br i1 %cond, label %common.ret, label %if.then

common.ret:                                       ; preds = %entry
  ret i32 0

if.then:                                         ; preds = %entry
  %unreachable_call = call i32 @<!-- -->callee(i32 2)
  ret i32 %unreachable_call
}

define internal i32 @<!-- -->callee(i32 %ac) {
entry:
  br label %ai

ai:                                               ; preds = %ai, %entry
  %add = or i32 0, 0
  %cond = icmp eq i32 %ac, 1
  br i1 %cond, label %aj, label %ai

aj:                                               ; preds = %ai
  ret i32 0
}
```
Before specialization, the SCCP solver determines that `unreachable_call` is unexecutable, as the value of `callee` can only be zero.
After specializing the call sites `call1` and `call2`, FnSpecializer announces `callee` is a dead function since all executable call sites are specialized. However, the unexecutable call sites can become executable again after solving specialized calls.
In this testcase, `call2` is considered `Overdefined` after specialization, making `cond` also `Overdefined`. Thus, `unreachable_call` becomes executable.
This patch prevents marking a function as dead and fully specialized if any unexecutable call site exists.

---
Full diff: https://github.com/llvm/llvm-project/pull/154668.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/IPO/FunctionSpecialization.cpp (+11-7) 
- (added) llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll (+42) 


``````````diff
diff --git a/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp b/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
index c876a47ef2129..c799bb54a34b6 100644
--- a/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
@@ -1167,15 +1167,17 @@ Constant *FunctionSpecializer::getCandidateConstant(Value *V) {
 
 void FunctionSpecializer::updateCallSites(Function *F, const Spec *Begin,
                                           const Spec *End) {
-  // Collect the call sites that need updating.
+  // Collect the call sites that need updating and count ALL the call sites.
   SmallVector<CallBase *> ToUpdate;
-  for (User *U : F->users())
-    if (auto *CS = dyn_cast<CallBase>(U);
-        CS && CS->getCalledFunction() == F &&
-        Solver.isBlockExecutable(CS->getParent()))
-      ToUpdate.push_back(CS);
+  unsigned NCallsLeft = 0;
+  for (User *U : F->users()) {
+    if (auto *CS = dyn_cast<CallBase>(U); CS && CS->getCalledFunction() == F) {
+      NCallsLeft++;
+      if (Solver.isBlockExecutable(CS->getParent()))
+        ToUpdate.push_back(CS);
+    }
+  }
 
-  unsigned NCallsLeft = ToUpdate.size();
   for (CallBase *CS : ToUpdate) {
     bool ShouldDecrementCount = CS->getFunction() == F;
 
@@ -1207,6 +1209,8 @@ void FunctionSpecializer::updateCallSites(Function *F, const Spec *Begin,
 
   // If the function has been completely specialized, the original function
   // is no longer needed. Mark it unreachable.
+  // NOTE: We cannot mark it unreachable if any unexecutable call site exists,
+  // as the unexecutable call site may become executable due to specialization.
   if (NCallsLeft == 0 && Solver.isArgumentTrackedFunction(F)) {
     Solver.markFunctionUnreachable(F);
     FullySpecialized.insert(F);
diff --git a/llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll b/llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll
new file mode 100644
index 0000000000000..92685c5c72f2e
--- /dev/null
+++ b/llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll
@@ -0,0 +1,42 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes=ipsccp  --funcspec-min-function-size=1 -S < %s | FileCheck %s
+
+define i32 @caller() {
+; CHECK-LABEL: define i32 @caller() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CALL1:%.*]] = call i32 @callee.specialized.1(i32 1)
+; CHECK-NEXT:    [[CALL2:%.*]] = call i32 @callee.specialized.2(i32 0)
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i32 undef, 0
+; CHECK-NEXT:    br i1 [[COND]], label %[[COMMON_RET:.*]], label %[[IF_THEN:.*]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 0
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    [[UNREACHABLE_CALL:%.*]] = call i32 @callee.specialized.3(i32 2)
+; CHECK-NEXT:    ret i32 undef
+;
+entry:
+  %call1 = call i32 @callee(i32 1)
+  %call2 = call i32 @callee(i32 0)
+  %cond = icmp eq i32 %call2, 0
+  br i1 %cond, label %common.ret, label %if.then
+
+common.ret:                                       ; preds = %entry
+  ret i32 0
+
+if.then:                                         ; preds = %entry
+  %unreachable_call = call i32 @callee(i32 2)
+  ret i32 %unreachable_call
+}
+
+define internal i32 @callee(i32 %ac) {
+entry:
+  br label %ai
+
+ai:                                               ; preds = %ai, %entry
+  %add = or i32 0, 0
+  %cond = icmp eq i32 %ac, 1
+  br i1 %cond, label %aj, label %ai
+
+aj:                                               ; preds = %ai
+  ret i32 0
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/154668


More information about the llvm-commits mailing list