[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:34:32 PDT 2025
    
    
  
https://github.com/XChy created https://github.com/llvm/llvm-project/pull/154668
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.
>From 33995e8917dd12593ed4244ced93604a583b8312 Mon Sep 17 00:00:00 2001
From: XChy <xxs_chy at outlook.com>
Date: Thu, 21 Aug 2025 12:22:24 +0800
Subject: [PATCH] [FunctionnSpecializer] Do not mark function dead if any
 unexecutable callsite exists
---
 .../Transforms/IPO/FunctionSpecialization.cpp | 18 ++++----
 .../reachable-after-specialization.ll         | 42 +++++++++++++++++++
 2 files changed, 53 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll
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
+}
    
    
More information about the llvm-commits
mailing list