[llvm] 8a69fb0 - [InstCombine] Fold (X + C) + (Y & ~C) to X + (Y | C) (#191334)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 14 09:34:21 PDT 2026


Author: hanbeom
Date: 2026-04-15T00:34:16+08:00
New Revision: 8a69fb096cb3f3f99ee9bf25247f4b0bc442ca84

URL: https://github.com/llvm/llvm-project/commit/8a69fb096cb3f3f99ee9bf25247f4b0bc442ca84
DIFF: https://github.com/llvm/llvm-project/commit/8a69fb096cb3f3f99ee9bf25247f4b0bc442ca84.diff

LOG: [InstCombine] Fold (X + C) + (Y & ~C) to X + (Y | C) (#191334)

Add an InstCombine fold for masked overwrite patterns where the add
constant matches the cleared bits in the mask:

  (X + C) + (Y & ~C) -> X + (Y | C)

Since `Y & ~C` clears all bits set in C, adding C cannot generate carry
through those bits and is equivalent to setting them with `or`.

Proof: https://alive2.llvm.org/ce/z/277UFK
Fixed: https://github.com/llvm/llvm-project/issues/191171

Added: 
    

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
    llvm/test/Transforms/InstCombine/add.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 635502f08b483..01d97484b090d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1345,6 +1345,23 @@ Instruction *InstCombinerImpl::foldAddLikeCommutative(Value *LHS, Value *RHS,
     }
   }
 
+  // (A + C) + (B & ~C) == A + (B | C)
+  if (match(LHS, m_c_Add(m_Value(A), m_APInt(C1))) &&
+      match(RHS, m_c_And(m_Value(B), m_SpecificInt(~*C1)))) {
+    // Replacing one add with {or, add}. Avoid growth if both sides are shared.
+    if (!LHS->hasOneUse() && !RHS->hasOneUse())
+      return nullptr;
+
+    bool NSWOut = NSW && match(LHS, m_NSWAdd(m_Value(), m_Value()));
+    bool NUWOut = NUW && match(LHS, m_NUWAdd(m_Value(), m_Value()));
+    Value *NewOr =
+        Builder.CreateOr(B, Constant::getIntegerValue(LHS->getType(), *C1));
+    Instruction *NewAdd = BinaryOperator::CreateAdd(A, NewOr);
+    NewAdd->setHasNoSignedWrap(NSWOut);
+    NewAdd->setHasNoUnsignedWrap(NUWOut);
+    return NewAdd;
+  }
+
   return nullptr;
 }
 

diff  --git a/llvm/test/Transforms/InstCombine/add.ll b/llvm/test/Transforms/InstCombine/add.ll
index aa68dfb540064..780a0a7872365 100644
--- a/llvm/test/Transforms/InstCombine/add.ll
+++ b/llvm/test/Transforms/InstCombine/add.ll
@@ -5,6 +5,372 @@
 declare void @use(i8)
 declare void @use_i1(i1)
 
