[llvm] [TailRecElim] Introduce support for shift accumulator optimization (PR #181331)

Federico Bruzzone via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 09:01:47 PDT 2026


https://github.com/FedericoBruzzone updated https://github.com/llvm/llvm-project/pull/181331

>From 433ff0fe41b120693036008a922c8cf943f6b45a Mon Sep 17 00:00:00 2001
From: FedericoBruzzone <federico.bruzzone.i at gmail.com>
Date: Tue, 17 Feb 2026 20:11:35 +0100
Subject: [PATCH 01/11] [TailRecElim] Add support for shift accumulator
 optimization

Signed-off-by: FedericoBruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 193 +++++++++++++++---
 .../TailCallElim/shl-accumulator-opt.ll       | 118 +++++++++++
 2 files changed, 287 insertions(+), 24 deletions(-)
 create mode 100644 llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 98ba75d1d45ae..509f2b849df35 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -82,6 +82,7 @@
 #include "llvm/Transforms/Scalar.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include <cmath>
+#include <variant>
 using namespace llvm;
 
 #define DEBUG_TYPE "tailcallelim"
@@ -374,28 +375,136 @@ static bool canMoveAboveCall(Instruction *I, CallInst *CI, AliasAnalysis *AA) {
   return !is_contained(I->operands(), CI);
 }
 
-static bool canTransformAccumulatorRecursion(Instruction *I, CallInst *CI) {
-  if (!I->isAssociative() || !I->isCommutative())
+// While shifts are neither associative nor commutative, a chain of shifts by a
+// constant amount C is equivalent to a single shift by the sum of the amounts:
+//     ... (Base << C) << C) ... << C == Base << (C * Iterations)
+// This relation applies to left shifts as well as arithmetic/logical right
+// shifts when the shift amount is a constant.
+static bool isPseudoAssociative(Instruction *I) {
+  switch (I->getOpcode()) {
+  case Instruction::Shl:
+  case Instruction::AShr:
+  case Instruction::LShr:
+    break;
+  default:
+    return false;
+  }
+
+  ConstantInt *CI = dyn_cast<ConstantInt>(I->getOperand(1));
+  if (!CI)
+    return false;
+
+  return true;
+}
+
+// Find the base-case return value for function F: examine all
+// return instructions and pick return values that do not depend on a
+// recursive call to F. If there is exactly one distinct such value,
+// return it. If there are none or more than one distinct value, return
+// nullptr to indicate failure.
+//
+// FIXME: There is a room for improvement here in the future, e.g., consider
+// non-constant values and multiple base cases -- e.g., we want to be able to
+// handle code like:
+// ```
+// int f(int x) {
+//  if (x == 1) return 1;
+//  if (x == 10) return 10;
+//  return f(x-1) << 1;
+// }
+// ```
+static Constant *getReturnValue(Function &F) {
+  Constant *BaseCaseVal = nullptr;
+
+  auto ValueUsesRecursiveCall = [&](Value *V) {
+    SmallVector<Value *, 8> Worklist;
+    SmallPtrSet<Value *, 8> Visited;
+    Worklist.push_back(V);
+    while (!Worklist.empty()) {
+      Value *Cur = Worklist.pop_back_val();
+      if (!Visited.insert(Cur).second)
+        continue;
+      if (Instruction *I = dyn_cast<Instruction>(Cur)) {
+        if (CallInst *CI = dyn_cast<CallInst>(I))
+          if (CI->getCalledFunction() == &F)
+            return true;
+        for (Use &U : I->operands())
+          Worklist.push_back(U.get());
+      } else if (ConstantExpr *CE = dyn_cast<ConstantExpr>(Cur)) {
+        for (unsigned I = 0, E = CE->getNumOperands(); I != E; ++I)
+          Worklist.push_back(CE->getOperand(I));
+      }
+    }
+    return false;
+  };
+
+  for (BasicBlock &BB : F) {
+    if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator())) {
+      Value *RV = RI->getReturnValue();
+      if (!RV)
+        continue;
+      // It isn't so trivial to perform TRE if the return value depends on the
+      // result of a recursive call, because the return value will be different
+      // for different iterations of the recursion. So we ignore return values
+      // that depend on recursive calls.
+      if (ValueUsesRecursiveCall(RV))
+        continue;
+      if (!BaseCaseVal && isa<Constant>(RV))
+        BaseCaseVal = cast<Constant>(RV);
+      else if (BaseCaseVal != RV)
+        return nullptr;
+    }
+  }
+
+  return BaseCaseVal;
+}
+
+// This function checks whether the instruction I can be used
+// to perform accumulator recursion elimination for the
+// call instruction CI.
+// In the presence of pseudo-associative operations, it returns
+// the base case constant value to both indicate success and
+// provide the value needed to initialize the accumulator.
+static std::variant<bool, Constant *>
+canTransformAccumulatorRecursion(Instruction *I, CallInst *CI) {
+  auto IsPseudoAssociative = isPseudoAssociative(I);
+  if ((!I->isAssociative() || !I->isCommutative()) && !IsPseudoAssociative)
     return false;
 
   assert(I->getNumOperands() >= 2 &&
          "Associative/commutative operations should have at least 2 args!");
 
-  if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
-    // Accumulators must have an identity.
-    if (!ConstantExpr::getIntrinsicIdentity(II->getIntrinsicID(), I->getType()))
+  Constant *BaseCaseConst = nullptr;
+  if (IsPseudoAssociative) {
+    // For pseudo-associative operations, we require that the recursive call
+    // is always on the first operand.
+    if (I->getOperand(0) != CI)
       return false;
-  }
 
-  // Exactly one operand should be the result of the call instruction.
-  if ((I->getOperand(0) == CI && I->getOperand(1) == CI) ||
-      (I->getOperand(0) != CI && I->getOperand(1) != CI))
-    return false;
+    BaseCaseConst = getReturnValue(*CI->getCalledFunction());
+    if (!BaseCaseConst)
+      return false;
+  } else {
+    if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
+      // Accumulators must have an identity.
+      if (!ConstantExpr::getIntrinsicIdentity(II->getIntrinsicID(),
+                                              I->getType()))
+        return false;
+    }
+
+    // Exactly one operand should be the result of the call instruction.
+    if ((I->getOperand(0) == CI && I->getOperand(1) == CI) ||
+        (I->getOperand(0) != CI && I->getOperand(1) != CI))
+      return false;
+  }
 
   // The only user of this instruction we allow is a single return instruction.
   if (!I->hasOneUse() || !isa<ReturnInst>(I->user_back()))
     return false;
 
+  if (BaseCaseConst)
+    return BaseCaseConst;
+
   return true;
 }
 
