[InstCombine] Fold X + ((-X) & (C - 1)) to (X + C - 1) & -C - #215122
[InstCombine] Fold X + ((-X) & (C - 1)) to (X + C - 1) & -C#215122Bagodiya wants to merge 1 commit into
Conversation
|
@llvm/pr-subscribers-llvm-transforms Author: Bagodiya (Bagodiya) Changes
This is the align-up idiom as allocators usually write it. The other fold in the issue, One case that doesn't hit this: https://alive2.llvm.org/ce/z/sCCJzq Fixes #214675 Full diff: https://github.com/llvm/llvm-project/pull/215122.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 52a57e153bb4b..7f1f0d674c88b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1811,6 +1811,19 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
return BinaryOperator::CreateAnd(Add, A);
}
+ // Align-up idiom:
+ // X + ((-X) & (C - 1)) --> (X + C - 1) & -C, for a power-of-two C.
+ // Note -C == ~(C - 1), so the mask is simply the inverted low-bit mask.
+ {
+ const APInt *LowMask;
+ if (match(&I, m_c_Add(m_OneUse(m_c_And(m_Neg(m_Value(A)),
+ m_LowBitMask(LowMask))),
+ m_Deferred(A)))) {
+ Value *NewAdd = Builder.CreateAdd(A, ConstantInt::get(Ty, *LowMask));
+ return BinaryOperator::CreateAnd(NewAdd, ConstantInt::get(Ty, ~*LowMask));
+ }
+ }
+
// Canonicalize ((A & -A) - 1) --> ((A - 1) & ~A)
// Forms all commutable operations, and simplifies ctpop -> cttz folds.
if (match(&I,
diff --git a/llvm/test/Transforms/InstCombine/add-mask-neg.ll b/llvm/test/Transforms/InstCombine/add-mask-neg.ll
index cb23763249d63..a83d86da58b82 100644
--- a/llvm/test/Transforms/InstCombine/add-mask-neg.ll
+++ b/llvm/test/Transforms/InstCombine/add-mask-neg.ll
@@ -119,5 +119,173 @@ define <2 x i32> @dec_mask_multiuse_neg_multiuse_v2i32(<2 x i32> %X) {
ret <2 x i32> %dec
}
+;
+; X + ((-X) & (C - 1)) --> (X + C - 1) & -C, for a power-of-two C
+;
+define i32 @align_up(i32 %x) {
+; CHECK-LABEL: @align_up(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP1]], -16
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 %neg, 15
+ %r = add i32 %x, %and
+ ret i32 %r
+}
+
+define i32 @align_up_commuted(i32 %x) {
+; CHECK-LABEL: @align_up_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP1]], -16
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 %neg, 15
+ %r = add i32 %and, %x
+ ret i32 %r
+}
+
+define i32 @align_up_commuted_and(i32 %x) {
+; CHECK-LABEL: @align_up_commuted_and(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP1]], -16
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 15, %neg
+ %r = add i32 %x, %and
+ ret i32 %r
+}
+
+define <2 x i32> @align_up_vec(<2 x i32> %x) {
+; CHECK-LABEL: @align_up_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = add <2 x i32> [[X:%.*]], splat (i32 15)
+; CHECK-NEXT: [[R:%.*]] = and <2 x i32> [[TMP1]], splat (i32 -16)
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %neg = sub <2 x i32> zeroinitializer, %x
+ %and = and <2 x i32> %neg, splat (i32 15)
+ %r = add <2 x i32> %x, %and
+ ret <2 x i32> %r
+}
+
+; negative test - the mask is not a low-bit mask
+
+define i32 @align_up_not_lowbitmask(i32 %x) {
+; CHECK-LABEL: @align_up_not_lowbitmask(
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[NEG]], 13
+; CHECK-NEXT: [[R:%.*]] = add i32 [[X]], [[AND]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 %neg, 13
+ %r = add i32 %x, %and
+ ret i32 %r
+}
+
+; negative test - extra use of the and
+
+define i32 @align_up_multiuse_and(i32 %x) {
+; CHECK-LABEL: @align_up_multiuse_and(
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[NEG]], 15
+; CHECK-NEXT: [[R:%.*]] = add i32 [[X]], [[AND]]
+; CHECK-NEXT: call void @use(i32 [[AND]])
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 %neg, 15
+ %r = add i32 %x, %and
+ call void @use(i32 %and)
+ ret i32 %r
+}
+
+; negative test - a different value is negated
+
+define i32 @align_up_wrong_value(i32 %x, i32 %y) {
+; CHECK-LABEL: @align_up_wrong_value(
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[Y:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[NEG]], 15
+; CHECK-NEXT: [[R:%.*]] = add i32 [[X:%.*]], [[AND]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %y
+ %and = and i32 %neg, 15
+ %r = add i32 %x, %and
+ ret i32 %r
+}
+
+; negative test - non-splat vector
+
+define <2 x i32> @align_up_nonsplat_vec(<2 x i32> %x) {
+; CHECK-LABEL: @align_up_nonsplat_vec(
+; CHECK-NEXT: [[NEG:%.*]] = sub <2 x i32> zeroinitializer, [[X:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and <2 x i32> [[NEG]], <i32 15, i32 7>
+; CHECK-NEXT: [[R:%.*]] = add <2 x i32> [[X]], [[AND]]
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %neg = sub <2 x i32> zeroinitializer, %x
+ %and = and <2 x i32> %neg, <i32 15, i32 7>
+ %r = add <2 x i32> %x, %and
+ ret <2 x i32> %r
+}
+
+; the neg has an extra use
+
+define i32 @align_up_multiuse_neg(i32 %x) {
+; CHECK-LABEL: @align_up_multiuse_neg(
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X]], 15
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP1]], -16
+; CHECK-NEXT: call void @use(i32 [[NEG]])
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub i32 0, %x
+ %and = and i32 %neg, 15
+ %r = add i32 %x, %and
+ call void @use(i32 %neg)
+ ret i32 %r
+}
+
+; nsw/nuw on the source add are dropped
+
+define i32 @align_up_nsw_nuw(i32 %x) {
+; CHECK-LABEL: @align_up_nsw_nuw(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], 15
+; CHECK-NEXT: [[R:%.*]] = and i32 [[TMP1]], -16
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %neg = sub nsw i32 0, %x
+ %and = and i32 %neg, 15
+ %r = add nuw nsw i32 %x, %and
+ ret i32 %r
+}
+
+define <vscale x 2 x i32> @align_up_scalable_vec(<vscale x 2 x i32> %x) {
+; CHECK-LABEL: @align_up_scalable_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = add <vscale x 2 x i32> [[X:%.*]], splat (i32 15)
+; CHECK-NEXT: [[R:%.*]] = and <vscale x 2 x i32> [[TMP1]], splat (i32 -16)
+; CHECK-NEXT: ret <vscale x 2 x i32> [[R]]
+;
+ %neg = sub <vscale x 2 x i32> zeroinitializer, %x
+ %and = and <vscale x 2 x i32> %neg, splat (i32 15)
+ %r = add <vscale x 2 x i32> %x, %and
+ ret <vscale x 2 x i32> %r
+}
+
+define <2 x i32> @align_up_vec_poison_elt(<2 x i32> %x) {
+; CHECK-LABEL: @align_up_vec_poison_elt(
+; CHECK-NEXT: [[TMP1:%.*]] = add <2 x i32> [[X:%.*]], splat (i32 15)
+; CHECK-NEXT: [[R:%.*]] = and <2 x i32> [[TMP1]], splat (i32 -16)
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %neg = sub <2 x i32> zeroinitializer, %x
+ %and = and <2 x i32> %neg, <i32 15, i32 poison>
+ %r = add <2 x i32> %x, %and
+ ret <2 x i32> %r
+}
+
declare void @use(i32)
declare void @usev(<2 x i32>)
|
dtcxzyw
left a comment
There was a problem hiding this comment.
LGTM.
Generalized proof: https://alive2.llvm.org/ce/z/bD7eR4
| // Note -C == ~(C - 1), so the mask is simply the inverted low-bit mask. | ||
| { | ||
| const APInt *LowMask; | ||
| if (match(&I, m_c_Add(m_OneUse(m_c_And(m_Neg(m_Value(A)), |
There was a problem hiding this comment.
| if (match(&I, m_c_Add(m_OneUse(m_c_And(m_Neg(m_Value(A)), | |
| if (match(&I, m_c_Add(m_OneUse(m_And(m_Neg(m_Value(A)), |
121abe3 to
38cf609
Compare
X + ((-X) & (C - 1)) --> (X + C - 1) & -Cfor power-of-twoC.This is the align-up idiom as allocators usually write it.
-C == ~(C - 1), sothe mask is just the inverted low-bit mask.
No wrapping flags needed - it verifies without nsw/nuw on either side. Flags on
the original add are dropped.
The other fold in the issue,
X + (X | -X) --> X & (X - 1), already exists invisitAdd, so this only adds the second one.
One case that doesn't hit this:
C = 2. InstCombine turns(-X) & 1intoX & 1first, so the neg is already gone.https://alive2.llvm.org/ce/z/sCCJzq
C = 2: https://alive2.llvm.org/ce/z/xeQqkz
Fixes #214675