[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