Skip to content

[AArch64] Signed comparison using CMN is safe when the subtraction is nsw #141993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 12, 2025

Conversation

AZero13
Copy link
Contributor

@AZero13 AZero13 commented May 29, 2025

nsw means no signed wrap, and 0 - INT_MIN is a signed wrap.

Now, this is going to be a point I need to get out of the way:

So is it okay to always transform a > -b into cmn if it is a signed comparison, even if b is INT_MIN because -INT_MIN is undefined, at least in C, because unless fwrapv is specified, opt puts nsw on signed integer operations, allowing for more folds anyway.

@llvmbot
Copy link
Member

llvmbot commented May 29, 2025

@llvm/pr-subscribers-backend-aarch64

Author: AZero13 (AZero13)

Changes

nsw means no signed wrap, and 0 - INT_MIN is a signed wrap.

Now, this is going to be a point I need to get out of the way:

So is it okay to always transform a > -b into cmn if it is a signed comparison, even if b is INT_MIN because -INT_MIN is undefined, at least in C.

Taking advantage of UB doesn't introduce bugs. But, this could change behavior in programs with UB.


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

2 Files Affected:

  • (modified) llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (+8-3)
  • (modified) llvm/test/CodeGen/AArch64/cmp-to-cmn.ll (+46)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index a07afea963e20..27b612053cfd9 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -3337,8 +3337,13 @@ static bool isLegalArithImmed(uint64_t C) {
   return IsLegal;
 }
 
-static bool cannotBeIntMin(SDValue CheckedVal, SelectionDAG &DAG) {
-  KnownBits KnownSrc = DAG.computeKnownBits(CheckedVal);
+static bool cannotBeIntMin(SDValue Op, SelectionDAG &DAG) {
+  // 0 - INT_MIN sign wraps, so no signed wrap means cmn.
+  if (Op->getFlags().hasNoSignedWrap())
+    return true;
+
+  // Maybe nsw was not set here...
+  KnownBits KnownSrc = DAG.computeKnownBits(Op.getOperand(1));
   return !KnownSrc.getSignedMinValue().isMinSignedValue();
 }
 
@@ -3356,7 +3361,7 @@ static bool isCMN(SDValue Op, ISD::CondCode CC, SelectionDAG &DAG) {
   return Op.getOpcode() == ISD::SUB && isNullConstant(Op.getOperand(0)) &&
          (isIntEqualitySetCC(CC) ||
           (isUnsignedIntSetCC(CC) && DAG.isKnownNeverZero(Op.getOperand(1))) ||
-          (isSignedIntSetCC(CC) && cannotBeIntMin(Op.getOperand(1), DAG)));
+          (isSignedIntSetCC(CC) && cannotBeIntMin(Op, DAG)));
 }
 
 static SDValue emitStrictFPComparison(SDValue LHS, SDValue RHS, const SDLoc &dl,
diff --git a/llvm/test/CodeGen/AArch64/cmp-to-cmn.ll b/llvm/test/CodeGen/AArch64/cmp-to-cmn.ll
index e87d43161a895..7a3bbc3307461 100644
--- a/llvm/test/CodeGen/AArch64/cmp-to-cmn.ll
+++ b/llvm/test/CodeGen/AArch64/cmp-to-cmn.ll
@@ -430,3 +430,49 @@ entry:
   %cmp = icmp ne i32 %conv, %add
   ret i1 %cmp
 }
+
+define i1 @cmn_nsw(i32 %a, i32 %b) {
+; CHECK-LABEL: cmn_nsw:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w0, w1
+; CHECK-NEXT:    cset w0, gt
+; CHECK-NEXT:    ret
+%sub = sub nsw i32 0, %b
+%cmp = icmp sgt i32 %a, %sub
+ret i1 %cmp
+}
+
+define i1 @cmn_nsw_64(i64 %a, i64 %b) {
+; CHECK-LABEL: cmn_nsw_64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn x0, x1
+; CHECK-NEXT:    cset w0, gt
+; CHECK-NEXT:    ret
+%sub = sub nsw i64 0, %b
+%cmp = icmp sgt i64 %a, %sub
+ret i1 %cmp
+}
+
+define i1 @cmn_nsw_neg(i32 %a, i32 %b) {
+; CHECK-LABEL: cmn_nsw_neg:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    neg w8, w1
+; CHECK-NEXT:    cmp w0, w8
+; CHECK-NEXT:    cset w0, gt
+; CHECK-NEXT:    ret
+%sub = sub i32 0, %b
+%cmp = icmp sgt i32 %a, %sub
+ret i1 %cmp
+}
+
+define i1 @cmn_nsw_neg_64(i64 %a, i64 %b) {
+; CHECK-LABEL: cmn_nsw_neg_64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    neg x8, x1
+; CHECK-NEXT:    cmp x0, x8
+; CHECK-NEXT:    cset w0, gt
+; CHECK-NEXT:    ret
+%sub = sub i64 0, %b
+%cmp = icmp sgt i64 %a, %sub
+ret i1 %cmp
+}

@AZero13 AZero13 requested a review from topperc May 29, 2025 17:51
@AZero13
Copy link
Contributor Author

AZero13 commented May 29, 2025

@topperc @dtcxzyw Actually, now that I think about it, if I check for nsw, is the Knownbits needed at all?

