[llvm] [Inline] Promote bounded statically known indirect calls (PR #212148)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 26 13:25:28 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Kevin Choi (choikwa)

<details>
<summary>Changes</summary>

Teach the CGSCC inliner to consider indirect calls whose complete target set can be recovered from select and phi nodes.

Move static target discovery into IndirectCallPromotionAnalysis so profile-guided and static promotion share the existing ICP target bound. Static promotion reuses -icp-max-prom, which defaults to three targets. Use an iterative worklist with a separate 64-value traversal budget to bound PHI fan-out, graph depth, and total discovery work. Follow only same-representation pointer casts when recovering targets.

This deliberately does not look through loads or perform vtable analysis. Virtual calls benefit when existing devirtualization exposes their callees as a select or phi of function constants; otherwise they remain with the existing virtual-call and profile-guided promotion paths.

For an exhaustive static target set, query the configured InlineAdvisor for every target and promote the call atomically only when all targets are defined, legal, and profitable to inline. Complete speculative advice through a dedicated protocol so mandatory and ML advisors do not treat promotion queries as failed inline attempts.

Guard the first N-1 targets and promote the final exhaustive target unconditionally, eliminating the residual indirect fallback and redundant final comparison. Update the lazy call graph before the promoted direct calls receive fresh advice and reach InlineFunction.

Add coverage for traversal limits, cyclic phis, dynamic target sources, function-pointer address spaces, select and phi target sets, exact inline-cost thresholds, candidate-count limits, mandatory advice, adjacent calls, invokes, musttail calls, recursion and SCC updates, unknown and unavailable targets, aliases, ifuncs, incompatible signatures, noinline targets, and partially profitable sets.

Fixes #<!-- -->211678.

---

Patch is 30.00 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/212148.diff


7 Files Affected:

- (modified) llvm/include/llvm/Analysis/IndirectCallPromotionAnalysis.h (+13) 
- (modified) llvm/include/llvm/Analysis/InlineAdvisor.h (+10) 
- (modified) llvm/include/llvm/Analysis/MLInlineAdvisor.h (+1) 
- (modified) llvm/lib/Analysis/IndirectCallPromotionAnalysis.cpp (+78) 
- (modified) llvm/lib/Analysis/MLInlineAdvisor.cpp (+5) 
- (modified) llvm/lib/Transforms/IPO/Inliner.cpp (+100-25) 
- (added) llvm/test/Transforms/Inline/indirect-call.ll (+452) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/IndirectCallPromotionAnalysis.h b/llvm/include/llvm/Analysis/IndirectCallPromotionAnalysis.h
index e8f93be09c952..fbdf6f7949cb2 100644
--- a/llvm/include/llvm/Analysis/IndirectCallPromotionAnalysis.h
+++ b/llvm/include/llvm/Analysis/IndirectCallPromotionAnalysis.h
@@ -13,12 +13,25 @@
 #ifndef LLVM_ANALYSIS_INDIRECTCALLPROMOTIONANALYSIS_H
 #define LLVM_ANALYSIS_INDIRECTCALLPROMOTIONANALYSIS_H
 
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ProfileData/InstrProf.h"
 
 namespace llvm {
 
+class CallBase;
+class Function;
 class Instruction;
 
+/// Find all possible function targets of an indirect call whose called operand
+/// is formed entirely from selects, phis, and function constants. Returns
+/// false if the target set is not exhaustive, exceeds the configured target
+/// limit, or requires more than the configured traversal budget.
+/// \p Targets is replaced with the discovered targets on success and is empty
+/// on failure.
+LLVM_ABI bool
+getStaticIndirectCallTargets(const CallBase &CB,
+                             SmallVectorImpl<Function *> &Targets);
+
 // Class for identifying profitable indirect call promotion candidates when
 // the indirect-call value profile metadata is available.
 class ICallPromotionAnalysis {
diff --git a/llvm/include/llvm/Analysis/InlineAdvisor.h b/llvm/include/llvm/Analysis/InlineAdvisor.h
index 0b48da60431f1..f58f7c00cf135 100644
--- a/llvm/include/llvm/Analysis/InlineAdvisor.h
+++ b/llvm/include/llvm/Analysis/InlineAdvisor.h
@@ -108,6 +108,15 @@ class InlineAdvice {
     recordUnattemptedInliningImpl();
   }
 
+  /// Call when advice was requested only to decide whether an indirect call
+  /// should be promoted to this callee. No inlining outcome should be
+  /// attributed to this speculative query, and any state prepared for an
+  /// inline attempt must be discarded.
+  void recordCallPromotionQuery() {
+    markRecorded();
+    recordCallPromotionQueryImpl();
+  }
+
   /// Get the inlining recommendation.
   bool isInliningRecommended() const { return IsInliningRecommended; }
   const DebugLoc &getOriginalCallSiteDebugLoc() const { return DLoc; }
@@ -118,6 +127,7 @@ class InlineAdvice {
   virtual void recordInliningWithCalleeDeletedImpl() {}
   virtual void recordUnsuccessfulInliningImpl(const InlineResult &Result) {}
   virtual void recordUnattemptedInliningImpl() {}
+  virtual void recordCallPromotionQueryImpl() {}
 
   InlineAdvisor *const Advisor;
   /// Caller and Callee are pre-inlining.
diff --git a/llvm/include/llvm/Analysis/MLInlineAdvisor.h b/llvm/include/llvm/Analysis/MLInlineAdvisor.h
index 9e5b9d7ef5297..9ba1b890dfe30 100644
--- a/llvm/include/llvm/Analysis/MLInlineAdvisor.h
+++ b/llvm/include/llvm/Analysis/MLInlineAdvisor.h
@@ -111,6 +111,7 @@ class LLVM_ABI MLInlineAdvice : public InlineAdvice {
   void recordInliningWithCalleeDeletedImpl() override;
   void recordUnsuccessfulInliningImpl(const InlineResult &Result) override;
   void recordUnattemptedInliningImpl() override;
+  void recordCallPromotionQueryImpl() override;
 
   Function *getCaller() const { return Caller; }
   Function *getCallee() const { return Callee; }
diff --git a/llvm/lib/Analysis/IndirectCallPromotionAnalysis.cpp b/llvm/lib/Analysis/IndirectCallPromotionAnalysis.cpp
index 6dc03bcfdf49c..c2c6c34d09aa5 100644
--- a/llvm/lib/Analysis/IndirectCallPromotionAnalysis.cpp
+++ b/llvm/lib/Analysis/IndirectCallPromotionAnalysis.cpp
@@ -13,7 +13,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Analysis/IndirectCallPromotionAnalysis.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/ProfileData/InstrProf.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
@@ -52,12 +55,87 @@ static cl::opt<unsigned>
                      cl::desc("Max number of promotions for a single indirect "
                               "call callsite"));
 
+static cl::opt<unsigned> MaxStaticTargetTraversal(
+    "icp-max-static-target-traversal", cl::init(64), cl::Hidden,
+    cl::desc("Max number of values traversed when collecting static indirect "
+             "call targets"));
+
 cl::opt<unsigned> MaxNumVTableAnnotations(
     "icp-max-num-vtables", cl::init(6), cl::Hidden,
     cl::desc("Max number of vtables annotated for a vtable load instruction."));
 
 } // end namespace llvm
 
+static bool
+collectStaticIndirectCallTargets(Value *Root,
+                                 SmallVectorImpl<Function *> &Targets) {
+  SmallPtrSet<Value *, 16> Visited;
+  SmallVector<Value *, 16> Worklist;
+  unsigned NumEnqueued = 0;
+
+  // Count queued operands, including duplicates, so a PHI with arbitrarily
+  // many incoming edges cannot consume unbounded time or worklist storage.
+  auto Enqueue = [&](Value *V) {
+    if (NumEnqueued >= MaxStaticTargetTraversal)
+      return false;
+    ++NumEnqueued;
+    Worklist.push_back(V);
+    return true;
+  };
+
+  if (!Enqueue(Root))
+    return false;
+
+  while (!Worklist.empty()) {
+    Value *V = Worklist.pop_back_val();
+
+    // Call promotion compares the indirect callee with the function symbol
+    // after converting them to the same pointer type. Do not look through
+    // address-space casts that may change the pointer representation, since the
+    // function symbol cannot then safely stand in for the original value.
+    V = V->stripPointerCastsSameRepresentation();
+    if (!Visited.insert(V).second)
+      continue;
+
+    if (auto *F = dyn_cast<Function>(V)) {
+      if (!is_contained(Targets, F))
+        Targets.push_back(F);
+      if (Targets.size() > MaxNumPromotions)
+        return false;
+      continue;
+    }
+
+    if (auto *SI = dyn_cast<SelectInst>(V)) {
+      if (!Enqueue(SI->getFalseValue()) || !Enqueue(SI->getTrueValue()))
+        return false;
+      continue;
+    }
+
+    if (auto *PN = dyn_cast<PHINode>(V)) {
+      for (Value *Incoming : reverse(PN->incoming_values()))
+        if (!Enqueue(Incoming))
+          return false;
+      continue;
+    }
+
+    return false;
+  }
+
+  return true;
+}
+
+bool llvm::getStaticIndirectCallTargets(const CallBase &CB,
+                                        SmallVectorImpl<Function *> &Targets) {
+  assert(!CB.getCalledFunction() && "expected an indirect call");
+  Targets.clear();
+  bool Success =
+      collectStaticIndirectCallTargets(CB.getCalledOperand(), Targets) &&
+      !Targets.empty();
+  if (!Success)
+    Targets.clear();
+  return Success;
+}
+
 bool ICallPromotionAnalysis::isPromotionProfitable(uint64_t Count,
                                                    uint64_t TotalCount,
                                                    uint64_t RemainingCount) {
diff --git a/llvm/lib/Analysis/MLInlineAdvisor.cpp b/llvm/lib/Analysis/MLInlineAdvisor.cpp
index 9a5ae2ae26799..635adab45f630 100644
--- a/llvm/lib/Analysis/MLInlineAdvisor.cpp
+++ b/llvm/lib/Analysis/MLInlineAdvisor.cpp
@@ -635,3 +635,8 @@ void MLInlineAdvice::recordUnattemptedInliningImpl() {
     return R;
   });
 }
+
+void MLInlineAdvice::recordCallPromotionQueryImpl() {
+  getAdvisor()->getCachedFPI(*Caller) = PreInlineCallerFPI;
+  FPU.reset();
+}
diff --git a/llvm/lib/Transforms/IPO/Inliner.cpp b/llvm/lib/Transforms/IPO/Inliner.cpp
index 65ef084218500..4dfdbe24f2b1d 100644
--- a/llvm/lib/Transforms/IPO/Inliner.cpp
+++ b/llvm/lib/Transforms/IPO/Inliner.cpp
@@ -27,6 +27,7 @@
 #include "llvm/Analysis/BlockFrequencyInfo.h"
 #include "llvm/Analysis/CGSCCPassManager.h"
 #include "llvm/Analysis/EphemeralValuesCache.h"
+#include "llvm/Analysis/IndirectCallPromotionAnalysis.h"
 #include "llvm/Analysis/InlineAdvisor.h"
 #include "llvm/Analysis/InlineCost.h"
 #include "llvm/Analysis/LazyCallGraph.h"
@@ -49,6 +50,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/IR/Value.h"
+#include "llvm/IR/ValueHandle.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
@@ -99,7 +101,6 @@ static cl::opt<bool>
     EnablePostSCCAdvisorPrinting("enable-scc-inline-advisor-printing",
                                  cl::init(false), cl::Hidden);
 
-
 static cl::opt<std::string> CGSCCInlineReplayFile(
     "cgscc-inline-replay", cl::init(""), cl::value_desc("filename"),
     cl::desc(
@@ -151,6 +152,40 @@ static cl::opt<CallSiteFormat::Format> CGSCCInlineReplayFormat(
                    "<Line Number>:<Column Number>.<Discriminator> (default)")),
     cl::desc("How cgscc inline replay file is formatted"), cl::Hidden);
 
+static bool
+promoteInlineableIndirectCall(CallBase &CB, InlineAdvisor &Advisor,
+                              bool OnlyMandatory,
+                              SmallVectorImpl<CallBase *> &PromotedCalls) {
+  SmallVector<Function *> Targets;
+  if (!getStaticIndirectCallTargets(CB, Targets) || Targets.size() < 2)
+    return false;
+
+  // This advice only gates the all-or-nothing indirect-to-direct promotion.
+  // The promoted direct calls are distinct call sites and will get fresh advice
+  // before InlineFunction processes them. Thus, this is a promotion heuristic,
+  // not a guarantee that every promoted call will subsequently be inlined.
+  for (Function *Target : Targets) {
+    if (Target->isDeclaration() || !isLegalToPromote(CB, Target))
+      return false;
+
+    Value *IndirectCallee = CB.getCalledOperand();
+    CB.setCalledOperand(Target);
+    std::unique_ptr<InlineAdvice> Advice = Advisor.getAdvice(CB, OnlyMandatory);
+    CB.setCalledOperand(IndirectCallee);
+    if (!Advice)
+      return false;
+    bool ShouldPromote = Advice->isInliningRecommended();
+    Advice->recordCallPromotionQuery();
+    if (!ShouldPromote)
+      return false;
+  }
+
+  for (Function *Target : ArrayRef(Targets).drop_back())
+    PromotedCalls.push_back(&promoteCallWithIfThenElse(CB, Target));
+  PromotedCalls.push_back(&promoteCall(CB, Targets.back()));
+  return true;
+}
+
 InlineAdvisor &
 InlinerPass::getAdvisor(const ModuleAnalysisManagerCGSCCProxy::Result &MAM,
                         FunctionAnalysisManager &FAM, Module &M) {
@@ -240,7 +275,10 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
   // this model, but it is uniformly spread across all the functions in the SCC
   // and eventually they all become too large to inline, rather than
   // incrementally making a single function grow in a super linear fashion.
-  SmallVector<CallBase *, 16> Calls;
+  //
+  // Promotion and inlining can delete later calls already queued here. Use
+  // weak handles so those entries become null instead of dangling pointers.
+  SmallVector<WeakTrackingVH, 16> Calls;
 
   // Populate the initial list of calls in this SCC.
   for (auto &N : InitialC) {
@@ -252,7 +290,7 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
     // FIXME: Using instructions sequence is a really bad way to do this.
     // Instead we should do an actual RPO walk of the function body.
     for (Instruction &I : instructions(N.getFunction()))
-      if (auto *CB = dyn_cast<CallBase>(&I))
+      if (auto *CB = dyn_cast<CallBase>(&I)) {
         if (Function *Callee = CB->getCalledFunction()) {
           if (!Callee->isDeclaration())
             Calls.push_back(CB);
@@ -267,7 +305,9 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
                      << setIsVerbose();
             });
           }
-        }
+        } else if (isa<CallInst, InvokeInst>(CB))
+          Calls.push_back(CB);
+      }
   }
 
   // Capture updatable variable for the current SCC.
