[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