+; fold (X + C) + (Y & ~C) -> X + (Y | C)
+define i32 @add_masked_overwrite_basic(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_basic(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[OR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -2
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[OR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -2
+  %r = add i32 %b, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_preserve_nsw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_preserve_nsw(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[X:%.*]], [[OR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nsw i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_preserve_nuw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_preserve_nuw(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[X:%.*]], [[OR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add nuw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_preserve_nuw_nsw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_preserve_nuw_nsw(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw nsw i32 [[X:%.*]], [[OR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add nuw nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw nsw i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_do_not_preserve_outer_nsw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_do_not_preserve_outer_nsw_only(
+; CHECK-NEXT:    [[M:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[M]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nsw i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_do_not_preserve_outer_nuw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_do_not_preserve_outer_nuw_only(
+; CHECK-NEXT:    [[M:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[M]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %a = add i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_do_not_preserve_inner_nsw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_do_not_preserve_inner_nsw_only(
+; CHECK-NEXT:    [[M:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A:%.*]], [[M]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_do_not_preserve_inner_nuw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_do_not_preserve_inner_nuw_only(
+; CHECK-NEXT:    [[M:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A:%.*]], [[M]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nuw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add i32 %a, %m
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_preserve_nsw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_preserve_nsw(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nsw i32 [[M:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nsw i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_preserve_nuw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_preserve_nuw(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nuw i32 [[M:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nuw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_preserve_nuw_nsw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_preserve_nuw_nsw(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nuw nsw i32 [[M:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nuw nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw nsw i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_do_not_preserve_outer_nsw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_do_not_preserve_outer_nsw_only(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[X:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nsw i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_do_not_preserve_outer_nuw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_do_not_preserve_outer_nuw_only(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[X:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add nuw i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_do_not_preserve_inner_nsw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_do_not_preserve_inner_nsw_only(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[M:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nsw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add i32 %m, %a
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_commuted_do_not_preserve_inner_nuw_only(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_commuted_do_not_preserve_inner_nuw_only(
+; CHECK-NEXT:    [[A:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[M:%.*]], [[A]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nuw i32 %x, 1
+  %m = and i32 %y, -2
+  %r = add i32 %m, %a
+  ret i32 %r
+}
+
+; only AndOp is multi-use -> fold is still OK
+define i32 @add_masked_overwrite_one_side_multiuse_and(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_one_side_multiuse_and(
+; CHECK-NEXT:    [[B:%.*]] = and i32 [[Y:%.*]], -2
+; CHECK-NEXT:    call void @use(i32 [[B]])
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -2
+  call void @use(i32 %b)
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+; non-trivial constant: fold (X + 5) + (Y & ~5)
+define i32 @add_masked_overwrite_const_5(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_const_5(
+; CHECK-NEXT:    [[B:%.*]] = or i32 [[Y:%.*]], 5
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 5
+  %b = and i32 %y, -6
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_const_5_preserve_nsw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_const_5_preserve_nsw(
+; CHECK-NEXT:    [[B:%.*]] = or i32 [[Y:%.*]], 5
+; CHECK-NEXT:    [[R:%.*]] = add nsw i32 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nsw i32 %x, 5
+  %b = and i32 %y, -6
+  %r = add nsw i32 %a, %b
+  ret i32 %r
+}
+
+define i32 @add_masked_overwrite_const_5_preserve_nuw(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_const_5_preserve_nuw(
+; CHECK-NEXT:    [[B:%.*]] = or i32 [[Y:%.*]], 5
+; CHECK-NEXT:    [[R:%.*]] = add nuw i32 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add nuw i32 %x, 5
+  %b = and i32 %y, -6
+  %r = add nuw i32 %a, %b
+  ret i32 %r
+}
+
+; boundary case: C = 0, ~C = -1
+define i32 @add_masked_overwrite_const_0(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_const_0(
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 0
+  %b = and i32 %y, -1
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+define i8 @add_masked_overwrite_i8(i8 %x, i8 %y) {
+; CHECK-LABEL: @add_masked_overwrite_i8(
+; CHECK-NEXT:    [[B:%.*]] = or i8 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i8 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %a = add i8 %x, 1
+  %b = and i8 %y, -2
+  %r = add i8 %a, %b
+  ret i8 %r
+}
+
+define i8 @add_masked_overwrite_i8_preserve_nuw_nsw(i8 %x, i8 %y) {
+; CHECK-LABEL: @add_masked_overwrite_i8_preserve_nuw_nsw(
+; CHECK-NEXT:    [[B:%.*]] = or i8 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nuw nsw i8 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %a = add nuw nsw i8 %x, 1
+  %b = and i8 %y, -2
+  %r = add nuw nsw i8 %a, %b
+  ret i8 %r
+}
+
+define i64 @add_masked_overwrite_i64(i64 %x, i64 %y) {
+; CHECK-LABEL: @add_masked_overwrite_i64(
+; CHECK-NEXT:    [[B:%.*]] = or i64 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i64 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i64 [[R]]
+;
+  %a = add i64 %x, 1
+  %b = and i64 %y, -2
+  %r = add i64 %a, %b
+  ret i64 %r
+}
+
+define i64 @add_masked_overwrite_i64_preserve_nsw(i64 %x, i64 %y) {
+; CHECK-LABEL: @add_masked_overwrite_i64_preserve_nsw(
+; CHECK-NEXT:    [[B:%.*]] = or i64 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nsw i64 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i64 [[R]]
+;
+  %a = add nsw i64 %x, 1
+  %b = and i64 %y, -2
+  %r = add nsw i64 %a, %b
+  ret i64 %r
+}
+
+define i64 @add_masked_overwrite_i64_preserve_nuw(i64 %x, i64 %y) {
+; CHECK-LABEL: @add_masked_overwrite_i64_preserve_nuw(
+; CHECK-NEXT:    [[B:%.*]] = or i64 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add nuw i64 [[A:%.*]], [[B]]
+; CHECK-NEXT:    ret i64 [[R]]
+;
+  %a = add nuw i64 %x, 1
+  %b = and i64 %y, -2
+  %r = add nuw i64 %a, %b
+  ret i64 %r
+}
+
+; both AddOp and AndOp are multi-use -> do not fold
+define i32 @add_masked_overwrite_multiuse_both(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_multiuse_both(
+; CHECK-NEXT:    [[A:%.*]] = add i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[B:%.*]] = and i32 [[Y:%.*]], -2
+; CHECK-NEXT:    call void @use(i32 [[A]])
+; CHECK-NEXT:    call void @use(i32 [[B]])
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -2
+  call void @use(i32 %a)
+  call void @use(i32 %b)
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+; exact-mask mismatch -> do not fold
+define i32 @add_masked_overwrite_mask_mismatch(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_mask_mismatch(
+; CHECK-NEXT:    [[A:%.*]] = add i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[B:%.*]] = and i32 [[Y:%.*]], -4
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -4
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
+; optional: only one side multi-use -> fold is still OK
+define i32 @add_masked_overwrite_one_side_multiuse(i32 %x, i32 %y) {
+; CHECK-LABEL: @add_masked_overwrite_one_side_multiuse(
+; CHECK-NEXT:    [[A:%.*]] = add i32 [[X:%.*]], 1
+; CHECK-NEXT:    call void @use(i32 [[A]])
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[X]], [[TMP1]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %a = add i32 %x, 1
+  %b = and i32 %y, -2
+  call void @use(i32 %a)
+  %r = add i32 %a, %b
+  ret i32 %r
+}
+
 define i32 @select_0_or_1_from_bool(i1 %x) {
 ; CHECK-LABEL: @select_0_or_1_from_bool(
 ; CHECK-NEXT:    [[NOT_X:%.*]] = xor i1 [[X:%.*]], true


        


More information about the llvm-commits mailing list