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/IntrinsicCall.h"
18 #include "flang/Optimizer/Builder/Todo.h"
19 
20 /// Is this a call to MIN or MAX intrinsic with arguments that may be absent at
21 /// runtime? This is a special case because MIN and MAX can have any number of
22 /// arguments.
isMinOrMaxWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef,Fortran::evaluate::FoldingContext & foldingContex)23 static bool isMinOrMaxWithDynamicallyOptionalArg(
24     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef,
25     Fortran::evaluate::FoldingContext &foldingContex) {
26   if (name != "min" && name != "max")
27     return false;
28   const auto &args = procRef.arguments();
29   std::size_t argSize = args.size();
30   if (argSize <= 2)
31     return false;
32   for (std::size_t i = 2; i < argSize; ++i) {
33     if (auto *expr =
34             Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(args[i]))
35       if (Fortran::evaluate::MayBePassedAsAbsentOptional(*expr, foldingContex))
36         return true;
37   }
38   return false;
39 }
40 
41 /// Is this a call to ISHFTC intrinsic with a SIZE argument that may be absent
42 /// at runtime? This is a special case because the SIZE value to be applied
43 /// when absent is not zero.
isIshftcWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef,Fortran::evaluate::FoldingContext & foldingContex)44 static bool isIshftcWithDynamicallyOptionalArg(
45     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef,
46     Fortran::evaluate::FoldingContext &foldingContex) {
47   if (name != "ishftc" || procRef.arguments().size() < 3)
48     return false;
49   auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(
50       procRef.arguments()[2]);
51   return expr &&
52          Fortran::evaluate::MayBePassedAsAbsentOptional(*expr, foldingContex);
53 }
54 
55 /// Is this a call to the RANDOM_SEED intrinsic with arguments that may be
56 /// absent at runtime? This is a special case because that aspect cannot
57 /// be delegated to the runtime via a null fir.box or address given the current
58 /// runtime entry point.
isRandomSeedWithDynamicallyOptionalArg(llvm::StringRef name,const Fortran::evaluate::ProcedureRef & procRef,Fortran::evaluate::FoldingContext & foldingContex)59 static bool isRandomSeedWithDynamicallyOptionalArg(
60     llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef,
61     Fortran::evaluate::FoldingContext &foldingContex) {
62   if (name != "random_seed")
63     return false;
64   for (const auto &arg : procRef.arguments()) {
65     auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg);
66     if (expr &&
67         Fortran::evaluate::MayBePassedAsAbsentOptional(*expr, foldingContex))
68       return true;
69   }
70   return false;
71 }
72 
intrinsicRequiresCustomOptionalHandling(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,AbstractConverter & converter)73 bool Fortran::lower::intrinsicRequiresCustomOptionalHandling(
74     const Fortran::evaluate::ProcedureRef &procRef,
75     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
76     AbstractConverter &converter) {
77   llvm::StringRef name = intrinsic.name;
78   Fortran::evaluate::FoldingContext &fldCtx = converter.getFoldingContext();
79   return isMinOrMaxWithDynamicallyOptionalArg(name, procRef, fldCtx) ||
80          isIshftcWithDynamicallyOptionalArg(name, procRef, fldCtx) ||
81          isRandomSeedWithDynamicallyOptionalArg(name, procRef, fldCtx);
82 }
83 
prepareMinOrMaxArguments(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,llvm::Optional<mlir::Type> retTy,const Fortran::lower::OperandPrepare & prepareOptionalArgument,const Fortran::lower::OperandPrepare & prepareOtherArgument,Fortran::lower::AbstractConverter & converter)84 static void prepareMinOrMaxArguments(
85     const Fortran::evaluate::ProcedureRef &procRef,
86     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
87     llvm::Optional<mlir::Type> retTy,
88     const Fortran::lower::OperandPrepare &prepareOptionalArgument,
89     const Fortran::lower::OperandPrepare &prepareOtherArgument,
90     Fortran::lower::AbstractConverter &converter) {
91   assert(retTy && "MIN and MAX must have a return type");
92   mlir::Type resultType = *retTy;
93   mlir::Location loc = converter.getCurrentLocation();
94   if (fir::isa_char(resultType))
95     TODO(loc, "CHARACTER MIN and MAX with dynamically optional arguments");
96   for (auto arg : llvm::enumerate(procRef.arguments())) {
97     const auto *expr =
98         Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value());
99     if (!expr)
100       continue;
101     if (arg.index() <= 1 || !Fortran::evaluate::MayBePassedAsAbsentOptional(
102                                 *expr, converter.getFoldingContext())) {
103       // Non optional arguments.
104       prepareOtherArgument(*expr);
105     } else {
106       // Dynamically optional arguments.
107       // Subtle: even for scalar the if-then-else will be generated in the loop
108       // nest because the then part will require the current extremum value that
109       // may depend on previous array element argument and cannot be outlined.
110       prepareOptionalArgument(*expr);
111     }
112   }
113 }
114 
115 static fir::ExtendedValue
lowerMinOrMax(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,llvm::Optional<mlir::Type> retTy,const Fortran::lower::OperandPresent & isPresentCheck,const Fortran::lower::OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)116 lowerMinOrMax(fir::FirOpBuilder &builder, mlir::Location loc,
117               llvm::StringRef name, llvm::Optional<mlir::Type> retTy,
118               const Fortran::lower::OperandPresent &isPresentCheck,
119               const Fortran::lower::OperandGetter &getOperand,
120               std::size_t numOperands,
121               Fortran::lower::StatementContext &stmtCtx) {
122   assert(numOperands >= 2 && !isPresentCheck(0) && !isPresentCheck(1) &&
123          "min/max must have at least two non-optional args");
124   assert(retTy && "MIN and MAX must have a return type");
125   mlir::Type resultType = *retTy;
126   llvm::SmallVector<fir::ExtendedValue> args;
127   args.push_back(getOperand(0));
128   args.push_back(getOperand(1));
129   mlir::Value extremum = fir::getBase(Fortran::lower::genIntrinsicCall(
130       builder, loc, name, resultType, args, stmtCtx));
131 
132   for (std::size_t opIndex = 2; opIndex < numOperands; ++opIndex) {
133     if (llvm::Optional<mlir::Value> isPresentRuntimeCheck =
134             isPresentCheck(opIndex)) {
135       // Argument is dynamically optional.
136       extremum =
137           builder
138               .genIfOp(loc, {resultType}, *isPresentRuntimeCheck,
139                        /*withElseRegion=*/true)
140               .genThen([&]() {
141                 llvm::SmallVector<fir::ExtendedValue> args;
142                 args.emplace_back(extremum);
143                 args.emplace_back(getOperand(opIndex));
144                 fir::ExtendedValue newExtremum =
145                     Fortran::lower::genIntrinsicCall(builder, loc, name,
146                                                      resultType, args, stmtCtx);
147                 builder.create<fir::ResultOp>(loc, fir::getBase(newExtremum));
148               })
149               .genElse([&]() { builder.create<fir::ResultOp>(loc, extremum); })
150               .getResults()[0];
151     } else {
152       // Argument is know to be present at compile time.
153       llvm::SmallVector<fir::ExtendedValue> args;
154       args.emplace_back(extremum);
155       args.emplace_back(getOperand(opIndex));
156       extremum = fir::getBase(Fortran::lower::genIntrinsicCall(
157           builder, loc, name, resultType, args, stmtCtx));
158     }
159   }
160   return extremum;
161 }
162 
prepareIshftcArguments(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,llvm::Optional<mlir::Type> retTy,const Fortran::lower::OperandPrepare & prepareOptionalArgument,const Fortran::lower::OperandPrepare & prepareOtherArgument,Fortran::lower::AbstractConverter & converter)163 static void prepareIshftcArguments(
164     const Fortran::evaluate::ProcedureRef &procRef,
165     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
166     llvm::Optional<mlir::Type> retTy,
167     const Fortran::lower::OperandPrepare &prepareOptionalArgument,
168     const Fortran::lower::OperandPrepare &prepareOtherArgument,
169     Fortran::lower::AbstractConverter &converter) {
170   for (auto arg : llvm::enumerate(procRef.arguments())) {
171     const auto *expr =
172         Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value());
173     assert(expr && "expected all ISHFTC argument to be textually present here");
174     if (arg.index() == 2) {
175       assert(Fortran::evaluate::MayBePassedAsAbsentOptional(
176                  *expr, converter.getFoldingContext()) &&
177              "expected ISHFTC SIZE arg to be dynamically optional");
178       prepareOptionalArgument(*expr);
179     } else {
180       // Non optional arguments.
181       prepareOtherArgument(*expr);
182     }
183   }
184 }
185 
186 static fir::ExtendedValue
lowerIshftc(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,llvm::Optional<mlir::Type> retTy,const Fortran::lower::OperandPresent & isPresentCheck,const Fortran::lower::OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)187 lowerIshftc(fir::FirOpBuilder &builder, mlir::Location loc,
188             llvm::StringRef name, llvm::Optional<mlir::Type> retTy,
189             const Fortran::lower::OperandPresent &isPresentCheck,
190             const Fortran::lower::OperandGetter &getOperand,
191             std::size_t numOperands,
192             Fortran::lower::StatementContext &stmtCtx) {
193   assert(numOperands == 3 && !isPresentCheck(0) && !isPresentCheck(1) &&
194          isPresentCheck(2) &&
195          "only ISHFTC SIZE arg is expected to be dynamically optional here");
196   assert(retTy && "ISFHTC must have a return type");
197   mlir::Type resultType = retTy.value();
198   llvm::SmallVector<fir::ExtendedValue> args;
199   args.push_back(getOperand(0));
200   args.push_back(getOperand(1));
201   args.push_back(builder
202                      .genIfOp(loc, {resultType}, isPresentCheck(2).value(),
203                               /*withElseRegion=*/true)
204                      .genThen([&]() {
205                        fir::ExtendedValue sizeExv = getOperand(2);
206                        mlir::Value size = builder.createConvert(
207                            loc, resultType, fir::getBase(sizeExv));
208                        builder.create<fir::ResultOp>(loc, size);
209                      })
210                      .genElse([&]() {
211                        mlir::Value bitSize = builder.createIntegerConstant(
212                            loc, resultType,
213                            resultType.cast<mlir::IntegerType>().getWidth());
214                        builder.create<fir::ResultOp>(loc, bitSize);
215                      })
216                      .getResults()[0]);
217   return Fortran::lower::genIntrinsicCall(builder, loc, name, resultType, args,
218                                           stmtCtx);
219 }
220 
prepareCustomIntrinsicArgument(const Fortran::evaluate::ProcedureRef & procRef,const Fortran::evaluate::SpecificIntrinsic & intrinsic,llvm::Optional<mlir::Type> retTy,const OperandPrepare & prepareOptionalArgument,const OperandPrepare & prepareOtherArgument,AbstractConverter & converter)221 void Fortran::lower::prepareCustomIntrinsicArgument(
222     const Fortran::evaluate::ProcedureRef &procRef,
223     const Fortran::evaluate::SpecificIntrinsic &intrinsic,
224     llvm::Optional<mlir::Type> retTy,
225     const OperandPrepare &prepareOptionalArgument,
226     const OperandPrepare &prepareOtherArgument, AbstractConverter &converter) {
227   llvm::StringRef name = intrinsic.name;
228   if (name == "min" || name == "max")
229     return prepareMinOrMaxArguments(procRef, intrinsic, retTy,
230                                     prepareOptionalArgument,
231                                     prepareOtherArgument, converter);
232   if (name == "ishftc")
233     return prepareIshftcArguments(procRef, intrinsic, retTy,
234                                   prepareOptionalArgument, prepareOtherArgument,
235                                   converter);
236   TODO(converter.getCurrentLocation(),
237        "unhandled dynamically optional arguments in SYSTEM_CLOCK or "
238        "RANDOM_SEED");
239 }
240 
lowerCustomIntrinsic(fir::FirOpBuilder & builder,mlir::Location loc,llvm::StringRef name,llvm::Optional<mlir::Type> retTy,const OperandPresent & isPresentCheck,const OperandGetter & getOperand,std::size_t numOperands,Fortran::lower::StatementContext & stmtCtx)241 fir::ExtendedValue Fortran::lower::lowerCustomIntrinsic(
242     fir::FirOpBuilder &builder, mlir::Location loc, llvm::StringRef name,
243     llvm::Optional<mlir::Type> retTy, const OperandPresent &isPresentCheck,
244     const OperandGetter &getOperand, std::size_t numOperands,
245     Fortran::lower::StatementContext &stmtCtx) {
246   if (name == "min" || name == "max")
247     return lowerMinOrMax(builder, loc, name, retTy, isPresentCheck, getOperand,
248                          numOperands, stmtCtx);
249   if (name == "ishftc")
250     return lowerIshftc(builder, loc, name, retTy, isPresentCheck, getOperand,
251                        numOperands, stmtCtx);
252   TODO(loc, "unhandled dynamically optional arguments in SYSTEM_CLOCK or "
253             "RANDOM_SEED");
254 }
255