Skip to content

Conversation

@rez5427
Copy link
Contributor

@rez5427 rez5427 commented Nov 6, 2025

Add fold (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)

The IR like this:

  %a = ashr i8 %x, 6
  %n = xor i8 %a, -1
  %s = sext i8 %n to i16
  %r = and i16 %s, %y
  ret i16 %r

llvm will produce:

	slli	a0, a0, 56
	srai	a0, a0, 56
	not	a0, a0
	srai	a0, a0, 6
	and	a0, a0, a1
	ret

56 and 6 can be add up

alive2: https://alive2.llvm.org/ce/z/yxRQf9

@llvmbot llvmbot added backend:RISC-V llvm:SelectionDAG SelectionDAGISel as well labels Nov 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 6, 2025

@llvm/pr-subscribers-llvm-selectiondag

Author: guan jian (rez5427)

Changes

Add fold (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)

alive2: https://alive2.llvm.org/ce/z/yxRQf9


Full diff: https://github.com/llvm/llvm-project/pull/166777.diff

2 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp (+20)
  • (added) llvm/test/CodeGen/RISCV/sra-xor-sra.ll (+32)
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index d2ea6525e1116..7ab460bef019e 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -10968,6 +10968,26 @@ SDValue DAGCombiner::visitSRA(SDNode *N) {
     }
   }
 
+  // fold (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)
+  if (N0.getOpcode() == ISD::XOR && N0.hasOneUse() &&
+      isAllOnesConstant(N0.getOperand(1))) {
+    SDValue Inner = N0.getOperand(0);
+    if (Inner.getOpcode() == ISD::SRA && N1C) {
+      if (ConstantSDNode *InnerShiftAmt = isConstOrConstSplat(Inner.getOperand(1))) {
+        APInt c1 = InnerShiftAmt->getAPIntValue();
+        APInt c2 = N1C->getAPIntValue();
+        zeroExtendToMatch(c1, c2, 1 /* Overflow Bit */);
+        APInt Sum = c1 + c2;
+        unsigned ShiftSum =
+            Sum.uge(OpSizeInBits) ? (OpSizeInBits - 1) : Sum.getZExtValue();
+        SDValue NewShift = DAG.getNode(ISD::SRA, DL, VT, Inner.getOperand(0),
+                                       DAG.getConstant(ShiftSum, DL, N1.getValueType()));
+        return DAG.getNode(ISD::XOR, DL, VT, NewShift,
+                          DAG.getAllOnesConstant(DL, VT));
+      }
+    }
+  }
+
   // fold (sra (shl X, m), (sub result_size, n))
   // -> (sign_extend (trunc (shl X, (sub (sub result_size, n), m)))) for
   // result_size - n != m.
diff --git a/llvm/test/CodeGen/RISCV/sra-xor-sra.ll b/llvm/test/CodeGen/RISCV/sra-xor-sra.ll
new file mode 100644
index 0000000000000..b04f0a29d07f3
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/sra-xor-sra.ll
@@ -0,0 +1,32 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s
+
+; Test folding of: (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)
+; Original motivating example: should merge sra+sra across xor
+define i16 @not_invert_signbit_splat_mask(i8 %x, i16 %y) {
+; CHECK-LABEL: not_invert_signbit_splat_mask:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 56
+; CHECK-NEXT:    srai a0, a0, 62
+; CHECK-NEXT:    not a0, a0
+; CHECK-NEXT:    and a0, a0, a1
+; CHECK-NEXT:    ret
+  %a = ashr i8 %x, 6
+  %n = xor i8 %a, -1
+  %s = sext i8 %n to i16
+  %r = and i16 %s, %y
+  ret i16 %r
+}
+
+; Edge case
+define i16 @sra_xor_sra_overflow(i8 %x, i16 %y) {
+; CHECK-LABEL: sra_xor_sra_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    li a0, 0
+; CHECK-NEXT:    ret
+  %a = ashr i8 %x, 10
+  %n = xor i8 %a, -1
+  %s = sext i8 %n to i16
+  %r = and i16 %s, %y
+  ret i16 %r
+}

