[llvm] [X86] Add DAG combiner for X86ISD::XOR to eliminate redundant XORs (PR #216134)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 14:09:56 PDT 2026


https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/216134

>From 1d1eac48dd849fdd300acc9bee77dd56c299390f Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 13 Aug 2026 13:26:33 -0400
Subject: [PATCH 1/4] Pre-commit test

---
 llvm/test/CodeGen/X86/cmp-xor.ll | 133 +++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/llvm/test/CodeGen/X86/cmp-xor.ll b/llvm/test/CodeGen/X86/cmp-xor.ll
index a8a7da4871ba9..efb165f584db9 100644
--- a/llvm/test/CodeGen/X86/cmp-xor.ll
+++ b/llvm/test/CodeGen/X86/cmp-xor.ll
@@ -54,3 +54,136 @@ define i32 @cmp_xor_i32_commute(i32 %a, i32 %b, i32 %c)
   ret i32 %sel
 }
 
+define zeroext i1 @xor_cmp_rmw_i32(ptr %x) {
+; X86-LABEL: xor_cmp_rmw_i32:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    movl (%eax), %ecx
+; X86-NEXT:    xorl $7, %ecx
+; X86-NEXT:    movl %ecx, (%eax)
+; X86-NEXT:    setne %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: xor_cmp_rmw_i32:
+; X64:       # %bb.0:
+; X64-NEXT:    movl (%rdi), %eax
+; X64-NEXT:    xorl $7, %eax
+; X64-NEXT:    movl %eax, (%rdi)
+; X64-NEXT:    setne %al
+; X64-NEXT:    retq
+  %load = load i32, ptr %x
+  %xor = xor i32 %load, 7
+  store i32 %xor, ptr %x
+  %cmp = icmp ne i32 %load, 7
+  ret i1 %cmp
+}
+
+define i32 @xor_cmp_multiuse(i32 %a, i32 %b, ptr %p) {
+; X86-LABEL: xor_cmp_multiuse:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    xorl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    movl %ecx, (%eax)
+; X86-NEXT:    movl $42, %eax
+; X86-NEXT:    je .LBB3_2
+; X86-NEXT:  # %bb.1:
+; X86-NEXT:    movl %ecx, %eax
+; X86-NEXT:  .LBB3_2:
+; X86-NEXT:    retl
+;
+; X64-LABEL: xor_cmp_multiuse:
+; X64:       # %bb.0:
+; X64-NEXT:    xorl %esi, %edi
+; X64-NEXT:    movl %edi, (%rdx)
+; X64-NEXT:    movl $42, %eax
+; X64-NEXT:    cmovnel %edi, %eax
+; X64-NEXT:    retq
+  %xor = xor i32 %a, %b
+  store i32 %xor, ptr %p
+  %cmp = icmp eq i32 %a, %b
+  %sel = select i1 %cmp, i32 42, i32 %xor
+  ret i32 %sel
+}
+
+define zeroext i1 @xor_cmp_rmw_eq_i32(ptr %x) {
+; X86-LABEL: xor_cmp_rmw_eq_i32:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    movl (%eax), %ecx
+; X86-NEXT:    xorl $7, %ecx
+; X86-NEXT:    movl %ecx, (%eax)
+; X86-NEXT:    sete %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: xor_cmp_rmw_eq_i32:
+; X64:       # %bb.0:
+; X64-NEXT:    movl (%rdi), %eax
+; X64-NEXT:    xorl $7, %eax
+; X64-NEXT:    movl %eax, (%rdi)
+; X64-NEXT:    sete %al
+; X64-NEXT:    retq
+  %load = load i32, ptr %x
+  %xor = xor i32 %load, 7
+  store i32 %xor, ptr %x
+  %cmp = icmp eq i32 %load, 7
+  ret i1 %cmp
+}
+
+define zeroext i1 @xor_cmp_rmw_sgt_i32(ptr %x) {
+; X86-LABEL: xor_cmp_rmw_sgt_i32:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    movl (%eax), %ecx
+; X86-NEXT:    movl %ecx, %edx
+; X86-NEXT:    xorl $7, %edx
+; X86-NEXT:    movl %edx, (%eax)
+; X86-NEXT:    cmpl $8, %ecx
+; X86-NEXT:    setge %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: xor_cmp_rmw_sgt_i32:
+; X64:       # %bb.0:
+; X64-NEXT:    movl (%rdi), %eax
+; X64-NEXT:    movl %eax, %ecx
+; X64-NEXT:    xorl $7, %ecx
+; X64-NEXT:    movl %ecx, (%rdi)
+; X64-NEXT:    cmpl $8, %eax
+; X64-NEXT:    setge %al
+; X64-NEXT:    retq
+  %load = load i32, ptr %x
+  %xor = xor i32 %load, 7
+  store i32 %xor, ptr %x
+  %cmp = icmp sgt i32 %load, 7
+  ret i1 %cmp
+}
+
+define zeroext i1 @xor_cmp_rmw_ult_i32(ptr %x) {
+; X86-LABEL: xor_cmp_rmw_ult_i32:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    movl (%eax), %ecx
+; X86-NEXT:    movl %ecx, %edx
+; X86-NEXT:    xorl $7, %edx
+; X86-NEXT:    movl %edx, (%eax)
+; X86-NEXT:    cmpl $7, %ecx
+; X86-NEXT:    setb %al
+; X86-NEXT:    retl
+;
+; X64-LABEL: xor_cmp_rmw_ult_i32:
+; X64:       # %bb.0:
+; X64-NEXT:    movl (%rdi), %eax
+; X64-NEXT:    movl %eax, %ecx
+; X64-NEXT:    xorl $7, %ecx
+; X64-NEXT:    movl %ecx, (%rdi)
+; X64-NEXT:    cmpl $7, %eax
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+  %load = load i32, ptr %x
+  %xor = xor i32 %load, 7
+  store i32 %xor, ptr %x
+  %cmp = icmp ult i32 %load, 7
+  ret i1 %cmp
+}
+
+

