[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