[llvm] [TailRecElim] Introduce support for shift accumulator optimization (PR #181331)
Federico Bruzzone via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 26 01:23:11 PDT 2026
https://github.com/FedericoBruzzone updated https://github.com/llvm/llvm-project/pull/181331
>From ce79a47079fb54465cdf3cc1fd7dee4eea8bba83 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/17] [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 4ce2f08524baa..43309e6429665 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -83,6 +83,7 @@
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cmath>
+#include <variant>
using namespace llvm;
#define DEBUG_TYPE "tailcallelim"
@@ -415,28 +416,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;
}
@@ -479,6 +588,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,
@@ -640,9 +753,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);
}
@@ -713,15 +834,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();
@@ -865,13 +995,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 {
@@ -938,7 +1081,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 05c96f3731047bcf99aa927e726816e75829bbd3 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/17] 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 bba5f9811122f82756464748760a5bcb7a7bbe77 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/17] 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 43309e6429665..8fc08363e9d91 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -422,20 +422,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
@@ -471,9 +460,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 9002803b84f366cfb587030d4a8e28bfe487482a 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/17] 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 8fc08363e9d91..77fa1621af152 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -422,9 +422,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
@@ -446,39 +446,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 3bfbaa7bbac7446c60a36c685e23fdac7fd76725 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/17] 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 77fa1621af152..8bec7671bf119 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -83,7 +83,6 @@
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cmath>
-#include <variant>
using namespace llvm;
#define DEBUG_TYPE "tailcallelim"
@@ -495,17 +494,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!");
@@ -515,33 +521,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 {
@@ -833,11 +836,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
@@ -845,8 +847,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 a55a97f78d9fef0d5fb3bab6a32c59771d1ee7ea 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/17] 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 8bec7671bf119..c3407cca089f5 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -75,6 +75,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"
@@ -84,6 +85,7 @@
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cmath>
using namespace llvm;
+using namespace llvm::PatternMatch;
#define DEBUG_TYPE "tailcallelim"
@@ -445,106 +447,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 {
@@ -588,7 +564,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,
@@ -751,17 +727,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);
}
@@ -836,19 +814,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();
@@ -992,18 +969,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,
@@ -1035,13 +1009,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 68fa40f7c7915a69fb9ab09ebe21b281ecf3da20 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/17] 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 c3407cca089f5..d3e3b4ca28560 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -449,7 +449,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 bc014a27476b2ba840b1fcdf256df4fdb2bf8534 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/17] 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 d3e3b4ca28560..4c30d492d17a7 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -483,9 +483,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 3243c08c348946635b81efce00d48794c294717e 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/17] 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 4c30d492d17a7..1a9b4e344a653 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -449,7 +449,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();
@@ -483,6 +483,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);
@@ -724,18 +727,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 08259219cada4dfcc8fa669168fe10b652a15162 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/17] 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 1a9b4e344a653..29ab068216c44 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -939,6 +939,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.
@@ -951,7 +962,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)
@@ -966,13 +976,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()));
}
}
}
@@ -995,18 +1000,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 ac3d11c0d5d0175a85966b9a434e945edbc223df 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/17] 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 29ab068216c44..b54d0f5c05b85 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -75,7 +75,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"
@@ -85,7 +84,6 @@
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include <cmath>
using namespace llvm;
-using namespace llvm::PatternMatch;
#define DEBUG_TYPE "tailcallelim"
@@ -428,11 +426,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
@@ -453,27 +467,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;
}
@@ -502,7 +505,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
>From 2e3be2d321526d7f2a3047d569cd33828eca28a2 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 8 Jul 2026 18:20:12 +0200
Subject: [PATCH 12/17] 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 b54d0f5c05b85..43c47729a90bb 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -491,7 +491,7 @@ static Constant *getReturnValue(Function &F) {
// provide the value needed to initialize the accumulator.
static Constant *canTransformAccumulatorRecursion(Instruction *I,
CallInst *CI) {
- auto IsPseudoAssociative = isPseudoAssociative(I);
+ bool IsPseudoAssociative = isPseudoAssociative(I);
if ((!I->isAssociative() || !I->isCommutative()) && !IsPseudoAssociative)
return nullptr;
>From 2a9e2105585bf7ba390d0a804814a2de2df38963 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 8 Jul 2026 18:20:40 +0200
Subject: [PATCH 13/17] 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 43c47729a90bb..02b593ee85415 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -486,9 +486,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) {
bool IsPseudoAssociative = isPseudoAssociative(I);
>From 082abe22a3006c489e28ee4e368c0df758dfab41 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 8 Jul 2026 18:22:31 +0200
Subject: [PATCH 14/17] 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 02b593ee85415..9a88e7f754199 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -463,7 +463,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 ad640188c1175c52a1fcf6e915acb1c6b3351745 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 8 Jul 2026 18:22:43 +0200
Subject: [PATCH 15/17] 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, 2 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 9a88e7f754199..f42b48324f49c 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -564,8 +564,6 @@ 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 *AccumulatorInitialValue = nullptr;
TailRecursionEliminator(Function &F, const TargetTransformInfo *TTI,
>From 593537d3387f203b9a0345c7796d25c3b0b86329 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 8 Jul 2026 18:25:11 +0200
Subject: [PATCH 16/17] Rename
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index f42b48324f49c..35e5da99e736f 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -430,7 +430,7 @@ static bool isPseudoAssociative(Instruction *I) {
// 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.
+// is rejected by findBaseCaseRetConstant below as a non-constant anyway.
static bool usesRecursiveCall(Value *V, Function &F) {
auto IsRecursiveCall = [&F](Value *V) {
auto *CI = dyn_cast<CallInst>(V);
@@ -458,7 +458,7 @@ static bool usesRecursiveCall(Value *V, Function &F) {
// return f(x-1) << 1;
// }
// ```
-static Constant *getReturnValue(Function &F) {
+static Constant *findBaseCaseRetConstant(Function &F) {
Constant *BaseCaseVal = nullptr;
for (BasicBlock &BB : F) {
@@ -504,7 +504,7 @@ static Constant *canTransformAccumulatorRecursion(Instruction *I,
// 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());
+ AccInitVal = findBaseCaseRetConstant(*CI->getFunction());
if (!AccInitVal)
return nullptr;
} else {
>From fb27121849ee885a6085f57f1e2168c8fda7c0c8 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Sun, 26 Jul 2026 10:21:32 +0200
Subject: [PATCH 17/17] Use `UnaryAccumulatorRecurrence` as new name
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
.../Scalar/TailRecursionElimination.cpp | 44 ++++++++++++-------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 35e5da99e736f..14e99ffc600bc 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -415,14 +415,25 @@ static bool canMoveAboveCall(Instruction *I, CallInst *CI, AliasAnalysis *AA) {
return !is_contained(I->operands(), CI);
}
-// 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) {
+// Return true if I is a unary accumulator recurrence: a chain of
+// applications of a unary function `g` composed with itself,
+// `g(g(...g(Base)...))`, which is equivalent to a single application of the
+// N-times-composed function when `g` is pure. Neither associative nor
+// commutative, this differs from the ordinary accumulator recurrence handled
+// below, which requires I to be associative and commutative.
+//
+// TODO: Generalize this beyond shifts by a constant amount to arbitrary pure
+// unary functions (e.g., `f(x) = x == 0 ? Base : g(f(x - 1))` for any pure
+// unary `g`).
+static bool isUnaryAccumulatorRecurrence(Instruction *I) {
if (!I->isShift())
return false;
+
+ // 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.
return isa<ConstantInt>(I->getOperand(1));
}
@@ -488,16 +499,17 @@ static Constant *findBaseCaseRetConstant(Function &F) {
// call instruction CI.
static Constant *canTransformAccumulatorRecursion(Instruction *I,
CallInst *CI) {
- bool IsPseudoAssociative = isPseudoAssociative(I);
- if ((!I->isAssociative() || !I->isCommutative()) && !IsPseudoAssociative)
+ bool IsUnaryAccumulatorRecurrence = isUnaryAccumulatorRecurrence(I);
+ if ((!I->isAssociative() || !I->isCommutative()) &&
+ !IsUnaryAccumulatorRecurrence)
return nullptr;
assert(I->getNumOperands() >= 2 &&
"Associative/commutative operations should have at least 2 args!");
Constant *AccInitVal = nullptr;
- if (IsPseudoAssociative) {
- // For pseudo-associative operations, we require that the recursive call
+ if (IsUnaryAccumulatorRecurrence) {
+ // For unary accumulator recurrences, we require that the recursive call
// is always on the first operand.
if (I->getOperand(0) != CI)
return nullptr;
@@ -798,10 +810,10 @@ 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) 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.
+ // is an (associative and commutative) or unary accumulator recurrence
+ // 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.
Constant *AccInitVal = canTransformAccumulatorRecursion(&*BBI, CI);
if (AccPN || !AccInitVal)
@@ -967,7 +979,7 @@ void TailRecursionEliminator::cleanupAndFinalize() {
if (!RI)
continue;
- if (isPseudoAssociative(AccRecInstr)) {
+ if (isUnaryAccumulatorRecurrence(AccRecInstr)) {
// Base-case initialization: the accumulator PHI already holds the
// final result, so return it directly.
RI->setOperand(0, AccPN);
@@ -1001,7 +1013,7 @@ void TailRecursionEliminator::cleanupAndFinalize() {
// We need to insert a copy of our accumulator instruction before any
// of the selects we inserted, and select its result instead.
for (SelectInst *SI : RetSelects) {
- if (isPseudoAssociative(AccRecInstr)) {
+ if (isUnaryAccumulatorRecurrence(AccRecInstr)) {
SI->setFalseValue(AccPN);
} else {
SI->setFalseValue(
More information about the llvm-commits
mailing list