[VectorCombine] Fold vector sign-bit checks#175194
Conversation
|
@llvm/pr-subscribers-llvm-transforms @llvm/pr-subscribers-vectorizers Author: Valeriy Savchenko (SavchenkoValeriy) ChangesFold patterns that extract sign bits, reduce them, and compare against boundary values into direct sign checks on the reduced vector. When the comparison is against 0 or MAX (1 for boolean reductions, NumElts for add), the pattern reduces to one of four quantified predicates:
The transform eliminates the shift and selects between reduce.or/reduce.umax or reduce.and/reduce.umin based on cost modeling. The matrix of Alive2 proofs for every pair of {reduction, comparison}:
Other test cases
or/umax and and/umin equivalence
Patch is 33.45 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/175194.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 243f685cf25e2..c1412f7d12f95 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -145,6 +145,7 @@ class VectorCombine {
bool foldShuffleFromReductions(Instruction &I);
bool foldShuffleChainsToReduce(Instruction &I);
bool foldCastFromReductions(Instruction &I);
+ bool foldSignBitReductionCmp(Instruction &I);
bool foldSelectShuffle(Instruction &I, bool FromReduction = false);
bool foldInterleaveIntrinsics(Instruction &I);
bool shrinkType(Instruction &I);
@@ -3806,6 +3807,216 @@ bool VectorCombine::foldCastFromReductions(Instruction &I) {
return true;
}
+/// Fold:
+/// icmp pred (reduce.{add,or,and,umax,umin}(signbit_extract(x))), C
+/// into:
+/// icmp sgt/slt (reduce.{or,umax,and,umin}(x)), -1/0
+///
+/// Sign-bit reductions produce values with known semantics:
+/// - reduce.{or,umax}: 0 if no element is negative, 1 if any is
+/// - reduce.{and,umin}: 1 if all elements are negative, 0 if any isn't
+/// - reduce.add: count of negative elements (0 to NumElts)
+///
+/// We transform to a direct sign check on reduce.{or,umax} or
+/// reduce.{and,umin} without explicit sign-bit extraction.
+///
+/// In spirit, it's similar to foldSignBitCheck in InstCombine.
+bool VectorCombine::foldSignBitReductionCmp(Instruction &I) {
+ CmpPredicate Pred;
+ Value *ReduceOp;
+ const APInt *CmpVal;
+ if (!match(&I, m_ICmp(Pred, m_Value(ReduceOp), m_APInt(CmpVal))))
+ return false;
+
+ auto *II = dyn_cast<IntrinsicInst>(ReduceOp);
+ if (!II || !II->hasOneUse())
+ return false;
+
+ Intrinsic::ID OrigIID = II->getIntrinsicID();
+ switch (OrigIID) {
+ case Intrinsic::vector_reduce_or:
+ case Intrinsic::vector_reduce_umax:
+ case Intrinsic::vector_reduce_and:
+ case Intrinsic::vector_reduce_umin:
+ case Intrinsic::vector_reduce_add:
+ break;
+ default:
+ return false;
+ }
+
+ Value *ReductionSrc = II->getArgOperand(0);
+ if (!ReductionSrc->hasOneUse())
+ return false;
+
+ auto *VecTy = dyn_cast<FixedVectorType>(ReductionSrc->getType());
+ if (!VecTy)
+ return false;
+
+ unsigned BitWidth = VecTy->getScalarSizeInBits();
+ unsigned NumElts = VecTy->getNumElements();
+
+ // Match sign-bit extraction: shr X, (bitwidth-1)
+ Value *X;
+ Constant *C;
+ if (!match(ReductionSrc, m_Shr(m_Value(X), m_Constant(C))) ||
+ !match(C, m_SpecificInt_ICMP(ICmpInst::ICMP_EQ,
+ APInt(BitWidth, BitWidth - 1))))
+ return false;
+
+ // MaxVal: 1 for or/and/umax/umin, NumElts for add
+ unsigned MaxVal = OrigIID == Intrinsic::vector_reduce_add ? NumElts : 1;
+
+ // In addition to direct comparisons EQ 0, NE 0, EQ 1, NE 1, etc. we support
+ // inequalities that can be interpreted as either EQ or NE considering a
+ // rather narrow range of possible value of sign-bit reductions.
+ bool IsEq;
+ uint64_t NormalizedCmpVal;
+ if (Pred == ICmpInst::ICMP_EQ) {
+ IsEq = true;
+ NormalizedCmpVal = CmpVal->getZExtValue();
+ } else if (Pred == ICmpInst::ICMP_NE) {
+ IsEq = false;
+ NormalizedCmpVal = CmpVal->getZExtValue();
+ } else if (Pred == ICmpInst::ICMP_SLT && CmpVal->isOne()) {
+ IsEq = true;
+ NormalizedCmpVal = 0; // slt 1 → eq 0
+ } else if (Pred == ICmpInst::ICMP_SGT && CmpVal->isZero()) {
+ IsEq = false;
+ NormalizedCmpVal = 0; // sgt 0 → ne 0
+ } else if ((Pred == ICmpInst::ICMP_SLT || Pred == ICmpInst::ICMP_ULT) &&
+ *CmpVal == MaxVal) {
+ IsEq = false;
+ NormalizedCmpVal = MaxVal; // s/ult MaxVal → ne MaxVal
+ } else if ((Pred == ICmpInst::ICMP_SGT || Pred == ICmpInst::ICMP_UGT) &&
+ *CmpVal == MaxVal - 1) {
+ IsEq = true;
+ NormalizedCmpVal = MaxVal; // s/ugt MaxVal-1 → eq MaxVal
+ } else {
+ return false;
+ }
+
+ bool TestsHigh = NormalizedCmpVal == MaxVal;
+ if (NormalizedCmpVal != 0 && !TestsHigh)
+ return false;
+
+ // For this fold we support for types of checks:
+ //
+ // 1. All lanes are negative - AllNeg
+ // 2. All lanes are non-negative - AllNonNeg
+ // 3. At least one negative lane - AnyNeg
+ // 4. At least one non-negative lane - AnyNonNeg
+ //
+ // For each case, we can generate the following code:
+ //
+ // 1. AllNeg - reduce.and/umin(X) < 0
+ // 2. AllNonNeg - reduce.or/umax(X) > -1
+ // 3. AnyNeg - reduce.or/umax(X) < 0
+ // 4. AnyNonNeg - reduce.and/umin(X) > -1
+ //
+ // The table below shows the aggregation of all supported cases
+ // using these four cases.
+ //
+ // Reduction | == 0 | != 0 | == MAX | != MAX
+ // ------------+-----------+-----------+-----------+-----------
+ // or/umax | AllNonNeg | AnyNeg | AnyNeg | AllNonNeg
+ // and/umin | AnyNonNeg | AllNeg | AllNeg | AnyNonNeg
+ // add | AllNonNeg | AnyNeg | AllNeg | AnyNonNeg
+ //
+ // NOTE: MAX = 1 for or/and/umax/umin, and the vector size N for add
+ //
+ // For easier codegen and check inversion, we use the following encoding:
+ //
+ // 1. Bit-3 === requires or/umax (1) or and/umin (0) check
+ // 2. Bit-2 === checks < 0 (1) or > -1 (0)
+ // 3. Bit-1 === universal (1) or existential (0) check
+ //
+ // AnyNeg = 0b110: uses or/umax, checks negative, any-check
+ // AllNonNeg = 0b101: uses or/umax, checks non-neg, all-check
+ // AnyNonNeg = 0b000: uses and/umin, checks non-neg, any-check
+ // AllNeg = 0b011: uses and/umin, checks negative, all-check
+ //
+ // XOR with 0b011 inverts the check (swaps all/any and neg/non-neg).
+ //
+ enum CheckKind : unsigned {
+ AnyNonNeg = 0b000,
+ AllNeg = 0b011,
+ AllNonNeg = 0b101,
+ AnyNeg = 0b110,
+ };
+ // Return true if we fold this check into or/umax and false for and/umin
+ auto RequiresOr = [](CheckKind C) -> bool { return C & 0b100; };
+ // Return true if we should check if result is negative and false otherwise
+ auto IsNegativeCheck = [](CheckKind C) -> bool { return C & 0b010; };
+ // Logically invert the check
+ auto Invert = [](CheckKind C) { return CheckKind(C ^ 0b011); };
+
+ CheckKind Base;
+ switch (OrigIID) {
+ case Intrinsic::vector_reduce_or:
+ case Intrinsic::vector_reduce_umax:
+ Base = TestsHigh ? AnyNeg : AllNonNeg;
+ break;
+ case Intrinsic::vector_reduce_and:
+ case Intrinsic::vector_reduce_umin:
+ Base = TestsHigh ? AllNeg : AnyNonNeg;
+ break;
+ default: // vector_reduce_add
+ Base = TestsHigh ? AllNeg : AllNonNeg;
+ break;
+ }
+
+ CheckKind Check = IsEq ? Base : Invert(Base);
+
+ // Calculate old cost: shift + reduction
+ unsigned ShiftOpcode = cast<Instruction>(ReductionSrc)->getOpcode();
+ InstructionCost OldCost =
+ TTI.getArithmeticInstrCost(ShiftOpcode, VecTy, CostKind);
+ unsigned OrigReductionOpc = getArithmeticReductionInstruction(OrigIID);
+ if (OrigReductionOpc != Instruction::ICmp)
+ OldCost += TTI.getArithmeticReductionCost(OrigReductionOpc, VecTy,
+ std::nullopt, CostKind);
+ else
+ OldCost +=
+ TTI.getMinMaxReductionCost(OrigIID, VecTy, FastMathFlags(), CostKind);
+
+ auto PickCheaper = [&](Intrinsic::ID Arith, Intrinsic::ID MinMax) {
+ InstructionCost ArithCost =
+ TTI.getArithmeticReductionCost(getArithmeticReductionInstruction(Arith),
+ VecTy, std::nullopt, CostKind);
+ InstructionCost MinMaxCost =
+ TTI.getMinMaxReductionCost(MinMax, VecTy, FastMathFlags(), CostKind);
+ return ArithCost <= MinMaxCost ? std::make_pair(Arith, ArithCost)
+ : std::make_pair(MinMax, MinMaxCost);
+ };
+
+ // Choose output reduction based on encoding's MSB
+ auto [NewIID, NewCost] = RequiresOr(Check)
+ ? PickCheaper(Intrinsic::vector_reduce_or,
+ Intrinsic::vector_reduce_umax)
+ : PickCheaper(Intrinsic::vector_reduce_and,
+ Intrinsic::vector_reduce_umin);
+
+ LLVM_DEBUG(dbgs() << "Found sign-bit reduction cmp: " << I << "\n OldCost: "
+ << OldCost << " vs NewCost: " << NewCost << "\n");
+
+ if (NewCost > OldCost)
+ return false;
+
+ // Generate comparison based on encoding's neg bit: slt 0 for neg, sgt -1 for
+ // non-neg
+ Builder.SetInsertPoint(&I);
+ Type *ScalarTy = VecTy->getScalarType();
+ Value *NewReduce = Builder.CreateIntrinsic(ScalarTy, NewIID, {X});
+ Value *NewCmp = IsNegativeCheck(Check)
+ ? Builder.CreateICmpSLT(
+ NewReduce, ConstantInt::getNullValue(ScalarTy))
+ : Builder.CreateICmpSGT(
+ NewReduce, ConstantInt::getAllOnesValue(ScalarTy));
+
+ replaceValue(I, *NewCmp);
+ return true;
+}
+
/// Returns true if this ShuffleVectorInst eventually feeds into a
/// vector reduction intrinsic (e.g., vector_reduce_add) by only following
/// chains of shuffles and binary operators (in any combination/order).
@@ -4844,6 +5055,9 @@ bool VectorCombine::run() {
return true;
break;
case Instruction::ICmp:
+ if (foldSignBitReductionCmp(I))
+ return true;
+ [[fallthrough]];
case Instruction::FCmp:
if (foldExtractExtract(I))
return true;
diff --git a/llvm/test/Transforms/VectorCombine/fold-signbit-reduction-cmp.ll b/llvm/test/Transforms/VectorCombine/fold-signbit-reduction-cmp.ll
new file mode 100644
index 0000000000000..a598fa7bd22f9
--- /dev/null
+++ b/llvm/test/Transforms/VectorCombine/fold-signbit-reduction-cmp.ll
@@ -0,0 +1,635 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=vector-combine -mtriple=aarch64 < %s | FileCheck %s --check-prefixes=CHECK,AARCH64
+; RUN: opt -S -passes=vector-combine -mtriple=x86_64-- < %s | FileCheck %s --check-prefixes=CHECK,X86
+
+define i1 @or_eq_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @or_eq_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @or_eq_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @or_ne_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @or_ne_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @or_ne_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @or_eq_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @or_eq_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @or_eq_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @or_ne_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @or_ne_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @or_ne_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @umax_eq_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umax_eq_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umax_eq_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @umax_ne_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umax_ne_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umax_ne_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @umax_eq_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umax_eq_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umax_eq_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @umax_ne_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umax_ne_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umax_ne_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @and_eq_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @and_eq_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @and_eq_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @and_ne_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @and_ne_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @and_ne_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @and_eq_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @and_eq_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @and_eq_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @and_ne_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @and_ne_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @and_ne_max(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 1
+ ret i1 %cmp
+}
+
+define i1 @umin_eq_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umin_eq_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umin_eq_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp sgt i32 [[TMP1]], -1
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> %shr)
+ %cmp = icmp eq i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @umin_ne_0(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umin_ne_0(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umin_ne_0(
+; X86-SAME: <4 x i32> [[X:%.*]]) {
+; X86-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.and.v4i32(<4 x i32> [[X]])
+; X86-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; X86-NEXT: ret i1 [[CMP]]
+;
+ %shr = lshr <4 x i32> %x, splat (i32 31)
+ %red = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> %shr)
+ %cmp = icmp ne i32 %red, 0
+ ret i1 %cmp
+}
+
+define i1 @umin_eq_max(<4 x i32> %x) {
+; AARCH64-LABEL: define i1 @umin_eq_max(
+; AARCH64-SAME: <4 x i32> [[X:%.*]]) {
+; AARCH64-NEXT: [[TMP1:%.*]] = call i32 @llvm.vector.reduce.umin.v4i32(<4 x i32> [[X]])
+; AARCH64-NEXT: [[CMP:%.*]] = icmp slt i32 [[TMP1]], 0
+; AARCH64-NEXT: ret i1 [[CMP]]
+;
+; X86-LABEL: define i1 @umin_eq_max(
+; X86-SAME: <4 x i32> [[X:%.*]])...
[truncated]
|
19ab77e to
c500cd8
Compare
| /// Sign-bit reductions produce values with known semantics: | ||
| /// - reduce.{or,umax}: 0 if no element is negative, 1 if any is | ||
| /// - reduce.{and,umin}: 1 if all elements are negative, 0 if any isn't | ||
| /// - reduce.add: count of negative elements (0 to NumElts) |
There was a problem hiding this comment.
It is a bit weird to use reduce.add for sign bit checking. Can you share some real-world cases? Is it generated by auto-vectorization?
There was a problem hiding this comment.
While agree with this in general, it's quite common in practice because platforms like AArch64 don't have horizontal or and thus horizontal add becomes a viable alternative.
As I mentioned earlier in my other patch: #173069 (comment) that pattern of implementing _mm_movemask_ps for NEON. Then that implementation that might look something like this:
uint32x4_t input = vreinterpretq_u32_m128(a);
static const int32x4_t shift = {0, 1, 2, 3};
uint32x4_t tmp = vshrq_n_u32(input, 31);
return vaddvq_u32(vshlq_u32(tmp, shift));
gets inlined in various places producing quite inefficient signedness check in computationally heavy applications.
Combined with #173069 this change optimizes it away. And it is exactly the add version that gives us a substantial boost on the internal benchmarks.
c500cd8 to
b6666ce
Compare
b6666ce to
f236f8d
Compare
f236f8d to
24882ef
Compare
|
@RKSimon do you mind taking another look here? |
don't forget to use the "re-request-review" icon by the reviewer, otherwise I don't usually see it (and I often miss CC mentions) |
|
@RKSimon I can't re-request the review on this PR, but do you mind checking it? I split the test case between X86 and AArch64 targets as you asked. |
|
High level comment. I think this transform can be split into several sub-transforms. I am not expressing an opinion as to whether this change should land before or after such a split, and will leave that up to area reviewers. Your proposed transform of Possible splits: (1) (This generalizes for any logical op. Note that we should already be canonicalizing smin/smax to umin/umax; if not that's another to play with.) (2) Your inequality handling is just a transform to convert the inequality form to the equality form (of the source). I believe that (1) generalizes for any single bit extract (i.e. and X, 2). |
This doesn't work for the original motivating issue: #175194 (comment) But I agree (1) should simplify the implementation. |
Thanks for the comment, I always strive to split desired folds into smaller more general folds (as actually the case here as well).
I don't oppose it, but it actually doesn't help with the motivation example that is about reduce.add Maybe that can be a follow up work? |
24882ef to
0ce381c
Compare
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
0ce381c to
7d26c62
Compare
d0b8428 to
9d64f3d
Compare
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
🪟 Windows x64 Test Results
✅ The build succeeded and all tests passed. |
9d64f3d to
3a0b80a
Compare
Fold patterns that extract sign bits, reduce them, and compare against boundary values into direct sign checks on the reduced vector.
```
icmp pred (reduce.{add,or,and,umax,umin}(lshr X, BitWidth-1)), C
-> icmp slt/sgt (reduce.{or,umax,and,umin}(X)), 0/-1
```
When the comparison is against 0 or MAX (1 for boolean reductions, NumElts for add), the pattern reduces to one of four quantified predicates:
- ∀x: x < 0 (AllNeg)
- ∀x: x ≥ 0 (AllNonNeg)
- ∃x: x < 0 (AnyNeg)
- ∃x: x ≥ 0 (AnyNonNeg)
The transform eliminates the shift and selects between reduce.or/reduce.umax or reduce.and/reduce.umin based on cost modeling.
The matrix of Alive2 proofs for every pair of {reduction, comparison}:
| Reduction | == 0 | != 0 | == MAX | != MAX |
|-----------|------|------|--------|--------|
| or | [proof](https://alive2.llvm.org/ce/z/_BWxJW) | [proof](https://alive2.llvm.org/ce/z/k3EiK6) | [proof](https://alive2.llvm.org/ce/z/a8cAjp) | [proof](https://alive2.llvm.org/ce/z/ci-HMt) |
| umax | [proof](https://alive2.llvm.org/ce/z/dWt28G) | [proof](https://alive2.llvm.org/ce/z/_MqxXC) | [proof](https://alive2.llvm.org/ce/z/KQebnF) | [proof](https://alive2.llvm.org/ce/z/mixEgN) |
| and | [proof](https://alive2.llvm.org/ce/z/JgYrLj) | [proof](https://alive2.llvm.org/ce/z/FZuPLy) | [proof](https://alive2.llvm.org/ce/z/bYCa8V) | [proof](https://alive2.llvm.org/ce/z/9fsLsN) |
| umin | [proof](https://alive2.llvm.org/ce/z/YnaSL-) | [proof](https://alive2.llvm.org/ce/z/rGrgoM) | [proof](https://alive2.llvm.org/ce/z/pb-ezQ) | [proof](https://alive2.llvm.org/ce/z/JkoqEi) |
| add | [proof](https://alive2.llvm.org/ce/z/d5w5CF) | [proof](https://alive2.llvm.org/ce/z/GUgQ2Z) | [proof](https://alive2.llvm.org/ce/z/HnstY8) | [proof](https://alive2.llvm.org/ce/z/j8z_3C) |
| Test | Proof |
|------|-------|
| add_ult_max (ult 4 ≡ ne 4) | [proof](https://alive2.llvm.org/ce/z/pyDgTg) |
| add_ugt_max_minus_1 (ugt 3 ≡ eq 4) | [proof](https://alive2.llvm.org/ce/z/mHVXJk) |
| ashr_add_eq_0 (ashr instead of lshr) | [proof](https://alive2.llvm.org/ce/z/oa9Kgo) |
| Check | Equivalence | Proof |
|-----------------|-------------|-------|
| AnyNeg | or slt 0 ≡ umax slt 0 | [proof](https://alive2.llvm.org/ce/z/Do2tNQ) |
| AllNonNeg | or sgt -1 ≡ umax sgt -1 | [proof](https://alive2.llvm.org/ce/z/N4kZ8Z) |
| AllNeg | and slt 0 ≡ umin slt 0 | [proof](https://alive2.llvm.org/ce/z/4mNpMk) |
| AnyNonNeg | and sgt -1 ≡ umin sgt -1 | [proof](https://alive2.llvm.org/ce/z/2pVnyg) |
3a0b80a to
6459136
Compare
|
@RKSimon the new commit fixes the failure |
Fold patterns that extract sign bits, reduce them, and compare against
boundary values into direct sign checks on the reduced vector.
```
icmp pred (reduce.{add,or,and,umax,umin}(lshr X, BitWidth-1)), C
-> icmp slt/sgt (reduce.{or,umax,and,umin}(X)), 0/-1
```
When the comparison is against 0 or MAX (1 for boolean reductions,
NumElts for add), the pattern reduces to one of four quantified
predicates:
- ∀x: x < 0 (AllNeg)
- ∀x: x ≥ 0 (AllNonNeg)
- ∃x: x < 0 (AnyNeg)
- ∃x: x ≥ 0 (AnyNonNeg)
The transform eliminates the shift and selects between
reduce.or/reduce.umax or reduce.and/reduce.umin based on cost modeling.
comparison}:
| Reduction | == 0 | != 0 | == MAX | != MAX |
|-----------|------|------|--------|--------|
| or | [proof](https://alive2.llvm.org/ce/z/_BWxJW) |
[proof](https://alive2.llvm.org/ce/z/k3EiK6) |
[proof](https://alive2.llvm.org/ce/z/a8cAjp) |
[proof](https://alive2.llvm.org/ce/z/ci-HMt) |
| umax | [proof](https://alive2.llvm.org/ce/z/dWt28G) |
[proof](https://alive2.llvm.org/ce/z/_MqxXC) |
[proof](https://alive2.llvm.org/ce/z/KQebnF) |
[proof](https://alive2.llvm.org/ce/z/mixEgN) |
| and | [proof](https://alive2.llvm.org/ce/z/JgYrLj) |
[proof](https://alive2.llvm.org/ce/z/FZuPLy) |
[proof](https://alive2.llvm.org/ce/z/bYCa8V) |
[proof](https://alive2.llvm.org/ce/z/9fsLsN) |
| umin | [proof](https://alive2.llvm.org/ce/z/YnaSL-) |
[proof](https://alive2.llvm.org/ce/z/rGrgoM) |
[proof](https://alive2.llvm.org/ce/z/pb-ezQ) |
[proof](https://alive2.llvm.org/ce/z/JkoqEi) |
| add | [proof](https://alive2.llvm.org/ce/z/d5w5CF) |
[proof](https://alive2.llvm.org/ce/z/GUgQ2Z) |
[proof](https://alive2.llvm.org/ce/z/HnstY8) |
[proof](https://alive2.llvm.org/ce/z/j8z_3C) |
| Test | Proof |
|------|-------|
| or_slt_1 (slt 1 ≡ eq 0) | [proof](https://alive2.llvm.org/ce/z/Wdb_uN)
|
| umax_sgt_0 (sgt 0 ≡ ne 0) |
[proof](https://alive2.llvm.org/ce/z/nw6NZc) |
| and_slt_max (slt 1 ≡ ne 1) |
[proof](https://alive2.llvm.org/ce/z/ZDMSXZ) |
| umin_sgt_max_minus_1 (sgt 0 ≡ eq 1) |
[proof](https://alive2.llvm.org/ce/z/Uynf8P) |
| add_ult_max (ult 4 ≡ ne 4) |
[proof](https://alive2.llvm.org/ce/z/pyDgTg) |
| add_ugt_max_minus_1 (ugt 3 ≡ eq 4) |
[proof](https://alive2.llvm.org/ce/z/mHVXJk) |
| ashr_add_eq_0 (ashr instead of lshr) |
[proof](https://alive2.llvm.org/ce/z/oa9Kgo) |
| Check | Equivalence | Proof |
|-----------------|-------------|-------|
| AnyNeg | or slt 0 ≡ umax slt 0 |
[proof](https://alive2.llvm.org/ce/z/Do2tNQ) |
| AllNonNeg | or sgt -1 ≡ umax sgt -1 |
[proof](https://alive2.llvm.org/ce/z/N4kZ8Z) |
| AllNeg | and slt 0 ≡ umin slt 0 |
[proof](https://alive2.llvm.org/ce/z/4mNpMk) |
| AnyNonNeg | and sgt -1 ≡ umin sgt -1 |
[proof](https://alive2.llvm.org/ce/z/2pVnyg) |
Fold patterns that extract sign bits, reduce them, and compare against
boundary values into direct sign checks on the reduced vector.
```
icmp pred (reduce.{add,or,and,umax,umin}(lshr X, BitWidth-1)), C
-> icmp slt/sgt (reduce.{or,umax,and,umin}(X)), 0/-1
```
When the comparison is against 0 or MAX (1 for boolean reductions,
NumElts for add), the pattern reduces to one of four quantified
predicates:
- ∀x: x < 0 (AllNeg)
- ∀x: x ≥ 0 (AllNonNeg)
- ∃x: x < 0 (AnyNeg)
- ∃x: x ≥ 0 (AnyNonNeg)
The transform eliminates the shift and selects between
reduce.or/reduce.umax or reduce.and/reduce.umin based on cost modeling.
comparison}:
| Reduction | == 0 | != 0 | == MAX | != MAX |
|-----------|------|------|--------|--------|
| or | [proof](https://alive2.llvm.org/ce/z/_BWxJW) |
[proof](https://alive2.llvm.org/ce/z/k3EiK6) |
[proof](https://alive2.llvm.org/ce/z/a8cAjp) |
[proof](https://alive2.llvm.org/ce/z/ci-HMt) |
| umax | [proof](https://alive2.llvm.org/ce/z/dWt28G) |
[proof](https://alive2.llvm.org/ce/z/_MqxXC) |
[proof](https://alive2.llvm.org/ce/z/KQebnF) |
[proof](https://alive2.llvm.org/ce/z/mixEgN) |
| and | [proof](https://alive2.llvm.org/ce/z/JgYrLj) |
[proof](https://alive2.llvm.org/ce/z/FZuPLy) |
[proof](https://alive2.llvm.org/ce/z/bYCa8V) |
[proof](https://alive2.llvm.org/ce/z/9fsLsN) |
| umin | [proof](https://alive2.llvm.org/ce/z/YnaSL-) |
[proof](https://alive2.llvm.org/ce/z/rGrgoM) |
[proof](https://alive2.llvm.org/ce/z/pb-ezQ) |
[proof](https://alive2.llvm.org/ce/z/JkoqEi) |
| add | [proof](https://alive2.llvm.org/ce/z/d5w5CF) |
[proof](https://alive2.llvm.org/ce/z/GUgQ2Z) |
[proof](https://alive2.llvm.org/ce/z/HnstY8) |
[proof](https://alive2.llvm.org/ce/z/j8z_3C) |
| Test | Proof |
|------|-------|
| or_slt_1 (slt 1 ≡ eq 0) | [proof](https://alive2.llvm.org/ce/z/Wdb_uN)
|
| umax_sgt_0 (sgt 0 ≡ ne 0) |
[proof](https://alive2.llvm.org/ce/z/nw6NZc) |
| and_slt_max (slt 1 ≡ ne 1) |
[proof](https://alive2.llvm.org/ce/z/ZDMSXZ) |
| umin_sgt_max_minus_1 (sgt 0 ≡ eq 1) |
[proof](https://alive2.llvm.org/ce/z/Uynf8P) |
| add_ult_max (ult 4 ≡ ne 4) |
[proof](https://alive2.llvm.org/ce/z/pyDgTg) |
| add_ugt_max_minus_1 (ugt 3 ≡ eq 4) |
[proof](https://alive2.llvm.org/ce/z/mHVXJk) |
| ashr_add_eq_0 (ashr instead of lshr) |
[proof](https://alive2.llvm.org/ce/z/oa9Kgo) |
| Check | Equivalence | Proof |
|-----------------|-------------|-------|
| AnyNeg | or slt 0 ≡ umax slt 0 |
[proof](https://alive2.llvm.org/ce/z/Do2tNQ) |
| AllNonNeg | or sgt -1 ≡ umax sgt -1 |
[proof](https://alive2.llvm.org/ce/z/N4kZ8Z) |
| AllNeg | and slt 0 ≡ umin slt 0 |
[proof](https://alive2.llvm.org/ce/z/4mNpMk) |
| AnyNonNeg | and sgt -1 ≡ umin sgt -1 |
[proof](https://alive2.llvm.org/ce/z/2pVnyg) |
…m#177996) llvm#175194, llvm#177159, and llvm#173069 introduced the code calling `TTI.getMinMaxReductionCost` with unexpected `Intrinsic::ID` causing RISC-V to fail with `llvm_unreachable` panic. Functionally, this is a small fix that also ports tests for the aforementioned folds to RISCV.
Fold patterns that extract sign bits, reduce them, and compare against boundary values into direct sign checks on the reduced vector.
When the comparison is against 0 or MAX (1 for boolean reductions, NumElts for add), the pattern reduces to one of four quantified predicates:
The transform eliminates the shift and selects between reduce.or/reduce.umax or reduce.and/reduce.umin based on cost modeling.
The matrix of Alive2 proofs for every pair of {reduction, comparison}:
Other test cases
or/umax and and/umin equivalence