>From 060943da965875caf8d4ec3ef0fe5a5b54302b3b Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 13 Aug 2026 15:11:30 -0400
Subject: [PATCH 2/4] [X86] Add DAG combiner for X86ISD::XOR to eliminate
 redundant XORs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The combiner does two safe things:
Demote to generic XOR when flags aren't used — if X86ISD::XOR's flag output has no users, replace it with a plain ISD::XOR (plus a dummy zero flags value). This is always safe.
CSE with existing generic XORs — if an ISD::XOR with the same operands already exists, replace its uses with the X86 XOR's arithmetic result. This is safe because both compute a ^ b; the X86 node just happens to also emit flags that no generic user cares about.

Tests confirm correct behavior:
- xor_cmp_rmw_i32 (ne) -> Folds XOR+CMP into single xorl + setne
- xor_cmp_rmw_eq_i32 (eq) -> Folds into xorl + sete
- xor_cmp_multiuse (eq) -> Folds, result reused by cmovne
- xor_cmp_rmw_sgt_i32 (sgt) -> Does NOT fold — emits separate cmpl
- xor_cmp_rmw_ult_i32 (ult) -> Does NOT fold — emits separate cmpl

The critical sgt/ult tests prove the optimization isn't overreaching. XOR only sets ZF/SF/PF; it does not set CF/OF, so ordered comparisons (less-than, greater-than) correctly remain as separate cmpl instructions. The DAG combiner only CSEs the arithmetic XOR — it doesn't force comparisons to use XOR flags. The backend's existing EmitCmp logic still decides which comparisons can legally consume XOR flags.
---
 llvm/lib/Target/X86/X86ISelLowering.cpp | 74 +++++++++++++++++--------
 llvm/test/CodeGen/X86/cmp-xor.ll        | 16 ++----
 2 files changed, 56 insertions(+), 34 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index eed9416741c17..f1edb0c7dcbb7 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -59394,6 +59394,28 @@ static SDValue combineCMP(SDNode *N, SelectionDAG &DAG,
   return Op.getValue(1);
 }
 
