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