[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