xref: /llvm-project/flang/lib/Lower/ConvertArrayConstructor.cpp (revision ffde9f17300b52c9b53311455b8991cbfc6da377)
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/Todo.h"
18 #include "flang/Optimizer/HLFIR/HLFIROps.h"
19 
20 // Array constructors are lowered with three different strategies.
21 // All strategies are not possible with all array constructors.
22 //
23 // - Strategy 1: runtime approach (RuntimeTempStrategy).
24 //   This strategy works will all array constructors, but will create more
25 //   complex code that is harder to optimize. An allocatable temp is created,
26 //   it may be unallocated if the array constructor length parameters or extent
27 //   could not be computed. Then, the runtime is called to push lowered
28 //   ac-value (array constructor elements) into the allocatable. The runtime
29 //   will allocate or reallocate as needed while values are being pushed.
30 //   In the end, the allocatable contain a temporary with all the array
31 //   constructor evaluated elements.
32 //
33 // - Strategy 2: inlined temporary approach (InlinedTempStrategyImpl)
34 //   This strategy can only be used if the array constructor extent and length
35 //   parameters can be pre-computed without evaluating any ac-value, and if all
36 //   of the ac-value are scalars (at least for now).
37 //   A temporary is allocated inline in one go, and an index pointing at the
38 //   current ac-value position in the array constructor element sequence is
39 //   maintained and used to store ac-value as they are being lowered.
40 //
41 // - Strategy 3: "function of the indices" approach (AsElementalStrategy)
42 //   This strategy can only be used if the array constructor extent and length
43 //   parameters can be pre-computed and, if the array constructor is of the
44 //   form "[(scalar_expr, ac-implied-do-control)]". In this case, it is lowered
45 //   into an hlfir.elemental without creating any temporary in lowering. This
46 //   form should maximize the chance of array temporary elision when assigning
47 //   the array constructor, potentially reshaped, to an array variable.
48 //
49 //   The array constructor lowering looks like:
50 //   ```
51 //     strategy = selectArrayCtorLoweringStrategy(array-ctor-expr);
52 //     for (ac-value : array-ctor-expr)
53 //       if (ac-value is expression) {
54 //         strategy.pushValue(ac-value);
55 //       } else if (ac-value is implied-do) {
56 //         strategy.startImpliedDo(lower, upper, stride);
57 //         // lower nested values
58 //       }
59 //     result = strategy.finishArrayCtorLowering();
60 //   ```
61 
62 //===----------------------------------------------------------------------===//
63 //   Definition of the lowering strategies. Each lowering strategy is defined
64 //   as a class that implements "pushValue", "startImpliedDo", and
65 //   "finishArrayCtorLowering".
66 //===----------------------------------------------------------------------===//
67 
68 namespace {
69 /// Class that implements the "inlined temp strategy" to lower array
70 /// constructors. It must be further provided a CounterType class to specify how
71 /// the current ac-value insertion position is tracked.
72 template <typename CounterType>
73 class InlinedTempStrategyImpl {
74   /// Name that will be given to the temporary allocation and hlfir.declare in
75   /// the IR.
76   static constexpr char tempName[] = ".tmp.arrayctor";
77 
78 public:
79   /// Start lowering an array constructor according to the inline strategy.
80   /// The temporary is created right away.
81   InlinedTempStrategyImpl(mlir::Location loc, fir::FirOpBuilder &builder,
82                           fir::SequenceType declaredType, mlir::Value extent,
83                           llvm::ArrayRef<mlir::Value> lengths)
84       : one{builder.createIntegerConstant(loc, builder.getIndexType(), 1)},
85         counter{loc, builder, one} {
86     // Allocate the temporary storage.
87     llvm::SmallVector<mlir::Value, 1> extents{extent};
88     mlir::Value tempStorage = builder.createHeapTemporary(
89         loc, declaredType, tempName, extents, lengths);
90     mlir::Value shape = builder.genShape(loc, extents);
91     temp =
92         builder
93             .create<hlfir::DeclareOp>(loc, tempStorage, tempName, shape,
94                                       lengths, fir::FortranVariableFlagsAttr{})
95             .getBase();
96   }
97 
98   /// Push a lowered ac-value into the current insertion point and
99   /// increment the insertion point.
100   void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
101                  hlfir::Entity value) {
102     assert(value.isScalar() && "cannot use inlined temp with array values");
103     mlir::Value indexValue = counter.getAndIncrementIndex(loc, builder, one);
104     hlfir::Entity tempElement = hlfir::getElementAt(
105         loc, builder, hlfir::Entity{temp}, mlir::ValueRange{indexValue});
106     // TODO: "copy" would probably be better than assign to ensure there are no
107     // side effects (user assignments, temp, lhs finalization)?
108     // This only makes a difference for derived types, so for now derived types
109     // will use the runtime strategy to avoid any bad behaviors.
110     builder.create<hlfir::AssignOp>(loc, value, tempElement);
111   }
112 
113   /// Start a fir.do_loop with the control from an implied-do and return
114   /// the loop induction variable that is the ac-do-variable value.
115   /// Only usable if the counter is able to track the position through loops.
116   mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
117                              mlir::Value lower, mlir::Value upper,
118                              mlir::Value stride) {
119     if constexpr (!CounterType::canCountThroughLoops)
120       fir::emitFatalError(loc, "array constructor lowering is inconsistent");
121     auto loop = builder.create<fir::DoLoopOp>(loc, lower, upper, stride,
122                                               /*unordered=*/false,
123                                               /*finalCount=*/false);
124     builder.setInsertionPointToStart(loop.getBody());
125     return loop.getInductionVar();
126   }
127 
128   /// Move the temporary to an hlfir.expr value (array constructors are not
129   /// variables and cannot be further modified).
130   hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
131                                         fir::FirOpBuilder &builder) {
132     // Temp is created using createHeapTemporary.
133     mlir::Value mustFree = builder.createBool(loc, true);
134     auto hlfirExpr = builder.create<hlfir::AsExprOp>(loc, temp, mustFree);
135     return hlfir::Entity{hlfirExpr};
136   }
137 
138 private:
139   mlir::Value one;
140   CounterType counter;
141   mlir::Value temp;
142 };
143 
144 /// A simple SSA value counter to lower array constructors without any
145 /// implied-do in the "inlined temp strategy".
146 /// The SSA value being tracked by the counter (hence, this
147 /// cannot count through loops since the SSA value in the loop becomes
148 /// inaccessible after the loop).
149 /// Semantic analysis expression rewrites unroll implied do loop with
150 /// compile time constant bounds (even if huge). So this minimalistic
151 /// counter greatly reduces the generated IR for simple but big array
152 /// constructors [(i,i=1,constant-expr)] that are expected to be quite
153 /// common.
154 class ValueCounter {
155 public:
156   static constexpr bool canCountThroughLoops = false;
157   ValueCounter(mlir::Location loc, fir::FirOpBuilder &builder,
158                mlir::Value initialValue) {
159     indexValue = initialValue;
160   }
161 
162   mlir::Value getAndIncrementIndex(mlir::Location loc,
163                                    fir::FirOpBuilder &builder,
164                                    mlir::Value increment) {
165     mlir::Value currentValue = indexValue;
166     indexValue =
167         builder.create<mlir::arith::AddIOp>(loc, indexValue, increment);
168     return currentValue;
169   }
170 
171 private:
172   mlir::Value indexValue;
173 };
174 using LooplessInlinedTempStrategy = InlinedTempStrategyImpl<ValueCounter>;
175 
176 /// A generic memory based counter that can deal with all cases of
177 /// "inlined temp strategy". The counter value is stored in a temp
178 /// from which it is loaded, incremented, and stored every time an
179 /// ac-value is pushed.
180 class InMemoryCounter {
181 public:
182   static constexpr bool canCountThroughLoops = true;
183   InMemoryCounter(mlir::Location loc, fir::FirOpBuilder &builder,
184                   mlir::Value initialValue) {
185     indexVar = builder.createTemporary(loc, initialValue.getType());
186     builder.create<fir::StoreOp>(loc, initialValue, indexVar);
187   }
188 
189   mlir::Value getAndIncrementIndex(mlir::Location loc,
190                                    fir::FirOpBuilder &builder,
191                                    mlir::Value increment) const {
192     mlir::Value indexValue = builder.create<fir::LoadOp>(loc, indexVar);
193     indexValue =
194         builder.create<mlir::arith::AddIOp>(loc, indexValue, increment);
195     builder.create<fir::StoreOp>(loc, indexValue, indexVar);
196     return indexValue;
197   }
198 
199 private:
200   mlir::Value indexVar;
201 };
202 using InlinedTempStrategy = InlinedTempStrategyImpl<InMemoryCounter>;
203 
204 // TODO: add and implement AsElementalStrategy.
205 
206 // TODO: add and implement RuntimeTempStrategy.
207 
208 /// Wrapper class that dispatch to the selected array constructor lowering
209 /// strategy and does nothing else.
210 class ArrayCtorLoweringStrategy {
211 public:
212   template <typename A>
213   ArrayCtorLoweringStrategy(A &&impl) : implVariant{std::forward<A>(impl)} {}
214 
215   void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
216                  hlfir::Entity value) {
217     return std::visit(
218         [&](auto &impl) { return impl.pushValue(loc, builder, value); },
219         implVariant);
220   }
221 
222   mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
223                              mlir::Value lower, mlir::Value upper,
224                              mlir::Value stride) {
225     return std::visit(
226         [&](auto &impl) {
227           return impl.startImpliedDo(loc, builder, lower, upper, stride);
228         },
229         implVariant);
230   }
231 
232   hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
233                                         fir::FirOpBuilder &builder) {
234     return std::visit(
235         [&](auto &impl) { return impl.finishArrayCtorLowering(loc, builder); },
236         implVariant);
237   }
238 
239 private:
240   std::variant<InlinedTempStrategy, LooplessInlinedTempStrategy> implVariant;
241 };
242 } // namespace
243 
244 //===----------------------------------------------------------------------===//
245 //   Definition of selectArrayCtorLoweringStrategy and its helpers.
246 //   This is the code that analyses the evaluate::ArrayConstructor<T>,
247 //   pre-lowers the array constructor extent and length parameters if it can,
248 //   and chooses the lowering strategy.
249 //===----------------------------------------------------------------------===//
250 
251 namespace {
252 /// Helper class to lower the array constructor type and its length parameters.
253 /// The length parameters, if any, are only lowered if this does not require
254 /// evaluating an ac-value.
255 template <typename T>
256 struct LengthAndTypeCollector {
257   static mlir::Type collect(mlir::Location,
258                             Fortran::lower::AbstractConverter &converter,
259                             const Fortran::evaluate::ArrayConstructor<T> &,
260                             Fortran::lower::SymMap &,
261                             Fortran::lower::StatementContext &,
262                             mlir::SmallVectorImpl<mlir::Value> &) {
263     // Numerical and Logical types.
264     return Fortran::lower::getFIRType(&converter.getMLIRContext(), T::category,
265                                       T::kind, /*lenParams*/ {});
266   }
267 };
268 
269 template <>
270 struct LengthAndTypeCollector<Fortran::evaluate::SomeDerived> {
271   static mlir::Type collect(
272       mlir::Location loc, Fortran::lower::AbstractConverter &converter,
273       const Fortran::evaluate::ArrayConstructor<Fortran::evaluate::SomeDerived>
274           &arrayCtorExpr,
275       Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
276       mlir::SmallVectorImpl<mlir::Value> &lengths) {
277     TODO(loc, "collect derived type and length");
278   }
279 };
280 
281 template <int Kind>
282 using Character =
283     Fortran::evaluate::Type<Fortran::common::TypeCategory::Character, Kind>;
284 template <int Kind>
285 struct LengthAndTypeCollector<Character<Kind>> {
286   static mlir::Type collect(
287       mlir::Location loc, Fortran::lower::AbstractConverter &converter,
288       const Fortran::evaluate::ArrayConstructor<Character<Kind>> &arrayCtorExpr,
289       Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
290       mlir::SmallVectorImpl<mlir::Value> &lengths) {
291     TODO(loc, "collect character type and length");
292   }
293 };
294 } // namespace
295 
296 /// Does the array constructor have length parameters that
297 /// LengthAndTypeCollector::collect could not lower because this requires
298 /// lowering an ac-value and must be delayed?
299 static bool
300 failedToGatherLengthParameters(mlir::Type elementType,
301                                llvm::ArrayRef<mlir::Value> lengths) {
302   return (elementType.isa<fir::CharacterType>() ||
303           fir::isRecordWithTypeParameters(elementType)) &&
304          lengths.empty();
305 }
306 
307 namespace {
308 /// Structure that analyses the ac-value and implied-do of
309 /// evaluate::ArrayConstructor before they are lowered. It does not generate any
310 /// IR. The result of this analysis pass is used to select the lowering
311 /// strategy.
312 struct ArrayCtorAnalysis {
313   template <typename T>
314   ArrayCtorAnalysis(
315       const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr);
316 
317   // Can the array constructor easily be rewritten into an hlfir.elemental ?
318   bool isSingleImpliedDoWithOneScalarExpr() const {
319     return !anyArrayExpr && isPerfectLoopNest &&
320            innerNumberOfExprIfPrefectNest == 1 && depthIfPerfectLoopNest == 1;
321   }
322 
323   bool anyImpliedDo{false};
324   bool anyArrayExpr{false};
325   bool isPerfectLoopNest{true};
326   std::int64_t innerNumberOfExprIfPrefectNest = 0;
327   std::int64_t depthIfPerfectLoopNest = 0;
328 };
329 } // namespace
330 
331 template <typename T>
332 ArrayCtorAnalysis::ArrayCtorAnalysis(
333     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr) {
334   llvm::SmallVector<const Fortran::evaluate::ArrayConstructorValues<T> *>
335       arrayValueListStack{&arrayCtorExpr};
336   // Loop through the ac-value-list(s) of the array constructor.
337   while (!arrayValueListStack.empty()) {
338     std::int64_t localNumberOfImpliedDo = 0;
339     std::int64_t localNumberOfExpr = 0;
340     // Loop though the ac-value of an ac-value list, and add any nested
341     // ac-value-list of ac-implied-do to the stack.
342     for (const Fortran::evaluate::ArrayConstructorValue<T> &acValue :
343          *arrayValueListStack.pop_back_val())
344       std::visit(Fortran::common::visitors{
345                      [&](const Fortran::evaluate::ImpliedDo<T> &impledDo) {
346                        arrayValueListStack.push_back(&impledDo.values());
347                        localNumberOfImpliedDo++;
348                      },
349                      [&](const Fortran::evaluate::Expr<T> &expr) {
350                        localNumberOfExpr++;
351                        anyArrayExpr = anyArrayExpr || expr.Rank() > 0;
352                      }},
353                  acValue.u);
354     anyImpliedDo = anyImpliedDo || localNumberOfImpliedDo > 0;
355 
356     if (localNumberOfImpliedDo == 0) {
357       // Leaf ac-value-list in the array constructor ac-value tree.
358       if (isPerfectLoopNest)
359         // This this the only leaf of the array-constructor (the array
360         // constructor is a nest of single implied-do with a list of expression
361         // in the last deeper implied do). e.g: "[((i+j, i=1,n)j=1,m)]".
362         innerNumberOfExprIfPrefectNest = localNumberOfExpr;
363     } else if (localNumberOfImpliedDo == 1 && localNumberOfExpr == 0) {
364       // Perfect implied-do nest new level.
365       ++depthIfPerfectLoopNest;
366     } else {
367       // More than one implied-do, or at least one implied-do and an expr
368       // at that level. This will not form a perfect nest. Examples:
369       // "[a, (i, i=1,n)]" or "[(i, i=1,n), (j, j=1,m)]".
370       isPerfectLoopNest = false;
371     }
372   }
373 }
374 
375 /// Helper to lower a scalar extent expression (like implied-do bounds).
376 static mlir::Value lowerExtentExpr(mlir::Location loc,
377                                    Fortran::lower::AbstractConverter &converter,
378                                    Fortran::lower::SymMap &symMap,
379                                    Fortran::lower::StatementContext &stmtCtx,
380                                    const Fortran::evaluate::ExtentExpr &expr) {
381   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
382   mlir::IndexType idxTy = builder.getIndexType();
383   hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
384       loc, converter, toEvExpr(expr), symMap, stmtCtx);
385   value = hlfir::loadTrivialScalar(loc, builder, value);
386   return builder.createConvert(loc, idxTy, value);
387 }
388 
389 /// Does \p expr contain no calls to user function?
390 static bool isCallFreeExpr(const Fortran::evaluate::ExtentExpr &expr) {
391   for (const Fortran::semantics::Symbol &symbol :
392        Fortran::evaluate::CollectSymbols(expr))
393     if (Fortran::semantics::IsProcedure(symbol))
394       return false;
395   return true;
396 }
397 
398 /// Core function that pre-lowers the extent and length parameters of
399 /// array constructors if it can, runs the ac-value analysis and
400 /// select the lowering strategy accordingly.
401 template <typename T>
402 static ArrayCtorLoweringStrategy selectArrayCtorLoweringStrategy(
403     mlir::Location loc, Fortran::lower::AbstractConverter &converter,
404     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
405     Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
406   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
407   mlir::Type idxType = builder.getIndexType();
408   // Try to gather the array constructor extent.
409   mlir::Value extent;
410   fir::SequenceType::Extent typeExtent = fir::SequenceType::getUnknownExtent();
411   auto shapeExpr =
412       Fortran::evaluate::GetShape(converter.getFoldingContext(), arrayCtorExpr);
413   if (shapeExpr && shapeExpr->size() == 1 && (*shapeExpr)[0]) {
414     const Fortran::evaluate::ExtentExpr &extentExpr = *(*shapeExpr)[0];
415     if (auto constantExtent = Fortran::evaluate::ToInt64(extentExpr)) {
416       typeExtent = *constantExtent;
417       extent = builder.createIntegerConstant(loc, idxType, typeExtent);
418     } else if (isCallFreeExpr(extentExpr)) {
419       // The expression built by expression analysis for the array constructor
420       // extent does not contain procedure symbols. It is side effect free.
421       // This could be relaxed to allow pure procedure, but some care must
422       // be taken to not bring in "unmapped" symbols from callee scopes.
423       extent = lowerExtentExpr(loc, converter, symMap, stmtCtx, extentExpr);
424     }
425     // Otherwise, the temporary will have to be built step by step with
426     // reallocation and the extent will only be known at the end of the array
427     // constructor evaluation.
428   }
429   // Convert the array constructor type and try to gather its length parameter
430   // values, if any.
431   mlir::SmallVector<mlir::Value> lengths;
432   mlir::Type elementType = LengthAndTypeCollector<T>::collect(
433       loc, converter, arrayCtorExpr, symMap, stmtCtx, lengths);
434   // Run an analysis of the array constructor ac-value.
435   ArrayCtorAnalysis analysis(arrayCtorExpr);
436   bool needToEvaluateOneExprToGetLengthParameters =
437       failedToGatherLengthParameters(elementType, lengths);
438 
439   // Based on what was gathered and the result of the analysis, select and
440   // instantiate the right lowering strategy for the array constructor.
441   if (!extent || needToEvaluateOneExprToGetLengthParameters ||
442       analysis.anyArrayExpr)
443     TODO(loc, "Lowering of array constructor requiring the runtime");
444 
445   auto declaredType = fir::SequenceType::get({typeExtent}, elementType);
446   if (analysis.isSingleImpliedDoWithOneScalarExpr())
447     TODO(loc, "Lowering of array constructor as hlfir.elemental");
448 
449   if (analysis.anyImpliedDo)
450     return InlinedTempStrategy(loc, builder, declaredType, extent, lengths);
451 
452   return LooplessInlinedTempStrategy(loc, builder, declaredType, extent,
453                                      lengths);
454 }
455 
456 /// Lower an ac-value expression \p expr and forward it to the selected
457 /// lowering strategy \p arrayBuilder,
458 template <typename T>
459 static void genAcValue(mlir::Location loc,
460                        Fortran::lower::AbstractConverter &converter,
461                        const Fortran::evaluate::Expr<T> &expr,
462                        Fortran::lower::SymMap &symMap,
463                        Fortran::lower::StatementContext &stmtCtx,
464                        ArrayCtorLoweringStrategy &arrayBuilder) {
465   if (expr.Rank() != 0)
466     TODO(loc, "array constructor with array ac-value in HLFIR");
467   // TODO: get rid of the toEvExpr indirection.
468   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
469   hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
470       loc, converter, toEvExpr(expr), symMap, stmtCtx);
471   value = hlfir::loadTrivialScalar(loc, builder, value);
472   arrayBuilder.pushValue(loc, builder, value);
473 }
474 
475 /// Lowers an ac-value implied-do \p impledDo according to the selected
476 /// lowering strategy \p arrayBuilder.
477 template <typename T>
478 static void genAcValue(mlir::Location loc,
479                        Fortran::lower::AbstractConverter &converter,
480                        const Fortran::evaluate::ImpliedDo<T> &impledDo,
481                        Fortran::lower::SymMap &symMap,
482                        Fortran::lower::StatementContext &stmtCtx,
483                        ArrayCtorLoweringStrategy &arrayBuilder) {
484   auto lowerIndex =
485       [&](const Fortran::evaluate::ExtentExpr expr) -> mlir::Value {
486     return lowerExtentExpr(loc, converter, symMap, stmtCtx, expr);
487   };
488   mlir::Value lower = lowerIndex(impledDo.lower());
489   mlir::Value upper = lowerIndex(impledDo.upper());
490   mlir::Value stride = lowerIndex(impledDo.stride());
491   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
492   mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint();
493   mlir::Value impliedDoIndexValue =
494       arrayBuilder.startImpliedDo(loc, builder, lower, upper, stride);
495   symMap.pushImpliedDoBinding(toStringRef(impledDo.name()),
496                               impliedDoIndexValue);
497   stmtCtx.pushScope();
498 
499   for (const auto &acValue : impledDo.values())
500     std::visit(
501         [&](const auto &x) {
502           genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
503         },
504         acValue.u);
505 
506   stmtCtx.finalizeAndPop();
507   symMap.popImpliedDoBinding();
508   builder.restoreInsertionPoint(insertPt);
509 }
510 
511 /// Entry point for evaluate::ArrayConstructor lowering.
512 template <typename T>
513 hlfir::EntityWithAttributes Fortran::lower::ArrayConstructorBuilder<T>::gen(
514     mlir::Location loc, Fortran::lower::AbstractConverter &converter,
515     const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
516     Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
517   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
518   // Select the lowering strategy given the array constructor.
519   auto arrayBuilder = selectArrayCtorLoweringStrategy(
520       loc, converter, arrayCtorExpr, symMap, stmtCtx);
521   // Run the array lowering strategy through the ac-values.
522   for (const auto &acValue : arrayCtorExpr)
523     std::visit(
524         [&](const auto &x) {
525           genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
526         },
527         acValue.u);
528   hlfir::Entity hlfirExpr = arrayBuilder.finishArrayCtorLowering(loc, builder);
529   // Insert the clean-up for the created hlfir.expr.
530   fir::FirOpBuilder *bldr = &builder;
531   stmtCtx.attachCleanup(
532       [=]() { bldr->create<hlfir::DestroyOp>(loc, hlfirExpr); });
533   return hlfir::EntityWithAttributes{hlfirExpr};
534 }
535 
536 using namespace Fortran::evaluate;
537 using namespace Fortran::common;
538 FOR_EACH_SPECIFIC_TYPE(template class Fortran::lower::ArrayConstructorBuilder, )
539