@AZero13
Copy link
Contributor Author

AZero13 commented May 29, 2025

When I remove the nsw check, tests fail, but for all those tests, alive2 says adding nsw to the sub causes no changes?

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

@AZero13
Copy link
Contributor Author

AZero13 commented May 29, 2025

Is it possible for a sub node to be made without nsw by mistake?

@topperc
Copy link
Collaborator

topperc commented May 29, 2025

Is it possible for a sub node to be made without nsw by mistake?

I guessing the tests that are failing are handwritten without consideration of the nsw flag and the test author did not run them through InstCombine to add the flag. Unfortunately, SelectionDAG only propagates the flag from IR and never infers it on any nodes.

@AZero13
Copy link
Contributor Author

AZero13 commented May 29, 2025

Alright so let's keep the KnownBits for now

@AZero13
Copy link
Contributor Author

AZero13 commented May 29, 2025

Is it possible for a sub node to be made without nsw by mistake?

I guessing the tests that are failing are handwritten without consideration of the nsw flag and the test author did not run them through InstCombine to add the flag. Unfortunately, SelectionDAG only propagates the flag from IR and never infers it on any nodes.

The test author

Yeah that would be me. I wrote these tests without realizing I could use nsw.

@AZero13 AZero13 force-pushed the cmn-what-is-it branch 3 times, most recently from bfec5a3 to 60ceb0e Compare May 30, 2025 16:29
@AZero13
Copy link
Contributor Author

AZero13 commented Jun 7, 2025

@davemgreen Thoughts on this?

@AZero13
Copy link
Contributor Author

AZero13 commented Jun 9, 2025

Yeah, we create SUB nodes and they do not carry that nsw information so we need to keep the knownzero check for now.

@AZero13
Copy link
Contributor Author

AZero13 commented Jun 9, 2025

Is it possible for a sub node to be made without nsw by mistake?

I guessing the tests that are failing are handwritten without consideration of the nsw flag and the test author did not run them through InstCombine to add the flag. Unfortunately, SelectionDAG only propagates the flag from IR and never infers it on any nodes.

We make SUB nodes in dagcombine, and they do not carry flags, so this check is still good.

@AZero13
Copy link
Contributor Author

AZero13 commented Jun 9, 2025

@topperc ping

@AZero13 AZero13 requested a review from topperc June 9, 2025 23:42
@AZero13
Copy link
Contributor Author

AZero13 commented Jun 10, 2025

@topperc Okay, I fixed it, and I learned my lesson. No force-pushing. Just a new commit.

@AZero13 AZero13 requested a review from davemgreen June 11, 2025 19:58
Copy link
Collaborator

@davemgreen davemgreen left a comment

Choose a reason for hiding this comment

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

Thanks - I think I convinced myself that this was OK. LGTM

Are you happy for it to be submitted?

@AZero13
Copy link
Contributor Author

AZero13 commented Jun 12, 2025

I am. Let's do this @davemgreen

AZero13 added 2 commits June 12, 2025 15:11
… nsw

nsw means no signed wrap, and 0 - INT_MIN is a signed wrap.

Now, this is going to be a point I need to get out of the way:

So is it okay to always transform a > -b into cmn if it is a signed comparison, even if b is INT_MIN because -INT_MIN is undefined, at least in C, because unless fwrapv is specified, opt puts nsw on signed integer operations, allowing for more folds anyway.
@AZero13
Copy link
Contributor Author

AZero13 commented Jun 12, 2025

I fixed a comment typo, build failed for unrelated reason, so I just rebased just to ensure everything is good. Everything is good. We can merge.

@davemgreen davemgreen merged commit c19e900 into llvm:main Jun 12, 2025
7 checks passed
@AZero13 AZero13 deleted the cmn-what-is-it branch June 12, 2025 20:55
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jun 13, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-flang-rhel-clang running on ppc64le-flang-rhel-test while building llvm at step 6 "test-build-unified-tree-check-flang".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/157/builds/30652

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-flang) failure: test (failure)
******************** TEST 'Flang :: Semantics/modfile75.F90' FAILED ********************
Exit Code: 2

Command Output (stderr):
--
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -c -fhermetic-module-files -DWHICH=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90 && /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -c -fhermetic-module-files -DWHICH=2 /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90 && /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -fc1 -fdebug-unparse /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90 | /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90 # RUN: at line 1
+ /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -c -fhermetic-module-files -DWHICH=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90
+ /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -c -fhermetic-module-files -DWHICH=2 /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90
+ /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/flang -fc1 -fdebug-unparse /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90
+ /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90
error: Semantic errors in /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90
/home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90:15:11: error: Must be a constant value
    integer(c_int) n
            ^^^^^
FileCheck error: '<stdin>' is empty.
FileCheck command line:  /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/build/bin/FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-flang-rhel-test/ppc64le-flang-rhel-clang-build/llvm-project/flang/test/Semantics/modfile75.F90

--

********************


tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
… nsw (llvm#141993)

nsw means no signed wrap, and 0 - INT_MIN is a signed wrap.

Now, this is going to be a point I need to get out of the way:

So is it okay to always transform a > -b into cmn if it is a signed
comparison, even if b is INT_MIN because -INT_MIN is undefined, at least
in C, because unless fwrapv is specified, opt puts nsw on signed integer
operations, allowing for more folds anyway.
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