[llvm] [AArch64][InstCombine] Fold zext cmpne umin boolean trees (PR #209234)

Matthew Devereau via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 02:29:50 PDT 2026


https://github.com/MDevereau updated https://github.com/llvm/llvm-project/pull/209234

>From 2723e7b1ba2ef5850f6a558aba250b93b8beebba Mon Sep 17 00:00:00 2001
From: Matt Devereau <matthew.devereau at arm.com>
Date: Wed, 8 Jul 2026 16:00:59 +0000
Subject: [PATCH 1/3] [AArch64][InstCombine] Fold zext cmpne umin boolean trees

Fold an all-active umin tree of zext(cmpne(0, umin)) nodes:

  zext(cmpne(0, umin(zext(cmpne(0, umin(%a, %b)))
                     zext(cmpne(0, umin(%c, %d))))))
  ->
  zext(cmpne(0, umin(umin(%a, %b),
                     umin(%c, %d))))
---
 .../AArch64/AArch64TargetTransformInfo.cpp    |  66 ++++++++
 .../AArch64/sve-intrinsic-opts-cmpne.ll       | 154 ++++++++++++++++++
 2 files changed, 220 insertions(+)

diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 9e1a0960617a5..024f9efaf18f2 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -2205,6 +2205,69 @@ static std::optional<Instruction *> instCombineZExtSVECmpNE(InstCombiner &IC,
   return std::nullopt;
 }
 
+static std::optional<Instruction *> isZextedCmpneUMin(Value *V) {
+  Instruction *CmpNE;
+  if (!match(V, m_ZExt(m_Instruction(CmpNE))))
+    return std::nullopt;
+
+  Value *Pg;
+  Instruction *UMin;
+  if (!match(CmpNE, m_Intrinsic<Intrinsic::aarch64_sve_cmpne>(
+                        m_Value(Pg), m_Zero(), m_Instruction(UMin))) ||
+      !isAllActivePredicate(Pg) || V->getType() != UMin->getType())
+    return std::nullopt;
+
+  Value *UMinPg;
+  if (!match(UMin, m_Intrinsic<Intrinsic::aarch64_sve_umin_u>(
+                       m_Value(UMinPg), m_Value(), m_Value())) ||
+      !isAllActivePredicate(UMinPg))
+    return std::nullopt;
+
+  return UMin;
+}
+
+// InstCombine:
+// zext(cmpne(0, umin(zext(cmpne(0, umin(%a, %b)))
+//                    zext(cmpne(0, umin(%c, %d))))))
+// ->
+// zext(cmpne(0, umin(umin(%a, %b),
+//                    umin(%c, %d))))
+//
+// To remove redundant ZExt and CmpNE intrinsics.
+static std::optional<Instruction *>
+instCombineZExtCmpNEUMinTree(InstCombiner &IC, IntrinsicInst &II) {
+  Value *Pg = II.getOperand(0);
+  if (!isAllActivePredicate(Pg) || !II.hasOneUse())
+    return std::nullopt;
+
+  auto ZExt = cast<Instruction>(*II.user_begin());
+  auto _UMin = isZextedCmpneUMin(ZExt);
+  if (!_UMin)
+    return std::nullopt;
+  Instruction *UMin = *_UMin;
+  // Check for outer UMin single use to avoid generating an extra UMin
+  if (!UMin->hasOneUse())
+    return std::nullopt;
+
+  auto LhsUMin = isZextedCmpneUMin(UMin->getOperand(1));
+  auto RhsUMin = isZextedCmpneUMin(UMin->getOperand(2));
+  if (!LhsUMin || !RhsUMin)
+    return std::nullopt;
+
+  IC.Builder.SetInsertPoint(ZExt);
+  Type *Ty = UMin->getType();
+  Value *NewUMin = IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_umin_u, Ty,
+                                              {Pg, *LhsUMin, *RhsUMin});
+  Value *NewCmpne =
+      IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_cmpne, Ty,
+                                 {Pg, ConstantInt::getNullValue(Ty), NewUMin});
+  Value *NewZext = IC.Builder.CreateZExt(NewCmpne, Ty);
+
+  IC.replaceInstUsesWith(*ZExt, NewZext);
+  IC.eraseInstFromFunction(*ZExt);
+  return &II;
+}
+
 static std::optional<Instruction *> instCombineSVECmpNE(InstCombiner &IC,
                                                         IntrinsicInst &II) {
   LLVMContext &Ctx = II.getContext();
@@ -2215,6 +2278,9 @@ static std::optional<Instruction *> instCombineSVECmpNE(InstCombiner &IC,
   if (auto Res = instCombineZExtSVECmpNE(IC, II))
     return Res;
 
+  if (auto Res = instCombineZExtCmpNEUMinTree(IC, II))
+    return Res;
+
   if (!isAllActivePredicate(II.getArgOperand(0)))
     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..a434ad086d891 100644
--- a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
+++ b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
@@ -573,6 +573,160 @@ define <vscale x 16 x i1> @dupq_b_idx(i64 %idx) #0 {
   ret <vscale x 16 x i1> %5
 }
 
+define <vscale x 16 x i8> @logical_bool_tree_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[ABCD:%.*]] = 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> [[AB]], <vscale x 16 x i8> [[CD]])
+; CHECK-NEXT:    [[ABCD_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> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    ret <vscale x 16 x i8> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = 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> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.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> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i8>
+  ret <vscale x 16 x i8> %abcd.bool
+}
+
+define <vscale x 16 x i8> @logical_bool_tree_outer_umin_extra_use_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_outer_umin_extra_use_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[AB_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> [[AB]])
+; CHECK-NEXT:    [[AB_BOOL:%.*]] = zext <vscale x 16 x i1> [[AB_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[CD_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> [[CD]])
+; CHECK-NEXT:    [[CD_BOOL:%.*]] = zext <vscale x 16 x i1> [[CD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[TMP1:%.*]] = 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> [[AB_BOOL]], <vscale x 16 x i8> [[CD_BOOL]])
+; CHECK-NEXT:    [[ABCD_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> [[TMP1]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    call void (...) @llvm.fake.use(<vscale x 16 x i8> [[TMP1]])
+; CHECK-NEXT:    ret <vscale x 16 x i8> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = 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> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.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> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i8>
+  call void (...) @llvm.fake.use(<vscale x 16 x i8> %abcd)
+  ret <vscale x 16 x i8> %abcd.bool
+}
+
+define <vscale x 16 x i8> @logical_bool_tree_nonzero_outer_cmp_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d, <vscale x 16 x i8> %mask) #0 {
+; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_nonzero_outer_cmp_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]], <vscale x 16 x i8> [[MASK:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[AB_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> [[AB]])
+; CHECK-NEXT:    [[AB_BOOL:%.*]] = zext <vscale x 16 x i1> [[AB_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[CD_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> [[CD]])
+; CHECK-NEXT:    [[CD_BOOL:%.*]] = zext <vscale x 16 x i1> [[CD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[ABCD:%.*]] = 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> [[AB_BOOL]], <vscale x 16 x i8> [[CD_BOOL]])
+; CHECK-NEXT:    [[ABCD_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> [[MASK]], <vscale x 16 x i8> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    ret <vscale x 16 x i8> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = 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> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.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> %mask, <vscale x 16 x i8> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i8>
+  ret <vscale x 16 x i8> %abcd.bool
+}
+
+define <vscale x 16 x i8> @logical_bool_tree_inactive_predicate_i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_inactive_predicate_i8(
+; CHECK-SAME: <vscale x 16 x i1> [[PG:%.*]], <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[AB_CMP:%.*]] = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> [[AB]])
+; CHECK-NEXT:    [[AB_BOOL:%.*]] = zext <vscale x 16 x i1> [[AB_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[CD:%.*]] = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[CD_CMP:%.*]] = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> [[CD]])
+; CHECK-NEXT:    [[CD_BOOL:%.*]] = zext <vscale x 16 x i1> [[CD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[ABCD:%.*]] = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> [[AB_BOOL]], <vscale x 16 x i8> [[CD_BOOL]])
+; CHECK-NEXT:    [[ABCD_CMP:%.*]] = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> [[PG]], <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    ret <vscale x 16 x i8> [[ABCD_BOOL]]
+;
+  %ab = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> %a, <vscale x 16 x i8> %b)
+  %ab.cmp = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d)
+  %cd.cmp = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = call <vscale x 16 x i8> @llvm.aarch64.sve.umin.u.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.cmp = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i8(<vscale x 16 x i1> %pg, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i8>
+  ret <vscale x 16 x i8> %abcd.bool
+}
+
+define <vscale x 16 x i16> @logical_bool_tree_wide_final_zext_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i16> @logical_bool_tree_wide_final_zext_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[AB_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> [[AB]])
+; CHECK-NEXT:    [[AB_BOOL:%.*]] = zext <vscale x 16 x i1> [[AB_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[CD_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> [[CD]])
+; CHECK-NEXT:    [[CD_BOOL:%.*]] = zext <vscale x 16 x i1> [[CD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    [[ABCD:%.*]] = 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> [[AB_BOOL]], <vscale x 16 x i8> [[CD_BOOL]])
+; CHECK-NEXT:    [[ABCD_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> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i16>
+; CHECK-NEXT:    ret <vscale x 16 x i16> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = 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> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.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> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i16>
+  ret <vscale x 16 x i16> %abcd.bool
+}
+
+define <vscale x 16 x i16> @logical_bool_tree_wide_inner_zext_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i16> @logical_bool_tree_wide_inner_zext_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[AB_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> [[AB]])
+; CHECK-NEXT:    [[AB_BOOL:%.*]] = zext <vscale x 16 x i1> [[AB_CMP]] to <vscale x 16 x i16>
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[CD_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> [[CD]])
+; CHECK-NEXT:    [[CD_BOOL:%.*]] = zext <vscale x 16 x i1> [[CD_CMP]] to <vscale x 16 x i16>
+; CHECK-NEXT:    [[ABCD:%.*]] = call <vscale x 16 x i16> @llvm.aarch64.sve.umin.u.nxv16i16(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i16> [[AB_BOOL]], <vscale x 16 x i16> [[CD_BOOL]])
+; CHECK-NEXT:    [[ABCD_CMP:%.*]] = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i16(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i16> zeroinitializer, <vscale x 16 x i16> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i16>
+; CHECK-NEXT:    ret <vscale x 16 x i16> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i16>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i16>
+  %abcd = call <vscale x 16 x i16> @llvm.aarch64.sve.umin.u.nxv16i16(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i16> %ab.bool, <vscale x 16 x i16> %cd.bool)
+  %abcd.cmp = call <vscale x 16 x i1> @llvm.aarch64.sve.cmpne.nxv16i16(<vscale x 16 x i1> splat (i1 true), <vscale x 16 x i16> zeroinitializer, <vscale x 16 x i16> %abcd)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i16>
+  ret <vscale x 16 x i16> %abcd.bool
+}
+
 declare <vscale x 2 x i1> @llvm.aarch64.sve.ptrue.nxv2i1(i32)
 
 declare <vscale x 16 x i8> @llvm.vector.insert.nxv16i8.v16i8(<vscale x 16 x i8>, <16 x i8>, i64)

>From fc74ead324e8ac0281db5c09dc4f2dca3e90ed05 Mon Sep 17 00:00:00 2001
From: Matt Devereau <matthew.devereau at arm.com>
Date: Mon, 20 Jul 2026 13:39:17 +0000
Subject: [PATCH 2/3] Add commutativity for cmpne

---
 .../AArch64/AArch64TargetTransformInfo.cpp    | 10 +++++----
 .../AArch64/sve-intrinsic-opts-cmpne.ll       | 22 +++++++++++++++++++
 2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 024f9efaf18f2..23b190ccc3df1 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -2212,8 +2212,10 @@ static std::optional<Instruction *> isZextedCmpneUMin(Value *V) {
 
   Value *Pg;
   Instruction *UMin;
-  if (!match(CmpNE, m_Intrinsic<Intrinsic::aarch64_sve_cmpne>(
-                        m_Value(Pg), m_Zero(), m_Instruction(UMin))) ||
+  if ((!match(CmpNE, m_Intrinsic<Intrinsic::aarch64_sve_cmpne>(
+                         m_Value(Pg), m_Zero(), m_Instruction(UMin))) &&
+       !match(CmpNE, m_Intrinsic<Intrinsic::aarch64_sve_cmpne>(
+                         m_Value(Pg), m_Instruction(UMin), m_Zero()))) ||
       !isAllActivePredicate(Pg) || V->getType() != UMin->getType())
     return std::nullopt;
 
@@ -2275,10 +2277,10 @@ static std::optional<Instruction *> instCombineSVECmpNE(InstCombiner &IC,
   if (auto Res = instCombineXorSVECmpCC(IC, II))
     return Res;
 
-  if (auto Res = instCombineZExtSVECmpNE(IC, II))
+  if (auto Res = instCombineZExtCmpNEUMinTree(IC, II))
     return Res;
 
-  if (auto Res = instCombineZExtCmpNEUMinTree(IC, II))
+  if (auto Res = instCombineZExtSVECmpNE(IC, II))
     return Res;
 
   if (!isAllActivePredicate(II.getArgOperand(0)))
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 a434ad086d891..40bee22f9c263 100644
--- a/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
+++ b/llvm/test/Transforms/InstCombine/AArch64/sve-intrinsic-opts-cmpne.ll
@@ -595,6 +595,28 @@ define <vscale x 16 x i8> @logical_bool_tree_i8(<vscale x 16 x i8> %a, <vscale x
   ret <vscale x 16 x i8> %abcd.bool
 }
 
+define <vscale x 16 x i8> @logical_bool_tree_commuted_outer_cmp_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
+; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_commuted_outer_cmp_i8(
+; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[AB:%.*]] = 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> [[A]], <vscale x 16 x i8> [[B]])
+; CHECK-NEXT:    [[CD:%.*]] = 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> [[C]], <vscale x 16 x i8> [[D]])
+; CHECK-NEXT:    [[ABCD:%.*]] = 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> [[AB]], <vscale x 16 x i8> [[CD]])
+; CHECK-NEXT:    [[ABCD_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> [[ABCD]])
+; CHECK-NEXT:    [[ABCD_BOOL:%.*]] = zext <vscale x 16 x i1> [[ABCD_CMP]] to <vscale x 16 x i8>
+; CHECK-NEXT:    ret <vscale x 16 x i8> [[ABCD_BOOL]]
+;
+  %ab = 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> %a, <vscale x 16 x i8> %b)
+  %ab.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> %ab)
+  %ab.bool = zext <vscale x 16 x i1> %ab.cmp to <vscale x 16 x i8>
+  %cd = 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> %c, <vscale x 16 x i8> %d)
+  %cd.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> %cd)
+  %cd.bool = zext <vscale x 16 x i1> %cd.cmp to <vscale x 16 x i8>
+  %abcd = 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> %ab.bool, <vscale x 16 x i8> %cd.bool)
+  %abcd.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> %abcd, <vscale x 16 x i8> zeroinitializer)
+  %abcd.bool = zext <vscale x 16 x i1> %abcd.cmp to <vscale x 16 x i8>
+  ret <vscale x 16 x i8> %abcd.bool
+}
+
 define <vscale x 16 x i8> @logical_bool_tree_outer_umin_extra_use_i8(<vscale x 16 x i8> %a, <vscale x 16 x i8> %b, <vscale x 16 x i8> %c, <vscale x 16 x i8> %d) #0 {
 ; CHECK-LABEL: define <vscale x 16 x i8> @logical_bool_tree_outer_umin_extra_use_i8(
 ; CHECK-SAME: <vscale x 16 x i8> [[A:%.*]], <vscale x 16 x i8> [[B:%.*]], <vscale x 16 x i8> [[C:%.*]], <vscale x 16 x i8> [[D:%.*]]) #[[ATTR0]] {

>From 40e723b5d61d396b1b0b4eab81641f02c4feb412 Mon Sep 17 00:00:00 2001
From: Matt Devereau <matthew.devereau at arm.com>
Date: Wed, 22 Jul 2026 09:16:46 +0000
Subject: [PATCH 3/3] remove std::optional

---
 .../AArch64/AArch64TargetTransformInfo.cpp    | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 23b190ccc3df1..d3c61a8da9b32 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -2205,10 +2205,10 @@ static std::optional<Instruction *> instCombineZExtSVECmpNE(InstCombiner &IC,
   return std::nullopt;
 }
 
-static std::optional<Instruction *> isZextedCmpneUMin(Value *V) {
+static Instruction *getZextedCmpneUMin(Value *V) {
   Instruction *CmpNE;
   if (!match(V, m_ZExt(m_Instruction(CmpNE))))
-    return std::nullopt;
+    return nullptr;
 
   Value *Pg;
   Instruction *UMin;
@@ -2217,13 +2217,13 @@ static std::optional<Instruction *> isZextedCmpneUMin(Value *V) {
        !match(CmpNE, m_Intrinsic<Intrinsic::aarch64_sve_cmpne>(
                          m_Value(Pg), m_Instruction(UMin), m_Zero()))) ||
       !isAllActivePredicate(Pg) || V->getType() != UMin->getType())
-    return std::nullopt;
+    return nullptr;
 
   Value *UMinPg;
   if (!match(UMin, m_Intrinsic<Intrinsic::aarch64_sve_umin_u>(
                        m_Value(UMinPg), m_Value(), m_Value())) ||
       !isAllActivePredicate(UMinPg))
-    return std::nullopt;
+    return nullptr;
 
   return UMin;
 }
@@ -2243,23 +2243,20 @@ instCombineZExtCmpNEUMinTree(InstCombiner &IC, IntrinsicInst &II) {
     return std::nullopt;
 
   auto ZExt = cast<Instruction>(*II.user_begin());
-  auto _UMin = isZextedCmpneUMin(ZExt);
-  if (!_UMin)
-    return std::nullopt;
-  Instruction *UMin = *_UMin;
-  // Check for outer UMin single use to avoid generating an extra UMin
-  if (!UMin->hasOneUse())
+  Instruction *UMin = getZextedCmpneUMin(ZExt);
+  // Avoid generating an extra UMin
+  if (!UMin || !UMin->hasOneUse())
     return std::nullopt;
 
-  auto LhsUMin = isZextedCmpneUMin(UMin->getOperand(1));
-  auto RhsUMin = isZextedCmpneUMin(UMin->getOperand(2));
+  Instruction *LhsUMin = getZextedCmpneUMin(UMin->getOperand(1));
+  Instruction *RhsUMin = getZextedCmpneUMin(UMin->getOperand(2));
   if (!LhsUMin || !RhsUMin)
     return std::nullopt;
 
   IC.Builder.SetInsertPoint(ZExt);
   Type *Ty = UMin->getType();
   Value *NewUMin = IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_umin_u, Ty,
-                                              {Pg, *LhsUMin, *RhsUMin});
+                                              {Pg, LhsUMin, RhsUMin});
   Value *NewCmpne =
       IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_cmpne, Ty,
                                  {Pg, ConstantInt::getNullValue(Ty), NewUMin});



More information about the llvm-commits mailing list