[llvm] [GlobalISel] Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)` (PR #181725)

Osman Yasar via llvm-commits llvm-commits at lists.llvm.org
Sat Feb 21 10:31:10 PST 2026


https://github.com/osmanyasar05 updated https://github.com/llvm/llvm-project/pull/181725

>From ddf94e276f7e5e0343d69c30064fc39404438e81 Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Mon, 16 Feb 2026 19:08:50 +0000
Subject: [PATCH 1/6] add binop_with_neg

---
 .../llvm/CodeGen/GlobalISel/CombinerHelper.h  |   3 +
 .../include/llvm/Target/GlobalISel/Combine.td |  10 +-
 .../lib/CodeGen/GlobalISel/CombinerHelper.cpp |  62 ++++++
 .../AArch64/GlobalISel/combine-binop-neg.mir  | 205 ++++++++++++++++++
 .../CodeGen/RISCV/GlobalISel/rv32zbb-zbkb.ll  |  57 +++++
 5 files changed, 336 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index da53005ed801e..33b0ebf686421 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -534,6 +534,9 @@ class CombinerHelper {
   void applySimplifyAddToSub(MachineInstr &MI,
                              std::tuple<Register, Register> &MatchInfo) const;
 
+  /// Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`
+  bool matchBinopWithNeg(MachineInstr &MI, BuildFnTy &MatchInfo) const;
+
   /// Match (logic_op (op x...), (op y...)) -> (op (logic_op x, y))
   bool matchHoistLogicOpWithSameOpcodeHands(
       MachineInstr &MI, InstructionStepsMatchInfo &MatchInfo) const;
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index f5c940bffc8fb..15e5ab6f974fc 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -621,6 +621,14 @@ def binop_left_to_zero: GICombineRule<
   (apply (GIReplaceReg $dst, $zero))
 >;
 
+// Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`
+def binop_with_neg : GICombineRule<
+  (defs root:$root, build_fn_matchinfo:$matchinfo),
+  (match (wip_match_opcode G_AND, G_OR, G_XOR):$root,
+    [{ return Helper.matchBinopWithNeg(*${root}, ${matchinfo}); }]),
+  (apply [{ Helper.applyBuildFn(*${root}, ${matchinfo}); }])
+>;
+
 def urem_pow2_to_mask : GICombineRule<
   (defs root:$root),
   (match (wip_match_opcode G_UREM):$root,
@@ -2296,7 +2304,7 @@ def all_combines : GICombineGroup<[integer_reassoc_combines, trivial_combines,
     simplify_neg_minmax, combine_concat_vector,
     sext_trunc, zext_trunc, prefer_sign_combines, shuffle_combines,
     combine_use_vector_truncate, merge_combines, overflow_combines, 
-    truncsat_combines, lshr_of_trunc_of_lshr, ctls_combines, add_shift]>;
+    truncsat_combines, lshr_of_trunc_of_lshr, ctls_combines, add_shift, binop_with_neg]>;
 
 // A combine group used to for prelegalizer combiners at -O0. The combines in
 // this group have been selected based on experiments to balance code size and
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index b9273d388ea70..70e755b8352b0 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -3185,6 +3185,68 @@ void CombinerHelper::applySimplifyAddToSub(
   MI.eraseFromParent();
 }
 
+bool CombinerHelper::matchBinopWithNeg(MachineInstr &MI,
+                                       BuildFnTy &MatchInfo) const {
+  // Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`
+  // Root MI is one of G_AND, G_OR, G_XOR.
+  // We also look for commuted forms of operations.
+
+  unsigned RootOpc = MI.getOpcode();
+  Register Dst = MI.getOperand(0).getReg();
+  LLT Ty = MRI.getType(Dst);
+
+  auto TryMatch = [&](Register MaybeInner, Register Other) -> bool {
+    MachineInstr *InnerDef = MRI.getVRegDef(MaybeInner);
+    if (!InnerDef)
+      return false;
+
+    unsigned InnerOpc = InnerDef->getOpcode();
+    if (InnerOpc != TargetOpcode::G_ADD && InnerOpc != TargetOpcode::G_SUB)
+      return false;
+
+    if (!MRI.hasOneNonDBGUse(MaybeInner))
+      return false;
+
+    Register InnerLHS = InnerDef->getOperand(1).getReg();
+    Register InnerRHS = InnerDef->getOperand(2).getReg();
+    Register NotSrc;
+    Register B, C;
+
+    // Check if either operand is ~b
+    if (mi_match(InnerLHS, MRI, m_Not(m_Reg(NotSrc)))) {
+      if (!MRI.hasOneNonDBGUse(InnerLHS))
+      return false;
+      B = NotSrc;
+      C = InnerRHS;
+    } else if (mi_match(InnerRHS, MRI, m_Not(m_Reg(NotSrc)))) {
+      if (!MRI.hasOneNonDBGUse(InnerRHS))
+      return false;
+      B = NotSrc;
+      C = InnerLHS;
+    } else {
+      return false;
+    }
+
+    // Flip add/sub
+    unsigned FlippedOpc = (InnerOpc == TargetOpcode::G_ADD)
+                              ? TargetOpcode::G_SUB
+                              : TargetOpcode::G_ADD;
+
+    Register A = Other;
+    MatchInfo = [=](MachineIRBuilder &Builder) {
+      auto NewInner = Builder.buildInstr(FlippedOpc, {Ty}, {B, C});
+      auto NewNot = Builder.buildNot(Ty, NewInner);
+      Builder.buildInstr(RootOpc, {Dst}, {A, NewNot});
+    };
+    return true;
+  };
+
+  Register LHS = MI.getOperand(1).getReg();
+  Register RHS = MI.getOperand(2).getReg();
+  // Check the commuted and uncommuted forms of the operation.
+  return TryMatch(LHS, RHS) || TryMatch(RHS, LHS);
+}
+
 bool CombinerHelper::matchHoistLogicOpWithSameOpcodeHands(
     MachineInstr &MI, InstructionStepsMatchInfo &MatchInfo) const {
   // Matches: logic (hand x, ...), (hand y, ...) -> hand (logic x, y), ...
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
new file mode 100644
index 0000000000000..17d0785f9f442
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
@@ -0,0 +1,205 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc -run-pass=aarch64-prelegalizer-combiner -verify-machineinstrs -mtriple aarch64-unknown-unknown %s -o - | FileCheck %s
+
+---
+name:            binop_with_neg_or
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_or
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_OR %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %neg_y, %c
+    %dst:_(s64) = G_OR %a, %add
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_or_commuted
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_or_commuted
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_OR %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %neg_y, %c
+    %dst:_(s64) = G_OR %add, %a
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_xor
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_xor
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_XOR %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %neg_y, %c
+    %dst:_(s64) = G_XOR %a, %add
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_xor_commuted
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_xor_commuted
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_XOR %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %c, %neg_y
+    %dst:_(s64) = G_XOR %add, %a
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_and
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_and
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_AND %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %neg_y, %c
+    %dst:_(s64) = G_AND %a, %add
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_and_commuted_add
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_and_commuted_add
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_AND %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %c, %neg_y
+    %dst:_(s64) = G_AND %a, %add
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_and_commuted_and
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_and_commuted_and
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: [[SUB:%[0-9]+]]:_(s64) = G_SUB %b, %c
+    ; CHECK-NEXT: [[XOR:%[0-9]+]]:_(s64) = G_XOR [[SUB]], %mone
+    ; CHECK-NEXT: %dst:_(s64) = G_AND %a, [[XOR]]
+    ; CHECK-NEXT: $x0 = COPY %dst(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %c, %neg_y
+    %dst:_(s64) = G_AND %add, %a
+    $x0 = COPY %dst
+    RET_ReallyLR implicit $x0
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/rv32zbb-zbkb.ll b/llvm/test/CodeGen/RISCV/GlobalISel/rv32zbb-zbkb.ll
index 83cf228402295..757070eaff802 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/rv32zbb-zbkb.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/rv32zbb-zbkb.ll
@@ -389,3 +389,60 @@ define i16 @srai_i16(i16 %a) nounwind {
   %1 = ashr i16 %a, 9
   ret i16 %1
 }
+
+define i32 @binop_neg_and(i32 %a, i32 %b, i32 %c) {
+; RV32I-LABEL: binop_neg_and:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    add a1, a1, a2
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    and a0, a0, a1
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: binop_neg_and:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    add a1, a1, a2
+; RV32ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:    ret
+  %not_b = xor i32 %b, -1
+  %sub = sub i32 %not_b, %c
+  %and = and i32 %a, %sub
+  ret i32 %and
+}
+
+define i32 @binop_neg_or(i32 %a, i32 %b, i32 %c) {
+; RV32I-LABEL: binop_neg_or:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    add a1, a1, a2
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    or a0, a0, a1
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: binop_neg_or:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    add a1, a1, a2
+; RV32ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:    ret
+  %not_b = xor i32 %b, -1
+  %sub = sub i32 %not_b, %c
+  %or = or i32 %a, %sub
+  ret i32 %or
+}
+
+define i32 @binop_neg_xor(i32 %a, i32 %b, i32 %c) {
+; RV32I-LABEL: binop_neg_xor:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    add a1, a1, a2
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    xor a0, a0, a1
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: binop_neg_xor:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    add a1, a1, a2
+; RV32ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV32ZBB-ZBKB-NEXT:    ret
+  %not_b = xor i32 %b, -1
+  %sub = sub i32 %not_b, %c
+  %xor = xor i32 %a, %sub
+  ret i32 %xor
+}

>From 9d432e1d783a901f6aec54cba20d253cad8faf92 Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Mon, 16 Feb 2026 19:17:52 +0000
Subject: [PATCH 2/6] commuted test

---
 .../CodeGen/AArch64/GlobalISel/combine-binop-neg.mir | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
index 17d0785f9f442..c3818a22e8d53 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
@@ -147,13 +147,13 @@ body: |
     RET_ReallyLR implicit $x0
 ...
 ---
-name:            binop_with_neg_and_commuted_add
+name:            binop_with_neg_and_commuted_and
 tracksRegLiveness: true
 body: |
   bb.0:
     liveins: $x0, $x1, $x2, $x3
 
-    ; CHECK-LABEL: name: binop_with_neg_and_commuted_add
+    ; CHECK-LABEL: name: binop_with_neg_and_commuted_and
     ; CHECK: liveins: $x0, $x1, $x2, $x3
     ; CHECK-NEXT: {{  $}}
     ; CHECK-NEXT: %a:_(s64) = COPY $x0
@@ -171,18 +171,18 @@ body: |
     %mone:_(s64) = G_CONSTANT i64 -1
     %neg_y:_(s64) = G_XOR %mone, %b
     %add:_(s64) = G_ADD %c, %neg_y
-    %dst:_(s64) = G_AND %a, %add
+    %dst:_(s64) = G_AND %add, %a
     $x0 = COPY %dst
     RET_ReallyLR implicit $x0
 ...
 ---
-name:            binop_with_neg_and_commuted_and
+name:            binop_with_neg_and_commuted_add
 tracksRegLiveness: true
 body: |
   bb.0:
     liveins: $x0, $x1, $x2, $x3
 
-    ; CHECK-LABEL: name: binop_with_neg_and_commuted_and
+    ; CHECK-LABEL: name: binop_with_neg_and_commuted_add
     ; CHECK: liveins: $x0, $x1, $x2, $x3
     ; CHECK-NEXT: {{  $}}
     ; CHECK-NEXT: %a:_(s64) = COPY $x0
@@ -200,6 +200,6 @@ body: |
     %mone:_(s64) = G_CONSTANT i64 -1
     %neg_y:_(s64) = G_XOR %mone, %b
     %add:_(s64) = G_ADD %c, %neg_y
-    %dst:_(s64) = G_AND %add, %a
+    %dst:_(s64) = G_AND %a, %add
     $x0 = COPY %dst
     RET_ReallyLR implicit $x0

>From 91050b9114ba2fe2576b1954e3543fac6c2c0595 Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Mon, 16 Feb 2026 19:29:20 +0000
Subject: [PATCH 3/6] formatting

---
 llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 70e755b8352b0..1aa6ca1733a78 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -3215,12 +3215,12 @@ bool CombinerHelper::matchBinopWithNeg(MachineInstr &MI,
     // Check if either operand is ~b
     if (mi_match(InnerLHS, MRI, m_Not(m_Reg(NotSrc)))) {
       if (!MRI.hasOneNonDBGUse(InnerLHS))
-      return false;
+        return false;
       B = NotSrc;
       C = InnerRHS;
     } else if (mi_match(InnerRHS, MRI, m_Not(m_Reg(NotSrc)))) {
       if (!MRI.hasOneNonDBGUse(InnerRHS))
-      return false;
+        return false;
       B = NotSrc;
       C = InnerLHS;
     } else {

>From 148ada1f875f96fd415364b939510abf6b73976c Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Sat, 21 Feb 2026 18:10:44 +0000
Subject: [PATCH 4/6] tests, use helper function

---
 .../llvm/CodeGen/GlobalISel/CombinerHelper.h  |  6 ++
 .../lib/CodeGen/GlobalISel/CombinerHelper.cpp | 99 ++++++++++---------
 .../AArch64/GlobalISel/combine-binop-neg.mir  | 32 ++++++
 3 files changed, 91 insertions(+), 46 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index 33b0ebf686421..180cc8566302d 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -1053,6 +1053,12 @@ class CombinerHelper {
 private:
   /// Checks for legality of an indexed variant of \p LdSt.
   bool isIndexedLoadStoreLegal(GLoadStore &LdSt) const;
+
+  /// Helper function for matchBinopWithNeg: tries to match one commuted form
+  /// of `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`.
+  bool matchBinopWithNegInner(Register MaybeInner, Register Other,
+                               unsigned RootOpc, Register Dst, LLT Ty,
+                               BuildFnTy &MatchInfo) const;
   /// Given a non-indexed load or store instruction \p MI, find an offset that
   /// can be usefully and legally folded into it as a post-indexing operation.
   ///
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 1aa6ca1733a78..70ff6a8a90b22 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -3185,66 +3185,73 @@ void CombinerHelper::applySimplifyAddToSub(
   MI.eraseFromParent();
 }
 
-bool CombinerHelper::matchBinopWithNeg(MachineInstr &MI,
-                                       BuildFnTy &MatchInfo) const {
-  // Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`
-  // Root MI is one of G_AND, G_OR, G_XOR.
-  // We also look for commuted forms of operations.
-
-  unsigned RootOpc = MI.getOpcode();
-  Register Dst = MI.getOperand(0).getReg();
-  LLT Ty = MRI.getType(Dst);
-
-  auto TryMatch = [&](Register MaybeInner, Register Other) -> bool {
-    MachineInstr *InnerDef = MRI.getVRegDef(MaybeInner);
-    if (!InnerDef)
-      return false;
+bool CombinerHelper::matchBinopWithNegInner(Register MaybeInner, Register Other,
+                                             unsigned RootOpc, Register Dst,
+                                             LLT Ty,
+                                             BuildFnTy &MatchInfo) const {
+  /// Helper function for matchBinopWithNeg: tries to match one commuted form
+  /// of `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`.
+  MachineInstr *InnerDef = MRI.getVRegDef(MaybeInner);
+  if (!InnerDef)
+    return false;
 
-    unsigned InnerOpc = InnerDef->getOpcode();
-    if (InnerOpc != TargetOpcode::G_ADD && InnerOpc != TargetOpcode::G_SUB)
-      return false;
+  unsigned InnerOpc = InnerDef->getOpcode();
+  if (InnerOpc != TargetOpcode::G_ADD && InnerOpc != TargetOpcode::G_SUB)
+    return false;
 
-    if (!MRI.hasOneNonDBGUse(MaybeInner))
-      return false;
+  if (!MRI.hasOneNonDBGUse(MaybeInner))
+    return false;
 
-    Register InnerLHS = InnerDef->getOperand(1).getReg();
-    Register InnerRHS = InnerDef->getOperand(2).getReg();
-    Register NotSrc;
-    Register B, C;
+  Register InnerLHS = InnerDef->getOperand(1).getReg();
+  Register InnerRHS = InnerDef->getOperand(2).getReg();
+  Register NotSrc;
+  Register B, C;
 
-    // Check if either operand is ~b
-    if (mi_match(InnerLHS, MRI, m_Not(m_Reg(NotSrc)))) {
-      if (!MRI.hasOneNonDBGUse(InnerLHS))
-        return false;
-      B = NotSrc;
-      C = InnerRHS;
-    } else if (mi_match(InnerRHS, MRI, m_Not(m_Reg(NotSrc)))) {
-      if (!MRI.hasOneNonDBGUse(InnerRHS))
+  // Check if either operand is ~b
+  auto TryMatch = [&](Register MaybeNot, Register Other) {
+    if (mi_match(MaybeNot, MRI, m_Not(m_Reg(NotSrc)))) {
+      if (!MRI.hasOneNonDBGUse(MaybeNot))
         return false;
       B = NotSrc;
-      C = InnerLHS;
-    } else {
-      return false;
+      C = Other;
+      return true;
     }
+    return false;
+  };
+
+  if (!TryMatch(InnerLHS, InnerRHS) && !TryMatch(InnerRHS, InnerLHS))
+    return false;
 
-    // Flip add/sub
-    unsigned FlippedOpc = (InnerOpc == TargetOpcode::G_ADD)
-                              ? TargetOpcode::G_SUB
-                              : TargetOpcode::G_ADD;
+  // Flip add/sub
+  unsigned FlippedOpc = (InnerOpc == TargetOpcode::G_ADD)
+                            ? TargetOpcode::G_SUB
+                            : TargetOpcode::G_ADD;
 
-    Register A = Other;
-    MatchInfo = [=](MachineIRBuilder &Builder) {
-      auto NewInner = Builder.buildInstr(FlippedOpc, {Ty}, {B, C});
-      auto NewNot = Builder.buildNot(Ty, NewInner);
-      Builder.buildInstr(RootOpc, {Dst}, {A, NewNot});
-    };
-    return true;
+  Register A = Other;
+  MatchInfo = [=](MachineIRBuilder &Builder) {
+    auto NewInner = Builder.buildInstr(FlippedOpc, {Ty}, {B, C});
+    auto NewNot = Builder.buildNot(Ty, NewInner);
+    Builder.buildInstr(RootOpc, {Dst}, {A, NewNot});
   };
+  return true;
+}
+
+bool CombinerHelper::matchBinopWithNeg(MachineInstr &MI,
+                                       BuildFnTy &MatchInfo) const {
+  // Fold `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`
+  // Root MI is one of G_AND, G_OR, G_XOR.
+  // We also look for commuted forms of operations. Pattern shouldn't apply 
+  // if there are multiple reasons of inner operations.
+
+  unsigned RootOpc = MI.getOpcode();
+  Register Dst = MI.getOperand(0).getReg();
+  LLT Ty = MRI.getType(Dst);
 
   Register LHS = MI.getOperand(1).getReg();
   Register RHS = MI.getOperand(2).getReg();
   // Check the commuted and uncommuted forms of the operation.
-  return TryMatch(LHS, RHS) || TryMatch(RHS, LHS);
+  return matchBinopWithNegInner(LHS, RHS, RootOpc, Dst, Ty, MatchInfo) ||
+         matchBinopWithNegInner(RHS, LHS, RootOpc, Dst, Ty, MatchInfo);
 }
 
 bool CombinerHelper::matchHoistLogicOpWithSameOpcodeHands(
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
index c3818a22e8d53..4e0bf7cd7bb4e 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/combine-binop-neg.mir
@@ -203,3 +203,35 @@ body: |
     %dst:_(s64) = G_AND %a, %add
     $x0 = COPY %dst
     RET_ReallyLR implicit $x0
+...
+---
+name:            binop_with_neg_and_one_use
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $x1, $x2, $x3
+
+    ; CHECK-LABEL: name: binop_with_neg_and_one_use
+    ; CHECK: liveins: $x0, $x1, $x2, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s64) = COPY $x0
+    ; CHECK-NEXT: %b:_(s64) = COPY $x1
+    ; CHECK-NEXT: %c:_(s64) = COPY $x2
+    ; CHECK-NEXT: %mone:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: %neg_y:_(s64) = G_XOR %b, %mone
+    ; CHECK-NEXT: %add:_(s64) = G_ADD %neg_y, %c
+    ; CHECK-NEXT: %dst:_(s64) = G_AND %a, %add
+    ; CHECK-NEXT: %and2:_(s64) = G_AND %neg_y, %dst
+    ; CHECK-NEXT: $x0 = COPY %and2(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = COPY $x0
+    %b:_(s64) = COPY $x1
+    %c:_(s64) = COPY $x2
+    %mone:_(s64) = G_CONSTANT i64 -1
+    %neg_y:_(s64) = G_XOR %mone, %b
+    %add:_(s64) = G_ADD %neg_y, %c
+    %dst:_(s64) = G_AND %a, %add
+    %and2:_(s64) = G_AND %neg_y, %dst
+    $x0 = COPY %and2
+    RET_ReallyLR implicit $x0
+...

>From c2de64e38e2fe2b99628f73af98415818d685953 Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Sat, 21 Feb 2026 18:22:20 +0000
Subject: [PATCH 5/6] format

---
 .../include/llvm/CodeGen/GlobalISel/CombinerHelper.h |  2 +-
 llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp       | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index 180cc8566302d..8007b341fe8bd 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -1056,7 +1056,7 @@ class CombinerHelper {
 
   /// Helper function for matchBinopWithNeg: tries to match one commuted form
   /// of `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`.
-  bool matchBinopWithNegInner(Register MaybeInner, Register Other,
+  bool matchBinopWithNegInner(Register MInner, Register Other,
                                unsigned RootOpc, Register Dst, LLT Ty,
                                BuildFnTy &MatchInfo) const;
   /// Given a non-indexed load or store instruction \p MI, find an offset that
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 70ff6a8a90b22..0f5188619e0d5 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -3185,13 +3185,13 @@ void CombinerHelper::applySimplifyAddToSub(
   MI.eraseFromParent();
 }
 
-bool CombinerHelper::matchBinopWithNegInner(Register MaybeInner, Register Other,
-                                             unsigned RootOpc, Register Dst,
-                                             LLT Ty,
-                                             BuildFnTy &MatchInfo) const {
+bool CombinerHelper::matchBinopWithNegInner(Register MInner, Register Other,
+                                            unsigned RootOpc, Register Dst,
+                                            LLT Ty,
+                                            BuildFnTy &MatchInfo) const {
   /// Helper function for matchBinopWithNeg: tries to match one commuted form
   /// of `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`.
-  MachineInstr *InnerDef = MRI.getVRegDef(MaybeInner);
+  MachineInstr *InnerDef = MRI.getVRegDef(MInner);
   if (!InnerDef)
     return false;
 
@@ -3199,7 +3199,7 @@ bool CombinerHelper::matchBinopWithNegInner(Register MaybeInner, Register Other,
   if (InnerOpc != TargetOpcode::G_ADD && InnerOpc != TargetOpcode::G_SUB)
     return false;
 
-  if (!MRI.hasOneNonDBGUse(MaybeInner))
+  if (!MRI.hasOneNonDBGUse(MInner))
     return false;
 
   Register InnerLHS = InnerDef->getOperand(1).getReg();

>From b659c849f5f709495fe99532853998334731f5ad Mon Sep 17 00:00:00 2001
From: osmanyasar05 <osmanyas05 at gmail.com>
Date: Sat, 21 Feb 2026 18:30:56 +0000
Subject: [PATCH 6/6] format

---
 llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index 8007b341fe8bd..72933c5f1a8b1 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -1056,9 +1056,8 @@ class CombinerHelper {
 
   /// Helper function for matchBinopWithNeg: tries to match one commuted form
   /// of `a bitwiseop (~b +/- c)` -> `a bitwiseop ~(b -/+ c)`.
-  bool matchBinopWithNegInner(Register MInner, Register Other,
-                               unsigned RootOpc, Register Dst, LLT Ty,
-                               BuildFnTy &MatchInfo) const;
+  bool matchBinopWithNegInner(Register MInner, Register Other, unsigned RootOpc,
+                              Register Dst, LLT Ty, BuildFnTy &MatchInfo) const;
   /// Given a non-indexed load or store instruction \p MI, find an offset that
   /// can be usefully and legally folded into it as a post-indexing operation.
   ///



More information about the llvm-commits mailing list