[flang-commits] [flang] [flang][hlfir] Optimize MINLOC/MAXLOC with equality mask (PR #211722)

via flang-commits flang-commits at lists.llvm.org
Thu Jul 23 22:15:11 PDT 2026


https://github.com/anoopkg6 created https://github.com/llvm/llvm-project/pull/211722

When the mask is an element-wise integer equality comparison against a loop-invariant scalar (e.g. MASK = A == val), tracking the minimum/maximum value throughout the reduction loop is redundant — every unmasked element is identical to val, so the first match is the result regardless of its value.

This is the second part of the optimization enabled by #186916, which relaxed InlineElementals to inline the mask hlfir.elemental at its hlfir.apply site inside the generated reduction loop.

The [coords, minval, isFirst] accumulator is replaced with [coords, isFirst], turning the loop into a short-circuiting find-first locator. The arith.andi %mask_val, %isFirst guard ensures subsequent masked elements are skipped once the first match is recorded.

>From da549bb719ded50d5eaa559db5a948ba5a5b8135 Mon Sep 17 00:00:00 2001
From: "anoop.kumar6 at ibm.com" <anoopk at b35lp63.lnxne.boe>
Date: Tue, 21 Jul 2026 03:14:13 +0200
Subject: [PATCH] [flang][hlfir] Optimize MINLOC/MAXLOC with equality mask
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the mask is an element-wise integer equality comparison against a
loop-invariant scalar (e.g. MASK = A == val), tracking the
minimum/maximum value throughout the reduction loop is redundant —
every unmasked element is identical to val, so the first match is the
result regardless of its value.

This is the second part of the optimization enabled by #186916, which
relaxed InlineElementals to inline the mask hlfir.elemental at its
hlfir.apply site inside the generated reduction loop.

The [coords, minval, isFirst] accumulator is replaced with [coords,
isFirst], turning the loop into a short-circuiting find-first locator.
The arith.andi %mask_val, %isFirst guard ensures subsequent masked
elements are skipped once the first match is recorded.
---
 .../flang/Optimizer/Builder/HLFIRTools.h      |   6 +
 flang/lib/Optimizer/Builder/HLFIRTools.cpp    | 129 +++
 .../Transforms/SimplifyHLFIRIntrinsics.cpp    |  78 ++
 ...fir-intrinsics-minmaxloc-equality-mask.fir | 759 ++++++++++++++++++
 4 files changed, 972 insertions(+)
 create mode 100644 flang/test/HLFIR/simplify-hlfir-intrinsics-minmaxloc-equality-mask.fir

diff --git a/flang/include/flang/Optimizer/Builder/HLFIRTools.h b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
index ce99a37052621..de3e115544533 100644
--- a/flang/include/flang/Optimizer/Builder/HLFIRTools.h
+++ b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
@@ -619,6 +619,12 @@ bool isSimplyContiguous(mlir::Value base, bool checkWhole = true);
 /// that is subject to the WHERE mask control.
 bool isInsideHlfirWhereMaskedExpression(mlir::Region &region);
 
+/// Return true if \p mask is an element-wise equality comparison of
+/// \p searchArray against an invariant scalar (e.g. MASK = A == val).
+/// On success, \p targetVal is set to the invariant scalar operand.
+std::optional<mlir::Value> getEqualityMaskTarget(mlir::Value mask,
+                                                 mlir::Value searchArray);
+
 } // namespace hlfir
 
 #endif // FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H
diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
index f35ed4dd59e12..43b9c530836dc 100644
--- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp
+++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
@@ -1829,3 +1829,132 @@ bool hlfir::isInsideHlfirWhereMaskedExpression(mlir::Region &region) {
   // all other expressions nested under the where must be evaluated masked.
   return !whereOp.getMaskRegion().isAncestor(&region);
 }
