diff --git a/llvm/include/llvm/IR/Constants.h b/llvm/include/llvm/IR/Constants.h --- a/llvm/include/llvm/IR/Constants.h +++ b/llvm/include/llvm/IR/Constants.h @@ -27,6 +27,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/IR/Constant.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Intrinsics.h" #include "llvm/IR/OperandTraits.h" #include "llvm/IR/User.h" #include "llvm/IR/Value.h" @@ -1112,18 +1113,25 @@ static Constant *getExactLogBase2(Constant *C); /// Return the identity constant for a binary opcode. - /// The identity constant C is defined as X op C = X and C op X = X for every - /// X when the binary operation is commutative. If the binop is not - /// commutative, callers can acquire the operand 1 identity constant by - /// setting AllowRHSConstant to true. For example, any shift has a zero - /// identity constant for operand 1: X shift 0 = X. - /// If this is a fadd/fsub operation and we don't care about signed zeros, - /// then setting NSZ to true returns the identity +0.0 instead of -0.0. - /// Return nullptr if the operator does not have an identity constant. + /// If the binop is not commutative, callers can acquire the operand 1 + /// identity constant by setting AllowRHSConstant to true. For example, any + /// shift has a zero identity constant for operand 1: X shift 0 = X. If this + /// is a fadd/fsub operation and we don't care about signed zeros, then + /// setting NSZ to true returns the identity +0.0 instead of -0.0. Return + /// nullptr if the operator does not have an identity constant. static Constant *getBinOpIdentity(unsigned Opcode, Type *Ty, bool AllowRHSConstant = false, bool NSZ = false); + static Constant *getIntrinsicIdentity(Intrinsic::ID, Type *Ty); + + /// Return the identity constant for a binary or intrinsic Instruction. + /// The identity constant C is defined as X op C = X and C op X = X where C + /// and X are the first two operands, and the operation is commutative. + static Constant *getIdentity(Instruction *I, Type *Ty, + bool AllowRHSConstant = false, + bool NSZ = false); + /// Return the absorbing element for the given binary /// operation, i.e. a constant C such that X op C = C and C op X = C for /// every X. For example, this returns zero for integer multiplication. diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -55,6 +55,24 @@ return getCalledFunction()->getIntrinsicID(); } + bool isAssociative() const { + switch (getIntrinsicID()) { + case Intrinsic::maxnum: + case Intrinsic::minnum: + case Intrinsic::maximum: + case Intrinsic::minimum: + case Intrinsic::smax: + case Intrinsic::smin: + case Intrinsic::umax: + case Intrinsic::umin: + case Intrinsic::sadd_sat: + case Intrinsic::uadd_sat: + return true; + default: + return false; + } + } + /// Return true if swapping the first two arguments to the intrinsic produces /// the same result. bool isCommutative() const { diff --git a/llvm/lib/IR/Constants.cpp b/llvm/lib/IR/Constants.cpp --- a/llvm/lib/IR/Constants.cpp +++ b/llvm/lib/IR/Constants.cpp @@ -2714,6 +2714,34 @@ } } +Constant *ConstantExpr::getIntrinsicIdentity(Intrinsic::ID ID, Type *Ty) { + switch (ID) { + case Intrinsic::umax: + case Intrinsic::sadd_sat: + case Intrinsic::uadd_sat: + return Constant::getNullValue(Ty); + case Intrinsic::umin: + return Constant::getAllOnesValue(Ty); + case Intrinsic::smax: + return Constant::getIntegerValue( + Ty, APInt::getSignedMinValue(Ty->getIntegerBitWidth())); + case Intrinsic::smin: + return Constant::getIntegerValue( + Ty, APInt::getSignedMaxValue(Ty->getIntegerBitWidth())); + default: + return nullptr; + } +} + +Constant *ConstantExpr::getIdentity(Instruction *I, Type *Ty, + bool AllowRHSConstant, bool NSZ) { + if (I->isBinaryOp()) + return getBinOpIdentity(I->getOpcode(), Ty, AllowRHSConstant, NSZ); + if (IntrinsicInst *II = dyn_cast(I)) + return getIntrinsicIdentity(II->getIntrinsicID(), Ty); + return nullptr; +} + Constant *ConstantExpr::getBinOpAbsorber(unsigned Opcode, Type *Ty) { switch (Opcode) { default: diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp --- a/llvm/lib/IR/Instruction.cpp +++ b/llvm/lib/IR/Instruction.cpp @@ -849,6 +849,8 @@ } bool Instruction::isAssociative() const { + if (auto *II = dyn_cast(this)) + return II->isAssociative(); unsigned Opcode = getOpcode(); if (isAssociative(Opcode)) return true; diff --git a/llvm/lib/Transforms/Scalar/Reassociate.cpp b/llvm/lib/Transforms/Scalar/Reassociate.cpp --- a/llvm/lib/Transforms/Scalar/Reassociate.cpp +++ b/llvm/lib/Transforms/Scalar/Reassociate.cpp @@ -2451,7 +2451,7 @@ // Make a "pairmap" of how often each operand pair occurs. for (BasicBlock *BI : RPOT) { for (Instruction &I : *BI) { - if (!I.isAssociative()) + if (!I.isAssociative() || !I.isBinaryOp()) continue; // Ignore nodes that aren't at the root of trees. diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp --- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp +++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp @@ -369,12 +369,26 @@ if (!I->isAssociative() || !I->isCommutative()) return false; - assert(I->getNumOperands() == 2 && - "Associative/commutative operations should have 2 args!"); + Value *LHS; + Value *RHS; + if (I->isBinaryOp()) { + LHS = I->getOperand(0); + RHS = I->getOperand(1); + } else if (IntrinsicInst *II = dyn_cast(I)) { + // Accumulators must have an identity. + if (!ConstantExpr::getIntrinsicIdentity(II->getIntrinsicID(), I->getType())) + return false; + // 0'th operand is the intrinsic function + LHS = I->getOperand(1); + RHS = I->getOperand(2); + } else { + llvm_unreachable( + "commutative operations must be either a binary or intrinsic op"); + } + // 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)) + if ((LHS == CI && RHS == CI) || (LHS != CI && RHS != CI)) return false; // The only user of this instruction we allow is a single return instruction. @@ -566,8 +580,8 @@ for (pred_iterator PI = PB; PI != PE; ++PI) { BasicBlock *P = *PI; if (P == &F.getEntryBlock()) { - Constant *Identity = ConstantExpr::getBinOpIdentity( - AccRecInstr->getOpcode(), AccRecInstr->getType()); + Constant *Identity = + ConstantExpr::getIdentity(AccRecInstr, AccRecInstr->getType()); AccPN->addIncoming(Identity, P); } else { AccPN->addIncoming(AccPN, P); diff --git a/llvm/test/Transforms/TailCallElim/tre-basic-intrinsic.ll b/llvm/test/Transforms/TailCallElim/tre-basic-intrinsic.ll --- a/llvm/test/Transforms/TailCallElim/tre-basic-intrinsic.ll +++ b/llvm/test/Transforms/TailCallElim/tre-basic-intrinsic.ll @@ -7,15 +7,19 @@ ; CHECK-LABEL: define i32 @uadd_sat ; CHECK-SAME: (i32 noundef [[N:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[N]], 0 -; CHECK-NEXT: br i1 [[CMP]], label [[COMMON_RET3:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[ELT_SAT:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[N_TR:%.*]] = phi i32 [ [[N]], [[ENTRY]] ], [ [[SUB:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[N_TR]], 0 +; CHECK-NEXT: br i1 [[CMP]], label [[COMMON_RET3:%.*]], label [[IF_END]] ; CHECK: common.ret3: -; CHECK-NEXT: ret i32 0 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.uadd.sat.i32(i32 0, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[SUB:%.*]] = add i32 [[N]], -1 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @uadd_sat(i32 noundef [[SUB]]) -; CHECK-NEXT: [[ELT_SAT:%.*]] = tail call i32 @llvm.uadd.sat.i32(i32 [[N]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[ELT_SAT]] +; CHECK-NEXT: [[SUB]] = add i32 [[N_TR]], -1 +; CHECK-NEXT: [[ELT_SAT]] = tail call i32 @llvm.uadd.sat.i32(i32 [[N_TR]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %cmp = icmp eq i32 %n, 0 @@ -36,15 +40,19 @@ ; CHECK-LABEL: define i32 @sadd_sat ; CHECK-SAME: (i32 noundef [[N:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[N]], 0 -; CHECK-NEXT: br i1 [[CMP]], label [[COMMON_RET3:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[ELT_SAT:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[N_TR:%.*]] = phi i32 [ [[N]], [[ENTRY]] ], [ [[SUB:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[N_TR]], 0 +; CHECK-NEXT: br i1 [[CMP]], label [[COMMON_RET3:%.*]], label [[IF_END]] ; CHECK: common.ret3: -; CHECK-NEXT: ret i32 0 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.sadd.sat.i32(i32 0, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[SUB:%.*]] = add i32 [[N]], -1 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @sadd_sat(i32 noundef [[SUB]]) -; CHECK-NEXT: [[ELT_SAT:%.*]] = tail call i32 @llvm.sadd.sat.i32(i32 [[N]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[ELT_SAT]] +; CHECK-NEXT: [[SUB]] = add i32 [[N_TR]], -1 +; CHECK-NEXT: [[ELT_SAT]] = tail call i32 @llvm.sadd.sat.i32(i32 [[N_TR]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %cmp = icmp eq i32 %n, 0 diff --git a/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll b/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll --- a/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll +++ b/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll @@ -7,17 +7,21 @@ ; CHECK-LABEL: define noundef i32 @umin ; CHECK-SAME: (ptr noundef readonly [[A:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A]], null -; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ -1, [[ENTRY:%.*]] ], [ [[DOTSROA_SPECULATED:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END]] ; CHECK: common.ret6: -; CHECK-NEXT: ret i32 -1 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.umin.i32(i32 -1, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 4 -; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A]], i64 0, i32 1 -; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NEXT]], align 8 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @umin(ptr noundef [[TMP1]]) -; CHECK-NEXT: [[DOTSROA_SPECULATED:%.*]] = tail call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[DOTSROA_SPECULATED]] +; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A_TR]], align 4 +; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1 +; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8 +; CHECK-NEXT: [[DOTSROA_SPECULATED]] = tail call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %tobool.not = icmp eq ptr %a, null @@ -40,17 +44,21 @@ ; CHECK-LABEL: define noundef i32 @umax ; CHECK-SAME: (ptr noundef readonly [[A:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A]], null -; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[DOTSROA_SPECULATED:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END]] ; CHECK: common.ret6: -; CHECK-NEXT: ret i32 0 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.umax.i32(i32 0, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 4 -; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A]], i64 0, i32 1 -; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NEXT]], align 8 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @umax(ptr noundef [[TMP1]]) -; CHECK-NEXT: [[DOTSROA_SPECULATED:%.*]] = tail call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[DOTSROA_SPECULATED]] +; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A_TR]], align 4 +; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1 +; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8 +; CHECK-NEXT: [[DOTSROA_SPECULATED]] = tail call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %tobool.not = icmp eq ptr %a, null @@ -73,17 +81,21 @@ ; CHECK-LABEL: define noundef i32 @smin ; CHECK-SAME: (ptr noundef readonly [[A:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A]], null -; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ 2147483647, [[ENTRY:%.*]] ], [ [[DOTSROA_SPECULATED:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END]] ; CHECK: common.ret6: -; CHECK-NEXT: ret i32 2147483647 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.smin.i32(i32 2147483647, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 4 -; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A]], i64 0, i32 1 -; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NEXT]], align 8 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @smin(ptr noundef [[TMP1]]) -; CHECK-NEXT: [[DOTSROA_SPECULATED:%.*]] = tail call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[DOTSROA_SPECULATED]] +; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A_TR]], align 4 +; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1 +; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8 +; CHECK-NEXT: [[DOTSROA_SPECULATED]] = tail call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %tobool.not = icmp eq ptr %a, null @@ -106,17 +118,21 @@ ; CHECK-LABEL: define noundef i32 @smax ; CHECK-SAME: (ptr noundef readonly [[A:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A]], null -; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END:%.*]] +; CHECK-NEXT: br label [[TAILRECURSE:%.*]] +; CHECK: tailrecurse: +; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi i32 [ -2147483648, [[ENTRY:%.*]] ], [ [[DOTSROA_SPECULATED:%.*]], [[IF_END:%.*]] ] +; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ] +; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null +; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET6:%.*]], label [[IF_END]] ; CHECK: common.ret6: -; CHECK-NEXT: ret i32 -2147483648 +; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call i32 @llvm.smax.i32(i32 -2147483648, i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: ret i32 [[ACCUMULATOR_RET_TR]] ; CHECK: if.end: -; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 4 -; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A]], i64 0, i32 1 -; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NEXT]], align 8 -; CHECK-NEXT: [[CALL:%.*]] = tail call noundef i32 @smax(ptr noundef [[TMP1]]) -; CHECK-NEXT: [[DOTSROA_SPECULATED:%.*]] = tail call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[CALL]]) -; CHECK-NEXT: ret i32 [[DOTSROA_SPECULATED]] +; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A_TR]], align 4 +; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_LISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1 +; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8 +; CHECK-NEXT: [[DOTSROA_SPECULATED]] = tail call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[ACCUMULATOR_TR]]) +; CHECK-NEXT: br label [[TAILRECURSE]] ; entry: %tobool.not = icmp eq ptr %a, null