[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