@@ -436,6 +545,10 @@ class TailRecursionEliminator {
   // The instruction doing the accumulating.
   Instruction *AccumulatorRecursionInstr = nullptr;
 
+  // The base case return value. It exists only if the accumulator
+  // operation is pseudo-associative.
+  Constant *BaseCaseValue = nullptr;
+
   TailRecursionEliminator(Function &F, const TargetTransformInfo *TTI,
                           AliasAnalysis *AA, OptimizationRemarkEmitter *ORE,
                           DomTreeUpdater &DTU, BlockFrequencyInfo *BFI)
@@ -594,9 +707,17 @@ void TailRecursionEliminator::insertAccumulator(Instruction *AccRecInstr) {
   for (pred_iterator PI = PB; PI != PE; ++PI) {
     BasicBlock *P = *PI;
     if (P == &F.getEntryBlock()) {
-      Constant *Identity =
+      Constant *InitialValue =
           ConstantExpr::getIdentity(AccRecInstr, AccRecInstr->getType());
-      AccPN->addIncoming(Identity, P);
+      if (!InitialValue) {
+        // We are in the presence of a pseudo-associative operation like shift.
+        // We didn't pass `AllowRHSConstant = true` in getIdentity above because
+        // the identity for shifts is zero, which is not valid for the LHS.
+        // We need the value of the base case(s) of the function to
+        // initialize the accumulator.
+        InitialValue = BaseCaseValue;
+      }
+      AccPN->addIncoming(InitialValue, P);
     } else {
       AccPN->addIncoming(AccPN, P);
     }
@@ -667,15 +788,24 @@ bool TailRecursionEliminator::eliminateCall(CallInst *CI) {
       continue;
 
     // If we can't move the instruction above the call, it might be because it
-    // is an associative and commutative operation that could be transformed
-    // using accumulator recursion elimination.  Check to see if this is the
-    // case, and if so, remember which instruction accumulates for later.
-    if (AccPN || !canTransformAccumulatorRecursion(&*BBI, CI))
+    // is an (associative and commutative) or pseudo-associative arithmetic
+    // operation that could be transformed using accumulator recursion
+    // elimination. Check to see if this is the case, and if so, remember which
+    // instruction accumulates for later.
+    std::variant<bool, Constant *> AccRecResult =
+        canTransformAccumulatorRecursion(&*BBI, CI);
+
+    if (AccPN || (std::holds_alternative<bool>(AccRecResult) &&
+                  !std::get<bool>(AccRecResult)))
       return false; // We cannot eliminate the tail recursion!
 
     // Yes, this is accumulator recursion.  Remember which instruction
     // accumulates.
     AccRecInstr = &*BBI;
+
+    // Keep track of the base case return value if any.
+    if (std::holds_alternative<Constant *>(AccRecResult))
+      BaseCaseValue = std::get<Constant *>(AccRecResult);
   }
 
   BasicBlock *BB = Ret->getParent();
@@ -818,13 +948,26 @@ void TailRecursionEliminator::cleanupAndFinalize() {
           if (!RI)
             continue;
 
-          Instruction *AccRecInstrNew = AccRecInstr->clone();
-          AccRecInstrNew->setName("accumulator.ret.tr");
-          AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
-                                     RI->getOperand(0));
-          AccRecInstrNew->insertBefore(RI->getIterator());
-          AccRecInstrNew->dropLocation();
-          RI->setOperand(0, AccRecInstrNew);
+          // Again, the BaseCaseValue exists only if the accumulator
+          // operation is pseudo-associative. In that case the accumulator PHI
+          // already holds the final result (it is initialized from the base
+          // case), so we should return it directly rather than applying the
+          // accumulation instruction one more time.
+          if (BaseCaseValue) {
+            RI->setOperand(0, AccPN);
+          } else {
+            // Otherwise, since the accumulator starts with the identity value,
+            // before the return we need to apply the accumulation instruction
+            // one more time to combine the last value with the result of the
+            // recursive call.
+            Instruction *AccRecInstrNew = AccRecInstr->clone();
+            AccRecInstrNew->setName("accumulator.ret.tr");
+            AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
+                                       RI->getOperand(0));
+            AccRecInstrNew->insertBefore(RI->getIterator());
+            AccRecInstrNew->dropLocation();
+            RI->setOperand(0, AccRecInstrNew);
+          }
         }
       }
     } else {
@@ -891,7 +1034,9 @@ bool TailRecursionEliminator::processBlock(BasicBlock &BB) {
 
     eliminateCall(CI);
     return true;
-  } else if (isa<ReturnInst>(TI)) {
+  }
+
+  if (isa<ReturnInst>(TI)) {
     CallInst *CI = findTRECandidate(&BB);
 
     if (CI)
diff --git a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
new file mode 100644
index 0000000000000..257a885672cf6
--- /dev/null
+++ b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
@@ -0,0 +1,118 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes="tailcallelim" -verify-dom-info -S | FileCheck %s
+
+; NOTE: All the following test cases are generate from the underlying C code (-O1)
+;   before that the shift accumulator optimization was implemented
+
+
+
+; InstCombine strength-reduce `f(x-1) + f(x-1)` to shl:
+; int f(int x) {
+;     if (x == 1) return 7;
+;     return f(x-1) + f(x-1); // f(x-1) * 2
+; }
+define dso_local i32 @f(i32 noundef %x) local_unnamed_addr {
+; CHECK-LABEL: define dso_local i32 @f(
+; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
+; CHECK:       [[TAILRECURSE]]:
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 7, %[[ENTRY]] ], [ [[ADD:%.*]], %[[IF_END:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
+; CHECK-NEXT:    [[ADD]] = shl nsw i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    br label %[[TAILRECURSE]]
+;
+entry:
+  %cmp = icmp eq i32 %x, 1
+  br i1 %cmp, label %common.ret, label %if.end
+
+common.ret:
+  %common.ret.op = phi i32 [ %add, %if.end ], [ 7, %entry ]
+  ret i32 %common.ret.op
+
+if.end:
+  %sub = add nsw i32 %x, -1
+  %call = tail call i32 @f(i32 noundef %sub)
+  %add = shl nsw i32 %call, 1
+  br label %common.ret
+}
+
+
+; int f2(int x) {
+;     if (x == 1) return 14;
+;     return f2(x-1) >> 1;
+; }
+define dso_local i32 @f2(i32 noundef %x) local_unnamed_addr {
+; CHECK-LABEL: define dso_local i32 @f2(
+; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
+; CHECK:       [[TAILRECURSE]]:
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 14, %[[ENTRY]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
+; CHECK-NEXT:    [[SHR]] = ashr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    br label %[[TAILRECURSE]]
+;
+entry:
+  %cmp = icmp eq i32 %x, 1
+  br i1 %cmp, label %common.ret, label %if.end
+
+common.ret:
+  %common.ret.op = phi i32 [ %shr, %if.end ], [ 14, %entry ]
+  ret i32 %common.ret.op
+
+if.end:
+  %sub = add nsw i32 %x, -1
+  %call = tail call i32 @f2(i32 noundef %sub)
+  %shr = ashr i32 %call, 1
+  br label %common.ret
+}
+
+
+; unsigned int f3(unsigned int x) {
+;     if (x <= 1) return 14;
+;     return f3(x - 1) >> 1;
+; }
+define dso_local i32 @f3(i32 noundef %x) local_unnamed_addr {
+; CHECK-LABEL: define dso_local i32 @f3(
+; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
+; CHECK:       [[TAILRECURSE]]:
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 21, %[[ENTRY]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X_TR]], 2
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[SUB]] = add i32 [[X_TR]], -1
+; CHECK-NEXT:    [[SHR]] = lshr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    br label %[[TAILRECURSE]]
+;
+entry:
+  %cmp = icmp ult i32 %x, 2
+  br i1 %cmp, label %common.ret, label %if.end
+
+common.ret:
+  %common.ret.op = phi i32 [ %shr, %if.end ], [ 21, %entry ]
+  ret i32 %common.ret.op
+
+if.end:
+  %sub = add i32 %x, -1
+  %call = tail call i32 @f3(i32 noundef %sub)
+  %shr = lshr i32 %call, 1
+  br label %common.ret
+}

>From 26c4f6e6b77a1205040f2246512312daca9d2ca7 Mon Sep 17 00:00:00 2001
From: FedericoBruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 18 Feb 2026 17:21:00 +0100
Subject: [PATCH 02/11] Add negative tests and remove useless attrs

Signed-off-by: FedericoBruzzone <federico.bruzzone.i at gmail.com>
---
 .../TailCallElim/shl-accumulator-opt.ll       | 88 +++++++++++++++++--
 1 file changed, 79 insertions(+), 9 deletions(-)

diff --git a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
index 257a885672cf6..de615b8664d18 100644
--- a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
+++ b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
@@ -11,9 +11,9 @@
 ;     if (x == 1) return 7;
 ;     return f(x-1) + f(x-1); // f(x-1) * 2
 ; }
-define dso_local i32 @f(i32 noundef %x) local_unnamed_addr {
-; CHECK-LABEL: define dso_local i32 @f(
-; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+define i32 @f(i32 %x) {
+; CHECK-LABEL: define i32 @f(
+; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
 ; CHECK:       [[TAILRECURSE]]:
@@ -48,9 +48,9 @@ if.end:
 ;     if (x == 1) return 14;
 ;     return f2(x-1) >> 1;
 ; }
-define dso_local i32 @f2(i32 noundef %x) local_unnamed_addr {
-; CHECK-LABEL: define dso_local i32 @f2(
-; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+define i32 @f2(i32 %x) {
+; CHECK-LABEL: define i32 @f2(
+; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
 ; CHECK:       [[TAILRECURSE]]:
@@ -85,9 +85,9 @@ if.end:
 ;     if (x <= 1) return 14;
 ;     return f3(x - 1) >> 1;
 ; }
-define dso_local i32 @f3(i32 noundef %x) local_unnamed_addr {
-; CHECK-LABEL: define dso_local i32 @f3(
-; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr {
+define i32 @f3(i32 %x) {
+; CHECK-LABEL: define i32 @f3(
+; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
 ; CHECK:       [[TAILRECURSE]]:
@@ -116,3 +116,73 @@ if.end:
   %shr = lshr i32 %call, 1
   br label %common.ret
 }
+
+; Negative test: non-constant shift amount prevents shift-accumulator optimization
+; int f4(int x, int k) {
+;   if (x == 1) return 7;
+;   return f4(x-1, k) << k; // variable shift amount
+; }
+define i32 @f4(i32 %x, i32 %k) {
+; CHECK-LABEL: define i32 @f4(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[K:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 7
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
+; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @f4(i32 [[SUB]], i32 [[K]])
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[CALL]], [[K]]
+; CHECK-NEXT:    ret i32 [[SHL]]
+;
+entry:
+  %cmp = icmp eq i32 %x, 1
+  br i1 %cmp, label %common.ret, label %if.end
+
+common.ret:
+  %common.ret.op = phi i32 [ %shl, %if.end ], [ 7, %entry ]
+  ret i32 %common.ret.op
+
+if.end:
+  %sub = add nsw i32 %x, -1
+  %call = tail call i32 @f4(i32 %sub, i32 %k)
+  %shl = shl i32 %call, %k
+  br label %common.ret
+}
+
+; Negative test: intervening operation on result prevents forming clean accumulator
+; int f5(int x) {
+;   if (x == 1) return 7;
+;   return (f5(x-1) + 1) << 1; // extra add breaks accumulator invariant
+; }
+define i32 @f5(i32 %x) {
+; CHECK-LABEL: define i32 @f5(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret i32 7
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
+; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @f5(i32 [[SUB]])
+; CHECK-NEXT:    [[TMP:%.*]] = add i32 [[CALL]], 1
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[TMP]], 1
+; CHECK-NEXT:    ret i32 [[SHL]]
+;
+entry:
+  %cmp = icmp eq i32 %x, 1
+  br i1 %cmp, label %common.ret, label %if.end
+
+common.ret:
+  %common.ret.op = phi i32 [ %shl, %if.end ], [ 7, %entry ]
+  ret i32 %common.ret.op
+
+if.end:
+  %sub = add nsw i32 %x, -1
+  %call = tail call i32 @f5(i32 %sub)
+  %tmp = add i32 %call, 1
+  %shl = shl i32 %tmp, 1
+  br label %common.ret
+}

>From dc33db48349182e19240ed25422ffbb0d4a12668 Mon Sep 17 00:00:00 2001
From: FedericoBruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 18 Feb 2026 18:01:53 +0100
Subject: [PATCH 03/11] Remove `ConstantExprs` and simplify
 `isPseudoAssociative`

Signed-off-by: FedericoBruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 20 +++----------------
 1 file changed, 3 insertions(+), 17 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 509f2b849df35..32be213cd0f62 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -381,20 +381,9 @@ static bool canMoveAboveCall(Instruction *I, CallInst *CI, AliasAnalysis *AA) {
 // This relation applies to left shifts as well as arithmetic/logical right
 // shifts when the shift amount is a constant.
 static bool isPseudoAssociative(Instruction *I) {
-  switch (I->getOpcode()) {
-  case Instruction::Shl:
-  case Instruction::AShr:
-  case Instruction::LShr:
-    break;
-  default:
-    return false;
-  }
-
-  ConstantInt *CI = dyn_cast<ConstantInt>(I->getOperand(1));
-  if (!CI)
-    return false;
-
-  return true;
+    if (!I->isShift())
+      return false;
+    return isa<ConstantInt>(I->getOperand(1));
 }
 
 // Find the base-case return value for function F: examine all
@@ -430,9 +419,6 @@ static Constant *getReturnValue(Function &F) {
             return true;
         for (Use &U : I->operands())
           Worklist.push_back(U.get());
-      } else if (ConstantExpr *CE = dyn_cast<ConstantExpr>(Cur)) {
-        for (unsigned I = 0, E = CE->getNumOperands(); I != E; ++I)
-          Worklist.push_back(CE->getOperand(I));
       }
     }
     return false;

>From 5d0f40a08f1566572f8cebe77427942ab4f3c6ff Mon Sep 17 00:00:00 2001
From: FedericoBruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 18 Feb 2026 18:42:42 +0100
Subject: [PATCH 04/11] Replace the use of use-def chains with simpler matches

Signed-off-by: FedericoBruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 69 +++++++++++--------
 .../TailCallElim/shl-accumulator-opt.ll       | 63 ++++++++---------
 2 files changed, 66 insertions(+), 66 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 32be213cd0f62..8e12a49aeb814 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -381,9 +381,9 @@ static bool canMoveAboveCall(Instruction *I, CallInst *CI, AliasAnalysis *AA) {
 // This relation applies to left shifts as well as arithmetic/logical right
 // shifts when the shift amount is a constant.
 static bool isPseudoAssociative(Instruction *I) {
-    if (!I->isShift())
-      return false;
-    return isa<ConstantInt>(I->getOperand(1));
+  if (!I->isShift())
+    return false;
+  return isa<ConstantInt>(I->getOperand(1));
 }
 
 // Find the base-case return value for function F: examine all
@@ -405,39 +405,48 @@ static bool isPseudoAssociative(Instruction *I) {
 static Constant *getReturnValue(Function &F) {
   Constant *BaseCaseVal = nullptr;
 
-  auto ValueUsesRecursiveCall = [&](Value *V) {
-    SmallVector<Value *, 8> Worklist;
-    SmallPtrSet<Value *, 8> Visited;
-    Worklist.push_back(V);
-    while (!Worklist.empty()) {
-      Value *Cur = Worklist.pop_back_val();
-      if (!Visited.insert(Cur).second)
-        continue;
-      if (Instruction *I = dyn_cast<Instruction>(Cur)) {
-        if (CallInst *CI = dyn_cast<CallInst>(I))
-          if (CI->getCalledFunction() == &F)
-            return true;
-        for (Use &U : I->operands())
-          Worklist.push_back(U.get());
+  // Local lambda with conservative bail-out: It isn't so trivial to perform TRE
+  // if the return value depends on the result of a recursive call, because the
+  // return value will be different for different iterations of the recursion.
+  // So we ignore return values that depend on recursive calls.
+  auto TryGetConstantBaseCase = [&](Value *RV) -> Constant * {
+    // Case 1: direct constant return.
+    if (auto *C = dyn_cast<Constant>(RV))
+      return C;
+
+    // Case 2: PHI with one constant incoming.
+    if (auto *PN = dyn_cast<PHINode>(RV)) {
+      if (PN->getNumIncomingValues() != 2)
+        return nullptr;
+      for (unsigned I = 0; I < 2; ++I) {
+        if (auto *C = dyn_cast<Constant>(PN->getIncomingValue(I)))
+          return C;
       }
+      return nullptr;
     }
-    return false;
+
+    // Case 3: select with a constant arm.
+    if (auto *SI = dyn_cast<SelectInst>(RV)) {
+      if (auto *CTrue = dyn_cast<Constant>(SI->getTrueValue()))
+        return CTrue;
+      if (auto *CFalse = dyn_cast<Constant>(SI->getFalseValue()))
+        return CFalse;
+      return nullptr;
+    }
+
+    return nullptr;
   };
 
   for (BasicBlock &BB : F) {
-    if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator())) {
+    if (auto *RI = dyn_cast<ReturnInst>(BB.getTerminator())) {
       Value *RV = RI->getReturnValue();
-      if (!RV)
-        continue;
-      // It isn't so trivial to perform TRE if the return value depends on the
-      // result of a recursive call, because the return value will be different
-      // for different iterations of the recursion. So we ignore return values
-      // that depend on recursive calls.
-      if (ValueUsesRecursiveCall(RV))
-        continue;
-      if (!BaseCaseVal && isa<Constant>(RV))
-        BaseCaseVal = cast<Constant>(RV);
-      else if (BaseCaseVal != RV)
+      Constant *Candidate = TryGetConstantBaseCase(RV);
+      if (!Candidate)
+        return nullptr;
+
+      if (!BaseCaseVal)
+        BaseCaseVal = Candidate;
+      else if (BaseCaseVal != Candidate)
         return nullptr;
     }
   }
diff --git a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
index de615b8664d18..81ba00e073b85 100644
--- a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
+++ b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
@@ -14,19 +14,16 @@
 define i32 @f(i32 %x) {
 ; CHECK-LABEL: define i32 @f(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*]]:
-; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
-; CHECK:       [[TAILRECURSE]]:
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 7, %[[ENTRY]] ], [ [[ADD:%.*]], %[[IF_END:.*]] ]
-; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK-NEXT:  [[TAILRECURSE:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK-NEXT:    ret i32 7
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
-; CHECK-NEXT:    [[ADD]] = shl nsw i32 [[ACCUMULATOR_TR]], 1
-; CHECK-NEXT:    br label %[[TAILRECURSE]]
+; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f(i32 [[SUB]])
+; CHECK-NEXT:    [[ADD:%.*]] = shl nsw i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    ret i32 [[ADD]]
 ;
 entry:
   %cmp = icmp eq i32 %x, 1
@@ -38,7 +35,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f(i32 noundef %sub)
+  %call = tail call i32 @f(i32 %sub)
   %add = shl nsw i32 %call, 1
   br label %common.ret
 }
@@ -51,19 +48,16 @@ if.end:
 define i32 @f2(i32 %x) {
 ; CHECK-LABEL: define i32 @f2(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*]]:
-; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
-; CHECK:       [[TAILRECURSE]]:
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 14, %[[ENTRY]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
-; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK-NEXT:  [[TAILRECURSE:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK-NEXT:    ret i32 14
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
-; CHECK-NEXT:    [[SHR]] = ashr i32 [[ACCUMULATOR_TR]], 1
-; CHECK-NEXT:    br label %[[TAILRECURSE]]
+; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f2(i32 [[SUB]])
+; CHECK-NEXT:    [[SHR:%.*]] = ashr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    ret i32 [[SHR]]
 ;
 entry:
   %cmp = icmp eq i32 %x, 1
@@ -75,7 +69,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f2(i32 noundef %sub)
+  %call = tail call i32 @f2(i32 %sub)
   %shr = ashr i32 %call, 1
   br label %common.ret
 }
@@ -88,19 +82,16 @@ if.end:
 define i32 @f3(i32 %x) {
 ; CHECK-LABEL: define i32 @f3(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*]]:
-; CHECK-NEXT:    br label %[[TAILRECURSE:.*]]
-; CHECK:       [[TAILRECURSE]]:
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 21, %[[ENTRY]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
-; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[ENTRY]] ], [ [[SUB:%.*]], %[[IF_END]] ]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X_TR]], 2
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
+; CHECK-NEXT:  [[TAILRECURSE:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 2
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
+; CHECK-NEXT:    ret i32 21
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB]] = add i32 [[X_TR]], -1
-; CHECK-NEXT:    [[SHR]] = lshr i32 [[ACCUMULATOR_TR]], 1
-; CHECK-NEXT:    br label %[[TAILRECURSE]]
+; CHECK-NEXT:    [[SUB:%.*]] = add i32 [[X]], -1
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f3(i32 [[SUB]])
+; CHECK-NEXT:    [[SHR:%.*]] = lshr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    ret i32 [[SHR]]
 ;
 entry:
   %cmp = icmp ult i32 %x, 2
@@ -112,7 +103,7 @@ common.ret:
 
 if.end:
   %sub = add i32 %x, -1
-  %call = tail call i32 @f3(i32 noundef %sub)
+  %call = tail call i32 @f3(i32 %sub)
   %shr = lshr i32 %call, 1
   br label %common.ret
 }

>From 3f86e75be313d2b1676edcd86fe83ef031c2a3e3 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Tue, 14 Apr 2026 11:58:02 +0200
Subject: [PATCH 05/11] Replace `std::variant` with a structured return type

---
 .../Scalar/TailRecursionElimination.cpp       | 40 ++++++++++---------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 8e12a49aeb814..4995f740c0a69 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -82,7 +82,6 @@
 #include "llvm/Transforms/Scalar.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include <cmath>
-#include <variant>
 using namespace llvm;
 
 #define DEBUG_TYPE "tailcallelim"
@@ -454,17 +453,24 @@ static Constant *getReturnValue(Function &F) {
   return BaseCaseVal;
 }
 
+namespace {
+struct AccumulatorRecursionInfo {
+  bool CanTransform = false;
+  Constant *BaseCaseConst = nullptr;
+};
+} // namespace
+
 // This function checks whether the instruction I can be used
 // to perform accumulator recursion elimination for the
 // call instruction CI.
 // In the presence of pseudo-associative operations, it returns
-// the base case constant value to both indicate success and
+// the base case constant value in BaseCaseConst to both indicate success and
 // provide the value needed to initialize the accumulator.
-static std::variant<bool, Constant *>
-canTransformAccumulatorRecursion(Instruction *I, CallInst *CI) {
+static AccumulatorRecursionInfo canTransformAccumulatorRecursion(Instruction *I,
+                                                                 CallInst *CI) {
   auto IsPseudoAssociative = isPseudoAssociative(I);
   if ((!I->isAssociative() || !I->isCommutative()) && !IsPseudoAssociative)
-    return false;
+    return {};
 
   assert(I->getNumOperands() >= 2 &&
          "Associative/commutative operations should have at least 2 args!");
@@ -474,33 +480,30 @@ canTransformAccumulatorRecursion(Instruction *I, CallInst *CI) {
     // For pseudo-associative operations, we require that the recursive call
     // is always on the first operand.
     if (I->getOperand(0) != CI)
-      return false;
+      return {};
 
     BaseCaseConst = getReturnValue(*CI->getCalledFunction());
     if (!BaseCaseConst)
-      return false;
+      return {};
   } else {
     if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
       // Accumulators must have an identity.
       if (!ConstantExpr::getIntrinsicIdentity(II->getIntrinsicID(),
                                               I->getType()))
-        return false;
+        return {};
     }
 
     // Exactly one operand should be the result of the call instruction.
     if ((I->getOperand(0) == CI && I->getOperand(1) == CI) ||
         (I->getOperand(0) != CI && I->getOperand(1) != CI))
-      return false;
+      return {};
   }
 
   // The only user of this instruction we allow is a single return instruction.
   if (!I->hasOneUse() || !isa<ReturnInst>(I->user_back()))
-    return false;
-
-  if (BaseCaseConst)
-    return BaseCaseConst;
+    return {};
 
-  return true;
+  return {true, BaseCaseConst};
 }
 
 namespace {
@@ -787,11 +790,10 @@ bool TailRecursionEliminator::eliminateCall(CallInst *CI) {
     // operation that could be transformed using accumulator recursion
     // elimination. Check to see if this is the case, and if so, remember which
     // instruction accumulates for later.
-    std::variant<bool, Constant *> AccRecResult =
+    AccumulatorRecursionInfo AccRecInfo =
         canTransformAccumulatorRecursion(&*BBI, CI);
 
-    if (AccPN || (std::holds_alternative<bool>(AccRecResult) &&
-                  !std::get<bool>(AccRecResult)))
+    if (AccPN || !AccRecInfo.CanTransform)
       return false; // We cannot eliminate the tail recursion!
 
     // Yes, this is accumulator recursion.  Remember which instruction
@@ -799,8 +801,8 @@ bool TailRecursionEliminator::eliminateCall(CallInst *CI) {
     AccRecInstr = &*BBI;
 
     // Keep track of the base case return value if any.
-    if (std::holds_alternative<Constant *>(AccRecResult))
-      BaseCaseValue = std::get<Constant *>(AccRecResult);
+    if (AccRecInfo.BaseCaseConst)
+      BaseCaseValue = AccRecInfo.BaseCaseConst;
   }
 
   BasicBlock *BB = Ret->getParent();

>From b6c283f56fb35901d766d0d98fcb6e279c4f6882 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Sun, 7 Jun 2026 11:03:16 +0200
Subject: [PATCH 06/11] Simplify canTransformAccumulatorRecursion and rewrite
 getReturnValue with PatternMatch

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 176 ++++++++----------
 1 file changed, 77 insertions(+), 99 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 4995f740c0a69..1091387950f94 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -74,6 +74,7 @@
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/PatternMatch.h"
 #include "llvm/InitializePasses.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/CommandLine.h"
@@ -83,6 +84,7 @@
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include <cmath>
 using namespace llvm;
+using namespace llvm::PatternMatch;
 
 #define DEBUG_TYPE "tailcallelim"
 
@@ -404,106 +406,80 @@ static bool isPseudoAssociative(Instruction *I) {
 static Constant *getReturnValue(Function &F) {
   Constant *BaseCaseVal = nullptr;
 
-  // Local lambda with conservative bail-out: It isn't so trivial to perform TRE
-  // if the return value depends on the result of a recursive call, because the
-  // return value will be different for different iterations of the recursion.
-  // So we ignore return values that depend on recursive calls.
-  auto TryGetConstantBaseCase = [&](Value *RV) -> Constant * {
-    // Case 1: direct constant return.
-    if (auto *C = dyn_cast<Constant>(RV))
-      return C;
-
-    // Case 2: PHI with one constant incoming.
-    if (auto *PN = dyn_cast<PHINode>(RV)) {
-      if (PN->getNumIncomingValues() != 2)
-        return nullptr;
-      for (unsigned I = 0; I < 2; ++I) {
-        if (auto *C = dyn_cast<Constant>(PN->getIncomingValue(I)))
-          return C;
-      }
-      return nullptr;
-    }
+  for (BasicBlock &BB : F) {
+    auto *RI = dyn_cast<ReturnInst>(BB.getTerminator());
+    if (!RI)
+      continue;
 
-    // Case 3: select with a constant arm.
-    if (auto *SI = dyn_cast<SelectInst>(RV)) {
-      if (auto *CTrue = dyn_cast<Constant>(SI->getTrueValue()))
-        return CTrue;
-      if (auto *CFalse = dyn_cast<Constant>(SI->getFalseValue()))
-        return CFalse;
-      return nullptr;
+    Value *RV = RI->getReturnValue();
+    Constant *Candidate = nullptr;
+
+    if (match(RV, m_Constant(Candidate))) {
+      // Direct constant return.
+    } else if (auto *PN = dyn_cast<PHINode>(RV)) {
+      for (Value *Inc : PN->incoming_values())
+        if (match(Inc, m_Constant(Candidate)))
+          break;
+    } else if (match(RV,
+                     m_Select(m_Value(), m_Constant(Candidate), m_Value())) ||
+               match(RV,
+                     m_Select(m_Value(), m_Value(), m_Constant(Candidate)))) {
+      // Select with a constant arm.
     }
 
-    return nullptr;
-  };
+    if (!Candidate)
+      return nullptr;
 
-  for (BasicBlock &BB : F) {
-    if (auto *RI = dyn_cast<ReturnInst>(BB.getTerminator())) {
-      Value *RV = RI->getReturnValue();
-      Constant *Candidate = TryGetConstantBaseCase(RV);
-      if (!Candidate)
-        return nullptr;
-
-      if (!BaseCaseVal)
-        BaseCaseVal = Candidate;
-      else if (BaseCaseVal != Candidate)
-        return nullptr;
-    }
+    if (!BaseCaseVal)
+      BaseCaseVal = Candidate;
+    else if (BaseCaseVal != Candidate)
+      return nullptr;
   }
 
   return BaseCaseVal;
 }
 
-namespace {
-struct AccumulatorRecursionInfo {
-  bool CanTransform = false;
-  Constant *BaseCaseConst = nullptr;
-};
-} // namespace
-
 // This function checks whether the instruction I can be used
 // to perform accumulator recursion elimination for the
 // call instruction CI.
 // In the presence of pseudo-associative operations, it returns
 // the base case constant value in BaseCaseConst to both indicate success and
 // provide the value needed to initialize the accumulator.
-static AccumulatorRecursionInfo canTransformAccumulatorRecursion(Instruction *I,
-                                                                 CallInst *CI) {
+static Constant *canTransformAccumulatorRecursion(Instruction *I,
+                                                  CallInst *CI) {
   auto IsPseudoAssociative = isPseudoAssociative(I);
   if ((!I->isAssociative() || !I->isCommutative()) && !IsPseudoAssociative)
-    return {};
+    return nullptr;
 
   assert(I->getNumOperands() >= 2 &&
          "Associative/commutative operations should have at least 2 args!");
 
-  Constant *BaseCaseConst = nullptr;
+  Constant *AccInitVal = nullptr;
   if (IsPseudoAssociative) {
     // For pseudo-associative operations, we require that the recursive call
     // is always on the first operand.
     if (I->getOperand(0) != CI)
-      return {};
+      return nullptr;
 
-    BaseCaseConst = getReturnValue(*CI->getCalledFunction());
-    if (!BaseCaseConst)
-      return {};
+    AccInitVal = getReturnValue(*CI->getCalledFunction());
+    if (!AccInitVal)
+      return nullptr;
   } else {
-    if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
-      // Accumulators must have an identity.
-      if (!ConstantExpr::getIntrinsicIdentity(II->getIntrinsicID(),
-                                              I->getType()))
-        return {};
-    }
+    AccInitVal = ConstantExpr::getIdentity(I, I->getType());
+    if (!AccInitVal)
+      return nullptr;
 
     // Exactly one operand should be the result of the call instruction.
     if ((I->getOperand(0) == CI && I->getOperand(1) == CI) ||
         (I->getOperand(0) != CI && I->getOperand(1) != CI))
-      return {};
+      return nullptr;
   }
 
   // The only user of this instruction we allow is a single return instruction.
   if (!I->hasOneUse() || !isa<ReturnInst>(I->user_back()))
-    return {};
+    return nullptr;
 
-  return {true, BaseCaseConst};
+  return AccInitVal;
 }
 
 namespace {
@@ -545,7 +521,7 @@ class TailRecursionEliminator {
 
   // The base case return value. It exists only if the accumulator
   // operation is pseudo-associative.
-  Constant *BaseCaseValue = nullptr;
+  Constant *AccumulatorInitialValue = nullptr;
 
   TailRecursionEliminator(Function &F, const TargetTransformInfo *TTI,
                           AliasAnalysis *AA, OptimizationRemarkEmitter *ORE,
@@ -705,17 +681,19 @@ void TailRecursionEliminator::insertAccumulator(Instruction *AccRecInstr) {
   for (pred_iterator PI = PB; PI != PE; ++PI) {
     BasicBlock *P = *PI;
     if (P == &F.getEntryBlock()) {
-      Constant *InitialValue =
-          ConstantExpr::getIdentity(AccRecInstr, AccRecInstr->getType());
-      if (!InitialValue) {
-        // We are in the presence of a pseudo-associative operation like shift.
-        // We didn't pass `AllowRHSConstant = true` in getIdentity above because
-        // the identity for shifts is zero, which is not valid for the LHS.
-        // We need the value of the base case(s) of the function to
-        // initialize the accumulator.
-        InitialValue = BaseCaseValue;
-      }
-      AccPN->addIncoming(InitialValue, P);
+      // Constant *InitialValue =
+      //     ConstantExpr::getIdentity(AccRecInstr, AccRecInstr->getType());
+      // if (!InitialValue) {
+      //   // We are in the presence of a pseudo-associative operation like
+      //   shift.
+      //   // We didn't pass `AllowRHSConstant = true` in getIdentity above
+      //   because
+      //   // the identity for shifts is zero, which is not valid for the LHS.
+      //   // We need the value of the base case(s) of the function to
+      //   // initialize the accumulator.
+      //   InitialValue = BaseCaseValue;
+      // }
+      AccPN->addIncoming(AccumulatorInitialValue, P);
     } else {
       AccPN->addIncoming(AccPN, P);
     }
@@ -790,19 +768,18 @@ bool TailRecursionEliminator::eliminateCall(CallInst *CI) {
     // operation that could be transformed using accumulator recursion
     // elimination. Check to see if this is the case, and if so, remember which
     // instruction accumulates for later.
-    AccumulatorRecursionInfo AccRecInfo =
-        canTransformAccumulatorRecursion(&*BBI, CI);
+    Constant *AccInitVal = canTransformAccumulatorRecursion(&*BBI, CI);
 
-    if (AccPN || !AccRecInfo.CanTransform)
+    if (AccPN || !AccInitVal)
       return false; // We cannot eliminate the tail recursion!
 
     // Yes, this is accumulator recursion.  Remember which instruction
     // accumulates.
     AccRecInstr = &*BBI;
 
-    // Keep track of the base case return value if any.
-    if (AccRecInfo.BaseCaseConst)
-      BaseCaseValue = AccRecInfo.BaseCaseConst;
+    // Keep track of the base case (i.e., initial value) of the accumulator
+    // return value if any.
+    AccumulatorInitialValue = AccInitVal;
   }
 
   BasicBlock *BB = Ret->getParent();
@@ -945,18 +922,15 @@ void TailRecursionEliminator::cleanupAndFinalize() {
           if (!RI)
             continue;
 
-          // Again, the BaseCaseValue exists only if the accumulator
-          // operation is pseudo-associative. In that case the accumulator PHI
-          // already holds the final result (it is initialized from the base
-          // case), so we should return it directly rather than applying the
-          // accumulation instruction one more time.
-          if (BaseCaseValue) {
+          if (isPseudoAssociative(AccRecInstr)) {
+            // Base-case initialization: the accumulator PHI already holds the
+            // final result, so return it directly.
             RI->setOperand(0, AccPN);
           } else {
-            // Otherwise, since the accumulator starts with the identity value,
-            // before the return we need to apply the accumulation instruction
-            // one more time to combine the last value with the result of the
-            // recursive call.
+            // Since the accumulator starts with the identity value, before the
+            // return we need to apply the accumulation instruction one more
+            // time to combine the last value with the result of the recursive
+            // call.
             Instruction *AccRecInstrNew = AccRecInstr->clone();
             AccRecInstrNew->setName("accumulator.ret.tr");
             AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
@@ -988,13 +962,17 @@ void TailRecursionEliminator::cleanupAndFinalize() {
         // of the selects we inserted, and select its result instead.
         Instruction *AccRecInstr = AccumulatorRecursionInstr;
         for (SelectInst *SI : RetSelects) {
-          Instruction *AccRecInstrNew = AccRecInstr->clone();
-          AccRecInstrNew->setName("accumulator.ret.tr");
-          AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
-                                     SI->getFalseValue());
-          AccRecInstrNew->insertBefore(SI->getIterator());
-          AccRecInstrNew->dropLocation();
-          SI->setFalseValue(AccRecInstrNew);
+          if (isPseudoAssociative(AccRecInstr)) {
+            SI->setFalseValue(AccPN);
+          } else {
+            Instruction *AccRecInstrNew = AccRecInstr->clone();
+            AccRecInstrNew->setName("accumulator.ret.tr");
+            AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
+                                       SI->getFalseValue());
+            AccRecInstrNew->insertBefore(SI->getIterator());
+            AccRecInstrNew->dropLocation();
+            SI->setFalseValue(AccRecInstrNew);
+          }
         }
       }
     }

>From 1bbb22175f9b87d992423e811b7eabf34aad98d2 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 10 Jun 2026 09:14:25 +0200
Subject: [PATCH 07/11] Update
 llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp

Co-authored-by: Antonio Frighetto <me at antoniofrighetto.com>
---
 llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 1091387950f94..cbadf3da7dbff 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -408,7 +408,7 @@ static Constant *getReturnValue(Function &F) {
 
   for (BasicBlock &BB : F) {
     auto *RI = dyn_cast<ReturnInst>(BB.getTerminator());
-    if (!RI)
+    if (!RI || !RI->getReturnValue())
       continue;
 
     Value *RV = RI->getReturnValue();

>From 5e0b70ece6c33b5f05246faf203d26593f5d046b Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 10 Jun 2026 09:14:44 +0200
Subject: [PATCH 08/11] Update
 llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp

Co-authored-by: Antonio Frighetto <me at antoniofrighetto.com>
---
 llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index cbadf3da7dbff..220d45b5b3988 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -442,9 +442,6 @@ static Constant *getReturnValue(Function &F) {
 // This function checks whether the instruction I can be used
 // to perform accumulator recursion elimination for the
 // call instruction CI.
-// In the presence of pseudo-associative operations, it returns
-// the base case constant value in BaseCaseConst to both indicate success and
-// provide the value needed to initialize the accumulator.
 static Constant *canTransformAccumulatorRecursion(Instruction *I,
                                                   CallInst *CI) {
   auto IsPseudoAssociative = isPseudoAssociative(I);

>From e3ca14b53bac7ac6f88b1552178c50f5ae8a2b04 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 10 Jun 2026 09:23:32 +0200
Subject: [PATCH 09/11] Remove comments and update tests

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 17 ++------
 .../TailCallElim/shl-accumulator-opt.ll       | 40 +++++++++----------
 2 files changed, 24 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 220d45b5b3988..f68887e42fcbf 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -408,7 +408,7 @@ static Constant *getReturnValue(Function &F) {
 
   for (BasicBlock &BB : F) {
     auto *RI = dyn_cast<ReturnInst>(BB.getTerminator());
-    if (!RI || !RI->getReturnValue())
+    if (!RI)
       continue;
 
     Value *RV = RI->getReturnValue();
@@ -442,6 +442,9 @@ static Constant *getReturnValue(Function &F) {
 // This function checks whether the instruction I can be used
 // to perform accumulator recursion elimination for the
 // call instruction CI.
+// In the presence of pseudo-associative operations, it returns
+// the base case constant value in BaseCaseConst to both indicate success and
+// provide the value needed to initialize the accumulator.
 static Constant *canTransformAccumulatorRecursion(Instruction *I,
                                                   CallInst *CI) {
   auto IsPseudoAssociative = isPseudoAssociative(I);
@@ -678,18 +681,6 @@ void TailRecursionEliminator::insertAccumulator(Instruction *AccRecInstr) {
   for (pred_iterator PI = PB; PI != PE; ++PI) {
     BasicBlock *P = *PI;
     if (P == &F.getEntryBlock()) {
-      // Constant *InitialValue =
-      //     ConstantExpr::getIdentity(AccRecInstr, AccRecInstr->getType());
-      // if (!InitialValue) {
-      //   // We are in the presence of a pseudo-associative operation like
-      //   shift.
-      //   // We didn't pass `AllowRHSConstant = true` in getIdentity above
-      //   because
-      //   // the identity for shifts is zero, which is not valid for the LHS.
-      //   // We need the value of the base case(s) of the function to
-      //   // initialize the accumulator.
-      //   InitialValue = BaseCaseValue;
-      // }
       AccPN->addIncoming(AccumulatorInitialValue, P);
     } else {
       AccPN->addIncoming(AccPN, P);
diff --git a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
index 81ba00e073b85..05ae1deeaaf5c 100644
--- a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
+++ b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
@@ -11,8 +11,8 @@
 ;     if (x == 1) return 7;
 ;     return f(x-1) + f(x-1); // f(x-1) * 2
 ; }
-define i32 @f(i32 %x) {
-; CHECK-LABEL: define i32 @f(
+define i32 @test_shl_const_accumulator(i32 %x) {
+; CHECK-LABEL: define i32 @test_shl_const_accumulator(
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[TAILRECURSE:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
@@ -21,7 +21,7 @@ define i32 @f(i32 %x) {
 ; CHECK-NEXT:    ret i32 7
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f(i32 [[SUB]])
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_shl_const_accumulator(i32 [[SUB]])
 ; CHECK-NEXT:    [[ADD:%.*]] = shl nsw i32 [[ACCUMULATOR_TR]], 1
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
@@ -35,7 +35,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f(i32 %sub)
+  %call = tail call i32 @test_shl_const_accumulator(i32 %sub)
   %add = shl nsw i32 %call, 1
   br label %common.ret
 }
@@ -45,8 +45,8 @@ if.end:
 ;     if (x == 1) return 14;
 ;     return f2(x-1) >> 1;
 ; }
-define i32 @f2(i32 %x) {
-; CHECK-LABEL: define i32 @f2(
+define i32 @test_ashr_const_accumulator(i32 %x) {
+; CHECK-LABEL: define i32 @test_ashr_const_accumulator(
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[TAILRECURSE:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
@@ -55,7 +55,7 @@ define i32 @f2(i32 %x) {
 ; CHECK-NEXT:    ret i32 14
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f2(i32 [[SUB]])
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_ashr_const_accumulator(i32 [[SUB]])
 ; CHECK-NEXT:    [[SHR:%.*]] = ashr i32 [[ACCUMULATOR_TR]], 1
 ; CHECK-NEXT:    ret i32 [[SHR]]
 ;
@@ -69,7 +69,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f2(i32 %sub)
+  %call = tail call i32 @test_ashr_const_accumulator(i32 %sub)
   %shr = ashr i32 %call, 1
   br label %common.ret
 }
@@ -79,8 +79,8 @@ if.end:
 ;     if (x <= 1) return 14;
 ;     return f3(x - 1) >> 1;
 ; }
-define i32 @f3(i32 %x) {
-; CHECK-LABEL: define i32 @f3(
+define i32 @test_lshr_const_unsigned(i32 %x) {
+; CHECK-LABEL: define i32 @test_lshr_const_unsigned(
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[TAILRECURSE:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 2
@@ -89,7 +89,7 @@ define i32 @f3(i32 %x) {
 ; CHECK-NEXT:    ret i32 21
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    [[SUB:%.*]] = add i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @f3(i32 [[SUB]])
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_lshr_const_unsigned(i32 [[SUB]])
 ; CHECK-NEXT:    [[SHR:%.*]] = lshr i32 [[ACCUMULATOR_TR]], 1
 ; CHECK-NEXT:    ret i32 [[SHR]]
 ;
@@ -103,7 +103,7 @@ common.ret:
 
 if.end:
   %sub = add i32 %x, -1
-  %call = tail call i32 @f3(i32 %sub)
+  %call = tail call i32 @test_lshr_const_unsigned(i32 %sub)
   %shr = lshr i32 %call, 1
   br label %common.ret
 }
@@ -113,8 +113,8 @@ if.end:
 ;   if (x == 1) return 7;
 ;   return f4(x-1, k) << k; // variable shift amount
 ; }
-define i32 @f4(i32 %x, i32 %k) {
-; CHECK-LABEL: define i32 @f4(
+define i32 @test_neg_variable_shift_amount(i32 %x, i32 %k) {
+; CHECK-LABEL: define i32 @test_neg_variable_shift_amount(
 ; CHECK-SAME: i32 [[X:%.*]], i32 [[K:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
@@ -123,7 +123,7 @@ define i32 @f4(i32 %x, i32 %k) {
 ; CHECK-NEXT:    ret i32 7
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @f4(i32 [[SUB]], i32 [[K]])
+; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @test_neg_variable_shift_amount(i32 [[SUB]], i32 [[K]])
 ; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[CALL]], [[K]]
 ; CHECK-NEXT:    ret i32 [[SHL]]
 ;
@@ -137,7 +137,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f4(i32 %sub, i32 %k)
+  %call = tail call i32 @test_neg_variable_shift_amount(i32 %sub, i32 %k)
   %shl = shl i32 %call, %k
   br label %common.ret
 }
@@ -147,8 +147,8 @@ if.end:
 ;   if (x == 1) return 7;
 ;   return (f5(x-1) + 1) << 1; // extra add breaks accumulator invariant
 ; }
-define i32 @f5(i32 %x) {
-; CHECK-LABEL: define i32 @f5(
+define i32 @test_neg_intervening_op(i32 %x) {
+; CHECK-LABEL: define i32 @test_neg_intervening_op(
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
@@ -157,7 +157,7 @@ define i32 @f5(i32 %x) {
 ; CHECK-NEXT:    ret i32 7
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @f5(i32 [[SUB]])
+; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @test_neg_intervening_op(i32 [[SUB]])
 ; CHECK-NEXT:    [[TMP:%.*]] = add i32 [[CALL]], 1
 ; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[TMP]], 1
 ; CHECK-NEXT:    ret i32 [[SHL]]
@@ -172,7 +172,7 @@ common.ret:
 
 if.end:
   %sub = add nsw i32 %x, -1
-  %call = tail call i32 @f5(i32 %sub)
+  %call = tail call i32 @test_neg_intervening_op(i32 %sub)
   %tmp = add i32 %call, 1
   %shl = shl i32 %tmp, 1
   br label %common.ret

>From 8c52938d0da92afc1a1153161edb46057542cdcd Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 10 Jun 2026 09:32:45 +0200
Subject: [PATCH 10/11] Small refactoring

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 .../Scalar/TailRecursionElimination.cpp       | 31 +++++++++----------
 1 file changed, 15 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index f68887e42fcbf..f3b95a1f8db89 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -892,6 +892,17 @@ void TailRecursionEliminator::cleanupAndFinalize() {
   }
 
   if (RetPN) {
+    Instruction *AccRecInstr = AccumulatorRecursionInstr;
+    auto MaterializeAccumulator = [&](Value *OtherVal,
+                                      BasicBlock::iterator InsertPt) {
+      Instruction *New = AccRecInstr->clone();
+      New->setName("accumulator.ret.tr");
+      New->setOperand(AccRecInstr->getOperand(0) == AccPN, OtherVal);
+      New->insertBefore(InsertPt);
+      New->dropLocation();
+      return New;
+    };
+
     if (RetSelects.empty()) {
       // If we didn't insert any select instructions, then we know we didn't
       // store a return value and we can remove the PHI nodes we inserted.
@@ -904,7 +915,6 @@ void TailRecursionEliminator::cleanupAndFinalize() {
       if (AccPN) {
         // We need to insert a copy of our accumulator instruction before any
         // return in the function, and return its result instead.
-        Instruction *AccRecInstr = AccumulatorRecursionInstr;
         for (BasicBlock &BB : F) {
           ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator());
           if (!RI)
@@ -919,13 +929,8 @@ void TailRecursionEliminator::cleanupAndFinalize() {
             // return we need to apply the accumulation instruction one more
             // time to combine the last value with the result of the recursive
             // call.
-            Instruction *AccRecInstrNew = AccRecInstr->clone();
-            AccRecInstrNew->setName("accumulator.ret.tr");
-            AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
-                                       RI->getOperand(0));
-            AccRecInstrNew->insertBefore(RI->getIterator());
-            AccRecInstrNew->dropLocation();
-            RI->setOperand(0, AccRecInstrNew);
+            RI->setOperand(0, MaterializeAccumulator(RI->getOperand(0),
+                                                     RI->getIterator()));
           }
         }
       }
@@ -948,18 +953,12 @@ void TailRecursionEliminator::cleanupAndFinalize() {
       if (AccPN) {
         // We need to insert a copy of our accumulator instruction before any
         // of the selects we inserted, and select its result instead.
-        Instruction *AccRecInstr = AccumulatorRecursionInstr;
         for (SelectInst *SI : RetSelects) {
           if (isPseudoAssociative(AccRecInstr)) {
             SI->setFalseValue(AccPN);
           } else {
-            Instruction *AccRecInstrNew = AccRecInstr->clone();
-            AccRecInstrNew->setName("accumulator.ret.tr");
-            AccRecInstrNew->setOperand(AccRecInstr->getOperand(0) == AccPN,
-                                       SI->getFalseValue());
-            AccRecInstrNew->insertBefore(SI->getIterator());
-            AccRecInstrNew->dropLocation();
-            SI->setFalseValue(AccRecInstrNew);
+            SI->setFalseValue(
+                MaterializeAccumulator(SI->getFalseValue(), SI->getIterator()));
           }
         }
       }

>From 9064add737bdf1e7417fe46fb4bde88ed95a23ea Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Thu, 11 Jun 2026 17:56:25 +0200
Subject: [PATCH 11/11] Restore base-case lookup by skipping returns that use
 the recursive call

---
 .../Scalar/TailRecursionElimination.cpp       | 55 ++++++++++--------
 .../TailCallElim/shl-accumulator-opt.ll       | 57 +++++++++++--------
 2 files changed, 63 insertions(+), 49 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index f3b95a1f8db89..f5003c45b67f4 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -74,7 +74,6 @@
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
-#include "llvm/IR/PatternMatch.h"
 #include "llvm/InitializePasses.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/CommandLine.h"
@@ -84,7 +83,6 @@
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include <cmath>
 using namespace llvm;
-using namespace llvm::PatternMatch;
 
 #define DEBUG_TYPE "tailcallelim"
 
@@ -387,11 +385,27 @@ static bool isPseudoAssociative(Instruction *I) {
   return isa<ConstantInt>(I->getOperand(1));
 }
 
-// Find the base-case return value for function F: examine all
-// return instructions and pick return values that do not depend on a
-// recursive call to F. If there is exactly one distinct such value,
-// return it. If there are none or more than one distinct value, return
-// nullptr to indicate failure.
+// Return true if V is a recursive call to F or an instruction directly using
+// the result of one. A depth-1 check is enough here: the value feeding a
+// return either uses the recursive call as an immediate operand (the
+// accumulator instruction, or the PHI merging it with the base case), or it
+// is rejected by getReturnValue below as a non-constant anyway.
+static bool usesRecursiveCall(Value *V, Function &F) {
+  auto IsRecursiveCall = [&F](Value *V) {
+    auto *CI = dyn_cast<CallInst>(V);
+    return CI && CI->getCalledFunction() == &F;
+  };
+  if (IsRecursiveCall(V))
+    return true;
+  auto *I = dyn_cast<Instruction>(V);
+  return I && llvm::any_of(I->operands(), IsRecursiveCall);
+}
+
+// Find the base-case return value for function F: examine all return
+// instructions, skipping those whose return value depends on a recursive call
+// to F (that value differs for each iteration of the recursion). If the
+// remaining returns yield exactly one distinct constant, return it; otherwise
+// return nullptr to indicate failure.
 //
 // FIXME: There is a room for improvement here in the future, e.g., consider
 // non-constant values and multiple base cases -- e.g., we want to be able to
@@ -412,27 +426,16 @@ static Constant *getReturnValue(Function &F) {
       continue;
 
     Value *RV = RI->getReturnValue();
-    Constant *Candidate = nullptr;
-
-    if (match(RV, m_Constant(Candidate))) {
-      // Direct constant return.
-    } else if (auto *PN = dyn_cast<PHINode>(RV)) {
-      for (Value *Inc : PN->incoming_values())
-        if (match(Inc, m_Constant(Candidate)))
-          break;
-    } else if (match(RV,
-                     m_Select(m_Value(), m_Constant(Candidate), m_Value())) ||
-               match(RV,
-                     m_Select(m_Value(), m_Value(), m_Constant(Candidate)))) {
-      // Select with a constant arm.
-    }
+    if (usesRecursiveCall(RV, F))
+      continue;
 
-    if (!Candidate)
+    auto *C = dyn_cast<Constant>(RV);
+    if (!C)
       return nullptr;
 
     if (!BaseCaseVal)
-      BaseCaseVal = Candidate;
-    else if (BaseCaseVal != Candidate)
+      BaseCaseVal = C;
+    else if (BaseCaseVal != C)
       return nullptr;
   }
 
@@ -461,7 +464,9 @@ static Constant *canTransformAccumulatorRecursion(Instruction *I,
     if (I->getOperand(0) != CI)
       return nullptr;
 
-    AccInitVal = getReturnValue(*CI->getCalledFunction());
+    // findTRECandidate guarantees CI is a recursive call to its own
+    // function, so scan the enclosing function for the base-case return.
+    AccInitVal = getReturnValue(*CI->getFunction());
     if (!AccInitVal)
       return nullptr;
   } else {
diff --git a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
index 05ae1deeaaf5c..41db471f2dbf0 100644
--- a/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
+++ b/llvm/test/Transforms/TailCallElim/shl-accumulator-opt.ll
@@ -14,16 +14,19 @@
 define i32 @test_shl_const_accumulator(i32 %x) {
 ; CHECK-LABEL: define i32 @test_shl_const_accumulator(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[TAILRECURSE:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
+; CHECK-NEXT:  [[TAILRECURSE:.*]]:
+; CHECK-NEXT:    br label %[[COMMON_RET:.*]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 7
+; CHECK-NEXT:    [[ADD:%.*]] = phi i32 [ 7, %[[TAILRECURSE]] ], [ [[ADD1:%.*]], %[[IF_END1:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[TAILRECURSE]] ], [ [[SUB:%.*]], %[[IF_END1]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[IF_END:.*]], label %[[IF_END1]]
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_shl_const_accumulator(i32 [[SUB]])
-; CHECK-NEXT:    [[ADD:%.*]] = shl nsw i32 [[ACCUMULATOR_TR]], 1
 ; CHECK-NEXT:    ret i32 [[ADD]]
+; CHECK:       [[IF_END1]]:
+; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
+; CHECK-NEXT:    [[ADD1]] = shl i32 [[ADD]], 1
+; CHECK-NEXT:    br label %[[COMMON_RET]]
 ;
 entry:
   %cmp = icmp eq i32 %x, 1
@@ -48,16 +51,19 @@ if.end:
 define i32 @test_ashr_const_accumulator(i32 %x) {
 ; CHECK-LABEL: define i32 @test_ashr_const_accumulator(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[TAILRECURSE:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 1
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
+; CHECK-NEXT:  [[TAILRECURSE:.*]]:
+; CHECK-NEXT:    br label %[[TAILRECURSE1:.*]]
+; CHECK:       [[TAILRECURSE1]]:
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 14, %[[TAILRECURSE]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[TAILRECURSE]] ], [ [[SUB:%.*]], %[[IF_END]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X_TR]], 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 14
+; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB:%.*]] = add nsw i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_ashr_const_accumulator(i32 [[SUB]])
-; CHECK-NEXT:    [[SHR:%.*]] = ashr i32 [[ACCUMULATOR_TR]], 1
-; CHECK-NEXT:    ret i32 [[SHR]]
+; CHECK-NEXT:    [[SUB]] = add nsw i32 [[X_TR]], -1
+; CHECK-NEXT:    [[SHR]] = ashr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    br label %[[TAILRECURSE1]]
 ;
 entry:
   %cmp = icmp eq i32 %x, 1
@@ -76,22 +82,25 @@ if.end:
 
 
 ; unsigned int f3(unsigned int x) {
-;     if (x <= 1) return 14;
+;     if (x <= 1) return 21;
 ;     return f3(x - 1) >> 1;
 ; }
 define i32 @test_lshr_const_unsigned(i32 %x) {
 ; CHECK-LABEL: define i32 @test_lshr_const_unsigned(
 ; CHECK-SAME: i32 [[X:%.*]]) {
-; CHECK-NEXT:  [[TAILRECURSE:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 2
-; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END:.*]]
+; CHECK-NEXT:  [[TAILRECURSE:.*]]:
+; CHECK-NEXT:    br label %[[TAILRECURSE1:.*]]
+; CHECK:       [[TAILRECURSE1]]:
+; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = phi i32 [ 21, %[[TAILRECURSE]] ], [ [[SHR:%.*]], %[[IF_END:.*]] ]
+; CHECK-NEXT:    [[X_TR:%.*]] = phi i32 [ [[X]], %[[TAILRECURSE]] ], [ [[SUB:%.*]], %[[IF_END]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X_TR]], 2
+; CHECK-NEXT:    br i1 [[CMP]], label %[[COMMON_RET:.*]], label %[[IF_END]]
 ; CHECK:       [[COMMON_RET]]:
-; CHECK-NEXT:    ret i32 21
+; CHECK-NEXT:    ret i32 [[ACCUMULATOR_TR]]
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    [[SUB:%.*]] = add i32 [[X]], -1
-; CHECK-NEXT:    [[ACCUMULATOR_TR:%.*]] = tail call i32 @test_lshr_const_unsigned(i32 [[SUB]])
-; CHECK-NEXT:    [[SHR:%.*]] = lshr i32 [[ACCUMULATOR_TR]], 1
-; CHECK-NEXT:    ret i32 [[SHR]]
+; CHECK-NEXT:    [[SUB]] = add i32 [[X_TR]], -1
+; CHECK-NEXT:    [[SHR]] = lshr i32 [[ACCUMULATOR_TR]], 1
+; CHECK-NEXT:    br label %[[TAILRECURSE1]]
 ;
 entry:
   %cmp = icmp ult i32 %x, 2



More information about the llvm-commits mailing list