Skip to content

Commit a21f2e4

Browse files
committed
[Flang] Add a HLFIR Minloc intrinsic
The adds a hlfir minloc intrinsic, similar to the minval intrinsic already added, to help in the lowering of minloc. The idea is to later add maxloc too, and from there add a simplification for producing minloc with inlined elemental and hopefully less temporaries.
1 parent b0f560b commit a21f2e4

File tree

10 files changed

+1260
-26
lines changed

10 files changed

+1260
-26
lines changed

flang/include/flang/Optimizer/HLFIR/HLFIROps.td

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,32 @@ def hlfir_MinvalOp : hlfir_Op<"minval", [AttrSizedOperandSegments,
458458
let hasVerifier = 1;
459459
}
460460

461+
def hlfir_MinlocOp : hlfir_Op<"minloc", [AttrSizedOperandSegments,
462+
DeclareOpInterfaceMethods<ArithFastMathInterface>,
463+
DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
464+
let summary = "MINLOC transformational intrinsic";
465+
let description = [{
466+
Minlocs of an array.
467+
}];
468+
469+
let arguments = (ins
470+
AnyFortranArrayObject:$array,
471+
Optional<AnyIntegerType>:$dim,
472+
Optional<AnyFortranLogicalOrI1ArrayObject>:$mask,
473+
Optional<Type<AnyLogicalLike.predicate>>:$back,
474+
DefaultValuedAttr<Arith_FastMathAttr,
475+
"::mlir::arith::FastMathFlags::none">:$fastmath
476+
);
477+
478+
let results = (outs AnyFortranValue);
479+
480+
let assemblyFormat = [{
481+
$array (`dim` $dim^)? (`mask` $mask^)? (`back` $back^)? attr-dict `:` functional-type(operands, results)
482+
}];
483+
484+
let hasVerifier = 1;
485+
}
486+
461487
def hlfir_ProductOp : hlfir_Op<"product", [AttrSizedOperandSegments,
462488
DeclareOpInterfaceMethods<ArithFastMathInterface>,
463489
DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {

flang/lib/Lower/HlfirIntrinsics.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ using HlfirMinvalLowering = HlfirReductionIntrinsic<hlfir::MinvalOp, true>;
9393
using HlfirAnyLowering = HlfirReductionIntrinsic<hlfir::AnyOp, false>;
9494
using HlfirAllLowering = HlfirReductionIntrinsic<hlfir::AllOp, false>;
9595

96+
template <typename OP>
97+
class HlfirMinMaxLocIntrinsic : public HlfirTransformationalIntrinsic {
98+
public:
99+
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
100+
101+
protected:
102+
mlir::Value
103+
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
104+
const fir::IntrinsicArgumentLoweringRules *argLowering,
105+
mlir::Type stmtResultType) override;
106+
};
107+
using HlfirMinlocLowering = HlfirMinMaxLocIntrinsic<hlfir::MinlocOp>;
108+
96109
template <typename OP>
97110
class HlfirProductIntrinsic : public HlfirTransformationalIntrinsic {
98111
public:
@@ -180,6 +193,31 @@ mlir::Value HlfirTransformationalIntrinsic::loadBoxAddress(
180193
return boxOrAbsent;
181194
}
182195

196+
static mlir::Value loadOptionalValue(
197+
mlir::Location loc, fir::FirOpBuilder &builder,
198+
const std::optional<Fortran::lower::PreparedActualArgument> &arg,
199+
hlfir::Entity actual) {
200+
if (!arg->handleDynamicOptional())
201+
return hlfir::loadTrivialScalar(loc, builder, actual);
202+
203+
mlir::Value isPresent = arg->getIsPresent();
204+
mlir::Type eleType = hlfir::getFortranElementType(actual.getType());
205+
return builder
206+
.genIfOp(loc, {eleType}, isPresent,
207+
/*withElseRegion=*/true)
208+
.genThen([&]() {
209+
assert(actual.isScalar() && fir::isa_trivial(eleType) &&
210+
"must be a numerical or logical scalar");
211+
hlfir::Entity val = hlfir::loadTrivialScalar(loc, builder, actual);
212+
builder.create<fir::ResultOp>(loc, val);
213+
})
214+
.genElse([&]() {
215+
mlir::Value zero = fir::factory::createZeroValue(builder, loc, eleType);
216+
builder.create<fir::ResultOp>(loc, zero);
217+
})
218+
.getResults()[0];
219+
}
220+
183221
llvm::SmallVector<mlir::Value> HlfirTransformationalIntrinsic::getOperandVector(
184222
const Fortran::lower::PreparedActualArguments &loweredActuals,
185223
const fir::IntrinsicArgumentLoweringRules *argLowering) {
@@ -206,6 +244,9 @@ llvm::SmallVector<mlir::Value> HlfirTransformationalIntrinsic::getOperandVector(
206244
else if (!argRules.handleDynamicOptional &&
207245
argRules.lowerAs != fir::LowerIntrinsicArgAs::Inquired)
208246
valArg = hlfir::derefPointersAndAllocatables(loc, builder, actual);
247+
else if (argRules.handleDynamicOptional &&
248+
argRules.lowerAs == fir::LowerIntrinsicArgAs::Value)
249+
valArg = loadOptionalValue(loc, builder, arg, actual);
209250
else if (argRules.handleDynamicOptional)
210251
TODO(loc, "hlfir transformational intrinsic dynamically optional "
211252
"argument without box lowering");
@@ -260,6 +301,27 @@ mlir::Value HlfirReductionIntrinsic<OP, HAS_MASK>::lowerImpl(
260301
return op;
261302
}
262303

304+
template <typename OP>
305+
mlir::Value HlfirMinMaxLocIntrinsic<OP>::lowerImpl(
306+
const Fortran::lower::PreparedActualArguments &loweredActuals,
307+
const fir::IntrinsicArgumentLoweringRules *argLowering,
308+
mlir::Type stmtResultType) {
309+
auto operands = getOperandVector(loweredActuals, argLowering);
310+
mlir::Value array = operands[0];
311+
mlir::Value dim = operands[1];
312+
mlir::Value mask = operands[2];
313+
mlir::Value back = operands[4];
314+
// dim, mask and back can be NULL if these arguments are not given.
315+
if (dim)
316+
dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
317+
if (back)
318+
back = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{back});
319+
320+
mlir::Type resultTy = computeResultType(array, stmtResultType);
321+
322+
return createOp<OP>(resultTy, array, dim, mask, back);
323+
}
324+
263325
template <typename OP>
264326
mlir::Value HlfirProductIntrinsic<OP>::lowerImpl(
265327
const Fortran::lower::PreparedActualArguments &loweredActuals,
@@ -364,6 +426,9 @@ std::optional<hlfir::EntityWithAttributes> Fortran::lower::lowerHlfirIntrinsic(
364426
if (name == "minval")
365427
return HlfirMinvalLowering{builder, loc}.lower(loweredActuals, argLowering,
366428
stmtResultType);
429+
if (name == "minloc")
430+
return HlfirMinlocLowering{builder, loc}.lower(loweredActuals, argLowering,
431+
stmtResultType);
367432
if (mlir::isa<fir::CharacterType>(stmtResultType)) {
368433
if (name == "min")
369434
return HlfirCharExtremumLowering{builder, loc,

flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,81 @@ void hlfir::MinvalOp::getEffects(
870870
getIntrinsicEffects(getOperation(), effects);
871871
}
872872

873+
//===----------------------------------------------------------------------===//
874+
// MinlocOp
875+
//===----------------------------------------------------------------------===//
876+
877+
mlir::LogicalResult hlfir::MinlocOp::verify() {
878+
mlir::Operation *op = getOperation();
879+
880+
auto results = op->getResultTypes();
881+
assert(results.size() == 1);
882+
mlir::Value array = getArray();
883+
mlir::Value dim = getDim();
884+
mlir::Value mask = getMask();
885+
886+
fir::SequenceType arrayTy =
887+
hlfir::getFortranElementOrSequenceType(array.getType())
888+
.cast<fir::SequenceType>();
889+
llvm::ArrayRef<int64_t> arrayShape = arrayTy.getShape();
890+
891+
if (mask) {
892+
fir::SequenceType maskSeq =
893+
hlfir::getFortranElementOrSequenceType(mask.getType())
894+
.dyn_cast<fir::SequenceType>();
895+
llvm::ArrayRef<int64_t> maskShape;
896+
897+
if (maskSeq)
898+
maskShape = maskSeq.getShape();
899+
900+
if (!maskShape.empty()) {
901+
if (maskShape.size() != arrayShape.size())
902+
return emitWarning("MASK must be conformable to ARRAY");
903+
static_assert(fir::SequenceType::getUnknownExtent() ==
904+
hlfir::ExprType::getUnknownExtent());
905+
constexpr int64_t unknownExtent = fir::SequenceType::getUnknownExtent();
906+
for (std::size_t i = 0; i < arrayShape.size(); ++i) {
907+
int64_t arrayExtent = arrayShape[i];
908+
int64_t maskExtent = maskShape[i];
909+
if ((arrayExtent != maskExtent) && (arrayExtent != unknownExtent) &&
910+
(maskExtent != unknownExtent))
911+
return emitWarning("MASK must be conformable to ARRAY");
912+
}
913+
}
914+
}
915+
916+
mlir::Type resultType = results[0];
917+
if (dim && arrayShape.size() == 1) {
918+
if (!fir::isa_integer(resultType))
919+
return emitOpError("result must be scalar integer");
920+
} else if (auto resultExpr =
921+
mlir::dyn_cast_or_null<hlfir::ExprType>(resultType)) {
922+
if (!resultExpr.isArray())
923+
return emitOpError("result must be an array");
924+
925+
if (!fir::isa_integer(resultExpr.getEleTy()))
926+
return emitOpError("result must have integer elements");
927+
928+
llvm::ArrayRef<int64_t> resultShape = resultExpr.getShape();
929+
// With dim the result has rank n-1
930+
if (dim && resultShape.size() != (arrayShape.size() - 1))
931+
return emitOpError("result rank must be one less than ARRAY");
932+
// With dim the result has rank n
933+
if (!dim && resultShape.size() != 1)
934+
return emitOpError("result rank must be 1");
935+
} else {
936+
return emitOpError("result must be of numerical expr type");
937+
}
938+
return mlir::success();
939+
}
940+
941+
void hlfir::MinlocOp::getEffects(
942+
llvm::SmallVectorImpl<
943+
mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>>
944+
&effects) {
945+
getIntrinsicEffects(getOperation(), effects);
946+
}
947+
873948
//===----------------------------------------------------------------------===//
874949
// SetLengthOp
875950
//===----------------------------------------------------------------------===//

flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIRIntrinsics.cpp

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,23 @@ class HlfirReductionIntrinsicConversion : public HlfirIntrinsicConversion<OP> {
201201
return lowerArguments(operation, inArgs, rewriter, argLowering);
202202
};
203203

204+
auto buildMinMaxLocArgs(OP operation, mlir::Type i32, mlir::Type logicalType,
205+
mlir::PatternRewriter &rewriter, std::string opName,
206+
fir::FirOpBuilder builder) const {
207+
llvm::SmallVector<IntrinsicArgument, 3> inArgs;
208+
inArgs.push_back({operation.getArray(), operation.getArray().getType()});
209+
inArgs.push_back({operation.getDim(), i32});
210+
inArgs.push_back({operation.getMask(), logicalType});
211+
mlir::Type T = hlfir::getFortranElementType(operation.getType());
212+
unsigned width = T.cast<mlir::IntegerType>().getWidth();
213+
mlir::Value kind =
214+
builder.createIntegerConstant(operation->getLoc(), i32, width / 8);
215+
inArgs.push_back({kind, i32});
216+
inArgs.push_back({operation.getBack(), i32});
217+
auto *argLowering = fir::getIntrinsicArgumentLowering(opName);
218+
return lowerArguments(operation, inArgs, rewriter, argLowering);
219+
};
220+
204221
auto buildLogicalArgs(OP operation, mlir::Type i32, mlir::Type logicalType,
205222
mlir::PatternRewriter &rewriter,
206223
std::string opName) const {
@@ -224,6 +241,8 @@ class HlfirReductionIntrinsicConversion : public HlfirIntrinsicConversion<OP> {
224241
opName = "maxval";
225242
} else if constexpr (std::is_same_v<OP, hlfir::MinvalOp>) {
226243
opName = "minval";
244+
} else if constexpr (std::is_same_v<OP, hlfir::MinlocOp>) {
245+
opName = "minloc";
227246
} else if constexpr (std::is_same_v<OP, hlfir::AnyOp>) {
228247
opName = "any";
229248
} else if constexpr (std::is_same_v<OP, hlfir::AllOp>) {
@@ -246,6 +265,9 @@ class HlfirReductionIntrinsicConversion : public HlfirIntrinsicConversion<OP> {
246265
std::is_same_v<OP, hlfir::MaxvalOp> ||
247266
std::is_same_v<OP, hlfir::MinvalOp>) {
248267
args = buildNumericalArgs(operation, i32, logicalType, rewriter, opName);
268+
} else if constexpr (std::is_same_v<OP, hlfir::MinlocOp>) {
269+
args = buildMinMaxLocArgs(operation, i32, logicalType, rewriter, opName,
270+
builder);
249271
} else {
250272
args = buildLogicalArgs(operation, i32, logicalType, rewriter, opName);
251273
}
@@ -269,6 +291,8 @@ using MaxvalOpConversion = HlfirReductionIntrinsicConversion<hlfir::MaxvalOp>;
269291

270292
using MinvalOpConversion = HlfirReductionIntrinsicConversion<hlfir::MinvalOp>;
271293

294+
using MinlocOpConversion = HlfirReductionIntrinsicConversion<hlfir::MinlocOp>;
295+
272296
using AnyOpConversion = HlfirReductionIntrinsicConversion<hlfir::AnyOp>;
273297

274298
using AllOpConversion = HlfirReductionIntrinsicConversion<hlfir::AllOp>;
@@ -441,20 +465,20 @@ class LowerHLFIRIntrinsics
441465
mlir::ModuleOp module = this->getOperation();
442466
mlir::MLIRContext *context = &getContext();
443467
mlir::RewritePatternSet patterns(context);
444-
patterns
445-
.insert<MatmulOpConversion, MatmulTransposeOpConversion,
446-
AllOpConversion, AnyOpConversion, SumOpConversion,
447-
ProductOpConversion, TransposeOpConversion, CountOpConversion,
448-
DotProductOpConversion, MaxvalOpConversion, MinvalOpConversion>(
449-
context);
468+
patterns.insert<MatmulOpConversion, MatmulTransposeOpConversion,
469+
AllOpConversion, AnyOpConversion, SumOpConversion,
470+
ProductOpConversion, TransposeOpConversion,
471+
CountOpConversion, DotProductOpConversion,
472+
MaxvalOpConversion, MinvalOpConversion, MinlocOpConversion>(
473+
context);
450474
mlir::ConversionTarget target(*context);
451475
target.addLegalDialect<mlir::BuiltinDialect, mlir::arith::ArithDialect,
452476
mlir::func::FuncDialect, fir::FIROpsDialect,
453477
hlfir::hlfirDialect>();
454478
target.addIllegalOp<hlfir::MatmulOp, hlfir::MatmulTransposeOp, hlfir::SumOp,
455479
hlfir::ProductOp, hlfir::TransposeOp, hlfir::AnyOp,
456480
hlfir::AllOp, hlfir::DotProductOp, hlfir::CountOp,
457-
hlfir::MaxvalOp, hlfir::MinvalOp>();
481+
hlfir::MaxvalOp, hlfir::MinvalOp, hlfir::MinlocOp>();
458482
target.markUnknownOpDynamicallyLegal(
459483
[](mlir::Operation *) { return true; });
460484
if (mlir::failed(

flang/test/HLFIR/invalid.fir

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,74 @@ func.func @bad_minval13(%arg0: !hlfir.expr<?x?x!fir.char<1,?>>, %arg1: i32){
548548
%0 = hlfir.minval %arg0 dim %arg1 : (!hlfir.expr<?x?x!fir.char<1,?>>, i32) -> !hlfir.expr<!fir.char<1,?>>
549549
}
550550

551+
// -----
552+
func.func @bad_minloc1(%arg0: !hlfir.expr<?xi32>, %arg1: i32, %arg2: !fir.box<!fir.logical<4>>) {
553+
// expected-error@+1 {{'hlfir.minloc' op result must be scalar integer}}
554+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?xi32>, i32, !fir.box<!fir.logical<4>>) -> f32
555+
}
556+
557+
// -----
558+
func.func @bad_minloc2(%arg0: !hlfir.expr<?xi32>, %arg1: i32, %arg2: !fir.box<!fir.array<?x?x?x?x?x!fir.logical<4>>>) {
559+
// expected-warning@+1 {{MASK must be conformable to ARRAY}}
560+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?xi32>, i32, !fir.box<!fir.array<?x?x?x?x?x!fir.logical<4>>>) -> !hlfir.expr<i32>
561+
}
562+
563+
// -----
564+
func.func @bad_minloc3(%arg0: !hlfir.expr<?x5x?xi32>, %arg1: i32, %arg2: !fir.box<!fir.array<2x6x?x!fir.logical<4>>>) {
565+
// expected-warning@+1 {{MASK must be conformable to ARRAY}}
566+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?x5x?xi32>, i32, !fir.box<!fir.array<2x6x?x!fir.logical<4>>>) -> !hlfir.expr<i32>
567+
}
568+
569+
// -----
570+
func.func @bad_minloc4(%arg0: !hlfir.expr<?x?xi32>, %arg1: i32, %arg2: !fir.box<!fir.logical<4>>) {
571+
// expected-error@+1 {{'hlfir.minloc' op result rank must be one less than ARRAY}}
572+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?x?xi32>, i32, !fir.box<!fir.logical<4>>) -> !hlfir.expr<?x?xi32>
573+
}
574+
575+
// -----
576+
func.func @bad_minloc5(%arg0: !hlfir.expr<?xi32>, %arg1: i32, %arg2: !fir.box<!fir.logical<4>>) {
577+
// expected-error@+1 {{'hlfir.minloc' op result must be scalar integer}}
578+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?xi32>, i32, !fir.box<!fir.logical<4>>) -> !fir.logical<4>
579+
}
580+
581+
// -----
582+
func.func @bad_minloc6(%arg0: !hlfir.expr<?x?xi32>, %arg1: i32){
583+
// expected-error@+1 {{'hlfir.minloc' op result must be an array}}
584+
%0 = hlfir.minloc %arg0 dim %arg1 : (!hlfir.expr<?x?xi32>, i32) -> !hlfir.expr<i32>
585+
}
586+
587+
// -----
588+
func.func @bad_minloc7(%arg0: !hlfir.expr<?xi32>){
589+
// expected-error@+1 {{'hlfir.minloc' op result must be of numerical expr type}}
590+
%0 = hlfir.minloc %arg0 : (!hlfir.expr<?xi32>) -> i32
591+
}
592+
593+
// -----
594+
func.func @bad_minloc8(%arg0: !hlfir.expr<?xi32>){
595+
// expected-error@+1 {{'hlfir.minloc' op result must have integer elements}}
596+
%0 = hlfir.minloc %arg0 : (!hlfir.expr<?xi32>) -> !hlfir.expr<?xf32>
597+
}
598+
599+
// -----
600+
func.func @bad_minloc9(%arg0: !hlfir.expr<?x!fir.char<1,?>>, %arg1: i32, %arg2: !fir.box<!fir.array<?x?x?x?x?x!fir.logical<4>>>) {
601+
// expected-warning@+1 {{MASK must be conformable to ARRAY}}
602+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?x!fir.char<1,?>>, i32, !fir.box<!fir.array<?x?x?x?x?x!fir.logical<4>>>) -> !hlfir.expr<!fir.char<1,?>>
603+
}
604+
605+
// -----
606+
func.func @bad_minloc10(%arg0: !hlfir.expr<?x5x?x!fir.char<1,?>>, %arg1: i32, %arg2: !fir.box<!fir.array<2x6x?x!fir.logical<4>>>) {
607+
// expected-warning@+1 {{MASK must be conformable to ARRAY}}
608+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?x5x?x!fir.char<1,?>>, i32, !fir.box<!fir.array<2x6x?x!fir.logical<4>>>) -> !hlfir.expr<!fir.char<1,?>>
609+
}
610+
611+
// -----
612+
func.func @bad_minloc11(%arg0: !hlfir.expr<?x?x!fir.char<1,?>>, %arg1: i32, %arg2: !fir.box<!fir.logical<4>>) {
613+
// expected-error@+1 {{'hlfir.minloc' op result rank must be one less than ARRAY}}
614+
%0 = hlfir.minloc %arg0 dim %arg1 mask %arg2 : (!hlfir.expr<?x?x!fir.char<1,?>>, i32, !fir.box<!fir.logical<4>>) -> !hlfir.expr<?x?xi32>
615+
}
616+
617+
618+
551619
// -----
552620
func.func @bad_product1(%arg0: !hlfir.expr<?xi32>, %arg1: i32, %arg2: !fir.box<!fir.logical<4>>) {
553621
// expected-error@+1 {{'hlfir.product' op result must have the same element type as ARRAY argument}}

flang/test/HLFIR/memory-effects.fir

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ func.func @minval_effects(%arg0: !fir.ref<!fir.array<2x2xf32>>, %arg1: i32) {
122122
return
123123
}
124124

125+
func.func @minloc_effects_simple(%arg0: !hlfir.expr<?xf32>) {
126+
// expected-remark@+1 {{found an instance of 'allocate' on a value, on resource '<Default>'}}
127+
%minloc = hlfir.minloc %arg0 : (!hlfir.expr<?xf32>) -> !hlfir.expr<?xi32>
128+
// expected-remark@+1 {{operation has no memory effects}}
129+
return
130+
}
131+
132+
func.func @minloc_effects(%arg0: !fir.ref<!fir.array<2x2xf32>>, %arg1: i32) {
133+
// expected-remark@+2 {{found an instance of 'allocate' on a value, on resource '<Default>'}}
134+
// expected-remark@+1 {{found an instance of 'read' on a value, on resource '<Default>'}}
135+
%minloc = hlfir.minloc %arg0 dim %arg1 : (!fir.ref<!fir.array<2x2xf32>>, i32) -> !hlfir.expr<2xi32>
136+
// expected-remark@+1 {{operation has no memory effects}}
137+
return
138+
}
139+
125140
func.func @dot_product_no_effects(%arg0: !hlfir.expr<?xf32>, %arg1: !hlfir.expr<?xf32>) {
126141
// expected-remark@+1 {{operation has no memory effects}}
127142
%0 = hlfir.dot_product %arg0 %arg1 : (!hlfir.expr<?xf32>, !hlfir.expr<?xf32>) -> f32

0 commit comments

Comments
 (0)