[llvm] [DAGCombiner] Fold `sub nuw C, x` -> `xor x, C` when C is a mask (PR #192692)
Madhur Kumar via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 10:03:33 PDT 2026
https://github.com/MadhurKumar004 updated https://github.com/llvm/llvm-project/pull/192692
>From 4a6ac712ed87a300329a27fa852e00aff82b3991 Mon Sep 17 00:00:00 2001
From: Madhur Kumar <madhurkumar004 at gmail.com>
Date: Fri, 17 Apr 2026 21:34:18 +0530
Subject: [PATCH] [DAGCombiner] Fold `sub nuw C, x` -> `xor x, C` when C is a
mask
---
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 11 +-
llvm/test/CodeGen/AArch64/pr86717.ll | 11 +-
llvm/test/CodeGen/RISCV/pr84200.ll | 9 +-
llvm/test/CodeGen/X86/sub-to-xor-masked.ll | 103 ++++++++++++++++++
4 files changed, 119 insertions(+), 15 deletions(-)
create mode 100644 llvm/test/CodeGen/X86/sub-to-xor-masked.ll
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index ec4be73a9966b..410e7f4484e03 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -4549,11 +4549,16 @@ SDValue DAGCombiner::visitSUB(SDNode *N) {
}
}
- // If there's no chance of borrowing from adjacent bits, then sub is xor:
- // sub C0, X --> xor X, C0
if (ConstantSDNode *C0 = isConstOrConstSplat(N0)) {
+ const APInt &C0Val = C0->getAPIntValue();
+
+ // sub nuw C, x --> xor x, C when C is a mask (2^k - 1)
+ if (N->getFlags().hasNoUnsignedWrap() && C0Val.isMask())
+ return DAG.getNode(ISD::XOR, DL, VT, N1, N0);
+
+ // If there's no chance of borrowing from adjacent bits, then sub is xor:
+ // sub C0, X --> xor X, C0
if (!C0->isOpaque()) {
- const APInt &C0Val = C0->getAPIntValue();
const APInt &MaybeOnes = ~DAG.computeKnownBits(N1).Zero;
if ((C0Val - MaybeOnes) == (C0Val ^ MaybeOnes))
return DAG.getNode(ISD::XOR, DL, VT, N1, N0);
diff --git a/llvm/test/CodeGen/AArch64/pr86717.ll b/llvm/test/CodeGen/AArch64/pr86717.ll
index aa8be954be72d..f1d423fb0f087 100644
--- a/llvm/test/CodeGen/AArch64/pr86717.ll
+++ b/llvm/test/CodeGen/AArch64/pr86717.ll
@@ -7,13 +7,12 @@ define <16 x i8> @f(i32 %0) {
; CHECK-NEXT: sub sp, sp, #16
; CHECK-NEXT: .cfi_def_cfa_offset 16
; CHECK-NEXT: movi v0.2d, #0000000000000000
-; CHECK-NEXT: mov w8, #1 // =0x1
-; CHECK-NEXT: mov x9, sp
-; CHECK-NEXT: sub w8, w8, w0
-; CHECK-NEXT: bfxil x9, x8, #0, #4
-; CHECK-NEXT: mov w8, #3 // =0x3
+; CHECK-NEXT: mov x8, sp
+; CHECK-NEXT: eor w9, w0, #0x1
+; CHECK-NEXT: bfxil x8, x9, #0, #4
+; CHECK-NEXT: mov w9, #3 // =0x3
; CHECK-NEXT: str q0, [sp]
-; CHECK-NEXT: strb w8, [x9]
+; CHECK-NEXT: strb w9, [x8]
; CHECK-NEXT: ldr q0, [sp], #16
; CHECK-NEXT: ret
%2 = sub nuw i32 1, %0
diff --git a/llvm/test/CodeGen/RISCV/pr84200.ll b/llvm/test/CodeGen/RISCV/pr84200.ll
index 19a102b84ed06..7674695790056 100644
--- a/llvm/test/CodeGen/RISCV/pr84200.ll
+++ b/llvm/test/CodeGen/RISCV/pr84200.ll
@@ -6,12 +6,9 @@
define i64 @foo(i64 %1) {
; CHECK-LABEL: foo:
; CHECK: # %bb.0: # %entry
-; CHECK-NEXT: li a1, 1
-; CHECK-NEXT: sub a1, a1, a0
-; CHECK-NEXT: sltiu a0, a0, 2
-; CHECK-NEXT: xori a1, a1, 1
-; CHECK-NEXT: neg a0, a0
-; CHECK-NEXT: and a0, a0, a1
+; CHECK-NEXT: sltiu a1, a0, 2
+; CHECK-NEXT: neg a1, a1
+; CHECK-NEXT: and a0, a1, a0
; CHECK-NEXT: ret
entry:
%.urem.i = sub nuw i64 1, %1
diff --git a/llvm/test/CodeGen/X86/sub-to-xor-masked.ll b/llvm/test/CodeGen/X86/sub-to-xor-masked.ll
new file mode 100644
index 0000000000000..3623cd9152f02
--- /dev/null
+++ b/llvm/test/CodeGen/X86/sub-to-xor-masked.ll
@@ -0,0 +1,103 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=X86
+
+define i8 @sub_nuw_mask_4bit(i8 %x) nounwind {
+; X86-LABEL: sub_nuw_mask_4bit:
+; X86: # %bb.0:
+; X86-NEXT: movl %edi, %eax
+; X86-NEXT: xorb $15, %al
+; X86-NEXT: # kill: def $al killed $al killed $eax
+; X86-NEXT: retq
+ %r = sub nuw i8 15, %x
+ ret i8 %r
+}
+
+define i8 @sub_nuw_mask_7bit(i8 %x) nounwind {
+; X86-LABEL: sub_nuw_mask_7bit:
+; X86: # %bb.0:
+; X86-NEXT: movl %edi, %eax
+; X86-NEXT: xorb $127, %al
+; X86-NEXT: # kill: def $al killed $al killed $eax
+; X86-NEXT: retq
+ %r = sub nuw i8 127, %x
+ ret i8 %r
+}
+
+define i8 @sub_nuw_allones_i8(i8 %x) nounwind {
+; X86-LABEL: sub_nuw_allones_i8:
+; X86: # %bb.0:
+; X86-NEXT: movl %edi, %eax
+; X86-NEXT: notb %al
+; X86-NEXT: # kill: def $al killed $al killed $eax
+; X86-NEXT: retq
+ %r = sub nuw i8 255, %x
+ ret i8 %r
+}
+
+define i32 @sub_nuw_mask_i32(i32 %x) nounwind {
+; X86-LABEL: sub_nuw_mask_i32:
+; X86: # %bb.0:
+; X86-NEXT: movl %edi, %eax
+; X86-NEXT: xorl $65535, %eax # imm = 0xFFFF
+; X86-NEXT: retq
+ %r = sub nuw i32 65535, %x
+ ret i32 %r
+}
+
+define i64 @sub_nuw_mask_i64(i64 %x) nounwind {
+; X86-LABEL: sub_nuw_mask_i64:
+; X86: # %bb.0:
+; X86-NEXT: movl $4294967295, %eax # imm = 0xFFFFFFFF
+; X86-NEXT: xorq %rdi, %rax
+; X86-NEXT: retq
+ %r = sub nuw i64 4294967295, %x
+ ret i64 %r
+}
+
+define i8 @sub_nuw_nonmask(i8 %x) nounwind {
+; X86-LABEL: sub_nuw_nonmask:
+; X86: # %bb.0:
+; X86-NEXT: movb $13, %al
+; X86-NEXT: subb %dil, %al
+; X86-NEXT: retq
+ %r = sub nuw i8 13, %x
+ ret i8 %r
+}
+
+define i8 @sub_no_nuw(i8 %x) nounwind {
+; X86-LABEL: sub_no_nuw:
+; X86: # %bb.0:
+; X86-NEXT: movb $15, %al
+; X86-NEXT: subb %dil, %al
+; X86-NEXT: retq
+ %r = sub i8 15, %x
+ ret i8 %r
+}
+
+define i8 @sub_nsw_only(i8 %x) nounwind {
+; X86-LABEL: sub_nsw_only:
+; X86: # %bb.0:
+; X86-NEXT: movb $15, %al
+; X86-NEXT: subb %dil, %al
+; X86-NEXT: retq
+ %r = sub nsw i8 15, %x
+ ret i8 %r
+}
+
+define i8 @sub_nuw_zero(i8 %x) nounwind {
+; X86-LABEL: sub_nuw_zero:
+; X86: # %bb.0:
+; X86-NEXT: xorl %eax, %eax
+; X86-NEXT: retq
+ %r = sub nuw i8 0, %x
+ ret i8 %r
+}
+
+define <4 x i8> @sub_nuw_mask_vec_splat(<4 x i8> %x) nounwind {
+; X86-LABEL: sub_nuw_mask_vec_splat:
+; X86: # %bb.0:
+; X86-NEXT: xorps {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm0
+; X86-NEXT: retq
+ %r = sub nuw <4 x i8> <i8 15, i8 15, i8 15, i8 15>, %x
+ ret <4 x i8> %r
+}
More information about the llvm-commits
mailing list