+
+static bool isElementalScalarEqualityMask(mlir::Value mask,
+                                          hlfir::ElementalOp &elementalOut,
+                                          mlir::Value &targetVal,
+                                          mlir::Value &arraySide) {
+  if (!mask)
+    return false;
+
+  mlir::Value currentMask = mask;
+  while (auto def = currentMask.getDefiningOp()) {
+    if (!mlir::isa<hlfir::AsExprOp, fir::ConvertOp, hlfir::DeclareOp,
+                   hlfir::CopyInOp>(def))
+      break;
+    currentMask = def->getOperand(0);
+  }
+  // The mask must be produced by an hlfir.elemental.
+  auto elemental = currentMask.getDefiningOp<hlfir::ElementalOp>();
+  if (!elemental)
+    return false;
+
+  mlir::Block &body = elemental.getRegion().front();
+  auto yieldOp = mlir::cast<hlfir::YieldElementOp>(body.getTerminator());
+  mlir::Value val = yieldOp.getElementValue();
+  while (auto conv = val.getDefiningOp<fir::ConvertOp>())
+    val = conv.getOperand();
+
+  // Optimizing only integer equality (arith.cmpi eq).
+  auto cmpOp = val.getDefiningOp<mlir::arith::CmpIOp>();
+  if (!cmpOp || cmpOp.getPredicate() != mlir::arith::CmpIPredicate::eq)
+    return false;
+
+  // Loop-invariance check for structures inside the elemental body.
+  std::function<bool(mlir::Value)> isInvariant = [&](mlir::Value v) -> bool {
+    if (auto arg = mlir::dyn_cast<mlir::BlockArgument>(v))
+      return arg.getOwner()->getParent() != &elemental.getRegion();
+    mlir::Operation *def = v.getDefiningOp();
+    if (!def)
+      return true;
+    if (!elemental.getRegion().isAncestor(def->getParentRegion()))
+      return true;
+    if (!mlir::isa<hlfir::DesignateOp, hlfir::DeclareOp, hlfir::ApplyOp,
+                   fir::LoadOp, fir::ConvertOp, hlfir::AsExprOp>(def))
+      return false;
+    return llvm::all_of(def->getOperands(), isInvariant);
+  };
+
+  mlir::Value lhs = cmpOp.getLhs(), rhs = cmpOp.getRhs();
+  bool lhsInv = isInvariant(lhs), rhsInv = isInvariant(rhs);
+  // The optimization is valid only if exactly one side is invariant (the
+  // target) and the other side is variant (the array element).
+  if (lhsInv == rhsInv)
+    return false;
+
+  elementalOut = elemental;
+  targetVal = lhsInv ? lhs : rhs;
+  arraySide = lhsInv ? rhs : lhs;
+  return true;
+}
+
+std::optional<mlir::Value>
+hlfir::getEqualityMaskTarget(mlir::Value mask, mlir::Value searchArray) {
+  hlfir::ElementalOp elemental;
+  mlir::Value targetVal, arraySide;
+  if (!isElementalScalarEqualityMask(mask, elemental, targetVal, arraySide))
+    return std::nullopt;
+
+  // Find the DesignateOp or ApplyOp that element-accesses the array.
+  mlir::Operation *accessOp = nullptr;
+  mlir::Value v = arraySide;
+  while (v) {
+    mlir::Operation *def = v.getDefiningOp();
+    if (!def)
+      break;
+    if (mlir::isa<hlfir::DesignateOp, hlfir::ApplyOp>(def)) {
+      accessOp = def;
+      break;
+    }
+    if (auto decl = mlir::dyn_cast<hlfir::DeclareOp>(def))
+      v = decl.getMemref();
+    else if (mlir::isa<fir::ConvertOp, hlfir::AsExprOp>(def))
+      v = def->getOperand(0);
+    else if (auto load = mlir::dyn_cast<fir::LoadOp>(def))
+      v = load.getMemref();
+    else
+      break;
+  }
+  if (!accessOp)
+    return std::nullopt;
+
+  mlir::Value accessedArray;
+  mlir::ValueRange accessIndices;
+  if (auto desig = mlir::dyn_cast<hlfir::DesignateOp>(accessOp)) {
+    for (bool isTriplet : desig.getIsTriplet())
+      if (isTriplet)
+        return std::nullopt;
+    accessedArray = desig.getMemref();
+    accessIndices = desig.getIndices();
+  } else {
+    auto apply = mlir::cast<hlfir::ApplyOp>(accessOp);
+    accessedArray = apply.getExpr();
+    accessIndices = apply.getIndices();
+  }
+
+  mlir::Value canonSearch = searchArray;
+  while (canonSearch) {
+    mlir::Operation *def = canonSearch.getDefiningOp();
+    if (!def)
+      break;
+    if (auto decl = mlir::dyn_cast<hlfir::DeclareOp>(def))
+      canonSearch = decl.getMemref();
+    else if (mlir::isa<fir::ConvertOp, hlfir::AsExprOp>(def))
+      canonSearch = def->getOperand(0);
+    else
+      break;
+  }
+
+  if (accessedArray != canonSearch)
+    return std::nullopt;
+
+  // Check the access indices map 1:1 to the elemental loop indices.
+  mlir::Block::BlockArgListType bodyArgs = elemental.getIndices();
+  if (accessIndices.size() != bodyArgs.size())
+    return std::nullopt;
+  for (auto [idx, arg] : llvm::zip(accessIndices, bodyArgs))
+    if (idx != arg)
+      return std::nullopt;
+
+  return targetVal;
+}
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/SimplifyHLFIRIntrinsics.cpp b/flang/lib/Optimizer/HLFIR/Transforms/SimplifyHLFIRIntrinsics.cpp
index 716737bb80ff4..25b87225fb01e 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/SimplifyHLFIRIntrinsics.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/SimplifyHLFIRIntrinsics.cpp
@@ -266,6 +266,11 @@ class ReductionAsElementalConverter {
     return hlfir::Entity{reductionResults[0]};
   }
 
+  /// Overridden by MinMaxlocAsElementalConverter to use the cached result.
+  virtual std::optional<mlir::Value> getEqualityMaskTargetForMask() const {
+    return std::nullopt;
+  }
+
   /// Return mlir::success(), if the operation can be converted.
   /// The default implementation always returns mlir::success().
   /// The derived type may override the default implementation
@@ -529,8 +534,24 @@ class MinMaxlocAsElementalConverter : public ReductionAsElementalConverter {
     return isTotalReduction() ? getSourceRank() : 1;
   }
 
+  std::optional<mlir::Value> getEqualityMaskTarget() const {
+    if (!cachedEqualityTarget.has_value())
+      cachedEqualityTarget = hlfir::getEqualityMaskTarget(
+          this->getMask(), mlir::cast<T>(this->op).getArray());
+    return *cachedEqualityTarget;
+  }
+
+  std::optional<mlir::Value> getEqualityMaskTargetForMask() const final {
+    return getEqualityMaskTarget();
+  }
+
   void
   checkReductions(const llvm::SmallVectorImpl<mlir::Value> &reductions) const {
+    if (getEqualityMaskTarget()) {
+      assert(reductions.size() == getNumCoors() + 1 &&
+             "invalid number of reductions for equality mask MINLOC/MAXLOC");
+      return;
+    }
     if (!useIsFirst())
       assert(reductions.size() == getNumCoors() + 1 &&
              "invalid number of reductions for MINLOC/MAXLOC");
@@ -542,6 +563,8 @@ class MinMaxlocAsElementalConverter : public ReductionAsElementalConverter {
   mlir::Value
   getCurrentMinMax(const llvm::SmallVectorImpl<mlir::Value> &reductions) const {
     checkReductions(reductions);
+    assert(!getEqualityMaskTarget() &&
+           "equality mask reductions have no minmax slot");
     return reductions[getNumCoors()];
   }
 
@@ -572,6 +595,7 @@ class MinMaxlocAsElementalConverter : public ReductionAsElementalConverter {
   // this control into account, though, we need to define what
   // this means exactly.
   [[maybe_unused]] Fortran::common::FPMaxminBehavior fpMaxminBehavior;
+  mutable std::optional<std::optional<mlir::Value>> cachedEqualityTarget;
 };
 
 template <typename T>
@@ -579,6 +603,16 @@ llvm::SmallVector<mlir::Value>
 MinMaxlocAsElementalConverter<T>::genReductionInitValues(
     mlir::ValueRange oneBasedIndices,
     const llvm::SmallVectorImpl<mlir::Value> &extents) {
+  // Equality-mask path only track coordinates and firstHit flag.
+  if (auto targetVal = getEqualityMaskTarget()) {
+    unsigned rank = getNumCoors();
+    mlir::Type resElemTy = getResultElementType();
+    mlir::Value zeroVal = builder.createIntegerConstant(loc, resElemTy, 0);
+    llvm::SmallVector<mlir::Value> result(rank, zeroVal);
+    result.push_back(builder.createBool(loc, true));
+    return result;
+  }
+
   fir::IfOp ifOp;
   if (!useIsFirst() && honorNans()) {
     // Check if we can load the value of the first element in the array
@@ -641,6 +675,23 @@ MinMaxlocAsElementalConverter<T>::reduceOneElement(
     const llvm::SmallVectorImpl<mlir::Value> &currentValue, hlfir::Entity array,
     mlir::ValueRange oneBasedIndices) {
   checkReductions(currentValue);
+
+  if (getEqualityMaskTarget()) {
+    int64_t dim = 1;
+    if (!isTotalReduction()) {
+      auto dimVal = getConstDim();
+      assert(mlir::succeeded(dimVal) &&
+             "partial MINLOC/MAXLOC reduction with invalid DIM");
+      dim = *dimVal;
+    }
+    llvm::SmallVector<mlir::Value> newValues;
+    for (unsigned i = 0; i < getNumCoors(); ++i)
+      newValues.push_back(builder.createConvert(loc, currentValue[i].getType(),
+                                                oneBasedIndices[i + dim - 1]));
+    newValues.push_back(builder.createBool(loc, false));
+    return newValues;
+  }
+
   hlfir::Entity elementValue =
       hlfir::loadElementAt(loc, builder, array, oneBasedIndices);
   mlir::Value cmp = genMinMaxComparison<isMax>(loc, builder, elementValue,
@@ -690,6 +741,30 @@ MinMaxlocAsElementalConverter<T>::reduceOneElement(
 template <typename T>
 hlfir::Entity MinMaxlocAsElementalConverter<T>::genFinalResult(
     const llvm::SmallVectorImpl<mlir::Value> &reductionResults) {
+  // Drop the firstHit bool and pack coordinates into the result array.
+  if (getEqualityMaskTarget()) {
+    if (getResultRank() == 0 || !isTotalReduction()) {
+      assert(getNumCoors() == 1 &&
+             "unexpected coordinate count for scalar result");
+      return hlfir::Entity{reductionResults[0]};
+    }
+
+    unsigned rank = getNumCoors();
+    mlir::Type indexType = builder.getIndexType();
+    mlir::Value tempArray = builder.createTemporary(
+        loc, fir::SequenceType::get(rank, getResultElementType()));
+    for (unsigned i = 0; i < rank; ++i) {
+      mlir::Value coor = reductionResults[i];
+      mlir::Value idx = builder.createIntegerConstant(loc, indexType, i + 1);
+      mlir::Value resultElement =
+          hlfir::getElementAt(loc, builder, hlfir::Entity{tempArray}, {idx});
+      hlfir::AssignOp::create(builder, loc, coor, resultElement);
+    }
+    mlir::Value tempExpr = hlfir::AsExprOp::create(
+        builder, loc, tempArray, builder.createBool(loc, false));
+    return hlfir::Entity{tempExpr};
+  }
+
   // Identification of the final result of MINLOC/MAXLOC:
   //   * If DIM is absent, the result is rank-one array.
   //   * If DIM is present:
@@ -1220,6 +1295,9 @@ mlir::LogicalResult ReductionAsElementalConverter::convert() {
         }
         mlir::Value isUnmasked = fir::ConvertOp::create(
             builder, loc, builder.getI1Type(), maskValue);
+        if (getEqualityMaskTargetForMask())
+          isUnmasked = mlir::arith::AndIOp::create(builder, loc, isUnmasked,
+                                                   reductionValues.back());
         ifOp = fir::IfOp::create(builder, loc, reductionTypes, isUnmasked,
                                  /*withElseRegion=*/true);
         // In the 'else' block return the current reduction value.
diff --git a/flang/test/HLFIR/simplify-hlfir-intrinsics-minmaxloc-equality-mask.fir b/flang/test/HLFIR/simplify-hlfir-intrinsics-minmaxloc-equality-mask.fir
new file mode 100644
index 0000000000000..d09f583808efc
--- /dev/null
+++ b/flang/test/HLFIR/simplify-hlfir-intrinsics-minmaxloc-equality-mask.fir
@@ -0,0 +1,759 @@
+// RUN: fir-opt %s --simplify-hlfir-intrinsics | FileCheck %s
+
+func.func @test_minloc_1d_eq_mask(%input: !fir.box<!fir.array<?xi32>>,
+                                   %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_1d_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+// CHECK-NOT:     arith.select {{.*}} : i32
+
+func.func @test_minloc_2d_eq_mask(%input: !fir.box<!fir.array<?x?xi32>>,
+                                   %val: i32) -> !hlfir.expr<2xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %d1:3 = fir.box_dims %input, %c1 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %d0#1, %d1#1 : (index, index) -> !fir.shape<2>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<?x?x!fir.logical<4>> {
+  ^bb0(%i: index, %j: index):
+    %elem = hlfir.designate %input (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?x?xi32>>, !hlfir.expr<?x?x!fir.logical<4>>) -> !hlfir.expr<2xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x?x!fir.logical<4>>
+  return %res : !hlfir.expr<2xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_2d_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           fir.do_loop {{.*}} iter_args(%{{.*}}, %{{.*}}, %{{.*}}) -> (i32, i32, i1)
+// CHECK:             hlfir.apply
+// CHECK:             arith.andi
+// CHECK:             fir.if {{.*}} -> (i32, i32, i1) {
+// CHECK:               fir.result %{{.*}}, %{{.*}}, %false : i32, i32, i1
+// CHECK-NOT:     arith.cmpi slt
+// CHECK-NOT:     arith.select {{.*}} : i32
+
+func.func @test_maxloc_1d_eq_mask(%input: !fir.box<!fir.array<?xi32>>,
+                                   %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.maxloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_maxloc_1d_eq_mask(
+// CHECK-NOT:     arith.constant -2147483648
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi sgt
+
+// Partial reduction (DIM=1) with equality mask on 2D array — maxloc.
+func.func @test_maxloc_dim1_eq_mask(%input: !fir.box<!fir.array<?x?xi32>>,
+                                     %val: i32) -> !hlfir.expr<?xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %dim = arith.constant 1 : i32
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %d1:3 = fir.box_dims %input, %c1 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %d0#1, %d1#1 : (index, index) -> !fir.shape<2>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<?x?x!fir.logical<4>> {
+  ^bb0(%i: index, %j: index):
+    %elem = hlfir.designate %input (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.maxloc %input dim %dim mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?x?xi32>>, i32, !hlfir.expr<?x?x!fir.logical<4>>) -> !hlfir.expr<?xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x?x!fir.logical<4>>
+  return %res : !hlfir.expr<?xi32>
+}
+// CHECK-LABEL: func.func @test_maxloc_dim1_eq_mask(
+// CHECK-NOT:     arith.constant -2147483648
+// CHECK:         hlfir.elemental
+// CHECK:           fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:             hlfir.apply
+// CHECK:             arith.andi
+// CHECK:             fir.if {{.*}} -> (i32, i1) {
+// CHECK:               fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi sgt
+
+// hlfir.expr input with equality mask.
+func.func @test_minloc_expr_eq_mask(%input: !hlfir.expr<?xi32>,
+                                     %val: i32) -> !hlfir.expr<1xi32> {
+  %0 = hlfir.shape_of %input : (!hlfir.expr<?xi32>) -> !fir.shape<1>
+  %mask = hlfir.elemental %0 unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.apply %input, %i : (!hlfir.expr<?xi32>, index) -> i32
+    %cmp  = arith.cmpi eq, %elem, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!hlfir.expr<?xi32>, !hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_expr_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// 3D total reduction with equality mask.
+func.func @test_minloc_3d_eq_mask(%input: !fir.box<!fir.array<?x?x?xi32>>,
+                                    %val: i32) -> !hlfir.expr<3xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?x?xi32>>, index) -> (index, index, index)
+  %d1:3 = fir.box_dims %input, %c1 : (!fir.box<!fir.array<?x?x?xi32>>, index) -> (index, index, index)
+  %d2:3 = fir.box_dims %input, %c2 : (!fir.box<!fir.array<?x?x?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %d0#1, %d1#1, %d2#1 : (index, index, index) -> !fir.shape<3>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<3>) -> !hlfir.expr<?x?x?x!fir.logical<4>> {
+  ^bb0(%i: index, %j: index, %k: index):
+    %elem = hlfir.designate %input (%i, %j, %k) : (!fir.box<!fir.array<?x?x?xi32>>, index, index, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?x?x?xi32>>, !hlfir.expr<?x?x?x!fir.logical<4>>) -> !hlfir.expr<3xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x?x?x!fir.logical<4>>
+  return %res : !hlfir.expr<3xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_3d_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i32, i1)
+// CHECK:           fir.do_loop {{.*}} iter_args(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) -> (i32, i32, i32, i1)
+// CHECK:             fir.do_loop {{.*}} iter_args(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) -> (i32, i32, i32, i1)
+// CHECK:               hlfir.apply
+// CHECK:               arith.andi
+// CHECK:               fir.if {{.*}} -> (i32, i32, i32, i1) {
+// CHECK:                 fir.result %{{.*}}, %{{.*}}, %{{.*}}, %false : i32, i32, i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Partial reduction (DIM=1) with equality mask on 2D array.
+func.func @test_minloc_dim1_eq_mask(%input: !fir.box<!fir.array<?x?xi32>>,
+                                     %val: i32) -> !hlfir.expr<?xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %dim = arith.constant 1 : i32
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %d1:3 = fir.box_dims %input, %c1 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %d0#1, %d1#1 : (index, index) -> !fir.shape<2>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<?x?x!fir.logical<4>> {
+  ^bb0(%i: index, %j: index):
+    %elem = hlfir.designate %input (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input dim %dim mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?x?xi32>>, i32, !hlfir.expr<?x?x!fir.logical<4>>) -> !hlfir.expr<?xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x?x!fir.logical<4>>
+  return %res : !hlfir.expr<?xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_dim1_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         hlfir.elemental
+// CHECK:           fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:             hlfir.apply
+// CHECK:             arith.andi
+// CHECK:             fir.if {{.*}} -> (i32, i1) {
+// CHECK:               fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Partial reduction (DIM=2) with equality mask on 2D array.
+func.func @test_minloc_dim2_eq_mask(%input: !fir.box<!fir.array<?x?xi32>>,
+                                     %val: i32) -> !hlfir.expr<?xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %dim = arith.constant 2 : i32
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %d1:3 = fir.box_dims %input, %c1 : (!fir.box<!fir.array<?x?xi32>>, index) -> (index, index, index)
+  %shape = fir.shape %d0#1, %d1#1 : (index, index) -> !fir.shape<2>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<2>) -> !hlfir.expr<?x?x!fir.logical<4>> {
+  ^bb0(%i: index, %j: index):
+    %elem = hlfir.designate %input (%i, %j) : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input dim %dim mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?x?xi32>>, i32, !hlfir.expr<?x?x!fir.logical<4>>) -> !hlfir.expr<?xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x?x!fir.logical<4>>
+  return %res : !hlfir.expr<?xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_dim2_eq_mask(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         hlfir.elemental
+// CHECK:           fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:             hlfir.apply
+// CHECK:             arith.andi
+// CHECK:             fir.if {{.*}} -> (i32, i1) {
+// CHECK:               fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// 1D expr input, DIM=1, scalar i32 result.
+func.func @test_minloc_1d_dim_expr(%input: !hlfir.expr<?xi32>,
+                                    %val: i32) -> i32 {
+  %dim = arith.constant 1 : i32
+  %0 = hlfir.shape_of %input : (!hlfir.expr<?xi32>) -> !fir.shape<1>
+  %mask = hlfir.elemental %0 unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.apply %input, %i : (!hlfir.expr<?xi32>, index) -> i32
+    %cmp  = arith.cmpi eq, %elem, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input dim %dim mask %mask {fastmath = #arith.fastmath<contract>} : (!hlfir.expr<?xi32>, i32, !hlfir.expr<?x!fir.logical<4>>) -> i32
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : i32
+}
+// CHECK-LABEL: func.func @test_minloc_1d_dim_expr(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// 1D box input, i16 result type.
+func.func @test_minloc_1d_i16(%input: !fir.box<!fir.array<?xi16>>,
+                                %val: i16) -> !hlfir.expr<1xi16> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi16>>, index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi16>>, index) -> !fir.ref<i16>
+    %ld   = fir.load %elem : !fir.ref<i16>
+    %cmp  = arith.cmpi eq, %ld, %val : i16
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?xi16>>, !hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<1xi16>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi16>
+}
+// CHECK-LABEL: func.func @test_minloc_1d_i16(
+// CHECK-NOT:     arith.constant 32767
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i16, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i16, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i16, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// 1D box input, i64 result type.
+func.func @test_minloc_1d_i64(%input: !fir.box<!fir.array<?xi64>>,
+                                %val: i64) -> !hlfir.expr<1xi64> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi64>>, index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi64>>, index) -> !fir.ref<i64>
+    %ld   = fir.load %elem : !fir.ref<i64>
+    %cmp  = arith.cmpi eq, %ld, %val : i64
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>} : (!fir.box<!fir.array<?xi64>>, !hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<1xi64>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi64>
+}
+// CHECK-LABEL: func.func @test_minloc_1d_i64(
+// CHECK-NOT:     arith.constant 9223372036854775807
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i64, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i64, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i64, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Invariant target on lhs: cmpi eq %val, %ld (swapped operands).
+func.func @test_minloc_val_lhs(%input: !fir.box<!fir.array<?xi32>>,
+                                %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %val, %ld : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_val_lhs(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Constant as the invariant target.
+func.func @test_minloc_const_target(
+    %input: !fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %c42 = arith.constant 42 : i32
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %c42 : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_const_target(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Mask elemental wrapped in fir.convert.
+func.func @test_minloc_mask_converted(%input: !fir.box<!fir.array<?xi32>>,
+                                       %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %wrapped = fir.convert %mask :
+      (!hlfir.expr<?x!fir.logical<4>>) -> !hlfir.expr<?x!fir.logical<4>>
+  %res = hlfir.minloc %input mask %wrapped {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_mask_converted(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// No mask operand, init from first element.
+func.func @test_minloc_no_mask(
+    %input: !fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32> {
+  %res = hlfir.minloc %input {fastmath = #arith.fastmath<contract>} :
+      (!fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_no_mask(
+// CHECK-NOT:     arith.constant true
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}}, %{{.*}}) -> (i32, i32)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.select
+
+// Elemental body yields a constant logical, no comparison op.
+func.func @test_minloc_const_logical_mask(
+    %input: !fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %true = arith.constant true
+    %conv = fir.convert %true : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_const_logical_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Following checks for isEqualityMask.
+
+// Mask is a box array, not an hlfir.elemental.
+func.func @test_minloc_mask_box(%input: !fir.box<!fir.array<?xi32>>,
+    %mask: !fir.box<!fir.array<?x!fir.logical<4>>>) -> !hlfir.expr<1xi32> {
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !fir.box<!fir.array<?x!fir.logical<4>>>) ->
+        !hlfir.expr<1xi32>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_mask_box(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Elemental yields arith.cmpf, not arith.cmpi eq.
+func.func @test_minloc_float_mask(%input: !fir.box<!fir.array<?xf32>>,
+                                   %val: f32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xf32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xf32>>,
+        index) -> !fir.ref<f32>
+    %ld   = fir.load %elem : !fir.ref<f32>
+    %cmp  = arith.cmpf oeq, %ld, %val : f32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xf32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_float_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, f32, i1)
+// CHECK:           arith.cmpf olt
+
+// Elemental yields arith.cmpi slt, not eq.
+func.func @test_minloc_cmpi_slt_mask(%input: !fir.box<!fir.array<?xi32>>,
+                                      %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi slt, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_cmpi_slt_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Mask compares a different array, not the search array.
+func.func @test_minloc_different_array_mask(
+    %input: !fir.box<!fir.array<?xi32>>,
+    %other: !fir.box<!fir.array<?xi32>>,
+    %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %other, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %other (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_different_array_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Both cmpi eq operands are loop-invariant scalars.
+func.func @test_minloc_both_invariant_mask(
+    %input: !fir.box<!fir.array<?xi32>>,
+    %a: i32, %b: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %cmp  = arith.cmpi eq, %a, %b : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_both_invariant_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Both cmpi eq operands are variant (two array element loads).
+func.func @test_minloc_both_variant_mask(
+    %input: !fir.box<!fir.array<?xi32>>,
+    %other: !fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %e1   = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld1  = fir.load %e1 : !fir.ref<i32>
+    %e2   = hlfir.designate %other (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld2  = fir.load %e2 : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld1, %ld2 : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_both_variant_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Mask compares a different slice of the same array.
+func.func @test_minloc_different_slice_mask(
+    %input: !fir.box<!fir.array<?x?xi32>>,
+    %val: i32) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %d0:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?x?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %d0#1 : (index) -> !fir.shape<1>
+  %col1 = hlfir.designate %input (%c1:%d0#1:%c1, %c1)  shape %shape :
+      (!fir.box<!fir.array<?x?xi32>>, index, index, index, index,
+       !fir.shape<1>) -> !fir.box<!fir.array<?xi32>>
+  %col2 = hlfir.designate %input (%c1:%d0#1:%c1, %c2)  shape %shape :
+      (!fir.box<!fir.array<?x?xi32>>, index, index, index, index,
+       !fir.shape<1>) -> !fir.box<!fir.array<?xi32>>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %elem = hlfir.designate %col1 (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %val : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %col2 mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_different_slice_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select
+
+// Invariant target from a struct field inside the elemental body.
+func.func @test_minloc_struct_field_target(
+    %input: !fir.box<!fir.array<?xi32>>,
+    %config: !fir.ref<!fir.type<cfg{threshold:i32}>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %fld  = hlfir.designate %config {"threshold"} :
+        (!fir.ref<!fir.type<cfg{threshold:i32}>>) -> !fir.ref<i32>
+    %tval = fir.load %fld : !fir.ref<i32>
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %tval : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_struct_field_target(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Invariant target from a constant-indexed global array element.
+func.func @test_minloc_global_elem_target(
+    %input: !fir.box<!fir.array<?xi32>>,
+    %globals: !fir.box<!fir.array<4xi32>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %gptr = hlfir.designate %globals (%c1) : (!fir.box<!fir.array<4xi32>>,
+        index) -> !fir.ref<i32>
+    %gval = fir.load %gptr : !fir.ref<i32>
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %gval : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_global_elem_target(
+// CHECK-NOT:     arith.constant 2147483647
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i1)
+// CHECK:           hlfir.apply
+// CHECK:           arith.andi
+// CHECK:           fir.if {{.*}} -> (i32, i1) {
+// CHECK:             fir.result %{{.*}}, %false : i32, i1
+// CHECK-NOT:     arith.cmpi slt
+
+// Mask uses A(%i+1) — shifted index is variant, optimization must not happen.
+func.func @test_minloc_shifted_index_mask(
+    %input: !fir.box<!fir.array<?xi32>>) -> !hlfir.expr<1xi32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %dims:3 = fir.box_dims %input, %c0 : (!fir.box<!fir.array<?xi32>>,
+      index) -> (index, index, index)
+  %shape = fir.shape %dims#1 : (index) -> !fir.shape<1>
+  %mask = hlfir.elemental %shape unordered : (!fir.shape<1>) ->
+      !hlfir.expr<?x!fir.logical<4>> {
+  ^bb0(%i: index):
+    %ip1  = arith.addi %i, %c1 : index
+    %nptr = hlfir.designate %input (%ip1) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %nval = fir.load %nptr : !fir.ref<i32>
+    %elem = hlfir.designate %input (%i) : (!fir.box<!fir.array<?xi32>>,
+        index) -> !fir.ref<i32>
+    %ld   = fir.load %elem : !fir.ref<i32>
+    %cmp  = arith.cmpi eq, %ld, %nval : i32
+    %conv = fir.convert %cmp : (i1) -> !fir.logical<4>
+    hlfir.yield_element %conv : !fir.logical<4>
+  }
+  %res = hlfir.minloc %input mask %mask {fastmath = #arith.fastmath<contract>}
+      : (!fir.box<!fir.array<?xi32>>, !hlfir.expr<?x!fir.logical<4>>) ->
+        !hlfir.expr<1xi32>
+  hlfir.destroy %mask : !hlfir.expr<?x!fir.logical<4>>
+  return %res : !hlfir.expr<1xi32>
+}
+// CHECK-LABEL: func.func @test_minloc_shifted_index_mask(
+// CHECK:         fir.do_loop {{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %{{.*}}, %{{.*}} = %true) -> (i32, i32, i1)
+// CHECK:           arith.cmpi slt
+// CHECK:           arith.ori
+// CHECK:           arith.select



More information about the flang-commits mailing list