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

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 9 20:16:00 PDT 2026


https://github.com/ParkHanbum created https://github.com/llvm/llvm-project/pull/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/MVyLay
Fixed: https://github.com/llvm/llvm-project/issues/191171

>From b15bf8beead395a1212e0b0e3bac033a9581dd56 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 10 Apr 2026 02:09:57 +0900
Subject: [PATCH 1/2] add testcases for upcoming patch

---
 llvm/test/Transforms/InstCombine/add.ll | 41 +++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/add.ll b/llvm/test/Transforms/InstCombine/add.ll
index aa68dfb540064..d77419cf9cca4 100644
--- a/llvm/test/Transforms/InstCombine/add.ll
+++ b/llvm/test/Transforms/InstCombine/add.ll
@@ -5,6 +5,47 @@
 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:    [[X:%.*]] = add i32 [[X1:%.*]], 1
+; CHECK-NEXT:    [[OR:%.*]] = and i32 [[Y:%.*]], -2
+; 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:%.*]] = add i32 [[X1:%.*]], 1
+; CHECK-NEXT:    [[X:%.*]] = and i32 [[Y:%.*]], -2
+; 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 <4 x i32> @add_masked_overwrite_vec_splat(<4 x i32> %x, <4 x i32> %y) {
+; CHECK-LABEL: @add_masked_overwrite_vec_splat(
+; CHECK-NEXT:    [[X:%.*]] = add <4 x i32> [[X1:%.*]], splat (i32 1)
+; CHECK-NEXT:    [[OR:%.*]] = and <4 x i32> [[Y:%.*]], splat (i32 -2)
+; CHECK-NEXT:    [[ADD:%.*]] = add <4 x i32> [[X]], [[OR]]
+; CHECK-NEXT:    ret <4 x i32> [[ADD]]
+;
+  %a = add <4 x i32> %x, splat (i32 1)
+  %b = and <4 x i32> %y, splat (i32 -2)
+  %r = add <4 x i32> %a, %b
+  ret <4 x 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

>From 8eb7cdc50c259bfdf8eec51447e189524206f5e9 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 10 Apr 2026 02:12:44 +0900
Subject: [PATCH 2/2] [InstCombine] Fold (X + C) + (Y & ~C) to X + (Y | C)

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/MVyLay
Fixed: #191171
---
 .../InstCombine/InstCombineAddSub.cpp         | 34 +++++++++++++++++++
 llvm/test/Transforms/InstCombine/add.ll       | 15 ++++----
 2 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index c781c6978b275..96010b66af468 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1523,6 +1523,36 @@ static Instruction *foldBoxMultiply(BinaryOperator &I) {
   return nullptr;
 }
 
+static Instruction *
+foldAddWithMaskedOverwrite(BinaryOperator &Add,
+                           InstCombiner::BuilderTy &Builder) {
+  Value *LHS = Add.getOperand(0), *RHS = Add.getOperand(1);
+  Value *X, *Y;
+  const APInt *C, *Mask;
+
+  auto Match = [&](Value *AddOp, Value *AndOp) -> Instruction * {
+    if (!match(AddOp, m_c_Add(m_Value(X), m_APInt(C))) ||
+        !match(AndOp, m_c_And(m_Value(Y), m_APInt(Mask))) || *Mask != ~(*C))
+      return nullptr;
+
+    // Replacing one add with {or, add}. Avoid growth if both sides are shared.
+    if (!AddOp->hasOneUse() && !AndOp->hasOneUse())
+      return nullptr;
+
+
+    Value *NewOr =
+        Builder.CreateOr(Y, Constant::getIntegerValue(Add.getType(), *C));
+    Instruction *NewAdd = cast<Instruction>(Builder.CreateAdd(X, NewOr));
+    NewAdd->setHasNoSignedWrap(Add.hasNoSignedWrap());
+    NewAdd->setHasNoUnsignedWrap(Add.hasNoUnsignedWrap());
+    return NewAdd;
+  };
+
+  if (Instruction *I = Match(LHS, RHS))
+    return I;
+  return Match(RHS, LHS);
+}
+
 Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Value *V = simplifyAddInst(I.getOperand(0), I.getOperand(1),
                                  I.hasNoSignedWrap(), I.hasNoUnsignedWrap(),
@@ -1604,6 +1634,10 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Value *V = checkForNegativeOperand(I, Builder))
     return replaceInstUsesWith(I, V);
 
+  // (X + C) + (Y & ~C) == X + (Y | C)
+  if (Value *V = foldAddWithMaskedOverwrite(I, Builder))
+    return replaceInstUsesWith(I, V);
+
   // (A + 1) + ~B --> A - B
   // ~B + (A + 1) --> A - B
   // (~B + A) + 1 --> A - B
diff --git a/llvm/test/Transforms/InstCombine/add.ll b/llvm/test/Transforms/InstCombine/add.ll
index d77419cf9cca4..3899fe88eb0cb 100644
--- a/llvm/test/Transforms/InstCombine/add.ll
+++ b/llvm/test/Transforms/InstCombine/add.ll
@@ -9,9 +9,8 @@ declare void @use_i1(i1)
 
 define i32 @add_masked_overwrite_basic(i32 %x, i32 %y) {
 ; CHECK-LABEL: @add_masked_overwrite_basic(
-; CHECK-NEXT:    [[X:%.*]] = add i32 [[X1:%.*]], 1
-; CHECK-NEXT:    [[OR:%.*]] = and i32 [[Y:%.*]], -2
-; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X]], [[OR]]
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[OR]]
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
   %a = add i32 %x, 1
@@ -22,9 +21,8 @@ define i32 @add_masked_overwrite_basic(i32 %x, i32 %y) {
 
 define i32 @add_masked_overwrite_commuted(i32 %x, i32 %y) {
 ; CHECK-LABEL: @add_masked_overwrite_commuted(
-; CHECK-NEXT:    [[OR:%.*]] = add i32 [[X1:%.*]], 1
-; CHECK-NEXT:    [[X:%.*]] = and i32 [[Y:%.*]], -2
-; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X]], [[OR]]
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[OR]]
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
   %a = add i32 %x, 1
@@ -35,9 +33,8 @@ define i32 @add_masked_overwrite_commuted(i32 %x, i32 %y) {
 
 define <4 x i32> @add_masked_overwrite_vec_splat(<4 x i32> %x, <4 x i32> %y) {
 ; CHECK-LABEL: @add_masked_overwrite_vec_splat(
-; CHECK-NEXT:    [[X:%.*]] = add <4 x i32> [[X1:%.*]], splat (i32 1)
-; CHECK-NEXT:    [[OR:%.*]] = and <4 x i32> [[Y:%.*]], splat (i32 -2)
-; CHECK-NEXT:    [[ADD:%.*]] = add <4 x i32> [[X]], [[OR]]
+; CHECK-NEXT:    [[OR:%.*]] = or <4 x i32> [[Y:%.*]], splat (i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add <4 x i32> [[X:%.*]], [[OR]]
 ; CHECK-NEXT:    ret <4 x i32> [[ADD]]
 ;
   %a = add <4 x i32> %x, splat (i32 1)



More information about the llvm-commits mailing list