[llvm] [LLVM][InstCombine] Add infrastructure to allow simplification of SVE compare intrinsics. (PR #211249)

Paul Walker via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 05:39:55 PDT 2026


https://github.com/paulwalker-arm created https://github.com/llvm/llvm-project/pull/211249

To help unblock other work I've deliberately restricted the compares to the minimum required to test all code paths, with the only simplification being to canonicalise constants to the RHS for commutable compares.


>From 3cc58be266663ee36181f4e7a63ff65c74b93132 Mon Sep 17 00:00:00 2001
From: Paul Walker <paul.walker at arm.com>
Date: Wed, 22 Jul 2026 12:22:43 +0000
Subject: [PATCH 1/2] Add tests for compare simplification.

---
 .../AArch64/sve-intrinsic-simplify-cmp.ll     | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll

diff --git a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll
new file mode 100644
index 0000000000000..656c1fa46b34f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll
@@ -0,0 +1,52 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+target triple = "aarch64-unknown-linux-gnu"
+
+; The follow tests verify the mechanics of simplification. The operation is not
+; important beyond being commutative.
+
+define <vscale x 4 x i1> @commute_constant_to_rhs(<vscale x 4 x i1> %pg, <vscale x 4 x i32> %a) #0 {
+; CHECK-LABEL: define <vscale x 4 x i1> @commute_constant_to_rhs(
+; CHECK-SAME: <vscale x 4 x i1> [[PG:%.*]], <vscale x 4 x i32> [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> [[A]])
+; CHECK-NEXT:    ret <vscale x 4 x i1> [[R]]
+;
+  %r = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> %pg, <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> %a)
+  ret <vscale x 4 x i1> %r
+}
+
+; Operation is not commutative.
+define <vscale x 4 x i1> @cannot_commute_constant_to_rhs_1(<vscale x 4 x i1> %pg, <vscale x 4 x i32> %a) #0 {
+; CHECK-LABEL: define <vscale x 4 x i1> @cannot_commute_constant_to_rhs_1(
+; CHECK-SAME: <vscale x 4 x i1> [[PG:%.*]], <vscale x 4 x i32> [[A:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpge.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> [[A]])
+; CHECK-NEXT:    ret <vscale x 4 x i1> [[R]]
+;
+  %r = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpge.nxv4i32(<vscale x 4 x i1> %pg, <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> %a)
+  ret <vscale x 4 x i1> %r
+}
+
+; Wide compares have incompatble operand types.
+define <vscale x 4 x i1> @cannot_commute_constant_to_rhs_2(<vscale x 4 x i1> %pg, <vscale x 2 x i64> %a) #0 {
+; CHECK-LABEL: define <vscale x 4 x i1> @cannot_commute_constant_to_rhs_2(
+; CHECK-SAME: <vscale x 4 x i1> [[PG:%.*]], <vscale x 2 x i64> [[A:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.wide.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> splat (i32 303), <vscale x 2 x i64> [[A]])
+; CHECK-NEXT:    ret <vscale x 4 x i1> [[R]]
+;
+  %r = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.wide.nxv4i32(<vscale x 4 x i1> %pg, <vscale x 4 x i32> splat (i32 303), <vscale x 2 x i64> %a)
+  ret <vscale x 4 x i1> %r
+}
+
+; NOTE: Test will be redundant once full simplification is enabled.
+define <vscale x 4 x i1> @do_not_commute_already_constant_rhs(<vscale x 4 x i1> %pg, <vscale x 4 x i32> %a) #0 {
+; CHECK-LABEL: define <vscale x 4 x i1> @do_not_commute_already_constant_rhs(
+; CHECK-SAME: <vscale x 4 x i1> [[PG:%.*]], <vscale x 4 x i32> [[A:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> splat (i32 777))
+; CHECK-NEXT:    ret <vscale x 4 x i1> [[R]]
+;
+  %r = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> %pg, <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> splat (i32 777))
+  ret <vscale x 4 x i1> %r
+}
+
+attributes #0 = { "target-features"="+sve" }

>From 85e524715487db7de25e24e732d2ee57b5b25515 Mon Sep 17 00:00:00 2001
From: Paul Walker <paul.walker at arm.com>
Date: Wed, 22 Jul 2026 12:37:36 +0100
Subject: [PATCH 2/2] [LLVM][InstCombine] Add infrastructure to allow
 simplification of SVE compare intrinsics.

To help unblock another PR I've deliberately restricted the compares
to the minimum required to test all code paths, with the only
simplification being to canonicalise constants to the RHS for
commutable compares.
---
 .../AArch64/AArch64TargetTransformInfo.cpp    | 69 +++++++++++++++++--
 .../AArch64/sve-intrinsic-opts-cmpne.ll       |  3 +-
 .../AArch64/sve-intrinsic-simplify-cmp.ll     |  2 +-
 3 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 960d50fb039f7..0c60be3efdbf1 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -1424,6 +1424,25 @@ struct SVEIntrinsicInfo {
     return *this;
   }
 
+  bool hasCmpPredicate() const {
+    assert(getMatchingIROpode() == Instruction::ICmp &&
+           "Property only available for compares!");
+    return CmpPredicate != CmpInst::BAD_ICMP_PREDICATE;
+  }
+
+  CmpInst::Predicate getCmpPredicate() const {
+    assert(hasCmpPredicate() && "Property not set!");
+    return CmpPredicate;
+  }
+
+  SVEIntrinsicInfo &setCmpPredicate(CmpInst::Predicate Pred) {
+    assert(getMatchingIROpode() == Instruction::ICmp &&
+           "Can only set predicate for compares!");
+    assert(!hasCmpPredicate() && "Cannot set property twice!");
+    CmpPredicate = Pred;
+    return *this;
+  }
+
   //
   // Properties relating to the result of inactive lanes.
   //
@@ -1500,6 +1519,7 @@ struct SVEIntrinsicInfo {
 
   Intrinsic::ID UndefIntrinsic = Intrinsic::not_intrinsic;
   unsigned IROpcode = 0;
+  CmpInst::Predicate CmpPredicate = CmpInst::BAD_ICMP_PREDICATE;
 
   enum PredicationStyle {
     Uninitialized,
@@ -1753,7 +1773,6 @@ static SVEIntrinsicInfo constructSVEIntrinsicInfo(IntrinsicInst &II) {
   case Intrinsic::aarch64_sve_umaxqv:
   case Intrinsic::aarch64_sve_cmpeq:
   case Intrinsic::aarch64_sve_cmpeq_wide:
-  case Intrinsic::aarch64_sve_cmpge:
   case Intrinsic::aarch64_sve_cmpge_wide:
   case Intrinsic::aarch64_sve_cmpgt:
   case Intrinsic::aarch64_sve_cmpgt_wide:
@@ -1765,8 +1784,6 @@ static SVEIntrinsicInfo constructSVEIntrinsicInfo(IntrinsicInst &II) {
   case Intrinsic::aarch64_sve_cmplo_wide:
   case Intrinsic::aarch64_sve_cmpls_wide:
   case Intrinsic::aarch64_sve_cmplt_wide:
-  case Intrinsic::aarch64_sve_cmpne:
-  case Intrinsic::aarch64_sve_cmpne_wide:
   case Intrinsic::aarch64_sve_facge:
   case Intrinsic::aarch64_sve_facgt:
   case Intrinsic::aarch64_sve_fcmpeq:
@@ -1811,6 +1828,16 @@ static SVEIntrinsicInfo constructSVEIntrinsicInfo(IntrinsicInst &II) {
   case Intrinsic::aarch64_sve_ldnt1_gather_uxtw:
     return SVEIntrinsicInfo::defaultZeroingOp();
 
+  case Intrinsic::aarch64_sve_cmpge:
+    return SVEIntrinsicInfo::defaultZeroingOp()
+        .setMatchingIROpcode(Instruction::ICmp)
+        .setCmpPredicate(CmpInst::ICMP_SGE);
+  case Intrinsic::aarch64_sve_cmpne:
+  case Intrinsic::aarch64_sve_cmpne_wide:
+    return SVEIntrinsicInfo::defaultZeroingOp()
+        .setMatchingIROpcode(Instruction::ICmp)
+        .setCmpPredicate(CmpInst::ICMP_NE);
+
   case Intrinsic::aarch64_sve_prf:
   case Intrinsic::aarch64_sve_prfb_gather_index:
   case Intrinsic::aarch64_sve_prfb_gather_scalar_offset:
@@ -1947,6 +1974,28 @@ simplifySVEIntrinsicBinOp(InstCombiner &IC, IntrinsicInst &II,
   return IC.replaceInstUsesWith(II, SimpleII);
 }
 
+static std::optional<Instruction *>
+simplifySVECompares(InstCombiner &IC, IntrinsicInst &II,
+                    const SVEIntrinsicInfo &IInfo) {
+  const unsigned Opc = IInfo.getMatchingIROpode();
+  assert(Opc == Instruction::ICmp && "Expected a compare!");
+
+  Value *LHS = II.getOperand(1);
+  Value *RHS = II.getOperand(2);
+  CmpInst::Predicate CmpPred = IInfo.getCmpPredicate();
+
+  // Canonicalise integer constants to the RHS.
+  if (Opc == Instruction::ICmp && ICmpInst::isCommutative(CmpPred) &&
+      isa<Constant>(LHS) && !isa<Constant>(RHS) &&
+      LHS->getType() == RHS->getType()) {
+    IC.replaceOperand(II, 1, RHS);
+    IC.replaceOperand(II, 2, LHS);
+    return &II;
+  }
+
+  return std::nullopt;
+}
+
 // Use SVE intrinsic info to eliminate redundant operands and/or canonicalise
 // to operations with less strict inactive lane requirements.
 static std::optional<Instruction *>
@@ -1987,11 +2036,21 @@ simplifySVEIntrinsic(InstCombiner &IC, IntrinsicInst &II,
     }
   }
 
+  if (!IInfo.hasMatchingIROpode())
+    return std::nullopt;
+
+  //
   // Operation specific simplifications.
-  if (IInfo.hasMatchingIROpode() &&
-      Instruction::isBinaryOp(IInfo.getMatchingIROpode()))
+  //
+
+  unsigned Opc = IInfo.getMatchingIROpode();
+
+  if (Instruction::isBinaryOp(Opc))
     return simplifySVEIntrinsicBinOp(IC, II, IInfo);
 
+  if (Opc == Instruction::ICmp)
+    return simplifySVECompares(IC, II, IInfo);
+
   return std::nullopt;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
index 11e6e0089293a..d9a5434c35269 100644
--- a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
+++ b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
@@ -275,8 +275,7 @@ define <vscale x 16 x i8> @zext_cmpne_i8(<vscale x 16 x i8> %vec) #0 {
 define <vscale x 16 x i8> @zext_cmpne_zero_lhs_i8(<vscale x 16 x i8> %vec) #0 {
 ; CHECK-LABEL: define <vscale x 16 x i8> @zext_cmpne_zero_lhs_i8(
 ; CHECK-SAME: <vscale x 16 x i8> [[VEC:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> [[VEC]])
-; CHECK-NEXT:    [[ZEXT:%.*]] = zext <vscale x 16 x i1> [[TMP1]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[ZEXT:%.*]] = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i8> [[VEC]], <vscale x 16 x i8> splat (i8 1))
 ; CHECK-NEXT:    ret <vscale x 16 x i8> [[ZEXT]]
 ;
   %cmp = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> %vec)
diff --git a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll
index 656c1fa46b34f..1962fcb535a1f 100644
--- a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll
+++ b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-simplify-cmp.ll
@@ -9,7 +9,7 @@ target triple = "aarch64-unknown-linux-gnu"
 define <vscale x 4 x i1> @commute_constant_to_rhs(<vscale x 4 x i1> %pg, <vscale x 4 x i32> %a) #0 {
 ; CHECK-LABEL: define <vscale x 4 x i1> @commute_constant_to_rhs(
 ; CHECK-SAME: <vscale x 4 x i1> [[PG:%.*]], <vscale x 4 x i32> [[A:%.*]]) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> [[A]])
+; CHECK-NEXT:    [[R:%.*]] = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> [[PG]], <vscale x 4 x i32> [[A]], <vscale x 4 x i32> splat (i32 303))
 ; CHECK-NEXT:    ret <vscale x 4 x i1> [[R]]
 ;
   %r = call <vscale x 4 x i1> @llvm.aarch64.sve.cmpne.nxv4i32(<vscale x 4 x i1> %pg, <vscale x 4 x i32> splat (i32 303), <vscale x 4 x i32> %a)



More information about the llvm-commits mailing list