@llvmbot
Copy link
Member

llvmbot commented Nov 6, 2025

@llvm/pr-subscribers-backend-risc-v

Author: guan jian (rez5427)

Changes

Add fold (sra (xor (sra x, c1), -1), c2) -&gt; (sra (xor x, -1), c3)

alive2: https://alive2.llvm.org/ce/z/yxRQf9


Full diff: https://github.com/llvm/llvm-project/pull/166777.diff

2 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp (+20)
  • (added) llvm/test/CodeGen/RISCV/sra-xor-sra.ll (+32)
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index d2ea6525e1116..7ab460bef019e 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -10968,6 +10968,26 @@ SDValue DAGCombiner::visitSRA(SDNode *N) {
     }
   }
 
+  // fold (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)
+  if (N0.getOpcode() == ISD::XOR && N0.hasOneUse() &&
+      isAllOnesConstant(N0.getOperand(1))) {
+    SDValue Inner = N0.getOperand(0);
+    if (Inner.getOpcode() == ISD::SRA && N1C) {
+      if (ConstantSDNode *InnerShiftAmt = isConstOrConstSplat(Inner.getOperand(1))) {
+        APInt c1 = InnerShiftAmt->getAPIntValue();
+        APInt c2 = N1C->getAPIntValue();
+        zeroExtendToMatch(c1, c2, 1 /* Overflow Bit */);
+        APInt Sum = c1 + c2;
+        unsigned ShiftSum =
+            Sum.uge(OpSizeInBits) ? (OpSizeInBits - 1) : Sum.getZExtValue();
+        SDValue NewShift = DAG.getNode(ISD::SRA, DL, VT, Inner.getOperand(0),
+                                       DAG.getConstant(ShiftSum, DL, N1.getValueType()));
+        return DAG.getNode(ISD::XOR, DL, VT, NewShift,
+                          DAG.getAllOnesConstant(DL, VT));
+      }
+    }
+  }
+
   // fold (sra (shl X, m), (sub result_size, n))
   // -> (sign_extend (trunc (shl X, (sub (sub result_size, n), m)))) for
   // result_size - n != m.
diff --git a/llvm/test/CodeGen/RISCV/sra-xor-sra.ll b/llvm/test/CodeGen/RISCV/sra-xor-sra.ll
new file mode 100644
index 0000000000000..b04f0a29d07f3
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/sra-xor-sra.ll
@@ -0,0 +1,32 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s
+
+; Test folding of: (sra (xor (sra x, c1), -1), c2) -> (sra (xor x, -1), c3)
+; Original motivating example: should merge sra+sra across xor
+define i16 @not_invert_signbit_splat_mask(i8 %x, i16 %y) {
+; CHECK-LABEL: not_invert_signbit_splat_mask:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 56
+; CHECK-NEXT:    srai a0, a0, 62
+; CHECK-NEXT:    not a0, a0
+; CHECK-NEXT:    and a0, a0, a1
+; CHECK-NEXT:    ret
+  %a = ashr i8 %x, 6
+  %n = xor i8 %a, -1
+  %s = sext i8 %n to i16
+  %r = and i16 %s, %y
+  ret i16 %r
+}
+
+; Edge case
+define i16 @sra_xor_sra_overflow(i8 %x, i16 %y) {
+; CHECK-LABEL: sra_xor_sra_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    li a0, 0
+; CHECK-NEXT:    ret
+  %a = ashr i8 %x, 10
+  %n = xor i8 %a, -1
+  %s = sext i8 %n to i16
+  %r = and i16 %s, %y
+  ret i16 %r
+}

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@RKSimon RKSimon requested a review from topperc November 6, 2025 14:16
@rez5427
Copy link
Contributor Author

rez5427 commented Nov 7, 2025

Sorry about the commit suggestion won't work, really clicked like a hundred times.

@RKSimon RKSimon self-requested a review November 8, 2025 11:25
Copy link
Collaborator

@RKSimon RKSimon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@arsenm arsenm merged commit 3618ed1 into llvm:main Nov 11, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:RISC-V llvm:SelectionDAG SelectionDAGISel as well

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants