Skip to content

Conversation

@rez5427
Copy link
Contributor

@rez5427 rez5427 commented Dec 1, 2025

I recently observed that LLVM generates the following code:

	addi	a1, a0, -1
	sltu	a0, a0, a1
	addi	a0, a0, -1
	and	a0, a0, a1
	ret

This could be optimized using the snez instruction instead.

@llvmbot
Copy link
Member

llvmbot commented Dec 1, 2025

@llvm/pr-subscribers-backend-x86
@llvm/pr-subscribers-llvm-selectiondag
@llvm/pr-subscribers-backend-aarch64

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

Author: guan jian (rez5427)

Changes

I recently observed that LLVM generates the following code:

	addi	a1, a0, -1
	sltu	a0, a0, a1
	addi	a0, a0, -1
	and	a0, a0, a1
	ret

This could be optimized using the snez instruction instead.


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

3 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp (+10)
  • (modified) llvm/test/CodeGen/AArch64/and-mask-removal.ll (+3-3)
  • (modified) llvm/test/CodeGen/RISCV/usub_sat.ll (+28)
diff --git a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
index 521d8f07434e6..b3af59b6528b9 100644
--- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
@@ -10867,6 +10867,16 @@ SDValue TargetLowering::expandAddSubSat(SDNode *Node, SelectionDAG &DAG) const {
   assert(VT == RHS.getValueType() && "Expected operands to be the same type");
   assert(VT.isInteger() && "Expected operands to be integers");
 
+  // usub.sat(a, 1) -> a - zext(a != 0)
+  if (Opcode == ISD::USUBSAT && !VT.isVector() && isOneConstant(RHS)) {
+    SDValue Zero = DAG.getConstant(0, dl, VT);
+    EVT BoolVT =
+        getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), VT);
+    SDValue IsNonZero = DAG.getSetCC(dl, BoolVT, LHS, Zero, ISD::SETNE);
+    SDValue Subtrahend = DAG.getBoolExtOrTrunc(IsNonZero, dl, VT, BoolVT);
+    return DAG.getNode(ISD::SUB, dl, VT, LHS, Subtrahend);
+  }
+
   // usub.sat(a, b) -> umax(a, b) - b
   if (Opcode == ISD::USUBSAT && isOperationLegal(ISD::UMAX, VT)) {
     SDValue Max = DAG.getNode(ISD::UMAX, dl, VT, LHS, RHS);
diff --git a/llvm/test/CodeGen/AArch64/and-mask-removal.ll b/llvm/test/CodeGen/AArch64/and-mask-removal.ll
index 5046c0571ad2b..855fe5caf97b2 100644
--- a/llvm/test/CodeGen/AArch64/and-mask-removal.ll
+++ b/llvm/test/CodeGen/AArch64/and-mask-removal.ll
@@ -483,9 +483,9 @@ define i64 @pr58109(i8 signext %0) {
 ; CHECK-SD-LABEL: pr58109:
 ; CHECK-SD:       ; %bb.0:
 ; CHECK-SD-NEXT:    add w8, w0, #1
-; CHECK-SD-NEXT:    and w8, w8, #0xff
-; CHECK-SD-NEXT:    subs w8, w8, #1
-; CHECK-SD-NEXT:    csel w0, wzr, w8, lo
+; CHECK-SD-NEXT:    ands w8, w8, #0xff
+; CHECK-SD-NEXT:    cset w9, ne
+; CHECK-SD-NEXT:    sub w0, w8, w9
 ; CHECK-SD-NEXT:    ret
 ;
 ; CHECK-GI-LABEL: pr58109:
diff --git a/llvm/test/CodeGen/RISCV/usub_sat.ll b/llvm/test/CodeGen/RISCV/usub_sat.ll
index 33056682dcc79..7084885a0ee2c 100644
--- a/llvm/test/CodeGen/RISCV/usub_sat.ll
+++ b/llvm/test/CodeGen/RISCV/usub_sat.ll
@@ -185,3 +185,31 @@ define zeroext i4 @func3(i4 zeroext %x, i4 zeroext %y) nounwind {
   %tmp = call i4 @llvm.usub.sat.i4(i4 %x, i4 %y);
   ret i4 %tmp;
 }
+
+define signext i32 @sat_dec_i32(i32 signext %x) nounwind {
+; RV32I-LABEL: sat_dec_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    snez a1, a0
+; RV32I-NEXT:    sub a0, a0, a1
+; RV32I-NEXT:    ret
+;
+; RV64I-LABEL: sat_dec_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    snez a1, a0
+; RV64I-NEXT:    subw a0, a0, a1
+; RV64I-NEXT:    ret
+;
+; RV32IZbb-LABEL: sat_dec_i32:
+; RV32IZbb:       # %bb.0:
+; RV32IZbb-NEXT:    snez a1, a0
+; RV32IZbb-NEXT:    sub a0, a0, a1
+; RV32IZbb-NEXT:    ret
+;
+; RV64IZbb-LABEL: sat_dec_i32:
+; RV64IZbb:       # %bb.0:
+; RV64IZbb-NEXT:    snez a1, a0
+; RV64IZbb-NEXT:    subw a0, a0, a1
+; RV64IZbb-NEXT:    ret
+  %tmp = call i32 @llvm.usub.sat.i32(i32 %x, i32 1)
+  ret i32 %tmp
+}

@github-actions
Copy link

github-actions bot commented Dec 1, 2025

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

EVT BoolVT =
getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), VT);
SDValue IsNonZero = DAG.getSetCC(dl, BoolVT, LHS, Zero, ISD::SETNE);
SDValue Subtrahend = DAG.getBoolExtOrTrunc(IsNonZero, dl, VT, BoolVT);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't this assume ZeroOrOneBooleanContent? Also what is Subtrahend short for?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't Subtrahend the word for the right hand side of a subtraction?

Copy link
Collaborator

Choose a reason for hiding this comment

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

TIL :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I learned "minuend" and "subtrahend" from the ZX Spectrum BASIC manual :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't this assume ZeroOrOneBooleanContent?

I think so. For ZeroNegativeOneBooleanContent we would need to use ADD instead of SUB.

assert(VT.isInteger() && "Expected operands to be integers");

// usub.sat(a, 1) -> a - zext(a != 0)
if (Opcode == ISD::USUBSAT && !VT.isVector() && isOneConstant(RHS)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why no vector support?

@jayfoad
Copy link
Contributor

jayfoad commented Dec 3, 2025

Another option is usub.sat(a,1) -> umin(a,a-1). The best option might depend on whether the target has setcc and/or min/max instructions.

SDValue Zero = DAG.getConstant(0, dl, VT);
EVT BoolVT =
getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), VT);
SDValue IsNonZero = DAG.getSetCC(dl, BoolVT, LHS, Zero, ISD::SETNE);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to freeze LHS?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

That shows that it works with freeze. Looks like it fails if the freeze is removed?

SDValue Zero = DAG.getConstant(0, dl, VT);
EVT BoolVT = getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), VT);
SDValue IsNonZero = DAG.getSetCC(dl, BoolVT, LHS, Zero, ISD::SETNE);
SDValue Subtrahend = DAG.getZExtOrTrunc(IsNonZero, dl, VT);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't going to work as BoolVT might already be the same size as VT

You might have to do this:

SDValue Subtrahend = DAG.getBoolExtOrTrunc(IsNonZero, dl, VT);
Subtrahend = getNode(ISD::AND, DL, VT, Subtrahend, getConstant(1, DL, VT));

RKSimon added a commit to RKSimon/llvm-project that referenced this pull request Dec 4, 2025
@RKSimon
Copy link
Collaborator

RKSimon commented Dec 4, 2025

@rez5427 I've added x86 test coverage at #170681 - you will need to merge and update the test after it lands

RKSimon added a commit to RKSimon/llvm-project that referenced this pull request Dec 4, 2025
alanzhao1 pushed a commit to alanzhao1/llvm-project that referenced this pull request Dec 4, 2025
assert(VT.isInteger() && "Expected operands to be integers");

// usub.sat(a, 1) -> sub(a, zext(a != 0))
if (Opcode == ISD::USUBSAT && isOneConstant(RHS)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

isOneConstant only returns true for scalar. So this still doesn't support vector.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw some regressions on some test files, I use !VT.isVector() && isOneConstant(RHS) now.

@rez5427
Copy link
Contributor Author

rez5427 commented Dec 6, 2025

Well, I rebase it, And add freeze, and limit if for scalar, because I saw some regressions on test files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants