xref: /llvm-project/flang/lib/Lower/ConvertArrayConstructor.cpp (revision 5226f8a9434a30d2be8273c41a2f23fd626fa878)
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/RTBuilder.h"
18 #include "flang/Optimizer/Builder/Todo.h"
19 #include "flang/Optimizer/HLFIR/HLFIROps.h"
20 #include "flang/Runtime/array-constructor.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 // TODO: add and implement RuntimeTempStrategy.
291 
292 /// Wrapper class that dispatch to the selected array constructor lowering
293 /// strategy and does nothing else.
294 class ArrayCtorLoweringStrategy {
295 public:
296   template <typename A>
297   ArrayCtorLoweringStrategy(A &&impl) : implVariant{std::forward<A>(impl)} {}
298 
299   void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
300                  hlfir::Entity value) {
301     return std::visit(
302         [&](auto &impl) { return impl.pushValue(loc, builder, value); },
303         implVariant);
304   }
305 
306   mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
307                              mlir::Value lower, mlir::Value upper,
308                              mlir::Value stride) {
309     return std::visit(
310         [&](auto &impl) {
311           return impl.startImpliedDo(loc, builder, lower, upper, stride);
312         },
313         implVariant);
314   }
315 
316   hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
317                                         fir::FirOpBuilder &builder) {
318     return std::visit(
319         [&](auto &impl) { return impl.finishArrayCtorLowering(loc, builder); },
320         implVariant);
321   }
322 
323 private:
324   std::variant<InlinedTempStrategy, LooplessInlinedTempStrategy,
325                AsElementalStrategy>
326       implVariant;
327 };
328 } // namespace
329 
330 //===----------------------------------------------------------------------===//
331 //   Definition of selectArrayCtorLoweringStrategy and its helpers.
332 //   This is the code that analyses the evaluate::ArrayConstructor<T>,
333 //   pre-lowers the array constructor extent and length parameters if it can,
334 //   and chooses the lowering strategy.
335 //===----------------------------------------------------------------------===//
336 
337 namespace {
338 /// Helper class to lower the array constructor type and its length parameters.
339 /// The length parameters, if any, are only lowered if this does not require
340 /// evaluating an ac-value.
341 template <typename T>
342 struct LengthAndTypeCollector {
343   static mlir::Type collect(mlir::Location,
344                             Fortran::lower::AbstractConverter &converter,
345                             const Fortran::evaluate::ArrayConstructor<T> &,
346                             Fortran::lower::SymMap &,
347                             Fortran::lower::StatementContext &,
348                             mlir::SmallVectorImpl<mlir::Value> &) {
349     // Numerical and Logical types.
350     return Fortran::lower::getFIRType(&converter.getMLIRContext(), T::category,
351                                       T::kind, /*lenParams*/ {});
352   }
353 };
354 
355 template <>
356 struct LengthAndTypeCollector<Fortran::evaluate::SomeDerived> {
357   static mlir::Type collect(
358       mlir::Location loc, Fortran::lower::AbstractConverter &converter,
359       const Fortran::evaluate::ArrayConstructor<Fortran::evaluate::SomeDerived>
360           &arrayCtorExpr,
361       Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
362       mlir::SmallVectorImpl<mlir::Value> &lengths) {
363     TODO(loc, "collect derived type and length");
364   }
365 };
366 
367 template <int Kind>
368 using Character =
369     Fortran::evaluate::Type<Fortran::common::TypeCategory::Character, Kind>;
370 template <int Kind>
371 struct LengthAndTypeCollector<Character<Kind>> {
372   static mlir::Type collect(
373       mlir::Location loc, Fortran::lower::AbstractConverter &converter,
374       const Fortran::evaluate::ArrayConstructor<Character<Kind>> &arrayCtorExpr,
375       Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
376       mlir::SmallVectorImpl<mlir::Value> &lengths) {
377     TODO(loc, "collect character type and length");
378   }
379 };
380 } // namespace
381 
382 /// Does the array constructor have length parameters that
383 /// LengthAndTypeCollector::collect could not lower because this requires
384 /// lowering an ac-value and must be delayed?
385 static bool
386 failedToGatherLengthParameters(mlir::Type elementType,
387                                llvm::ArrayRef<mlir::Value> lengths) {
388   return (elementType.isa<fir::CharacterType>() ||
389           fir::isRecordWithTypeParameters(elementType)) &&
390          lengths.empty();
391 }
392 
393 namespace {
394 /// Structure that analyses the ac-value and implied-do of
395 /// evaluate::ArrayConstructor before they are lowered. It does not generate any
396 /// IR. The result of this analysis pass is used to select the lowering
397 /// strategy.
398 struct ArrayCtorAnalysis {
399   template <typename T>
400   ArrayCtorAnalysis(
401       Fortran::evaluate::FoldingContext &,
402       const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr);
403 
404   // Can the array constructor easily be rewritten into an hlfir.elemental ?
405   bool isSingleImpliedDoWithOneScalarPureExpr() const {
406     return !anyArrayExpr && isPerfectLoopNest &&
407            innerNumberOfExprIfPrefectNest == 1 && depthIfPerfectLoopNest == 1 &&
408            innerExprIsPureIfPerfectNest;
409   }
410 
411   bool anyImpliedDo = false;
412   bool anyArrayExpr = false;
413   bool isPerfectLoopNest = true;
414   bool innerExprIsPureIfPerfectNest = false;
415   std::int64_t innerNumberOfExprIfPrefectNest = 0;
416   std::int64_t depthIfPerfectLoopNest = 0;
417 };
418 } // namespace
419 
420 template <typename T>
421 ArrayCtorAnalysis::ArrayCtorAnalysis(
422     Fortran::evaluate::FoldingContext &foldingContext,
423     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr) {
424   llvm::SmallVector<const Fortran::evaluate::ArrayConstructorValues<T> *>
425       arrayValueListStack{&arrayCtorExpr};
426   // Loop through the ac-value-list(s) of the array constructor.
427   while (!arrayValueListStack.empty()) {
428     std::int64_t localNumberOfImpliedDo = 0;
429     std::int64_t localNumberOfExpr = 0;
430     // Loop though the ac-value of an ac-value list, and add any nested
431     // ac-value-list of ac-implied-do to the stack.
432     const Fortran::evaluate::ArrayConstructorValues<T> *currentArrayValueList =
433         arrayValueListStack.pop_back_val();
434     for (const Fortran::evaluate::ArrayConstructorValue<T> &acValue :
435          *currentArrayValueList)
436       std::visit(Fortran::common::visitors{
437                      [&](const Fortran::evaluate::ImpliedDo<T> &impledDo) {
438                        arrayValueListStack.push_back(&impledDo.values());
439                        localNumberOfImpliedDo++;
440                      },
441                      [&](const Fortran::evaluate::Expr<T> &expr) {
442                        localNumberOfExpr++;
443                        anyArrayExpr = anyArrayExpr || expr.Rank() > 0;
444                      }},
445                  acValue.u);
446     anyImpliedDo = anyImpliedDo || localNumberOfImpliedDo > 0;
447 
448     if (localNumberOfImpliedDo == 0) {
449       // Leaf ac-value-list in the array constructor ac-value tree.
450       if (isPerfectLoopNest) {
451         // This this the only leaf of the array-constructor (the array
452         // constructor is a nest of single implied-do with a list of expression
453         // in the last deeper implied do). e.g: "[((i+j, i=1,n)j=1,m)]".
454         innerNumberOfExprIfPrefectNest = localNumberOfExpr;
455         if (localNumberOfExpr == 1)
456           innerExprIsPureIfPerfectNest = !Fortran::evaluate::FindImpureCall(
457               foldingContext, toEvExpr(std::get<Fortran::evaluate::Expr<T>>(
458                                   currentArrayValueList->begin()->u)));
459       }
460     } else if (localNumberOfImpliedDo == 1 && localNumberOfExpr == 0) {
461       // Perfect implied-do nest new level.
462       ++depthIfPerfectLoopNest;
463     } else {
464       // More than one implied-do, or at least one implied-do and an expr
465       // at that level. This will not form a perfect nest. Examples:
466       // "[a, (i, i=1,n)]" or "[(i, i=1,n), (j, j=1,m)]".
467       isPerfectLoopNest = false;
468     }
469   }
470 }
471 
472 /// Helper to lower a scalar extent expression (like implied-do bounds).
473 static mlir::Value lowerExtentExpr(mlir::Location loc,
474                                    Fortran::lower::AbstractConverter &converter,
475                                    Fortran::lower::SymMap &symMap,
476                                    Fortran::lower::StatementContext &stmtCtx,
477                                    const Fortran::evaluate::ExtentExpr &expr) {
478   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
479   mlir::IndexType idxTy = builder.getIndexType();
480   hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
481       loc, converter, toEvExpr(expr), symMap, stmtCtx);
482   value = hlfir::loadTrivialScalar(loc, builder, value);
483   return builder.createConvert(loc, idxTy, value);
484 }
485 
486 /// Does \p expr contain no calls to user function?
487 static bool isCallFreeExpr(const Fortran::evaluate::ExtentExpr &expr) {
488   for (const Fortran::semantics::Symbol &symbol :
489        Fortran::evaluate::CollectSymbols(expr))
490     if (Fortran::semantics::IsProcedure(symbol))
491       return false;
492   return true;
493 }
494 
495 /// Core function that pre-lowers the extent and length parameters of
496 /// array constructors if it can, runs the ac-value analysis and
497 /// select the lowering strategy accordingly.
498 template <typename T>
499 static ArrayCtorLoweringStrategy selectArrayCtorLoweringStrategy(
500     mlir::Location loc, Fortran::lower::AbstractConverter &converter,
501     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
502     Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
503   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
504   mlir::Type idxType = builder.getIndexType();
505   // Try to gather the array constructor extent.
506   mlir::Value extent;
507   fir::SequenceType::Extent typeExtent = fir::SequenceType::getUnknownExtent();
508   auto shapeExpr =
509       Fortran::evaluate::GetShape(converter.getFoldingContext(), arrayCtorExpr);
510   if (shapeExpr && shapeExpr->size() == 1 && (*shapeExpr)[0]) {
511     const Fortran::evaluate::ExtentExpr &extentExpr = *(*shapeExpr)[0];
512     if (auto constantExtent = Fortran::evaluate::ToInt64(extentExpr)) {
513       typeExtent = *constantExtent;
514       extent = builder.createIntegerConstant(loc, idxType, typeExtent);
515     } else if (isCallFreeExpr(extentExpr)) {
516       // The expression built by expression analysis for the array constructor
517       // extent does not contain procedure symbols. It is side effect free.
518       // This could be relaxed to allow pure procedure, but some care must
519       // be taken to not bring in "unmapped" symbols from callee scopes.
520       extent = lowerExtentExpr(loc, converter, symMap, stmtCtx, extentExpr);
521     }
522     // Otherwise, the temporary will have to be built step by step with
523     // reallocation and the extent will only be known at the end of the array
524     // constructor evaluation.
525   }
526   // Convert the array constructor type and try to gather its length parameter
527   // values, if any.
528   mlir::SmallVector<mlir::Value> lengths;
529   mlir::Type elementType = LengthAndTypeCollector<T>::collect(
530       loc, converter, arrayCtorExpr, symMap, stmtCtx, lengths);
531   // Run an analysis of the array constructor ac-value.
532   ArrayCtorAnalysis analysis(converter.getFoldingContext(), arrayCtorExpr);
533   bool needToEvaluateOneExprToGetLengthParameters =
534       failedToGatherLengthParameters(elementType, lengths);
535 
536   // Based on what was gathered and the result of the analysis, select and
537   // instantiate the right lowering strategy for the array constructor.
538   if (!extent || needToEvaluateOneExprToGetLengthParameters ||
539       analysis.anyArrayExpr)
540     TODO(loc, "Lowering of array constructor requiring the runtime");
541 
542   auto declaredType = fir::SequenceType::get({typeExtent}, elementType);
543   // Note: array constructors containing impure ac-value expr are currently not
544   // rewritten to hlfir.elemental because impure expressions should be evaluated
545   // in order, and hlfir.elemental currently misses a way to indicate that.
546   if (analysis.isSingleImpliedDoWithOneScalarPureExpr())
547     return AsElementalStrategy(loc, builder, declaredType, extent, lengths);
548 
549   if (analysis.anyImpliedDo)
550     return InlinedTempStrategy(loc, builder, declaredType, extent, lengths);
551 
552   return LooplessInlinedTempStrategy(loc, builder, declaredType, extent,
553                                      lengths);
554 }
555 
556 /// Lower an ac-value expression \p expr and forward it to the selected
557 /// lowering strategy \p arrayBuilder,
558 template <typename T>
559 static void genAcValue(mlir::Location loc,
560                        Fortran::lower::AbstractConverter &converter,
561                        const Fortran::evaluate::Expr<T> &expr,
562                        Fortran::lower::SymMap &symMap,
563                        Fortran::lower::StatementContext &stmtCtx,
564                        ArrayCtorLoweringStrategy &arrayBuilder) {
565   if (expr.Rank() != 0)
566     TODO(loc, "array constructor with array ac-value in HLFIR");
567   // TODO: get rid of the toEvExpr indirection.
568   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
569   hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
570       loc, converter, toEvExpr(expr), symMap, stmtCtx);
571   value = hlfir::loadTrivialScalar(loc, builder, value);
572   arrayBuilder.pushValue(loc, builder, value);
573 }
574 
575 /// Lowers an ac-value implied-do \p impledDo according to the selected
576 /// lowering strategy \p arrayBuilder.
577 template <typename T>
578 static void genAcValue(mlir::Location loc,
579                        Fortran::lower::AbstractConverter &converter,
580                        const Fortran::evaluate::ImpliedDo<T> &impledDo,
581                        Fortran::lower::SymMap &symMap,
582                        Fortran::lower::StatementContext &stmtCtx,
583                        ArrayCtorLoweringStrategy &arrayBuilder) {
584   auto lowerIndex =
585       [&](const Fortran::evaluate::ExtentExpr expr) -> mlir::Value {
586     return lowerExtentExpr(loc, converter, symMap, stmtCtx, expr);
587   };
588   mlir::Value lower = lowerIndex(impledDo.lower());
589   mlir::Value upper = lowerIndex(impledDo.upper());
590   mlir::Value stride = lowerIndex(impledDo.stride());
591   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
592   mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint();
593   mlir::Value impliedDoIndexValue =
594       arrayBuilder.startImpliedDo(loc, builder, lower, upper, stride);
595   symMap.pushImpliedDoBinding(toStringRef(impledDo.name()),
596                               impliedDoIndexValue);
597   stmtCtx.pushScope();
598 
599   for (const auto &acValue : impledDo.values())
600     std::visit(
601         [&](const auto &x) {
602           genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
603         },
604         acValue.u);
605 
606   stmtCtx.finalizeAndPop();
607   symMap.popImpliedDoBinding();
608   builder.restoreInsertionPoint(insertPt);
609 }
610 
611 /// Entry point for evaluate::ArrayConstructor lowering.
612 template <typename T>
613 hlfir::EntityWithAttributes Fortran::lower::ArrayConstructorBuilder<T>::gen(
614     mlir::Location loc, Fortran::lower::AbstractConverter &converter,
615     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
616     Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
617   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
618   // Select the lowering strategy given the array constructor.
619   auto arrayBuilder = selectArrayCtorLoweringStrategy(
620       loc, converter, arrayCtorExpr, symMap, stmtCtx);
621   // Run the array lowering strategy through the ac-values.
622   for (const auto &acValue : arrayCtorExpr)
623     std::visit(
624         [&](const auto &x) {
625           genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
626         },
627         acValue.u);
628   hlfir::Entity hlfirExpr = arrayBuilder.finishArrayCtorLowering(loc, builder);
629   // Insert the clean-up for the created hlfir.expr.
630   fir::FirOpBuilder *bldr = &builder;
631   stmtCtx.attachCleanup(
632       [=]() { bldr->create<hlfir::DestroyOp>(loc, hlfirExpr); });
633   return hlfir::EntityWithAttributes{hlfirExpr};
634 }
635 
636 using namespace Fortran::evaluate;
637 using namespace Fortran::common;
638 FOR_EACH_SPECIFIC_TYPE(template class Fortran::lower::ArrayConstructorBuilder, )
639