xref: /llvm-project/flang/lib/Lower/CustomIntrinsicCall.cpp (revision fac349a169976f822fb27f03e623fa0d28aec1f3)
1 //===-- CustomIntrinsicCall.cpp -------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "flang/Lower/CustomIntrinsicCall.h"
14 #include "flang/Evaluate/expression.h"
15 #include "flang/Evaluate/fold.h"
16 #include "flang/Evaluate/tools.h"
17 #include "flang/Lower/StatementContext.h"
18 #include "flang/Optimizer/Builder/IntrinsicCall.h"
19 #include "flang/Optimizer/Builder/Todo.h"
20 #include "flang/Semantics/tools.h"
21 #include <optional>
22 
23 /// Is this a call to MIN or MAX intrinsic with arguments that may be absent at
24 /// runtime? This is a special case because MIN and MAX can have any number of
25 /// arguments.
isMinOrMaxWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef)26 static bool isMinOrMaxWithDynamicallyOptionalArg(
27     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) {
28   if (name != "min" && name != "max")
29     return false;
30   const auto &args = procRef.arguments();
31   std::size_t argSize = args.size();
32   if (argSize <= 2)
33     return false;
34   for (std::size_t i = 2; i < argSize; ++i) {
35     if (auto *expr =
36             Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(args[i]))
37       if (Fortran::evaluate::MayBePassedAsAbsentOptional(*expr))
38         return true;
39   }
40   return false;
41 }
42 
43 /// Is this a call to ISHFTC intrinsic with a SIZE argument that may be absent
44 /// at runtime? This is a special case because the SIZE value to be applied
45 /// when absent is not zero.
isIshftcWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef)46 static bool isIshftcWithDynamicallyOptionalArg(
47     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) {
48   if (name != "ishftc" || procRef.arguments().size() < 3)
49     return false;
50   auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(
51       procRef.arguments()[2]);
52   return expr && Fortran::evaluate::MayBePassedAsAbsentOptional(*expr);
53 }
54 
55 /// Is this a call to ASSOCIATED where the TARGET is an OPTIONAL (but not a
56 /// deallocated allocatable or disassociated pointer)?
57 /// Subtle: contrary to other intrinsic optional arguments, disassociated
58 /// POINTER and unallocated ALLOCATABLE actual argument are not considered
59 /// absent here. This is because ASSOCIATED has special requirements for TARGET
60 /// actual arguments that are POINTERs. There is no precise requirements for
61 /// ALLOCATABLEs, but all existing Fortran compilers treat them similarly to
62 /// POINTERs. That is: unallocated TARGETs cause ASSOCIATED to rerun false.  The
63 /// runtime deals with the disassociated/unallocated case. Simply ensures that
64 /// TARGET that are OPTIONAL get conditionally emboxed here to convey the
65 /// optional aspect to the runtime.
isAssociatedWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef)66 static bool isAssociatedWithDynamicallyOptionalArg(
67     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) {
68   if (name != "associated" || procRef.arguments().size() < 2)
69     return false;
70   auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(
71       procRef.arguments()[1]);
72   const Fortran::semantics::Symbol *sym{
73       expr ? Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef(expr)
74            : nullptr};
75   return (sym && Fortran::semantics::IsOptional(*sym));
76 }
77 
intrinsicRequiresCustomOptionalHandling(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,AbstractConverter & converter)78 bool Fortran::lower::intrinsicRequiresCustomOptionalHandling(
79     const Fortran::evaluate::ProcedureRef &procRef,
80     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
81     AbstractConverter &converter) {
82   llvm::StringRef name = intrinsic.name;
83   return isMinOrMaxWithDynamicallyOptionalArg(name, procRef) ||
84          isIshftcWithDynamicallyOptionalArg(name, procRef) ||
85          isAssociatedWithDynamicallyOptionalArg(name, procRef);
86 }
87 
88 /// Generate the FIR+MLIR operations for the generic intrinsic \p name
89 /// with arguments \p args and the expected result type \p resultType.
90 /// Returned fir::ExtendedValue is the returned Fortran intrinsic value.
91 fir::ExtendedValue
genIntrinsicCall(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,std::optional<mlir::Type> resultType,llvm::ArrayRef<fir::ExtendedValue> args,Fortran::lower::StatementContext & stmtCtx,Fortran::lower::AbstractConverter * converter)92 Fortran::lower::genIntrinsicCall(fir::FirOpBuilder &builder, mlir::Location loc,
93                                  llvm::StringRef name,
94                                  std::optional<mlir::Type> resultType,
95                                  llvm::ArrayRef<fir::ExtendedValue> args,
96                                  Fortran::lower::StatementContext &stmtCtx,
97                                  Fortran::lower::AbstractConverter *converter) {
98   auto [result, mustBeFreed] =
99       fir::genIntrinsicCall(builder, loc, name, resultType, args, converter);
100   if (mustBeFreed) {
101     mlir::Value addr = fir::getBase(result);
102     if (auto *box = result.getBoxOf<fir::BoxValue>())
103       addr =
104           builder.create<fir::BoxAddrOp>(loc, box->getMemTy(), box->getAddr());
105     fir::FirOpBuilder *bldr = &builder;
106     stmtCtx.attachCleanup([=]() { bldr->create<fir::FreeMemOp>(loc, addr); });
107   }
108   return result;
109 }
110 
prepareMinOrMaxArguments(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,std::optional<mlir::Type> retTy,const Fortran::lower::OperandPrepare & prepareOptionalArgument,const Fortran::lower::OperandPrepareAs & prepareOtherArgument,Fortran::lower::AbstractConverter & converter)111 static void prepareMinOrMaxArguments(
112     const Fortran::evaluate::ProcedureRef &procRef,
113     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
114     std::optional<mlir::Type> retTy,
115     const Fortran::lower::OperandPrepare &prepareOptionalArgument,
116     const Fortran::lower::OperandPrepareAs &prepareOtherArgument,
117     Fortran::lower::AbstractConverter &converter) {
118   assert(retTy && "MIN and MAX must have a return type");
119   mlir::Type resultType = *retTy;
120   mlir::Location loc = converter.getCurrentLocation();
121   if (fir::isa_char(resultType))
122     TODO(loc, "CHARACTER MIN and MAX with dynamically optional arguments");
123   for (auto arg : llvm::enumerate(procRef.arguments())) {
124     const auto *expr =
125         Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value());
126     if (!expr)
127       continue;
128     if (arg.index() <= 1 ||
129         !Fortran::evaluate::MayBePassedAsAbsentOptional(*expr)) {
130       // Non optional arguments.
131       prepareOtherArgument(*expr, fir::LowerIntrinsicArgAs::Value);
132     } else {
133       // Dynamically optional arguments.
134       // Subtle: even for scalar the if-then-else will be generated in the loop
135       // nest because the then part will require the current extremum value that
136       // may depend on previous array element argument and cannot be outlined.
137       prepareOptionalArgument(*expr);
138     }
139   }
140 }
141 
142 static fir::ExtendedValue
lowerMinOrMax(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,std::optional<mlir::Type> retTy,const Fortran::lower::OperandPresent & isPresentCheck,const Fortran::lower::OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)143 lowerMinOrMax(fir::FirOpBuilder &builder, mlir::Location loc,
144               llvm::StringRef name, std::optional<mlir::Type> retTy,
145               const Fortran::lower::OperandPresent &isPresentCheck,
146               const Fortran::lower::OperandGetter &getOperand,
147               std::size_t numOperands,
148               Fortran::lower::StatementContext &stmtCtx) {
149   assert(numOperands >= 2 && !isPresentCheck(0) && !isPresentCheck(1) &&
150          "min/max must have at least two non-optional args");
151   assert(retTy && "MIN and MAX must have a return type");
152   mlir::Type resultType = *retTy;
153   llvm::SmallVector<fir::ExtendedValue> args;
154   const bool loadOperand = true;
155   args.push_back(getOperand(0, loadOperand));
156   args.push_back(getOperand(1, loadOperand));
157   mlir::Value extremum = fir::getBase(
158       genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx));
159 
160   for (std::size_t opIndex = 2; opIndex < numOperands; ++opIndex) {
161     if (std::optional<mlir::Value> isPresentRuntimeCheck =
162             isPresentCheck(opIndex)) {
163       // Argument is dynamically optional.
164       extremum =
165           builder
166               .genIfOp(loc, {resultType}, *isPresentRuntimeCheck,
167                        /*withElseRegion=*/true)
168               .genThen([&]() {
169                 llvm::SmallVector<fir::ExtendedValue> args;
170                 args.emplace_back(extremum);
171                 args.emplace_back(getOperand(opIndex, loadOperand));
172                 fir::ExtendedValue newExtremum = genIntrinsicCall(
173                     builder, loc, name, resultType, args, stmtCtx);
174                 builder.create<fir::ResultOp>(loc, fir::getBase(newExtremum));
175               })
176               .genElse([&]() { builder.create<fir::ResultOp>(loc, extremum); })
177               .getResults()[0];
178     } else {
179       // Argument is know to be present at compile time.
180       llvm::SmallVector<fir::ExtendedValue> args;
181       args.emplace_back(extremum);
182       args.emplace_back(getOperand(opIndex, loadOperand));
183       extremum = fir::getBase(
184           genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx));
185     }
186   }
187   return extremum;
188 }
189 
prepareIshftcArguments(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,std::optional<mlir::Type> retTy,const Fortran::lower::OperandPrepare & prepareOptionalArgument,const Fortran::lower::OperandPrepareAs & prepareOtherArgument,Fortran::lower::AbstractConverter & converter)190 static void prepareIshftcArguments(
191     const Fortran::evaluate::ProcedureRef &procRef,
192     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
193     std::optional<mlir::Type> retTy,
194     const Fortran::lower::OperandPrepare &prepareOptionalArgument,
195     const Fortran::lower::OperandPrepareAs &prepareOtherArgument,
196     Fortran::lower::AbstractConverter &converter) {
197   for (auto arg : llvm::enumerate(procRef.arguments())) {
198     const auto *expr =
199         Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value());
200     assert(expr && "expected all ISHFTC argument to be textually present here");
201     if (arg.index() == 2) {
202       assert(Fortran::evaluate::MayBePassedAsAbsentOptional(*expr) &&
203              "expected ISHFTC SIZE arg to be dynamically optional");
204       prepareOptionalArgument(*expr);
205     } else {
206       // Non optional arguments.
207       prepareOtherArgument(*expr, fir::LowerIntrinsicArgAs::Value);
208     }
209   }
210 }
211 
212 static fir::ExtendedValue
lowerIshftc(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,std::optional<mlir::Type> retTy,const Fortran::lower::OperandPresent & isPresentCheck,const Fortran::lower::OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)213 lowerIshftc(fir::FirOpBuilder &builder, mlir::Location loc,
214             llvm::StringRef name, std::optional<mlir::Type> retTy,
215             const Fortran::lower::OperandPresent &isPresentCheck,
216             const Fortran::lower::OperandGetter &getOperand,
217             std::size_t numOperands,
218             Fortran::lower::StatementContext &stmtCtx) {
219   assert(numOperands == 3 && !isPresentCheck(0) && !isPresentCheck(1) &&
220          isPresentCheck(2) &&
221          "only ISHFTC SIZE arg is expected to be dynamically optional here");
222   assert(retTy && "ISFHTC must have a return type");
223   mlir::Type resultType = *retTy;
224   llvm::SmallVector<fir::ExtendedValue> args;
225   const bool loadOperand = true;
226   args.push_back(getOperand(0, loadOperand));
227   args.push_back(getOperand(1, loadOperand));
228   auto iPC = isPresentCheck(2);
229   assert(iPC.has_value());
230   args.push_back(
231       builder
232           .genIfOp(loc, {resultType}, *iPC,
233                    /*withElseRegion=*/true)
234           .genThen([&]() {
235             fir::ExtendedValue sizeExv = getOperand(2, loadOperand);
236             mlir::Value size =
237                 builder.createConvert(loc, resultType, fir::getBase(sizeExv));
238             builder.create<fir::ResultOp>(loc, size);
239           })
240           .genElse([&]() {
241             mlir::Value bitSize = builder.createIntegerConstant(
242                 loc, resultType,
243                 mlir::cast<mlir::IntegerType>(resultType).getWidth());
244             builder.create<fir::ResultOp>(loc, bitSize);
245           })
246           .getResults()[0]);
247   return genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx);
248 }
249 
prepareAssociatedArguments(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,std::optional<mlir::Type> retTy,const Fortran::lower::OperandPrepare & prepareOptionalArgument,const Fortran::lower::OperandPrepareAs & prepareOtherArgument,Fortran::lower::AbstractConverter & converter)250 static void prepareAssociatedArguments(
251     const Fortran::evaluate::ProcedureRef &procRef,
252     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
253     std::optional<mlir::Type> retTy,
254     const Fortran::lower::OperandPrepare &prepareOptionalArgument,
255     const Fortran::lower::OperandPrepareAs &prepareOtherArgument,
256     Fortran::lower::AbstractConverter &converter) {
257   const auto *pointer = procRef.UnwrapArgExpr(0);
258   const auto *optionalTarget = procRef.UnwrapArgExpr(1);
259   assert(pointer && optionalTarget &&
260          "expected call to associated with a target");
261   prepareOtherArgument(*pointer, fir::LowerIntrinsicArgAs::Inquired);
262   prepareOptionalArgument(*optionalTarget);
263 }
264 
265 static fir::ExtendedValue
lowerAssociated(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,std::optional<mlir::Type> resultType,const Fortran::lower::OperandPresent & isPresentCheck,const Fortran::lower::OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)266 lowerAssociated(fir::FirOpBuilder &builder, mlir::Location loc,
267                 llvm::StringRef name, std::optional<mlir::Type> resultType,
268                 const Fortran::lower::OperandPresent &isPresentCheck,
269                 const Fortran::lower::OperandGetter &getOperand,
270                 std::size_t numOperands,
271                 Fortran::lower::StatementContext &stmtCtx) {
272   assert(numOperands == 2 && "expect two arguments when TARGET is OPTIONAL");
273   llvm::SmallVector<fir::ExtendedValue> args;
274   args.push_back(getOperand(0, /*loadOperand=*/false));
275   // Ensure a null descriptor is passed to the code lowering Associated if
276   // TARGET is absent.
277   fir::ExtendedValue targetExv = getOperand(1, /*loadOperand=*/false);
278   mlir::Value targetBase = fir::getBase(targetExv);
279   // subtle: isPresentCheck would test for an unallocated/disassociated target,
280   // while the optionality of the target pointer/allocatable is what must be
281   // checked here.
282   mlir::Value isPresent =
283       builder.create<fir::IsPresentOp>(loc, builder.getI1Type(), targetBase);
284   mlir::Type targetType = fir::unwrapRefType(targetBase.getType());
285   mlir::Type targetValueType = fir::unwrapPassByRefType(targetType);
286   mlir::Type boxType = mlir::isa<fir::BaseBoxType>(targetType)
287                            ? targetType
288                            : fir::BoxType::get(targetValueType);
289   fir::BoxValue targetBox =
290       builder
291           .genIfOp(loc, {boxType}, isPresent,
292                    /*withElseRegion=*/true)
293           .genThen([&]() {
294             mlir::Value box = builder.createBox(loc, targetExv);
295             mlir::Value cast = builder.createConvert(loc, boxType, box);
296             builder.create<fir::ResultOp>(loc, cast);
297           })
298           .genElse([&]() {
299             mlir::Value absentBox = builder.create<fir::AbsentOp>(loc, boxType);
300             builder.create<fir::ResultOp>(loc, absentBox);
301           })
302           .getResults()[0];
303   args.emplace_back(std::move(targetBox));
304   return genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx);
305 }
306 
prepareCustomIntrinsicArgument(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,std::optional<mlir::Type> retTy,const OperandPrepare & prepareOptionalArgument,const OperandPrepareAs & prepareOtherArgument,AbstractConverter & converter)307 void Fortran::lower::prepareCustomIntrinsicArgument(
308     const Fortran::evaluate::ProcedureRef &procRef,
309     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
310     std::optional<mlir::Type> retTy,
311     const OperandPrepare &prepareOptionalArgument,
312     const OperandPrepareAs &prepareOtherArgument,
313     AbstractConverter &converter) {
314   llvm::StringRef name = intrinsic.name;
315   if (name == "min" || name == "max")
316     return prepareMinOrMaxArguments(procRef, intrinsic, retTy,
317                                     prepareOptionalArgument,
318                                     prepareOtherArgument, converter);
319   if (name == "associated")
320     return prepareAssociatedArguments(procRef, intrinsic, retTy,
321                                       prepareOptionalArgument,
322                                       prepareOtherArgument, converter);
323   assert(name == "ishftc" && "unexpected custom intrinsic argument call");
324   return prepareIshftcArguments(procRef, intrinsic, retTy,
325                                 prepareOptionalArgument, prepareOtherArgument,
326                                 converter);
327 }
328 
lowerCustomIntrinsic(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,std::optional<mlir::Type> retTy,const OperandPresent & isPresentCheck,const OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)329 fir::ExtendedValue Fortran::lower::lowerCustomIntrinsic(
330     fir::FirOpBuilder &builder, mlir::Location loc, llvm::StringRef name,
331     std::optional<mlir::Type> retTy, const OperandPresent &isPresentCheck,
332     const OperandGetter &getOperand, std::size_t numOperands,
333     Fortran::lower::StatementContext &stmtCtx) {
334   if (name == "min" || name == "max")
335     return lowerMinOrMax(builder, loc, name, retTy, isPresentCheck, getOperand,
336                          numOperands, stmtCtx);
337   if (name == "associated")
338     return lowerAssociated(builder, loc, name, retTy, isPresentCheck,
339                            getOperand, numOperands, stmtCtx);
340   assert(name == "ishftc" && "unexpected custom intrinsic call");
341   return lowerIshftc(builder, loc, name, retTy, isPresentCheck, getOperand,
342                      numOperands, stmtCtx);
343 }
344