1 //===-- IterationSpace.h ----------------------------------------*- C++ -*-===//
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 #ifndef FORTRAN_LOWER_ITERATIONSPACE_H
14 #define FORTRAN_LOWER_ITERATIONSPACE_H
15 
16 #include "flang/Evaluate/tools.h"
17 #include "flang/Lower/StatementContext.h"
18 #include "flang/Lower/SymbolMap.h"
19 #include "flang/Optimizer/Builder/FIRBuilder.h"
20 
21 namespace llvm {
22 class raw_ostream;
23 }
24 
25 namespace Fortran {
26 namespace evaluate {
27 struct SomeType;
28 template <typename>
29 class Expr;
30 } // namespace evaluate
31 
32 namespace lower {
33 
34 using FrontEndExpr = const evaluate::Expr<evaluate::SomeType> *;
35 using FrontEndSymbol = const semantics::Symbol *;
36 
37 class AbstractConverter;
38 
39 unsigned getHashValue(FrontEndExpr x);
40 bool isEqual(FrontEndExpr x, FrontEndExpr y);
41 } // namespace lower
42 } // namespace Fortran
43 
44 namespace llvm {
45 template <>
46 struct DenseMapInfo<Fortran::lower::FrontEndExpr> {
47   static inline Fortran::lower::FrontEndExpr getEmptyKey() {
48     return reinterpret_cast<Fortran::lower::FrontEndExpr>(~0);
49   }
50   static inline Fortran::lower::FrontEndExpr getTombstoneKey() {
51     return reinterpret_cast<Fortran::lower::FrontEndExpr>(~0 - 1);
52   }
53   static unsigned getHashValue(Fortran::lower::FrontEndExpr v) {
54     return Fortran::lower::getHashValue(v);
55   }
56   static bool isEqual(Fortran::lower::FrontEndExpr lhs,
57                       Fortran::lower::FrontEndExpr rhs) {
58     return Fortran::lower::isEqual(lhs, rhs);
59   }
60 };
61 } // namespace llvm
62 
63 namespace Fortran::lower {
64 
65 /// Abstraction of the iteration space for building the elemental compute loop
66 /// of an array(-like) statement.
67 class IterationSpace {
68 public:
69   IterationSpace() = default;
70 
71   template <typename A>
72   explicit IterationSpace(mlir::Value inArg, mlir::Value outRes,
73                           llvm::iterator_range<A> range)
74       : inArg{inArg}, outRes{outRes}, indices{range.begin(), range.end()} {}
75 
76   explicit IterationSpace(const IterationSpace &from,
77                           llvm::ArrayRef<mlir::Value> idxs)
78       : inArg(from.inArg), outRes(from.outRes), element(from.element),
79         indices(idxs.begin(), idxs.end()) {}
80 
81   /// Create a copy of the \p from IterationSpace and prepend the \p prefix
82   /// values and append the \p suffix values, respectively.
83   explicit IterationSpace(const IterationSpace &from,
84                           llvm::ArrayRef<mlir::Value> prefix,
85                           llvm::ArrayRef<mlir::Value> suffix)
86       : inArg(from.inArg), outRes(from.outRes), element(from.element) {
87     indices.assign(prefix.begin(), prefix.end());
88     indices.append(from.indices.begin(), from.indices.end());
89     indices.append(suffix.begin(), suffix.end());
90   }
91 
92   bool empty() const { return indices.empty(); }
93 
94   /// This is the output value as it appears as an argument in the innermost
95   /// loop in the nest. The output value is threaded through the loop (and
96   /// conditionals) to maintain proper SSA form.
97   mlir::Value innerArgument() const { return inArg; }
98 
99   /// This is the output value as it appears as an output value from the
100   /// outermost loop in the loop nest. The output value is threaded through the
101   /// loop (and conditionals) to maintain proper SSA form.
102   mlir::Value outerResult() const { return outRes; }
103 
104   /// Returns a vector for the iteration space. This vector is used to access
105   /// elements of arrays in the compute loop.
106   llvm::SmallVector<mlir::Value> iterVec() const { return indices; }
107 
108   mlir::Value iterValue(std::size_t i) const {
109     assert(i < indices.size());
110     return indices[i];
111   }
112 
113   /// Set (rewrite) the Value at a given index.
114   void setIndexValue(std::size_t i, mlir::Value v) {
115     assert(i < indices.size());
116     indices[i] = v;
117   }
118 
119   void setIndexValues(llvm::ArrayRef<mlir::Value> vals) {
120     indices.assign(vals.begin(), vals.end());
121   }
122 
123   void insertIndexValue(std::size_t i, mlir::Value av) {
124     assert(i <= indices.size());
125     indices.insert(indices.begin() + i, av);
126   }
127 
128   /// Set the `element` value. This is the SSA value that corresponds to an
129   /// element of the resultant array value.
130   void setElement(fir::ExtendedValue &&ele) {
131     assert(!fir::getBase(element) && "result element already set");
132     element = ele;
133   }
134 
135   /// Get the value that will be merged into the resultant array. This is the
136   /// computed value that will be stored to the lhs of the assignment.
137   mlir::Value getElement() const {
138     assert(fir::getBase(element) && "element must be set");
139     return fir::getBase(element);
140   }
141 
142   /// Get the element as an extended value.
143   fir::ExtendedValue elementExv() const { return element; }
144 
145   void clearIndices() { indices.clear(); }
146 
147 private:
148   mlir::Value inArg;
149   mlir::Value outRes;
150   fir::ExtendedValue element;
151   llvm::SmallVector<mlir::Value> indices;
152 };
153 
154 using GenerateElementalArrayFunc =
155     std::function<fir::ExtendedValue(const IterationSpace &)>;
156 
157 template <typename A>
158 class StackableConstructExpr {
159 public:
160   bool empty() const { return stack.empty(); }
161 
162   void growStack() { stack.push_back(A{}); }
163 
164   /// Bind a front-end expression to a closure.
165   void bind(FrontEndExpr e, GenerateElementalArrayFunc &&fun) {
166     vmap.insert({e, std::move(fun)});
167   }
168 
169   /// Replace the binding of front-end expression `e` with a new closure.
170   void rebind(FrontEndExpr e, GenerateElementalArrayFunc &&fun) {
171     vmap.erase(e);
172     bind(e, std::move(fun));
173   }
174 
175   /// Get the closure bound to the front-end expression, `e`.
176   GenerateElementalArrayFunc getBoundClosure(FrontEndExpr e) const {
177     if (!vmap.count(e))
178       llvm::report_fatal_error(
179           "evaluate::Expr is not in the map of lowered mask expressions");
180     return vmap.lookup(e);
181   }
182 
183   /// Has the front-end expression, `e`, been lowered and bound?
184   bool isLowered(FrontEndExpr e) const { return vmap.count(e); }
185 
186   StatementContext &stmtContext() { return stmtCtx; }
187 
188 protected:
189   void shrinkStack() {
190     assert(!empty());
191     stack.pop_back();
192     if (empty()) {
193       stmtCtx.finalize();
194       vmap.clear();
195     }
196   }
197 
198   // The stack for the construct information.
199   llvm::SmallVector<A> stack;
200 
201   // Map each mask expression back to the temporary holding the initial
202   // evaluation results.
203   llvm::DenseMap<FrontEndExpr, GenerateElementalArrayFunc> vmap;
204 
205   // Inflate the statement context for the entire construct. We have to cache
206   // the mask expression results, which are always evaluated first, across the
207   // entire construct.
208   StatementContext stmtCtx;
209 };
210 
211 class ImplicitIterSpace;
212 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ImplicitIterSpace &);
213 
214 /// All array expressions have an implicit iteration space, which is isomorphic
215 /// to the shape of the base array that facilitates the expression having a
216 /// non-zero rank. This implied iteration space may be conditionalized
217 /// (disjunctively) with an if-elseif-else like structure, specifically
218 /// Fortran's WHERE construct.
219 ///
220 /// This class is used in the bridge to collect the expressions from the
221 /// front end (the WHERE construct mask expressions), forward them for lowering
222 /// as array expressions in an "evaluate once" (copy-in, copy-out) semantics.
223 /// See 10.2.3.2p3, 10.2.3.2p13, etc.
224 class ImplicitIterSpace
225     : public StackableConstructExpr<llvm::SmallVector<FrontEndExpr>> {
226 public:
227   using Base = StackableConstructExpr<llvm::SmallVector<FrontEndExpr>>;
228   using FrontEndMaskExpr = FrontEndExpr;
229 
230   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &,
231                                        const ImplicitIterSpace &);
232 
233   LLVM_DUMP_METHOD void dump() const;
234 
235   void append(FrontEndMaskExpr e) {
236     assert(!empty());
237     getMasks().back().push_back(e);
238   }
239 
240   llvm::SmallVector<FrontEndMaskExpr> getExprs() const {
241     llvm::SmallVector<FrontEndMaskExpr> maskList = getMasks()[0];
242     for (size_t i = 1, d = getMasks().size(); i < d; ++i)
243       maskList.append(getMasks()[i].begin(), getMasks()[i].end());
244     return maskList;
245   }
246 
247   /// Add a variable binding, `var`, along with its shape for the mask
248   /// expression `exp`.
249   void addMaskVariable(FrontEndExpr exp, mlir::Value var, mlir::Value shape,
250                        mlir::Value header) {
251     maskVarMap.try_emplace(exp, std::make_tuple(var, shape, header));
252   }
253 
254   /// Lookup the variable corresponding to the temporary buffer that contains
255   /// the mask array expression results.
256   mlir::Value lookupMaskVariable(FrontEndExpr exp) {
257     return std::get<0>(maskVarMap.lookup(exp));
258   }
259 
260   /// Lookup the variable containing the shape vector for the mask array
261   /// expression results.
262   mlir::Value lookupMaskShapeBuffer(FrontEndExpr exp) {
263     return std::get<1>(maskVarMap.lookup(exp));
264   }
265 
266   mlir::Value lookupMaskHeader(FrontEndExpr exp) {
267     return std::get<2>(maskVarMap.lookup(exp));
268   }
269 
270   // Stack of WHERE constructs, each building a list of mask expressions.
271   llvm::SmallVector<llvm::SmallVector<FrontEndMaskExpr>> &getMasks() {
272     return stack;
273   }
274   const llvm::SmallVector<llvm::SmallVector<FrontEndMaskExpr>> &
275   getMasks() const {
276     return stack;
277   }
278 
279   // Cleanup at the end of a WHERE statement or construct.
280   void shrinkStack() {
281     Base::shrinkStack();
282     if (stack.empty())
283       maskVarMap.clear();
284   }
285 
286 private:
287   llvm::DenseMap<FrontEndExpr,
288                  std::tuple<mlir::Value, mlir::Value, mlir::Value>>
289       maskVarMap;
290 };
291 
292 class ExplicitIterSpace;
293 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ExplicitIterSpace &);
294 
295 /// Create all the array_load ops for the explicit iteration space context. The
296 /// nest of FORALLs must have been analyzed a priori.
297 void createArrayLoads(AbstractConverter &converter, ExplicitIterSpace &esp,
298                       SymMap &symMap);
299 
300 /// Create the array_merge_store ops after the explicit iteration space context
301 /// is conmpleted.
302 void createArrayMergeStores(AbstractConverter &converter,
303                             ExplicitIterSpace &esp);
304 using ExplicitSpaceArrayBases =
305     std::variant<FrontEndSymbol, const evaluate::Component *,
306                  const evaluate::ArrayRef *>;
307 
308 unsigned getHashValue(const ExplicitSpaceArrayBases &x);
309 bool isEqual(const ExplicitSpaceArrayBases &x,
310              const ExplicitSpaceArrayBases &y);
311 
312 } // namespace Fortran::lower
313 
314 namespace llvm {
315 template <>
316 struct DenseMapInfo<Fortran::lower::ExplicitSpaceArrayBases> {
317   static inline Fortran::lower::ExplicitSpaceArrayBases getEmptyKey() {
318     return reinterpret_cast<Fortran::lower::FrontEndSymbol>(~0);
319   }
320   static inline Fortran::lower::ExplicitSpaceArrayBases getTombstoneKey() {
321     return reinterpret_cast<Fortran::lower::FrontEndSymbol>(~0 - 1);
322   }
323   static unsigned
324   getHashValue(const Fortran::lower::ExplicitSpaceArrayBases &v) {
325     return Fortran::lower::getHashValue(v);
326   }
327   static bool isEqual(const Fortran::lower::ExplicitSpaceArrayBases &lhs,
328                       const Fortran::lower::ExplicitSpaceArrayBases &rhs) {
329     return Fortran::lower::isEqual(lhs, rhs);
330   }
331 };
332 } // namespace llvm
333 
334 namespace Fortran::lower {
335 /// Fortran also allows arrays to be evaluated under constructs which allow the
336 /// user to explicitly specify the iteration space using concurrent-control
337 /// expressions. These constructs allow the user to define both an iteration
338 /// space and explicit access vectors on arrays. These need not be isomorphic.
339 /// The explicit iteration spaces may be conditionalized (conjunctively) with an
340 /// "and" structure and may be found in FORALL (and DO CONCURRENT) constructs.
341 ///
342 /// This class is used in the bridge to collect a stack of lists of
343 /// concurrent-control expressions to be used to generate the iteration space
344 /// and associated masks (if any) for a set of nested FORALL constructs around
345 /// assignment and WHERE constructs.
346 class ExplicitIterSpace {
347 public:
348   using IterSpaceDim =
349       std::tuple<FrontEndSymbol, FrontEndExpr, FrontEndExpr, FrontEndExpr>;
350   using ConcurrentSpec =
351       std::pair<llvm::SmallVector<IterSpaceDim>, FrontEndExpr>;
352   using ArrayBases = ExplicitSpaceArrayBases;
353 
354   friend void createArrayLoads(AbstractConverter &converter,
355                                ExplicitIterSpace &esp, SymMap &symMap);
356   friend void createArrayMergeStores(AbstractConverter &converter,
357                                      ExplicitIterSpace &esp);
358 
359   /// Is a FORALL context presently active?
360   /// If we are lowering constructs/statements nested within a FORALL, then a
361   /// FORALL context is active.
362   bool isActive() const { return forallContextOpen != 0; }
363 
364   /// Get the statement context.
365   StatementContext &stmtContext() { return stmtCtx; }
366 
367   //===--------------------------------------------------------------------===//
368   // Analysis support
369   //===--------------------------------------------------------------------===//
370 
371   /// Open a new construct. The analysis phase starts here.
372   void pushLevel();
373 
374   /// Close the construct.
375   void popLevel();
376 
377   /// Add new concurrent header control variable symbol.
378   void addSymbol(FrontEndSymbol sym);
379 
380   /// Collect array bases from the expression, `x`.
381   void exprBase(FrontEndExpr x, bool lhs);
382 
383   /// Called at the end of a assignment statement.
384   void endAssign();
385 
386   /// Return all the active control variables on the stack.
387   llvm::SmallVector<FrontEndSymbol> collectAllSymbols();
388 
389   //===--------------------------------------------------------------------===//
390   // Code gen support
391   //===--------------------------------------------------------------------===//
392 
393   /// Enter a FORALL context.
394   void enter() { forallContextOpen++; }
395 
396   /// Leave a FORALL context.
397   void leave();
398 
399   void pushLoopNest(std::function<void()> lambda) {
400     ccLoopNest.push_back(lambda);
401   }
402 
403   /// Get the inner arguments that correspond to the output arrays.
404   mlir::ValueRange getInnerArgs() const { return innerArgs; }
405 
406   /// Set the inner arguments for the next loop level.
407   void setInnerArgs(llvm::ArrayRef<mlir::BlockArgument> args) {
408     innerArgs.clear();
409     for (auto &arg : args)
410       innerArgs.push_back(arg);
411   }
412 
413   /// Reset the outermost `array_load` arguments to the loop nest.
414   void resetInnerArgs() { innerArgs = initialArgs; }
415 
416   /// Capture the current outermost loop.
417   void setOuterLoop(fir::DoLoopOp loop) {
418     clearLoops();
419     outerLoop = loop;
420   }
421 
422   /// Sets the inner loop argument at position \p offset to \p val.
423   void setInnerArg(size_t offset, mlir::Value val) {
424     assert(offset < innerArgs.size());
425     innerArgs[offset] = val;
426   }
427 
428   /// Get the types of the output arrays.
429   llvm::SmallVector<mlir::Type> innerArgTypes() const {
430     llvm::SmallVector<mlir::Type> result;
431     for (auto &arg : innerArgs)
432       result.push_back(arg.getType());
433     return result;
434   }
435 
436   /// Create a binding between an Ev::Expr node pointer and a fir::array_load
437   /// op. This bindings will be used when generating the IR.
438   void bindLoad(ArrayBases base, fir::ArrayLoadOp load) {
439     loadBindings.try_emplace(std::move(base), load);
440   }
441 
442   fir::ArrayLoadOp findBinding(const ArrayBases &base) {
443     return loadBindings.lookup(base);
444   }
445 
446   /// `load` must be a LHS array_load. Returns `llvm::None` on error.
447   llvm::Optional<size_t> findArgPosition(fir::ArrayLoadOp load);
448 
449   bool isLHS(fir::ArrayLoadOp load) {
450     return findArgPosition(load).has_value();
451   }
452 
453   /// `load` must be a LHS array_load. Determine the threaded inner argument
454   /// corresponding to this load.
455   mlir::Value findArgumentOfLoad(fir::ArrayLoadOp load) {
456     if (auto opt = findArgPosition(load))
457       return innerArgs[*opt];
458     llvm_unreachable("array load argument not found");
459   }
460 
461   size_t argPosition(mlir::Value arg) {
462     for (auto i : llvm::enumerate(innerArgs))
463       if (arg == i.value())
464         return i.index();
465     llvm_unreachable("inner argument value was not found");
466   }
467 
468   llvm::Optional<fir::ArrayLoadOp> getLhsLoad(size_t i) {
469     assert(i < lhsBases.size());
470     if (lhsBases[counter])
471       return findBinding(*lhsBases[counter]);
472     return llvm::None;
473   }
474 
475   /// Return the outermost loop in this FORALL nest.
476   fir::DoLoopOp getOuterLoop() {
477     assert(outerLoop.has_value());
478     return outerLoop.value();
479   }
480 
481   /// Return the statement context for the entire, outermost FORALL construct.
482   StatementContext &outermostContext() { return outerContext; }
483 
484   /// Generate the explicit loop nest.
485   void genLoopNest() {
486     for (auto &lambda : ccLoopNest)
487       lambda();
488   }
489 
490   /// Clear the array_load bindings.
491   void resetBindings() { loadBindings.clear(); }
492 
493   /// Get the current counter value.
494   std::size_t getCounter() const { return counter; }
495 
496   /// Increment the counter value to the next assignment statement.
497   void incrementCounter() { counter++; }
498 
499   bool isOutermostForall() const {
500     assert(forallContextOpen);
501     return forallContextOpen == 1;
502   }
503 
504   void attachLoopCleanup(std::function<void(fir::FirOpBuilder &builder)> fn) {
505     if (!loopCleanup) {
506       loopCleanup = fn;
507       return;
508     }
509     std::function<void(fir::FirOpBuilder &)> oldFn = *loopCleanup;
510     loopCleanup = [=](fir::FirOpBuilder &builder) {
511       oldFn(builder);
512       fn(builder);
513     };
514   }
515 
516   // LLVM standard dump method.
517   LLVM_DUMP_METHOD void dump() const;
518 
519   // Pretty-print.
520   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &,
521                                        const ExplicitIterSpace &);
522 
523   /// Finalize the current body statement context.
524   void finalizeContext() { stmtCtx.finalize(); }
525 
526   void appendLoops(const llvm::SmallVector<fir::DoLoopOp> &loops) {
527     loopStack.push_back(loops);
528   }
529 
530   void clearLoops() { loopStack.clear(); }
531 
532   llvm::SmallVector<llvm::SmallVector<fir::DoLoopOp>> getLoopStack() const {
533     return loopStack;
534   }
535 
536 private:
537   /// Cleanup the analysis results.
538   void conditionalCleanup();
539 
540   StatementContext outerContext;
541 
542   // A stack of lists of front-end symbols.
543   llvm::SmallVector<llvm::SmallVector<FrontEndSymbol>> symbolStack;
544   llvm::SmallVector<llvm::Optional<ArrayBases>> lhsBases;
545   llvm::SmallVector<llvm::SmallVector<ArrayBases>> rhsBases;
546   llvm::DenseMap<ArrayBases, fir::ArrayLoadOp> loadBindings;
547 
548   // Stack of lambdas to create the loop nest.
549   llvm::SmallVector<std::function<void()>> ccLoopNest;
550 
551   // Assignment statement context (inside the loop nest).
552   StatementContext stmtCtx;
553   llvm::SmallVector<mlir::Value> innerArgs;
554   llvm::SmallVector<mlir::Value> initialArgs;
555   llvm::Optional<fir::DoLoopOp> outerLoop;
556   llvm::SmallVector<llvm::SmallVector<fir::DoLoopOp>> loopStack;
557   llvm::Optional<std::function<void(fir::FirOpBuilder &)>> loopCleanup;
558   std::size_t forallContextOpen = 0;
559   std::size_t counter = 0;
560 };
561 
562 /// Is there a Symbol in common between the concurrent header set and the set
563 /// of symbols in the expression?
564 template <typename A>
565 bool symbolSetsIntersect(llvm::ArrayRef<FrontEndSymbol> ctrlSet,
566                          const A &exprSyms) {
567   for (const auto &sym : exprSyms)
568     if (std::find(ctrlSet.begin(), ctrlSet.end(), &sym.get()) != ctrlSet.end())
569       return true;
570   return false;
571 }
572 
573 /// Determine if the subscript expression symbols from an Ev::ArrayRef
574 /// intersects with the set of concurrent control symbols, `ctrlSet`.
575 template <typename A>
576 bool symbolsIntersectSubscripts(llvm::ArrayRef<FrontEndSymbol> ctrlSet,
577                                 const A &subscripts) {
578   for (auto &sub : subscripts) {
579     if (const auto *expr =
580             std::get_if<evaluate::IndirectSubscriptIntegerExpr>(&sub.u))
581       if (symbolSetsIntersect(ctrlSet, evaluate::CollectSymbols(expr->value())))
582         return true;
583   }
584   return false;
585 }
586 
587 } // namespace Fortran::lower
588 
589 #endif // FORTRAN_LOWER_ITERATIONSPACE_H
590