Bug type: crash
Component: FunctionSpecialization
SCCP
Bug location: llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h: getInstCostVisitorFor
llvm/lib/Transforms/IPO/FunctionSpecialization.cpp: FunctionSpecializer::removeDeadFunctions
FunctionSpecializer::updateCallSites
llvm/lib/Transforms/IPO/SCCP.cpp: runIPSCCP
Issue title: clang crashes at -O{1,2,3} on x86_64-linux-gnu: Assertion `pred_empty(DelBB) && "DelBB has one or more predecessors."' failed
Labels: ipo
confirmed
crash
function-specialization
regression:20
Reported by: zhendongsu

Compiler Explorer: https://godbolt.org/z/xcE69Pj9E

Note:
- fails: 20.1.0 and trunk
- works: 19.1.0 and earlier
- original code was valid, but the reduced isn't

```
[599] % clangtk -v
clang version 22.0.0git (https://github.com/llvm/llvm-project.git 44f41f55b4df5ec490df0d69e046b2d1a58acaeb)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /local/home/suz/suz-local/software/local/clang-trunk/bin
Build config: +assertions
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/11
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/12
Selected GCC installation: /usr/lib/gcc/x86_64-linux-gnu/12
Candidate multilib: .;@m64
Selected multilib: .;@m64
Found CUDA installation: /usr/local/cuda, version 12.1
[600] % 
[600] % clangtk -O1 -w -c small.c
clangtk: /local/suz-local/software/clangbuild/llvm-project/llvm/lib/Analysis/DomTreeUpdater.cpp:91: void llvm::DomTreeUpdater::validateDeleteBB(llvm::BasicBlock*): Assertion `pred_empty(DelBB) && "DelBB has one or more predecessors."' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script.
Stack dump:
0.	Program arguments: clangtk -I/usr/local/include -I/local/suz-local/software/local/include -O1 -w -c small.c
1.	<eof> parser at end of file
2.	Optimizer
3.	Running pass "ipsccp" on module "small.c"
 #0 0x000055bd288ea430 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4263430)
 #1 0x000055bd288e70df llvm::sys::RunSignalHandlers() (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x42600df)
 #2 0x000055bd288277d8 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0
 #3 0x00007f13e112e520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #4 0x00007f13e11829fc __pthread_kill_implementation ./nptl/./nptl/pthread_kill.c:44:76
 #5 0x00007f13e11829fc __pthread_kill_internal ./nptl/./nptl/pthread_kill.c:78:10
 #6 0x00007f13e11829fc pthread_kill ./nptl/./nptl/pthread_kill.c:89:10
 #7 0x00007f13e112e476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
 #8 0x00007f13e11147f3 abort ./stdlib/./stdlib/abort.c:81:7
 #9 0x00007f13e111471b _nl_load_domain ./intl/./intl/loadmsgcat.c:1177:9
#10 0x00007f13e1125e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
#11 0x000055bd276cf797 llvm::DomTreeUpdater::validateDeleteBB(llvm::BasicBlock*) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x3048797)
#12 0x000055bd276cf926 llvm::DomTreeUpdater::deleteBB(llvm::BasicBlock*) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x3048926)
#13 0x000055bd2a1de05a runIPSCCP(llvm::Module&, llvm::DataLayout const&, llvm::AnalysisManager<llvm::Function>*, std::function<llvm::TargetLibraryInfo const& (llvm::Function&)>, std::function<llvm::TargetTransformInfo& (llvm::Function&)>, std::function<llvm::AssumptionCache& (llvm::Function&)>, std::function<llvm::DominatorTree& (llvm::Function&)>, std::function<llvm::BlockFrequencyInfo& (llvm::Function&)>, bool) SCCP.cpp:0:0
#14 0x000055bd2a1deca7 llvm::IPSCCPPass::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x5b57ca7)
#15 0x000055bd299cf216 llvm::detail::PassModel<llvm::Module, llvm::IPSCCPPass, llvm::AnalysisManager<llvm::Module> >::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x5348216)
#16 0x000055bd2822a194 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module> >::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x3ba3194)
#17 0x000055bd28b90d13 (anonymous namespace)::EmitAssemblyHelper::RunOptimizationPipeline(clang::BackendAction, std::unique_ptr<llvm::raw_pwrite_stream, std::default_delete<llvm::raw_pwrite_stream> >&, std::unique_ptr<llvm::ToolOutputFile, std::default_delete<llvm::ToolOutputFile> >&, clang::BackendConsumer*) BackendUtil.cpp:0:0
#18 0x000055bd28b952b3 clang::emitBackendOutput(clang::CompilerInstance&, clang::CodeGenOptions&, llvm::StringRef, llvm::Module*, clang::BackendAction, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::unique_ptr<llvm::raw_pwrite_stream, std::default_delete<llvm::raw_pwrite_stream> >, clang::BackendConsumer*) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x450e2b3)
#19 0x000055bd2928f05f clang::BackendConsumer::HandleTranslationUnit(clang::ASTContext&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4c0805f)
#20 0x000055bd2acf2a0c clang::ParseAST(clang::Sema&, bool, bool) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x666ba0c)
#21 0x000055bd295acb59 clang::FrontendAction::Execute() (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4f25b59)
#22 0x000055bd295284c5 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4ea14c5)
#23 0x000055bd2969d628 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x5016628)
#24 0x000055bd25500d58 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0xe79d58)
#25 0x000055bd254f735a ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) driver.cpp:0:0
#26 0x000055bd292fe17d void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, bool*) const::'lambda'()>(long) Job.cpp:0:0
#27 0x000055bd28827c80 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x41a0c80)
#28 0x000055bd292fea1e clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, bool*) const (.part.0) Job.cpp:0:0
#29 0x000055bd292bbf31 clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4c34f31)
#30 0x000055bd292bd051 clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*> >&, bool) const (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4c36051)
#31 0x000055bd292c9a34 clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*> >&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0x4c42a34)
#32 0x000055bd254fcbfb clang_main(int, char**, llvm::ToolContext const&) (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0xe75bfb)
#33 0x000055bd253c806b main (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0xd4106b)
#34 0x00007f13e1115d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#35 0x00007f13e1115e40 call_init ./csu/../csu/libc-start.c:128:20
#36 0x00007f13e1115e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
#37 0x000055bd254f6a85 _start (/local/home/suz/suz-local/software/local/clang-trunk/bin/clang-22+0xe6fa85)
clangtk: error: clang frontend command failed with exit code 134 (use -v to see invocation)
clang version 22.0.0git (https://github.com/llvm/llvm-project.git 44f41f55b4df5ec490df0d69e046b2d1a58acaeb)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /local/home/suz/suz-local/software/local/clang-trunk/bin
Build config: +assertions
clangtk: note: diagnostic msg: 
********************

PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clangtk: note: diagnostic msg: /tmp/small-9f4e27.c
clangtk: note: diagnostic msg: /tmp/small-9f4e27.sh
clangtk: note: diagnostic msg: 

********************
[601] % 
[601] % cat small.c
int a, b, c, d, e, f, h, i, j, l, n;
long g;
char k, m;
int q() {}
long r();
int s();
int t();
int u();
int v() {}
int w();
int x(int, int, int, int) { return 0; }
char y();
int z(long);
int aa(int[]) { return 0; }
static int ab(int ac, int ad, int ae, int af, int ag) {
  int o = 0, p;
  a / 1;
  goto ah;
ai:
  1 % a + 1 % b + 0;
  if (5 * s(d + 2, d + 9 + d - 1) + d - 1 % ac - 2 * b + 9 + b - 3 + b -
      2 * s(ad + ad - 3) + ad - 1)
    a = o = (char)u - 1 + c + 7 / y(b + 1) + b - 3 + b + 1;
  if (1 % (s(o + 8 + o - 2) + o) + -p + 1 % (s(0, d + 2, d + 4 + d - 2) + d) +
      1 / ad - 3)
    goto ai;
  ad = 1 + -r(o - 2) + o + 3;
  if (1 + s(r) + ad - -s(c + 8 + c - 2) + c - 2 / a - 5)
    d = 1 % p + -z(a + 4 - 2) + a + -s((char)q + 5, a + 7, a + 9 + a - 6) + a -
        1 % s(p + 1 + p - 3) + p - 1;
  if (-(-u(0, o + o + 3, o + 3 + o - 1) + o - 1 % (v() + af - 2) + b - 7))
    goto aj;
ah:
  d + s(c + 6 + u) - 4294967294 + c + o + -s(0, 0, ad + 3 + ad - 4 - 8) + ad +
      -a + 1 % s(b + 9, b + 5 + b - 2) + b + 0;
  if (1 / (char)a - 2 + a + 2 * r(6, 5) - 4 + ac + 1 / s(0) - b + 7 / d + 1)
    p = -18 / (x(a + 5, 0, a + 2, ac + 3) + ae + ac + o + ag + af - 2) + ae -
        -ad - 1;
  goto ai;
aj:
  b = -s(ac + a + d - 4) + ac - -z(d + 1 + d - 3) + d - 1 % t(p + 1 + p - 1) +
      p - 1 % s((char)a + 4 + c + a + ad + q) + a - 2 + c - 1;
  n = m = u(ac + 9);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  w(w);
  l = s((char)s);
  k = y(b + 5);
  j = s(0, b + 7);
  i = s((char)c + c + 209);
  h = s(p + 4);
  g = r(8, 6, 9, 6, 5, 9, 6, 4, 5, 8, 4);
  f = w(r);
  e = s(d);
  int bb[] = {n + a - 2 + a, m + (char)q - 2 + ac, f + ad, l + a - 3 + a,
                    k + (char)t - 8 + b, j + b - 2 + b, i - 1 + c,
                    h + p - 5 + p, 0, g + -e + -d};
  return aa(bb);
}
void ak() {
  ab(2, 2, 3, -2, 2);
  if (q())
    if (ab(2, 2, 3, 2, 2))
      ab(2, 2, 9, 1, 1);
}
```



Comments: author: XChy
body: Interesting case. This is the minimal testcase I can reproduce: https://godbolt.org/z/4r68e88zd. It's still very large. When I tried reducing it manually, I found that even removing one instruction made it not reproducible.
author: XChy
body: Reduce to minimal by adding parameter `--funcspec-min-function-size=1`:
```llvm
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

define i32 @ak() {
entry:
  %call34 = call i32 @ab(i32 1)
  %call223 = call i32 @ab(i32 0)
  %tobool3.not = icmp eq i32 %call223, 0
  br i1 %tobool3.not, label %common.ret, label %if.then4

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

if.then4:                                         ; preds = %entry
  %call512 = call i32 @ab(i32 0)
  ret i32 %call512
}

define internal i32 @ab(i32 %ac) {
entry:
  br label %ai

ai:                                               ; preds = %ai, %entry
  %add37 = or i32 0, 0
  %tobool52.not = icmp eq i32 %ac, 1
  br i1 %tobool52.not, label %aj, label %ai

aj:                                               ; preds = %ai
  ret i32 0
}
```
I will look into this case this week.

Fix patch:
commit 6bd844812385dd5cb65e08fe9561be9f91ace876
Author: XChy <xxs_chy@outlook.com>
Date:   Thu Aug 28 01:04:52 2025 +0800

    [FuncSpec] Skip SCCP on blocks of dead functions and poison their callsites (#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 skips SCCP on the blocks in dead functions, and poisons the
    call sites of dead functions.

diff --git a/llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h b/llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h
index 1575afa50e19..5a682e8c7b5e 100644
--- a/llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h
+++ b/llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h
@@ -246,7 +246,7 @@ class FunctionSpecializer {
   std::function<AssumptionCache &(Function &)> GetAC;
 
   SmallPtrSet<Function *, 32> Specializations;
-  SmallPtrSet<Function *, 32> FullySpecialized;
+  SmallPtrSet<Function *, 32> DeadFunctions;
   DenseMap<Function *, CodeMetrics> FunctionMetrics;
   DenseMap<Function *, unsigned> FunctionGrowth;
   unsigned NGlobals = 0;
@@ -270,6 +270,8 @@ public:
     return InstCostVisitor(GetBFI, F, M.getDataLayout(), TTI, Solver);
   }
 
+  bool isDeadFunction(Function *F) { return DeadFunctions.contains(F); }
+
 private:
   Constant *getPromotableAlloca(AllocaInst *Alloca, CallInst *Call);
 
diff --git a/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp b/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
index 9196a0147c43..a459a9eddbcf 100644
--- a/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionSpecialization.cpp
@@ -838,14 +838,24 @@ bool FunctionSpecializer::run() {
 }
 
 void FunctionSpecializer::removeDeadFunctions() {
-  for (Function *F : FullySpecialized) {
+  for (Function *F : DeadFunctions) {
     LLVM_DEBUG(dbgs() << "FnSpecialization: Removing dead function "
                       << F->getName() << "\n");
     if (FAM)
       FAM->clear(*F, F->getName());
+
+    // Remove all the callsites that were proven unreachable once, and replace
+    // them with poison.
+    for (User *U : make_early_inc_range(F->users())) {
+      assert((isa<CallInst>(U) || isa<InvokeInst>(U)) &&
+             "User of dead function must be call or invoke");
+      Instruction *CS = cast<Instruction>(U);
+      CS->replaceAllUsesWith(PoisonValue::get(CS->getType()));
+      CS->eraseFromParent();
+    }
     F->eraseFromParent();
   }
-  FullySpecialized.clear();
+  DeadFunctions.clear();
 }
 
 /// Clone the function \p F and remove the ssa_copy intrinsics added by
@@ -1206,8 +1216,11 @@ 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.
-  if (NCallsLeft == 0 && Solver.isArgumentTrackedFunction(F)) {
+  // NOTE: If the address of a function is taken, we cannot treat it as dead
+  // function.
+  if (NCallsLeft == 0 && Solver.isArgumentTrackedFunction(F) &&
+      !F->hasAddressTaken()) {
     Solver.markFunctionUnreachable(F);
-    FullySpecialized.insert(F);
+    DeadFunctions.insert(F);
   }
 }
diff --git a/llvm/lib/Transforms/IPO/SCCP.cpp b/llvm/lib/Transforms/IPO/SCCP.cpp
index d50de34dfa48..e98a70f228ad 100644
--- a/llvm/lib/Transforms/IPO/SCCP.cpp
+++ b/llvm/lib/Transforms/IPO/SCCP.cpp
@@ -169,6 +169,10 @@ static bool runIPSCCP(
   for (Function &F : M) {
     if (F.isDeclaration())
       continue;
+    // Skip the dead functions marked by FunctionSpecializer, avoiding removing
+    // blocks in dead functions.
+    if (IsFuncSpecEnabled && Specializer.isDeadFunction(&F))
+      continue;
 
     SmallVector<BasicBlock *, 512> BlocksToErase;
 


Regression tests:
file: llvm/test/Transforms/FunctionSpecialization/reachable-after-specialization.ll
commands: opt -passes=ipsccp  --funcspec-min-function-size=1 -S < %s
tests: test_name: <module>
test_body: 
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 %arg) {
entry:
  br label %loop

loop:                                               ; preds = %ai, %entry
  %add = or i32 0, 0
  %cond = icmp eq i32 %arg, 1
  br i1 %cond, label %exit, label %loop

exit:                                               ; preds = %ai
  ret i32 0
}

declare void @other_user(ptr)

define i32 @caller2() {
;
entry:
  call void @other_user(ptr @callee2)
  %call1 = call i32 @callee2(i32 1)
  %call2 = call i32 @callee2(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 @callee2(i32 2)
  ret i32 %unreachable_call
}

define internal i32 @callee2(i32 %arg) {
;
entry:
  br label %loop

loop:                                               ; preds = %ai, %entry
  %add = or i32 0, 0
  %cond = icmp eq i32 %arg, 1
  br i1 %cond, label %exit, label %loop

exit:                                               ; preds = %ai
  ret i32 0
}

define i32 @caller3(i32 %arg) {
;
entry:
  %call1 = call i32 @callee3(i32 0)
  %call2 = call i32 @callee3(i32 1)
  %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 @callee3(i32 %arg)
  ret i32 %unreachable_call
}

define internal i32 @callee3(i32 %arg) {
entry:
  br label %loop

loop:                                               ; preds = %ai, %entry
  %add = or i32 0, 0
  %cond = icmp ne i32 %arg, 1
  br i1 %cond, label %exit, label %loop

exit:                                               ; preds = %ai
  ret i32 %arg
}