1 //===- ConvertArrayConstructor.cpp -- Array Constructor ---------*- 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 #include "flang/Lower/ConvertArrayConstructor.h" 10 #include "flang/Evaluate/expression.h" 11 #include "flang/Lower/AbstractConverter.h" 12 #include "flang/Lower/ConvertExprToHLFIR.h" 13 #include "flang/Lower/ConvertType.h" 14 #include "flang/Lower/StatementContext.h" 15 #include "flang/Lower/SymbolMap.h" 16 #include "flang/Optimizer/Builder/HLFIRTools.h" 17 #include "flang/Optimizer/Builder/Runtime/ArrayConstructor.h" 18 #include "flang/Optimizer/Builder/Runtime/RTBuilder.h" 19 #include "flang/Optimizer/Builder/Todo.h" 20 #include "flang/Optimizer/HLFIR/HLFIROps.h" 21 22 // Array constructors are lowered with three different strategies. 23 // All strategies are not possible with all array constructors. 24 // 25 // - Strategy 1: runtime approach (RuntimeTempStrategy). 26 // This strategy works will all array constructors, but will create more 27 // complex code that is harder to optimize. An allocatable temp is created, 28 // it may be unallocated if the array constructor length parameters or extent 29 // could not be computed. Then, the runtime is called to push lowered 30 // ac-value (array constructor elements) into the allocatable. The runtime 31 // will allocate or reallocate as needed while values are being pushed. 32 // In the end, the allocatable contain a temporary with all the array 33 // constructor evaluated elements. 34 // 35 // - Strategy 2: inlined temporary approach (InlinedTempStrategyImpl) 36 // This strategy can only be used if the array constructor extent and length 37 // parameters can be pre-computed without evaluating any ac-value, and if all 38 // of the ac-value are scalars (at least for now). 39 // A temporary is allocated inline in one go, and an index pointing at the 40 // current ac-value position in the array constructor element sequence is 41 // maintained and used to store ac-value as they are being lowered. 42 // 43 // - Strategy 3: "function of the indices" approach (AsElementalStrategy) 44 // This strategy can only be used if the array constructor extent and length 45 // parameters can be pre-computed and, if the array constructor is of the 46 // form "[(scalar_expr, ac-implied-do-control)]". In this case, it is lowered 47 // into an hlfir.elemental without creating any temporary in lowering. This 48 // form should maximize the chance of array temporary elision when assigning 49 // the array constructor, potentially reshaped, to an array variable. 50 // 51 // The array constructor lowering looks like: 52 // ``` 53 // strategy = selectArrayCtorLoweringStrategy(array-ctor-expr); 54 // for (ac-value : array-ctor-expr) 55 // if (ac-value is expression) { 56 // strategy.pushValue(ac-value); 57 // } else if (ac-value is implied-do) { 58 // strategy.startImpliedDo(lower, upper, stride); 59 // // lower nested values 60 // } 61 // result = strategy.finishArrayCtorLowering(); 62 // ``` 63 64 //===----------------------------------------------------------------------===// 65 // Definition of the lowering strategies. Each lowering strategy is defined 66 // as a class that implements "pushValue", "startImpliedDo", and 67 // "finishArrayCtorLowering". 68 //===----------------------------------------------------------------------===// 69 70 namespace { 71 /// Class that implements the "inlined temp strategy" to lower array 72 /// constructors. It must be further provided a CounterType class to specify how 73 /// the current ac-value insertion position is tracked. 74 template <typename CounterType> 75 class InlinedTempStrategyImpl { 76 /// Name that will be given to the temporary allocation and hlfir.declare in 77 /// the IR. 78 static constexpr char tempName[] = ".tmp.arrayctor"; 79 80 public: 81 /// Start lowering an array constructor according to the inline strategy. 82 /// The temporary is created right away. 83 InlinedTempStrategyImpl(mlir::Location loc, fir::FirOpBuilder &builder, 84 fir::SequenceType declaredType, mlir::Value extent, 85 llvm::ArrayRef<mlir::Value> lengths) 86 : one{builder.createIntegerConstant(loc, builder.getIndexType(), 1)}, 87 counter{loc, builder, one} { 88 // Allocate the temporary storage. 89 llvm::SmallVector<mlir::Value, 1> extents{extent}; 90 mlir::Value tempStorage = builder.createHeapTemporary( 91 loc, declaredType, tempName, extents, lengths); 92 mlir::Value shape = builder.genShape(loc, extents); 93 temp = 94 builder 95 .create<hlfir::DeclareOp>(loc, tempStorage, tempName, shape, 96 lengths, fir::FortranVariableFlagsAttr{}) 97 .getBase(); 98 } 99 100 /// Push a lowered ac-value into the current insertion point and 101 /// increment the insertion point. 102 void pushValue(mlir::Location loc, fir::FirOpBuilder &builder, 103 hlfir::Entity value) { 104 assert(value.isScalar() && "cannot use inlined temp with array values"); 105 mlir::Value indexValue = counter.getAndIncrementIndex(loc, builder, one); 106 hlfir::Entity tempElement = hlfir::getElementAt( 107 loc, builder, hlfir::Entity{temp}, mlir::ValueRange{indexValue}); 108 // TODO: "copy" would probably be better than assign to ensure there are no 109 // side effects (user assignments, temp, lhs finalization)? 110 // This only makes a difference for derived types, so for now derived types 111 // will use the runtime strategy to avoid any bad behaviors. 112 builder.create<hlfir::AssignOp>(loc, value, tempElement); 113 } 114 115 /// Start a fir.do_loop with the control from an implied-do and return 116 /// the loop induction variable that is the ac-do-variable value. 117 /// Only usable if the counter is able to track the position through loops. 118 mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder, 119 mlir::Value lower, mlir::Value upper, 120 mlir::Value stride) { 121 if constexpr (!CounterType::canCountThroughLoops) 122 fir::emitFatalError(loc, "array constructor lowering is inconsistent"); 123 auto loop = builder.create<fir::DoLoopOp>(loc, lower, upper, stride, 124 /*unordered=*/false, 125 /*finalCount=*/false); 126 builder.setInsertionPointToStart(loop.getBody()); 127 return loop.getInductionVar(); 128 } 129 130 /// Move the temporary to an hlfir.expr value (array constructors are not 131 /// variables and cannot be further modified). 132 hlfir::Entity finishArrayCtorLowering(mlir::Location loc, 133 fir::FirOpBuilder &builder) { 134 // Temp is created using createHeapTemporary. 135 mlir::Value mustFree = builder.createBool(loc, true); 136 auto hlfirExpr = builder.create<hlfir::AsExprOp>(loc, temp, mustFree); 137 return hlfir::Entity{hlfirExpr}; 138 } 139 140 private: 141 mlir::Value one; 142 CounterType counter; 143 mlir::Value temp; 144 }; 145 146 /// A simple SSA value counter to lower array constructors without any 147 /// implied-do in the "inlined temp strategy". 148 /// The SSA value being tracked by the counter (hence, this 149 /// cannot count through loops since the SSA value in the loop becomes 150 /// inaccessible after the loop). 151 /// Semantic analysis expression rewrites unroll implied do loop with 152 /// compile time constant bounds (even if huge). So this minimalistic 153 /// counter greatly reduces the generated IR for simple but big array 154 /// constructors [(i,i=1,constant-expr)] that are expected to be quite 155 /// common. 156 class ValueCounter { 157 public: 158 static constexpr bool canCountThroughLoops = false; 159 ValueCounter(mlir::Location loc, fir::FirOpBuilder &builder, 160 mlir::Value initialValue) { 161 indexValue = initialValue; 162 } 163 164 mlir::Value getAndIncrementIndex(mlir::Location loc, 165 fir::FirOpBuilder &builder, 166 mlir::Value increment) { 167 mlir::Value currentValue = indexValue; 168 indexValue = 169 builder.create<mlir::arith::AddIOp>(loc, indexValue, increment); 170 return currentValue; 171 } 172 173 private: 174 mlir::Value indexValue; 175 }; 176 using LooplessInlinedTempStrategy = InlinedTempStrategyImpl<ValueCounter>; 177 178 /// A generic memory based counter that can deal with all cases of 179 /// "inlined temp strategy". The counter value is stored in a temp 180 /// from which it is loaded, incremented, and stored every time an 181 /// ac-value is pushed. 182 class InMemoryCounter { 183 public: 184 static constexpr bool canCountThroughLoops = true; 185 InMemoryCounter(mlir::Location loc, fir::FirOpBuilder &builder, 186 mlir::Value initialValue) { 187 indexVar = builder.createTemporary(loc, initialValue.getType()); 188 builder.create<fir::StoreOp>(loc, initialValue, indexVar); 189 } 190 191 mlir::Value getAndIncrementIndex(mlir::Location loc, 192 fir::FirOpBuilder &builder, 193 mlir::Value increment) const { 194 mlir::Value indexValue = builder.create<fir::LoadOp>(loc, indexVar); 195 indexValue = 196 builder.create<mlir::arith::AddIOp>(loc, indexValue, increment); 197 builder.create<fir::StoreOp>(loc, indexValue, indexVar); 198 return indexValue; 199 } 200 201 private: 202 mlir::Value indexVar; 203 }; 204 using InlinedTempStrategy = InlinedTempStrategyImpl<InMemoryCounter>; 205 206 /// Class that implements the "as function of the indices" lowering strategy. 207 /// It will lower [(scalar_expr(i), i=l,u,s)] to: 208 /// ``` 209 /// %extent = max((%u-%l+1)/%s, 0) 210 /// %shape = fir.shape %extent 211 /// %elem = hlfir.elemental %shape { 212 /// ^bb0(%pos:index): 213 /// %i = %l+(%i-1)*%s 214 /// %value = scalar_expr(%i) 215 /// hlfir.yield_element %value 216 /// } 217 /// ``` 218 /// That way, no temporary is created in lowering, and if the array constructor 219 /// is part of a more complex elemental expression, or an assignment, it will be 220 /// trivial to "inline" it in the expression or assignment loops if allowed by 221 /// alias analysis. 222 /// This lowering is however only possible for the form of array constructors as 223 /// in the illustration above. It could be extended to deeper independent 224 /// implied-do nest and wrapped in an hlfir.reshape to a rank 1 array. But this 225 /// op does not exist yet, so this is left for the future if it appears 226 /// profitable. 227 class AsElementalStrategy { 228 public: 229 /// The constructor only gathers the operands to create the hlfir.elemental. 230 AsElementalStrategy(mlir::Location loc, fir::FirOpBuilder &builder, 231 fir::SequenceType declaredType, mlir::Value extent, 232 llvm::ArrayRef<mlir::Value> lengths) 233 : shape{builder.genShape(loc, {extent})}, 234 lengthParams{lengths.begin(), lengths.end()}, exprType{getExprType( 235 declaredType)} {} 236 237 static hlfir::ExprType getExprType(fir::SequenceType declaredType) { 238 // Note: 7.8 point 4: the dynamic type of an array constructor is its static 239 // type, it is not polymorphic. 240 return hlfir::ExprType::get(declaredType.getContext(), 241 declaredType.getShape(), 242 declaredType.getEleTy(), 243 /*isPolymorphic=*/false); 244 } 245 246 /// Create the hlfir.elemental and compute the ac-implied-do-index value 247 /// given the lower bound and stride (compute "%i" in the illustration above). 248 mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder, 249 mlir::Value lower, mlir::Value upper, 250 mlir::Value stride) { 251 assert(!elementalOp && "expected only one implied-do"); 252 mlir::Value one = 253 builder.createIntegerConstant(loc, builder.getIndexType(), 1); 254 elementalOp = 255 builder.create<hlfir::ElementalOp>(loc, exprType, shape, lengthParams); 256 builder.setInsertionPointToStart(elementalOp.getBody()); 257 // implied-do-index = lower+((i-1)*stride) 258 mlir::Value diff = builder.create<mlir::arith::SubIOp>( 259 loc, elementalOp.getIndices()[0], one); 260 mlir::Value mul = builder.create<mlir::arith::MulIOp>(loc, diff, stride); 261 mlir::Value add = builder.create<mlir::arith::AddIOp>(loc, lower, mul); 262 return add; 263 } 264 265 /// Create the elemental hlfir.yield_element with the scalar ac-value. 266 void pushValue(mlir::Location loc, fir::FirOpBuilder &builder, 267 hlfir::Entity value) { 268 assert(value.isScalar() && "cannot use hlfir.elemental with array values"); 269 assert(elementalOp && "array constructor must contain an outer implied-do"); 270 mlir::Value elementResult = value; 271 if (fir::isa_trivial(elementResult.getType())) 272 elementResult = 273 builder.createConvert(loc, exprType.getElementType(), elementResult); 274 builder.create<hlfir::YieldElementOp>(loc, elementResult); 275 } 276 277 /// Return the created hlfir.elemental. 278 hlfir::Entity finishArrayCtorLowering(mlir::Location loc, 279 fir::FirOpBuilder &builder) { 280 return hlfir::Entity{elementalOp}; 281 } 282 283 private: 284 mlir::Value shape; 285 llvm::SmallVector<mlir::Value> lengthParams; 286 hlfir::ExprType exprType; 287 hlfir::ElementalOp elementalOp{}; 288 }; 289 290 /// Class that implements the "runtime temp strategy" to lower array 291 /// constructors. 292 class RuntimeTempStrategy { 293 /// Name that will be given to the temporary allocation and hlfir.declare in 294 /// the IR. 295 static constexpr char tempName[] = ".tmp.arrayctor"; 296 297 public: 298 /// Start lowering an array constructor according to the runtime strategy. 299 /// The temporary is only created if the extents and length parameters are 300 /// already known. Otherwise, the handling of the allocation (and 301 /// reallocation) is left up to the runtime. 302 /// \p extent is the pre-computed extent of the array constructor, if it could 303 /// be pre-computed. It is std::nullopt otherwise. 304 /// \p lengths are the pre-computed length parameters of the array 305 /// constructor, if they could be precomputed. \p missingLengthParameters is 306 /// set to true if the length parameters could not be precomputed. 307 RuntimeTempStrategy(mlir::Location loc, fir::FirOpBuilder &builder, 308 fir::SequenceType declaredType, 309 std::optional<mlir::Value> extent, 310 llvm::ArrayRef<mlir::Value> lengths, 311 bool missingLengthParameters) 312 : arrayConstructorElementType{declaredType.getEleTy()} { 313 mlir::Type heapType = fir::HeapType::get(declaredType); 314 mlir::Type boxType = fir::BoxType::get(heapType); 315 allocatableTemp = builder.createTemporary(loc, boxType, tempName); 316 mlir::Value initialBoxValue; 317 if (extent && !missingLengthParameters) { 318 llvm::SmallVector<mlir::Value, 1> extents{*extent}; 319 mlir::Value tempStorage = builder.createHeapTemporary( 320 loc, declaredType, tempName, extents, lengths); 321 mlir::Value shape = builder.genShape(loc, extents); 322 declare = builder.create<hlfir::DeclareOp>( 323 loc, tempStorage, tempName, shape, lengths, 324 fir::FortranVariableFlagsAttr{}); 325 initialBoxValue = 326 builder.createBox(loc, boxType, declare->getOriginalBase(), shape, 327 /*slice=*/mlir::Value{}, lengths, /*tdesc=*/{}); 328 } else { 329 // The runtime will have to do the initial allocation. 330 // The declare operation cannot be emitted in this case since the final 331 // array constructor has not yet been allocated. Instead, the resulting 332 // temporary variable will be extracted from the allocatable descriptor 333 // after all the API calls. 334 // Prepare the initial state of the allocatable descriptor with a 335 // deallocated status and all the available knowledge about the extent 336 // and length parameters. 337 llvm::SmallVector<mlir::Value> emboxLengths(lengths.begin(), 338 lengths.end()); 339 if (!extent) 340 extent = builder.createIntegerConstant(loc, builder.getIndexType(), 0); 341 if (missingLengthParameters) { 342 if (declaredType.getEleTy().isa<fir::CharacterType>()) 343 emboxLengths.push_back(builder.createIntegerConstant( 344 loc, builder.getCharacterLengthType(), 0)); 345 else 346 TODO(loc, 347 "parametrized derived type array constructor without type-spec"); 348 } 349 mlir::Value nullAddr = builder.createNullConstant(loc, heapType); 350 mlir::Value shape = builder.genShape(loc, {*extent}); 351 initialBoxValue = builder.createBox(loc, boxType, nullAddr, shape, 352 /*slice=*/mlir::Value{}, emboxLengths, 353 /*tdesc=*/{}); 354 } 355 builder.create<fir::StoreOp>(loc, initialBoxValue, allocatableTemp); 356 arrayConstructorVector = fir::runtime::genInitArrayConstructorVector( 357 loc, builder, allocatableTemp, 358 builder.createBool(loc, missingLengthParameters)); 359 } 360 361 bool useSimplePushRuntime(hlfir::Entity value) { 362 return value.isScalar() && 363 !arrayConstructorElementType.isa<fir::CharacterType>() && 364 !fir::isRecordWithAllocatableMember(arrayConstructorElementType) && 365 !fir::isRecordWithTypeParameters(arrayConstructorElementType); 366 } 367 368 /// Push a lowered ac-value into the array constructor vector using 369 /// the runtime API. 370 void pushValue(mlir::Location loc, fir::FirOpBuilder &builder, 371 hlfir::Entity value) { 372 if (useSimplePushRuntime(value)) { 373 auto [addrExv, cleanUp] = hlfir::convertToAddress( 374 loc, builder, value, arrayConstructorElementType); 375 mlir::Value addr = fir::getBase(addrExv); 376 if (addr.getType().isa<fir::BaseBoxType>()) 377 addr = builder.create<fir::BoxAddrOp>(loc, addr); 378 fir::runtime::genPushArrayConstructorSimpleScalar( 379 loc, builder, arrayConstructorVector, addr); 380 if (cleanUp) 381 (*cleanUp)(); 382 return; 383 } 384 auto [boxExv, cleanUp] = 385 hlfir::convertToBox(loc, builder, value, arrayConstructorElementType); 386 fir::runtime::genPushArrayConstructorValue( 387 loc, builder, arrayConstructorVector, fir::getBase(boxExv)); 388 if (cleanUp) 389 (*cleanUp)(); 390 } 391 392 /// Start a fir.do_loop with the control from an implied-do and return 393 /// the loop induction variable that is the ac-do-variable value. 394 mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder, 395 mlir::Value lower, mlir::Value upper, 396 mlir::Value stride) { 397 auto loop = builder.create<fir::DoLoopOp>(loc, lower, upper, stride, 398 /*unordered=*/false, 399 /*finalCount=*/false); 400 builder.setInsertionPointToStart(loop.getBody()); 401 return loop.getInductionVar(); 402 } 403 404 /// Move the temporary to an hlfir.expr value (array constructors are not 405 /// variables and cannot be further modified). 406 hlfir::Entity finishArrayCtorLowering(mlir::Location loc, 407 fir::FirOpBuilder &builder) { 408 // Temp is created using createHeapTemporary, or allocated on the heap 409 // by the runtime. 410 mlir::Value mustFree = builder.createBool(loc, true); 411 mlir::Value temp; 412 if (declare) 413 temp = declare->getBase(); 414 else 415 temp = hlfir::derefPointersAndAllocatables( 416 loc, builder, hlfir::Entity{allocatableTemp}); 417 auto hlfirExpr = builder.create<hlfir::AsExprOp>(loc, temp, mustFree); 418 return hlfir::Entity{hlfirExpr}; 419 } 420 421 private: 422 /// Element type of the array constructor being built. 423 mlir::Type arrayConstructorElementType; 424 /// Allocatable descriptor for the storage of the array constructor being 425 /// built. 426 mlir::Value allocatableTemp; 427 /// Structure that allows the runtime API to maintain the status of 428 /// of the array constructor being built between two API calls. 429 mlir::Value arrayConstructorVector; 430 /// DeclareOp for the array constructor storage, if it was possible to 431 /// allocate it before any API calls. 432 std::optional<hlfir::DeclareOp> declare; 433 }; 434 435 /// Wrapper class that dispatch to the selected array constructor lowering 436 /// strategy and does nothing else. 437 class ArrayCtorLoweringStrategy { 438 public: 439 template <typename A> 440 ArrayCtorLoweringStrategy(A &&impl) : implVariant{std::forward<A>(impl)} {} 441 442 void pushValue(mlir::Location loc, fir::FirOpBuilder &builder, 443 hlfir::Entity value) { 444 return std::visit( 445 [&](auto &impl) { return impl.pushValue(loc, builder, value); }, 446 implVariant); 447 } 448 449 mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder, 450 mlir::Value lower, mlir::Value upper, 451 mlir::Value stride) { 452 return std::visit( 453 [&](auto &impl) { 454 return impl.startImpliedDo(loc, builder, lower, upper, stride); 455 }, 456 implVariant); 457 } 458 459 hlfir::Entity finishArrayCtorLowering(mlir::Location loc, 460 fir::FirOpBuilder &builder) { 461 return std::visit( 462 [&](auto &impl) { return impl.finishArrayCtorLowering(loc, builder); }, 463 implVariant); 464 } 465 466 private: 467 std::variant<InlinedTempStrategy, LooplessInlinedTempStrategy, 468 AsElementalStrategy, RuntimeTempStrategy> 469 implVariant; 470 }; 471 } // namespace 472 473 //===----------------------------------------------------------------------===// 474 // Definition of selectArrayCtorLoweringStrategy and its helpers. 475 // This is the code that analyses the evaluate::ArrayConstructor<T>, 476 // pre-lowers the array constructor extent and length parameters if it can, 477 // and chooses the lowering strategy. 478 //===----------------------------------------------------------------------===// 479 480 namespace { 481 /// Helper class to lower the array constructor type and its length parameters. 482 /// The length parameters, if any, are only lowered if this does not require 483 /// evaluating an ac-value. 484 template <typename T> 485 struct LengthAndTypeCollector { 486 static mlir::Type collect(mlir::Location, 487 Fortran::lower::AbstractConverter &converter, 488 const Fortran::evaluate::ArrayConstructor<T> &, 489 Fortran::lower::SymMap &, 490 Fortran::lower::StatementContext &, 491 mlir::SmallVectorImpl<mlir::Value> &) { 492 // Numerical and Logical types. 493 return Fortran::lower::getFIRType(&converter.getMLIRContext(), T::category, 494 T::kind, /*lenParams*/ {}); 495 } 496 }; 497 498 template <> 499 struct LengthAndTypeCollector<Fortran::evaluate::SomeDerived> { 500 static mlir::Type collect( 501 mlir::Location loc, Fortran::lower::AbstractConverter &converter, 502 const Fortran::evaluate::ArrayConstructor<Fortran::evaluate::SomeDerived> 503 &arrayCtorExpr, 504 Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx, 505 mlir::SmallVectorImpl<mlir::Value> &lengths) { 506 TODO(loc, "collect derived type and length"); 507 } 508 }; 509 510 template <int Kind> 511 using Character = 512 Fortran::evaluate::Type<Fortran::common::TypeCategory::Character, Kind>; 513 template <int Kind> 514 struct LengthAndTypeCollector<Character<Kind>> { 515 static mlir::Type collect( 516 mlir::Location loc, Fortran::lower::AbstractConverter &converter, 517 const Fortran::evaluate::ArrayConstructor<Character<Kind>> &arrayCtorExpr, 518 Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx, 519 mlir::SmallVectorImpl<mlir::Value> &lengths) { 520 TODO(loc, "collect character type and length"); 521 } 522 }; 523 } // namespace 524 525 /// Does the array constructor have length parameters that 526 /// LengthAndTypeCollector::collect could not lower because this requires 527 /// lowering an ac-value and must be delayed? 528 static bool missingLengthParameters(mlir::Type elementType, 529 llvm::ArrayRef<mlir::Value> lengths) { 530 return (elementType.isa<fir::CharacterType>() || 531 fir::isRecordWithTypeParameters(elementType)) && 532 lengths.empty(); 533 } 534 535 namespace { 536 /// Structure that analyses the ac-value and implied-do of 537 /// evaluate::ArrayConstructor before they are lowered. It does not generate any 538 /// IR. The result of this analysis pass is used to select the lowering 539 /// strategy. 540 struct ArrayCtorAnalysis { 541 template <typename T> 542 ArrayCtorAnalysis( 543 Fortran::evaluate::FoldingContext &, 544 const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr); 545 546 // Can the array constructor easily be rewritten into an hlfir.elemental ? 547 bool isSingleImpliedDoWithOneScalarPureExpr() const { 548 return !anyArrayExpr && isPerfectLoopNest && 549 innerNumberOfExprIfPrefectNest == 1 && depthIfPerfectLoopNest == 1 && 550 innerExprIsPureIfPerfectNest; 551 } 552 553 bool anyImpliedDo = false; 554 bool anyArrayExpr = false; 555 bool isPerfectLoopNest = true; 556 bool innerExprIsPureIfPerfectNest = false; 557 std::int64_t innerNumberOfExprIfPrefectNest = 0; 558 std::int64_t depthIfPerfectLoopNest = 0; 559 }; 560 } // namespace 561 562 template <typename T> 563 ArrayCtorAnalysis::ArrayCtorAnalysis( 564 Fortran::evaluate::FoldingContext &foldingContext, 565 const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr) { 566 llvm::SmallVector<const Fortran::evaluate::ArrayConstructorValues<T> *> 567 arrayValueListStack{&arrayCtorExpr}; 568 // Loop through the ac-value-list(s) of the array constructor. 569 while (!arrayValueListStack.empty()) { 570 std::int64_t localNumberOfImpliedDo = 0; 571 std::int64_t localNumberOfExpr = 0; 572 // Loop though the ac-value of an ac-value list, and add any nested 573 // ac-value-list of ac-implied-do to the stack. 574 const Fortran::evaluate::ArrayConstructorValues<T> *currentArrayValueList = 575 arrayValueListStack.pop_back_val(); 576 for (const Fortran::evaluate::ArrayConstructorValue<T> &acValue : 577 *currentArrayValueList) 578 std::visit(Fortran::common::visitors{ 579 [&](const Fortran::evaluate::ImpliedDo<T> &impledDo) { 580 arrayValueListStack.push_back(&impledDo.values()); 581 localNumberOfImpliedDo++; 582 }, 583 [&](const Fortran::evaluate::Expr<T> &expr) { 584 localNumberOfExpr++; 585 anyArrayExpr = anyArrayExpr || expr.Rank() > 0; 586 }}, 587 acValue.u); 588 anyImpliedDo = anyImpliedDo || localNumberOfImpliedDo > 0; 589 590 if (localNumberOfImpliedDo == 0) { 591 // Leaf ac-value-list in the array constructor ac-value tree. 592 if (isPerfectLoopNest) { 593 // This this the only leaf of the array-constructor (the array 594 // constructor is a nest of single implied-do with a list of expression 595 // in the last deeper implied do). e.g: "[((i+j, i=1,n)j=1,m)]". 596 innerNumberOfExprIfPrefectNest = localNumberOfExpr; 597 if (localNumberOfExpr == 1) 598 innerExprIsPureIfPerfectNest = !Fortran::evaluate::FindImpureCall( 599 foldingContext, toEvExpr(std::get<Fortran::evaluate::Expr<T>>( 600 currentArrayValueList->begin()->u))); 601 } 602 } else if (localNumberOfImpliedDo == 1 && localNumberOfExpr == 0) { 603 // Perfect implied-do nest new level. 604 ++depthIfPerfectLoopNest; 605 } else { 606 // More than one implied-do, or at least one implied-do and an expr 607 // at that level. This will not form a perfect nest. Examples: 608 // "[a, (i, i=1,n)]" or "[(i, i=1,n), (j, j=1,m)]". 609 isPerfectLoopNest = false; 610 } 611 } 612 } 613 614 /// Helper to lower a scalar extent expression (like implied-do bounds). 615 static mlir::Value lowerExtentExpr(mlir::Location loc, 616 Fortran::lower::AbstractConverter &converter, 617 Fortran::lower::SymMap &symMap, 618 Fortran::lower::StatementContext &stmtCtx, 619 const Fortran::evaluate::ExtentExpr &expr) { 620 fir::FirOpBuilder &builder = converter.getFirOpBuilder(); 621 mlir::IndexType idxTy = builder.getIndexType(); 622 hlfir::Entity value = Fortran::lower::convertExprToHLFIR( 623 loc, converter, toEvExpr(expr), symMap, stmtCtx); 624 value = hlfir::loadTrivialScalar(loc, builder, value); 625 return builder.createConvert(loc, idxTy, value); 626 } 627 628 /// Does \p expr contain no calls to user function? 629 static bool isCallFreeExpr(const Fortran::evaluate::ExtentExpr &expr) { 630 for (const Fortran::semantics::Symbol &symbol : 631 Fortran::evaluate::CollectSymbols(expr)) 632 if (Fortran::semantics::IsProcedure(symbol)) 633 return false; 634 return true; 635 } 636 637 /// Core function that pre-lowers the extent and length parameters of 638 /// array constructors if it can, runs the ac-value analysis and 639 /// select the lowering strategy accordingly. 640 template <typename T> 641 static ArrayCtorLoweringStrategy selectArrayCtorLoweringStrategy( 642 mlir::Location loc, Fortran::lower::AbstractConverter &converter, 643 const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr, 644 Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) { 645 fir::FirOpBuilder &builder = converter.getFirOpBuilder(); 646 mlir::Type idxType = builder.getIndexType(); 647 // Try to gather the array constructor extent. 648 mlir::Value extent; 649 fir::SequenceType::Extent typeExtent = fir::SequenceType::getUnknownExtent(); 650 auto shapeExpr = Fortran::evaluate::GetContextFreeShape( 651 converter.getFoldingContext(), arrayCtorExpr); 652 if (shapeExpr && shapeExpr->size() == 1 && (*shapeExpr)[0]) { 653 const Fortran::evaluate::ExtentExpr &extentExpr = *(*shapeExpr)[0]; 654 if (auto constantExtent = Fortran::evaluate::ToInt64(extentExpr)) { 655 typeExtent = *constantExtent; 656 extent = builder.createIntegerConstant(loc, idxType, typeExtent); 657 } else if (isCallFreeExpr(extentExpr)) { 658 // The expression built by expression analysis for the array constructor 659 // extent does not contain procedure symbols. It is side effect free. 660 // This could be relaxed to allow pure procedure, but some care must 661 // be taken to not bring in "unmapped" symbols from callee scopes. 662 extent = lowerExtentExpr(loc, converter, symMap, stmtCtx, extentExpr); 663 } 664 // Otherwise, the temporary will have to be built step by step with 665 // reallocation and the extent will only be known at the end of the array 666 // constructor evaluation. 667 } 668 // Convert the array constructor type and try to gather its length parameter 669 // values, if any. 670 mlir::SmallVector<mlir::Value> lengths; 671 mlir::Type elementType = LengthAndTypeCollector<T>::collect( 672 loc, converter, arrayCtorExpr, symMap, stmtCtx, lengths); 673 // Run an analysis of the array constructor ac-value. 674 ArrayCtorAnalysis analysis(converter.getFoldingContext(), arrayCtorExpr); 675 bool needToEvaluateOneExprToGetLengthParameters = 676 missingLengthParameters(elementType, lengths); 677 auto declaredType = fir::SequenceType::get({typeExtent}, elementType); 678 679 // Based on what was gathered and the result of the analysis, select and 680 // instantiate the right lowering strategy for the array constructor. 681 if (!extent || needToEvaluateOneExprToGetLengthParameters || 682 analysis.anyArrayExpr) 683 return RuntimeTempStrategy( 684 loc, builder, declaredType, 685 extent ? std::optional<mlir::Value>(extent) : std::nullopt, lengths, 686 needToEvaluateOneExprToGetLengthParameters); 687 // Note: array constructors containing impure ac-value expr are currently not 688 // rewritten to hlfir.elemental because impure expressions should be evaluated 689 // in order, and hlfir.elemental currently misses a way to indicate that. 690 if (analysis.isSingleImpliedDoWithOneScalarPureExpr()) 691 return AsElementalStrategy(loc, builder, declaredType, extent, lengths); 692 693 if (analysis.anyImpliedDo) 694 return InlinedTempStrategy(loc, builder, declaredType, extent, lengths); 695 696 return LooplessInlinedTempStrategy(loc, builder, declaredType, extent, 697 lengths); 698 } 699 700 /// Lower an ac-value expression \p expr and forward it to the selected 701 /// lowering strategy \p arrayBuilder, 702 template <typename T> 703 static void genAcValue(mlir::Location loc, 704 Fortran::lower::AbstractConverter &converter, 705 const Fortran::evaluate::Expr<T> &expr, 706 Fortran::lower::SymMap &symMap, 707 Fortran::lower::StatementContext &stmtCtx, 708 ArrayCtorLoweringStrategy &arrayBuilder) { 709 // TODO: get rid of the toEvExpr indirection. 710 fir::FirOpBuilder &builder = converter.getFirOpBuilder(); 711 hlfir::Entity value = Fortran::lower::convertExprToHLFIR( 712 loc, converter, toEvExpr(expr), symMap, stmtCtx); 713 value = hlfir::loadTrivialScalar(loc, builder, value); 714 arrayBuilder.pushValue(loc, builder, value); 715 } 716 717 /// Lowers an ac-value implied-do \p impledDo according to the selected 718 /// lowering strategy \p arrayBuilder. 719 template <typename T> 720 static void genAcValue(mlir::Location loc, 721 Fortran::lower::AbstractConverter &converter, 722 const Fortran::evaluate::ImpliedDo<T> &impledDo, 723 Fortran::lower::SymMap &symMap, 724 Fortran::lower::StatementContext &stmtCtx, 725 ArrayCtorLoweringStrategy &arrayBuilder) { 726 auto lowerIndex = 727 [&](const Fortran::evaluate::ExtentExpr expr) -> mlir::Value { 728 return lowerExtentExpr(loc, converter, symMap, stmtCtx, expr); 729 }; 730 mlir::Value lower = lowerIndex(impledDo.lower()); 731 mlir::Value upper = lowerIndex(impledDo.upper()); 732 mlir::Value stride = lowerIndex(impledDo.stride()); 733 fir::FirOpBuilder &builder = converter.getFirOpBuilder(); 734 mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint(); 735 mlir::Value impliedDoIndexValue = 736 arrayBuilder.startImpliedDo(loc, builder, lower, upper, stride); 737 symMap.pushImpliedDoBinding(toStringRef(impledDo.name()), 738 impliedDoIndexValue); 739 stmtCtx.pushScope(); 740 741 for (const auto &acValue : impledDo.values()) 742 std::visit( 743 [&](const auto &x) { 744 genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder); 745 }, 746 acValue.u); 747 748 stmtCtx.finalizeAndPop(); 749 symMap.popImpliedDoBinding(); 750 builder.restoreInsertionPoint(insertPt); 751 } 752 753 /// Entry point for evaluate::ArrayConstructor lowering. 754 template <typename T> 755 hlfir::EntityWithAttributes Fortran::lower::ArrayConstructorBuilder<T>::gen( 756 mlir::Location loc, Fortran::lower::AbstractConverter &converter, 757 const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr, 758 Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) { 759 fir::FirOpBuilder &builder = converter.getFirOpBuilder(); 760 // Select the lowering strategy given the array constructor. 761 auto arrayBuilder = selectArrayCtorLoweringStrategy( 762 loc, converter, arrayCtorExpr, symMap, stmtCtx); 763 // Run the array lowering strategy through the ac-values. 764 for (const auto &acValue : arrayCtorExpr) 765 std::visit( 766 [&](const auto &x) { 767 genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder); 768 }, 769 acValue.u); 770 hlfir::Entity hlfirExpr = arrayBuilder.finishArrayCtorLowering(loc, builder); 771 // Insert the clean-up for the created hlfir.expr. 772 fir::FirOpBuilder *bldr = &builder; 773 stmtCtx.attachCleanup( 774 [=]() { bldr->create<hlfir::DestroyOp>(loc, hlfirExpr); }); 775 return hlfir::EntityWithAttributes{hlfirExpr}; 776 } 777 778 using namespace Fortran::evaluate; 779 using namespace Fortran::common; 780 FOR_EACH_SPECIFIC_TYPE(template class Fortran::lower::ArrayConstructorBuilder, ) 781