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