[llvm] a4f740f - [InstCombine] Fold X + ((-X) & (C - 1)) to (X + C - 1) & -C (#215122)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 9 23:17:45 PDT 2026
Author: Bagodiya
Date: 2026-08-10T14:17:40+08:00
New Revision: a4f740f144ddccb30bd3b26c69740914518a642b
URL: https://github.com/llvm/llvm-project/commit/a4f740f144ddccb30bd3b26c69740914518a642b
DIFF: https://github.com/llvm/llvm-project/commit/a4f740f144ddccb30bd3b26c69740914518a642b.diff
LOG: [InstCombine] Fold X + ((-X) & (C - 1)) to (X + C - 1) & -C (#215122)
`X + ((-X) & (C - 1)) --> (X + C - 1) & -C` for power-of-two `C`.
This is the align-up idiom as allocators usually write it. `-C == ~(C -
1)`, so
the 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 in
visitAdd, so this only adds the second one.
One case that doesn't hit this: `C = 2`. InstCombine turns `(-X) & 1`
into
`X & 1` first, so the neg is already gone.
https://alive2.llvm.org/ce/z/sCCJzq
C = 2: https://alive2.llvm.org/ce/z/xeQqkz
Fixes #214675
Added:
Modified:
llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
llvm/test/Transforms/InstCombine/add-mask-neg.ll
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 52a57e153bb4b..9b7f2705cff81 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_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
diff erent 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>)
More information about the llvm-commits
mailing list