+/// If a matching generic opcode node exists, replace its uses with the value
+/// of the target-specific node N.
+static void matchGenericOp(unsigned Opc, SDNode *N, SDValue N0, SDValue N1,
+                           SelectionDAG &DAG,
+                           TargetLowering::DAGCombinerInfo &DCI,
+                           bool Negate = false) {
+  SDValue Ops[] = {N0, N1};
+  SDVTList VTs = DAG.getVTList(N->getValueType(0));
+  if (SDNode *Generic = DAG.getNodeIfExists(Opc, VTs, Ops)) {
+    SDValue Op(N, 0);
+    if (Negate) {
+      // If the generic node is only used by a node that also uses the
+      // target-specific node N, bailing out prevents potential DAG cycles and
+      // unprofitable replacements.
+      if (Generic->hasOneUse() && Generic->user_begin()->isOnlyUserOf(N))
+        return;
+      Op = DAG.getNegative(Op, SDLoc(N), N->getValueType(0));
+    }
+    DCI.CombineTo(Generic, Op);
+  }
+}
+
 static SDValue combineX86AddSub(SDNode *N, SelectionDAG &DAG,
                                 TargetLowering::DAGCombinerInfo &DCI,
                                 const X86Subtarget &ST) {
@@ -59418,38 +59440,23 @@ static SDValue combineX86AddSub(SDNode *N, SelectionDAG &DAG,
   }
 
   // Fold any similar generic ADD/SUB opcodes to reuse this node.
-  auto MatchGeneric = [&](unsigned Opc, SDValue N0, SDValue N1, bool Negate) {
-    SDValue Ops[] = {N0, N1};
-    SDVTList VTs = DAG.getVTList(N->getValueType(0));
-    if (SDNode *GenericAddSub = DAG.getNodeIfExists(Opc, VTs, Ops)) {
-      SDValue Op(N, 0);
-      if (Negate) {
-        // Bail if this is only used by a user of the x86 add/sub.
-        if (GenericAddSub->hasOneUse() &&
-            GenericAddSub->user_begin()->isOnlyUserOf(N))
-          return;
-        Op = DAG.getNegative(Op, DL, VT);
-      }
-      DCI.CombineTo(GenericAddSub, Op);
-    }
-  };
-  MatchGeneric(GenericOpc, LHS, RHS, false);
-  MatchGeneric(GenericOpc, RHS, LHS, X86ISD::SUB == N->getOpcode());
+  matchGenericOp(GenericOpc, N, LHS, RHS, DAG, DCI, false);
+  matchGenericOp(GenericOpc, N, RHS, LHS, DAG, DCI, IsSub);
 
   if (auto *Const = dyn_cast<ConstantSDNode>(RHS)) {
     SDValue NegC = DAG.getConstant(-Const->getAPIntValue(), DL, VT);
-    if (X86ISD::SUB == N->getOpcode()) {
+    if (IsSub) {
       // Fold generic add(LHS, -C) to X86ISD::SUB(LHS, C).
-      MatchGeneric(ISD::ADD, LHS, NegC, false);
+      matchGenericOp(ISD::ADD, N, LHS, NegC, DAG, DCI, false);
     } else {
       // Negate X86ISD::ADD(LHS, C) and replace generic sub(-C, LHS).
-      MatchGeneric(ISD::SUB, NegC, LHS, true);
+      matchGenericOp(ISD::SUB, N, NegC, LHS, DAG, DCI, true);
     }
   } else if (auto *Const = dyn_cast<ConstantSDNode>(LHS)) {
-    if (X86ISD::SUB == N->getOpcode()) {
+    if (IsSub) {
       SDValue NegC = DAG.getConstant(-Const->getAPIntValue(), DL, VT);
       // Negate X86ISD::SUB(C, RHS) and replace generic add(RHS, -C).
-      MatchGeneric(ISD::ADD, RHS, NegC, true);
+      matchGenericOp(ISD::ADD, N, RHS, NegC, DAG, DCI, true);
     }
   }
 
@@ -59481,6 +59488,28 @@ static SDValue combineX86AddSub(SDNode *N, SelectionDAG &DAG,
                                    /*ZeroSecondOpOnly*/ true);
 }
 
+static SDValue combineX86XOR(SDNode *N, SelectionDAG &DAG,
+                             TargetLowering::DAGCombinerInfo &DCI) {
+  assert(N->getOpcode() == X86ISD::XOR && "Expected X86ISD::XOR");
+
+  SDLoc DL(N);
+  SDValue LHS = N->getOperand(0);
+  SDValue RHS = N->getOperand(1);
+  MVT VT = LHS.getSimpleValueType();
+
+  // If we don't use the flag result, simplify back to a generic op.
+  if (!N->hasAnyUseOfValue(1)) {
+    SDValue Res = DAG.getNode(ISD::XOR, DL, VT, LHS, RHS);
+    return DAG.getMergeValues({Res, DAG.getConstant(0, DL, MVT::i32)}, DL);
+  }
+
+  // Fold any matching generic nodes to reuse this node.
+  matchGenericOp(ISD::XOR, N, LHS, RHS, DAG, DCI);
+  matchGenericOp(ISD::XOR, N, RHS, LHS, DAG, DCI);
+
+  return SDValue();
+}
+
 static SDValue combineSBB(SDNode *N, SelectionDAG &DAG) {
   SDValue LHS = N->getOperand(0);
   SDValue RHS = N->getOperand(1);
@@ -63240,6 +63269,7 @@ SDValue X86TargetLowering::PerformDAGCombine(SDNode *N,
   case ISD::SUB:            return combineSub(N, DAG, DCI, Subtarget);
   case X86ISD::ADD:
   case X86ISD::SUB:         return combineX86AddSub(N, DAG, DCI, Subtarget);
+  case X86ISD::XOR:         return combineX86XOR(N, DAG, DCI);
   case ISD::SADDSAT:
   case ISD::SSUBSAT:        return combineToHorizontalAddSub(N, DAG, Subtarget);
   case X86ISD::CLOAD:
diff --git a/llvm/test/CodeGen/X86/cmp-xor.ll b/llvm/test/CodeGen/X86/cmp-xor.ll
index efb165f584db9..7cf791f3cb0be 100644
--- a/llvm/test/CodeGen/X86/cmp-xor.ll
+++ b/llvm/test/CodeGen/X86/cmp-xor.ll
@@ -58,17 +58,13 @@ define zeroext i1 @xor_cmp_rmw_i32(ptr %x) {
 ; X86-LABEL: xor_cmp_rmw_i32:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-NEXT:    movl (%eax), %ecx
-; X86-NEXT:    xorl $7, %ecx
-; X86-NEXT:    movl %ecx, (%eax)
+; X86-NEXT:    xorl $7, (%eax)
 ; X86-NEXT:    setne %al
 ; X86-NEXT:    retl
 ;
 ; X64-LABEL: xor_cmp_rmw_i32:
 ; X64:       # %bb.0:
-; X64-NEXT:    movl (%rdi), %eax
-; X64-NEXT:    xorl $7, %eax
-; X64-NEXT:    movl %eax, (%rdi)
+; X64-NEXT:    xorl $7, (%rdi)
 ; X64-NEXT:    setne %al
 ; X64-NEXT:    retq
   %load = load i32, ptr %x
@@ -110,17 +106,13 @@ define zeroext i1 @xor_cmp_rmw_eq_i32(ptr %x) {
 ; X86-LABEL: xor_cmp_rmw_eq_i32:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-NEXT:    movl (%eax), %ecx
-; X86-NEXT:    xorl $7, %ecx
-; X86-NEXT:    movl %ecx, (%eax)
+; X86-NEXT:    xorl $7, (%eax)
 ; X86-NEXT:    sete %al
 ; X86-NEXT:    retl
 ;
 ; X64-LABEL: xor_cmp_rmw_eq_i32:
 ; X64:       # %bb.0:
-; X64-NEXT:    movl (%rdi), %eax
-; X64-NEXT:    xorl $7, %eax
-; X64-NEXT:    movl %eax, (%rdi)
+; X64-NEXT:    xorl $7, (%rdi)
 ; X64-NEXT:    sete %al
 ; X64-NEXT:    retq
   %load = load i32, ptr %x

>From d1fad19532976d30b18aecc751212f2097ca5070 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 13 Aug 2026 15:20:49 -0400
Subject: [PATCH 3/4] Remove unnecessary blank lines in cmp-xor.ll

---
 llvm/test/CodeGen/X86/cmp-xor.ll | 2 --
 1 file changed, 2 deletions(-)

diff --git a/llvm/test/CodeGen/X86/cmp-xor.ll b/llvm/test/CodeGen/X86/cmp-xor.ll
index 7cf791f3cb0be..1d15b0b0e3b73 100644
--- a/llvm/test/CodeGen/X86/cmp-xor.ll
+++ b/llvm/test/CodeGen/X86/cmp-xor.ll
@@ -177,5 +177,3 @@ define zeroext i1 @xor_cmp_rmw_ult_i32(ptr %x) {
   %cmp = icmp ult i32 %load, 7
   ret i1 %cmp
 }
-
-

>From 15f49ef462a101aa36e362267299f911d702c5b2 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 13 Aug 2026 17:09:13 -0400
Subject: [PATCH 4/4] Use DAG from DCI

---
 llvm/lib/Target/X86/X86ISelLowering.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index f1edb0c7dcbb7..77d5c8d111f8d 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -59397,10 +59397,10 @@ static SDValue combineCMP(SDNode *N, SelectionDAG &DAG,
 /// If a matching generic opcode node exists, replace its uses with the value
 /// of the target-specific node N.
 static void matchGenericOp(unsigned Opc, SDNode *N, SDValue N0, SDValue N1,
-                           SelectionDAG &DAG,
                            TargetLowering::DAGCombinerInfo &DCI,
                            bool Negate = false) {
   SDValue Ops[] = {N0, N1};
+  SelectionDAG &DAG = DCI.DAG;
   SDVTList VTs = DAG.getVTList(N->getValueType(0));
   if (SDNode *Generic = DAG.getNodeIfExists(Opc, VTs, Ops)) {
     SDValue Op(N, 0);
@@ -59440,23 +59440,23 @@ static SDValue combineX86AddSub(SDNode *N, SelectionDAG &DAG,
   }
 
   // Fold any similar generic ADD/SUB opcodes to reuse this node.
-  matchGenericOp(GenericOpc, N, LHS, RHS, DAG, DCI, false);
-  matchGenericOp(GenericOpc, N, RHS, LHS, DAG, DCI, IsSub);
+  matchGenericOp(GenericOpc, N, LHS, RHS, DCI, false);
+  matchGenericOp(GenericOpc, N, RHS, LHS, DCI, IsSub);
 
   if (auto *Const = dyn_cast<ConstantSDNode>(RHS)) {
     SDValue NegC = DAG.getConstant(-Const->getAPIntValue(), DL, VT);
     if (IsSub) {
       // Fold generic add(LHS, -C) to X86ISD::SUB(LHS, C).
-      matchGenericOp(ISD::ADD, N, LHS, NegC, DAG, DCI, false);
+      matchGenericOp(ISD::ADD, N, LHS, NegC, DCI, false);
     } else {
       // Negate X86ISD::ADD(LHS, C) and replace generic sub(-C, LHS).
-      matchGenericOp(ISD::SUB, N, NegC, LHS, DAG, DCI, true);
+      matchGenericOp(ISD::SUB, N, NegC, LHS, DCI, true);
     }
   } else if (auto *Const = dyn_cast<ConstantSDNode>(LHS)) {
     if (IsSub) {
       SDValue NegC = DAG.getConstant(-Const->getAPIntValue(), DL, VT);
       // Negate X86ISD::SUB(C, RHS) and replace generic add(RHS, -C).
-      matchGenericOp(ISD::ADD, N, RHS, NegC, DAG, DCI, true);
+      matchGenericOp(ISD::ADD, N, RHS, NegC, DCI, true);
     }
   }
 
@@ -59504,8 +59504,8 @@ static SDValue combineX86XOR(SDNode *N, SelectionDAG &DAG,
   }
 
   // Fold any matching generic nodes to reuse this node.
-  matchGenericOp(ISD::XOR, N, LHS, RHS, DAG, DCI);
-  matchGenericOp(ISD::XOR, N, RHS, LHS, DAG, DCI);
+  matchGenericOp(ISD::XOR, N, LHS, RHS, DCI);
+  matchGenericOp(ISD::XOR, N, RHS, LHS, DCI);
 
   return SDValue();
 }



More information about the llvm-commits mailing list