@@ -294,11 +334,15 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
   // Loop forward over all of the calls. Note that we cannot cache the size as
   // inlining can introduce new calls that need to be processed.
   for (int I = 0; I < (int)Calls.size(); ++I) {
+    auto *FirstCB = dyn_cast_or_null<CallBase>(Calls[I]);
+    if (!FirstCB)
+      continue;
+
     // We expect the calls to typically be batched with sequences of calls that
     // have the same caller, so we first set up some shared infrastructure for
     // this caller. We also do any pruning we can at this layer on the caller
     // alone.
-    Function &F = *Calls[I]->getCaller();
+    Function &F = *FirstCB->getCaller();
     LazyCallGraph::Node &N = *CG.lookup(F);
     if (CG.lookupSCC(N) != C)
       continue;
@@ -315,24 +359,13 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
     // We bail out as soon as the caller has to change so we can update the
     // call graph and prepare the context of that new caller.
     bool DidInline = false;
-    for (; I < (int)Calls.size() && Calls[I]->getCaller() == &F; ++I) {
-      CallBase *CB = Calls[I];
-      Function &Callee = *CB->getCalledFunction();
-
-      // Check if this inlining may repeat breaking an SCC apart that has
-      // already been split once before. In that case, inlining here may
-      // trigger infinite inlining, much like is prevented within the inliner
-      // itself by the InlineHistory above, but spread across CGSCC iterations
-      // and thus hidden from the full inline history.
-      LazyCallGraph::Node &CalleeN = *CG.lookup(Callee);
-      LazyCallGraph::SCC *CalleeSCC = CG.lookupSCC(CalleeN);
-      if (CalleeSCC == C && UR.InlinedInternalEdges.count({&N, C})) {
-        LLVM_DEBUG(dbgs() << "Skipping inlining internal SCC edge from a node "
-                             "previously split out of this SCC by inlining: "
-                          << F.getName() << " -> " << Callee.getName() << "\n");
-        setInlineRemark(*CB, "recursive SCC split");
+    bool DidPromote = false;
+    for (; I < (int)Calls.size(); ++I) {
+      auto *CB = dyn_cast_or_null<CallBase>(Calls[I]);
+      if (!CB)
         continue;
-      }
+      if (CB->getCaller() != &F)
+        break;
 
       // Store-to-load forwarding, loads can be sometimes simplified to
       // constants from stores introduced by previous inlining
@@ -362,6 +395,46 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
         }
       }
 
+      if (!CB->getCalledFunction()) {
+        SmallVector<CallBase *> PromotedCalls;
+        if (promoteInlineableIndirectCall(*CB, Advisor, OnlyMandatory,
+                                          PromotedCalls)) {
+          Calls.insert(Calls.begin() + I + 1, PromotedCalls.size(),
+                       WeakTrackingVH());
+          for (auto [Index, PromotedCall] : llvm::enumerate(PromotedCalls))
+            Calls[I + 1 + Index] = PromotedCall;
+          DidPromote = true;
+          FAM.invalidate(F, PreservedAnalyses::none());
+          // Stop processing this caller so the newly created direct call
+          // edges are reflected in the lazy call graph before they reach
+          // InlineFunction. Advance past the original indirect call; after
+          // the adjustment below, the outer loop resumes at the first
+          // promoted call.
+          ++I;
+          break;
+        }
+        continue;
+      }
+
+      assert(CB->getCalledFunction() &&
+             "expected a direct call after indirect-call handling");
+      Function &Callee = *CB->getCalledFunction();
+
+      // Check if this inlining may repeat breaking an SCC apart that has
+      // already been split once before. In that case, inlining here may
+      // trigger infinite inlining, much like is prevented within the inliner
+      // itself by the InlineHistory above, but spread across CGSCC iterations
+      // and thus hidden from the full inline history.
+      LazyCallGraph::Node &CalleeN = *CG.lookup(Callee);
+      LazyCallGraph::SCC *CalleeSCC = CG.lookupSCC(CalleeN);
+      if (CalleeSCC == C && UR.InlinedInternalEdges.count({&N, C})) {
+        LLVM_DEBUG(dbgs() << "Skipping inlining internal SCC edge from a node "
+                             "previously split out of this SCC by inlining: "
+                          << F.getName() << " -> " << Callee.getName() << "\n");
+        setInlineRemark(*CB, "recursive SCC split");
+        continue;
+      }
+
       std::unique_ptr<InlineAdvice> Advice =
           Advisor.getAdvice(*CB, OnlyMandatory);
 
@@ -466,8 +539,10 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
           !CG.isLibFunction(Callee)) {
         if (Callee.hasLocalLinkage() || !Callee.hasComdat()) {
           Calls.erase(std::remove_if(Calls.begin() + I + 1, Calls.end(),
-                                     [&](const CallBase *CB) {
-                                       return CB->getCaller() == &Callee;
+                                     [&](const WeakTrackingVH &Call) {
+                                       auto *CB =
+                                           dyn_cast_or_null<CallBase>(Call);
+                                       return !CB || CB->getCaller() == &Callee;
                                      }),
                       Calls.end());
 
@@ -493,7 +568,7 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
     // the outer loop.
     --I;
 
-    if (!DidInline)
+    if (!DidInline && !DidPromote)
       continue;
     Changed = true;
 
diff --git a/llvm/test/Transforms/Inline/indirect-call.ll b/llvm/test/Transforms/Inline/indirect-call.ll
new file mode 100644
index 0000000000000..9ff9d90e0da74
--- /dev/null
+++ b/llvm/test/Transforms/Inline/indirect-call.ll
@@ -0,0 +1,452 @@
+; RUN: opt < %s -passes='default<O2>' -S | FileCheck %s
+; RUN: opt < %s -passes='cgscc(inline)' -icp-max-prom=1 -S | \
+; RUN:   FileCheck %s --check-prefix=LIMIT
+; RUN: opt < %s -passes='cgscc(inline)' \
+; RUN:   -icp-max-static-target-traversal=2 -S | \
+; RUN:   FileCheck %s --check-prefix=TRAVERSAL
+; RUN: opt < %s -passes='cgscc(inline<only-mandatory>)' -S | \
+; RUN:   FileCheck %s --check-prefix=MANDATORY
+; RUN: opt < %s -passes='cgscc(inline)' -S | \
+; RUN:   FileCheck %s --check-prefix=DYNAMIC
+; RUN: opt < %s -passes='cgscc(inline),simplifycfg' -S | \
+; RUN:   FileCheck %s --check-prefix=ORDER
+
+ at fnptr = global ptr @foo
+ at foo_alias = alias i32 (), ptr @foo
+ at foo_ifunc = ifunc i32 (), ptr @resolve_foo
+
+define i32 @foo() {
+  ret i32 1
+}
+
+define i32 @bar() {
+  ret i32 2
+}
+
+define ptr @resolve_foo() {
+  ret ptr @foo
+}
+
+declare ptr @get_fnptr()
+
+define i32 @always_foo() alwaysinline {
+  ret i32 1
+}
+
+define i32 @always_bar() alwaysinline {
+  ret i32 2
+}
+
+define i32 @musttail_foo(i1 %c) {
+  ret i32 1
+}
+
+define i32 @musttail_bar(i1 %c) {
+  ret i32 2
+}
+
+define i32 @addrspace_function() addrspace(1) {
+  ret i32 3
+}
+
+define i32 @baz() {
+  ret i32 3
+}
+
+define i32 @qux() {
+  ret i32 4
+}
+
+define i32 @quux() {
+  ret i32 5
+}
+
+define i32 @select_callee(i1 %c) {
+; CHECK-LABEL: define {{.*}}i32 @select_callee(
+; CHECK-NOT: call
+; CHECK: select i1 %c, i32 1, i32 2
+; CHECK: ret i32
+; LIMIT-LABEL: define i32 @select_callee(
+; LIMIT: %callee = select i1 %c, ptr @foo, ptr @bar
+; LIMIT: call i32 %callee()
+; TRAVERSAL-LABEL: define i32 @select_callee(
+; TRAVERSAL: %callee = select i1 %c, ptr @foo, ptr @bar
+; TRAVERSAL: call i32 %callee()
+  %callee = select i1 %c, ptr @foo, ptr @bar
+  %result = call i32 %callee()
+  ret i32 %result
+}
+
+define i32 @mandatory_select_callee(i1 %c) {
+; MANDATORY-LABEL: define i32 @mandatory_select_callee(
+; MANDATORY-NOT: call i32
+; MANDATORY: phi i32 [ 2, %if.false.orig_indirect ], [ 1, %if.true.direct_targ ]
+; MANDATORY: ret i32
+  %callee = select i1 %c, ptr @always_foo, ptr @always_bar
+  %result = call i32 %callee()
+  ret i32 %result
+}
+
+define i32 @adjacent_indirect_calls(i1 %c1, i1 %c2) {
+; CHECK-LABEL: define {{.*}}i32 @adjacent_indirect_calls(
+; CHECK-NOT: call
+; CHECK: %[[FIRST:.*]] = select i1 %c1, i32 1, i32 2
+; CHECK: %[[SECOND:.*]] = select i1 %c2, i32 1, i32 2
+; CHECK: %sum = add {{.*}}i32
+  %callee1 = select i1 %c1, ptr @foo, ptr @bar
+  %result1 = call i32 %callee1()
+  %callee2 = select i1 %c2, ptr @foo, ptr @bar
+  %result2 = call i32 %callee2()
+  %sum = add i32 %result1, %result2
+  ret i32 %sum
+}
+
+declare i32 @__gxx_personality_v0(...)
+
+define i32 @invoke_callee(i1 %c) personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: define {{.*}}i32 @invoke_callee(
+; CHECK-NOT: call
+; CHECK-NOT: invoke
+; CHECK: select i1 %c, i32 1, i32 2
+; CHECK: ret...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list