//===- ConvertArrayConstructor.cpp -- Array Constructor ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "flang/Lower/ConvertArrayConstructor.h"
#include "flang/Evaluate/expression.h"
#include "flang/Lower/AbstractConverter.h"
#include "flang/Lower/ConvertExprToHLFIR.h"
#include "flang/Lower/ConvertType.h"
#include "flang/Lower/StatementContext.h"
#include "flang/Lower/SymbolMap.h"
#include "flang/Optimizer/Builder/HLFIRTools.h"
#include "flang/Optimizer/Builder/Runtime/ArrayConstructor.h"
#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
#include "flang/Optimizer/Builder/TemporaryStorage.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"

// Array constructors are lowered with three different strategies.
// All strategies are not possible with all array constructors.
//
// - Strategy 1: runtime approach (RuntimeTempStrategy).
//   This strategy works will all array constructors, but will create more
//   complex code that is harder to optimize. An allocatable temp is created,
//   it may be unallocated if the array constructor length parameters or extent
//   could not be computed. Then, the runtime is called to push lowered
//   ac-value (array constructor elements) into the allocatable. The runtime
//   will allocate or reallocate as needed while values are being pushed.
//   In the end, the allocatable contain a temporary with all the array
//   constructor evaluated elements.
//
// - Strategy 2: inlined temporary approach (InlinedTempStrategyImpl)
//   This strategy can only be used if the array constructor extent and length
//   parameters can be pre-computed without evaluating any ac-value, and if all
//   of the ac-value are scalars (at least for now).
//   A temporary is allocated inline in one go, and an index pointing at the
//   current ac-value position in the array constructor element sequence is
//   maintained and used to store ac-value as they are being lowered.
//
// - Strategy 3: "function of the indices" approach (AsElementalStrategy)
//   This strategy can only be used if the array constructor extent and length
//   parameters can be pre-computed and, if the array constructor is of the
//   form "[(scalar_expr, ac-implied-do-control)]". In this case, it is lowered
//   into an hlfir.elemental without creating any temporary in lowering. This
//   form should maximize the chance of array temporary elision when assigning
//   the array constructor, potentially reshaped, to an array variable.
//
//   The array constructor lowering looks like:
//   ```
//     strategy = selectArrayCtorLoweringStrategy(array-ctor-expr);
//     for (ac-value : array-ctor-expr)
//       if (ac-value is expression) {
//         strategy.pushValue(ac-value);
//       } else if (ac-value is implied-do) {
//         strategy.startImpliedDo(lower, upper, stride);
//         strategy.startImpliedDoScope();
//         // lower nested values
//         ...
//         strategy.endImpliedDoScope();
//       }
//     result = strategy.finishArrayCtorLowering();
//   ```

//===----------------------------------------------------------------------===//
//   Definition of the lowering strategies. Each lowering strategy is defined
//   as a class that implements "pushValue", "startImpliedDo" and
//   "finishArrayCtorLowering". A strategy may optionally override
//   "startImpliedDoScope" and "endImpliedDoScope" virtual methods
//   of its base class StrategyBase.
//===----------------------------------------------------------------------===//

namespace {
/// Class provides common implementation of scope push/pop methods
/// that update StatementContext scopes and SymMap bindings.
/// They might be overridden by the lowering strategies, e.g.
/// see AsElementalStrategy.
class StrategyBase {
public:
  StrategyBase(Fortran::lower::StatementContext &stmtCtx,
               Fortran::lower::SymMap &symMap)
      : stmtCtx{stmtCtx}, symMap{symMap} {};
  virtual ~StrategyBase() = default;

  virtual void startImpliedDoScope(llvm::StringRef doName,
                                   mlir::Value indexValue) {
    symMap.pushImpliedDoBinding(doName, indexValue);
    stmtCtx.pushScope();
  }

  virtual void endImpliedDoScope() {
    stmtCtx.finalizeAndPop();
    symMap.popImpliedDoBinding();
  }

protected:
  Fortran::lower::StatementContext &stmtCtx;
  Fortran::lower::SymMap &symMap;
};

/// Class that implements the "inlined temp strategy" to lower array
/// constructors. It must be provided a boolean to indicate if the array
/// constructor has any implied-do-loop.
template <bool hasLoops>
class InlinedTempStrategyImpl : public StrategyBase,
                                public fir::factory::HomogeneousScalarStack {
  /// Name that will be given to the temporary allocation and hlfir.declare in
  /// the IR.
  static constexpr char tempName[] = ".tmp.arrayctor";

public:
  /// Start lowering an array constructor according to the inline strategy.
  /// The temporary is created right away.
  InlinedTempStrategyImpl(mlir::Location loc, fir::FirOpBuilder &builder,
                          Fortran::lower::StatementContext &stmtCtx,
                          Fortran::lower::SymMap &symMap,
                          fir::SequenceType declaredType, mlir::Value extent,
                          llvm::ArrayRef<mlir::Value> lengths)
      : StrategyBase{stmtCtx, symMap},
        fir::factory::HomogeneousScalarStack{
            loc,      builder, declaredType,
            extent,   lengths, /*allocateOnHeap=*/true,
            hasLoops, tempName} {}

  /// Push a lowered ac-value into the current insertion point and
  /// increment the insertion point.
  using fir::factory::HomogeneousScalarStack::pushValue;

  /// Start a fir.do_loop with the control from an implied-do and return
  /// the loop induction variable that is the ac-do-variable value.
  /// Only usable if the counter is able to track the position through loops.
  mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
                             mlir::Value lower, mlir::Value upper,
                             mlir::Value stride) {
    if constexpr (!hasLoops)
      fir::emitFatalError(loc, "array constructor lowering is inconsistent");
    auto loop = builder.create<fir::DoLoopOp>(loc, lower, upper, stride,
                                              /*unordered=*/false,
                                              /*finalCount=*/false);
    builder.setInsertionPointToStart(loop.getBody());
    return loop.getInductionVar();
  }

  /// Move the temporary to an hlfir.expr value (array constructors are not
  /// variables and cannot be further modified).
  hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
                                        fir::FirOpBuilder &builder) {
    return moveStackAsArrayExpr(loc, builder);
  }
};

/// Semantic analysis expression rewrites unroll implied do loop with
/// compile time constant bounds (even if huge). So using a minimalistic
/// counter greatly reduces the generated IR for simple but big array
/// constructors [(i,i=1,constant-expr)] that are expected to be quite
/// common.
using LooplessInlinedTempStrategy = InlinedTempStrategyImpl</*hasLoops=*/false>;
/// A generic memory based counter that can deal with all cases of
/// "inlined temp strategy". The counter value is stored in a temp
/// from which it is loaded, incremented, and stored every time an
/// ac-value is pushed.
using InlinedTempStrategy = InlinedTempStrategyImpl</*hasLoops=*/true>;

/// Class that implements the "as function of the indices" lowering strategy.
/// It will lower [(scalar_expr(i), i=l,u,s)] to:
/// ```
///   %extent = max((%u-%l+1)/%s, 0)
///   %shape = fir.shape %extent
///   %elem = hlfir.elemental %shape {
///     ^bb0(%pos:index):
///      %i = %l+(%i-1)*%s
///      %value = scalar_expr(%i)
///       hlfir.yield_element %value
///    }
/// ```
/// That way, no temporary is created in lowering, and if the array constructor
/// is part of a more complex elemental expression, or an assignment, it will be
/// trivial to "inline" it in the expression or assignment loops if allowed by
/// alias analysis.
/// This lowering is however only possible for the form of array constructors as
/// in the illustration above. It could be extended to deeper independent
/// implied-do nest and wrapped in an hlfir.reshape to a rank 1 array. But this
/// op does not exist yet, so this is left for the future if it appears
/// profitable.
class AsElementalStrategy : public StrategyBase {
public:
  /// The constructor only gathers the operands to create the hlfir.elemental.
  AsElementalStrategy(mlir::Location loc, fir::FirOpBuilder &builder,
                      Fortran::lower::StatementContext &stmtCtx,
                      Fortran::lower::SymMap &symMap,
                      fir::SequenceType declaredType, mlir::Value extent,
                      llvm::ArrayRef<mlir::Value> lengths)
      : StrategyBase{stmtCtx, symMap}, shape{builder.genShape(loc, {extent})},
        lengthParams{lengths}, exprType{getExprType(declaredType)} {}

  static hlfir::ExprType getExprType(fir::SequenceType declaredType) {
    // Note: 7.8 point 4: the dynamic type of an array constructor is its static
    // type, it is not polymorphic.
    return hlfir::ExprType::get(declaredType.getContext(),
                                declaredType.getShape(),
                                declaredType.getEleTy(),
                                /*isPolymorphic=*/false);
  }

  /// Create the hlfir.elemental and compute the ac-implied-do-index value
  /// given the lower bound and stride (compute "%i" in the illustration above).
  mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
                             mlir::Value lower, mlir::Value upper,
                             mlir::Value stride) {
    assert(!elementalOp && "expected only one implied-do");
    mlir::Value one =
        builder.createIntegerConstant(loc, builder.getIndexType(), 1);
    elementalOp = builder.create<hlfir::ElementalOp>(
        loc, exprType, shape,
        /*mold=*/nullptr, lengthParams, /*isUnordered=*/true);
    builder.setInsertionPointToStart(elementalOp.getBody());
    // implied-do-index = lower+((i-1)*stride)
    mlir::Value diff = builder.create<mlir::arith::SubIOp>(
        loc, elementalOp.getIndices()[0], one);
    mlir::Value mul = builder.create<mlir::arith::MulIOp>(loc, diff, stride);
    mlir::Value add = builder.create<mlir::arith::AddIOp>(loc, lower, mul);
    return add;
  }

  /// Create the elemental hlfir.yield_element with the scalar ac-value.
  void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
                 hlfir::Entity value) {
    assert(value.isScalar() && "cannot use hlfir.elemental with array values");
    assert(elementalOp && "array constructor must contain an outer implied-do");
    mlir::Value elementResult = value;
    if (fir::isa_trivial(elementResult.getType()))
      elementResult =
          builder.createConvert(loc, exprType.getElementType(), elementResult);

    // The clean-ups associated with the implied-do body operations
    // must be initiated before the YieldElementOp, so we have to pop the scope
    // right now.
    stmtCtx.finalizeAndPop();

    // This is a hacky way to get rid of the DestroyOp clean-up
    // associated with the final ac-value result if it is hlfir.expr.
    // Example:
    //   ... = (/(REPEAT(REPEAT(CHAR(i),2),2),i=1,n)/)
    // Each intrinsic call lowering will produce hlfir.expr result
    // with the associated clean-up, but only the last of them
    // is wrong. It is wrong because the value is used in hlfir.yield_element,
    // so it cannot be destroyed.
    mlir::Operation *destroyOp = nullptr;
    for (mlir::Operation *useOp : elementResult.getUsers())
      if (mlir::isa<hlfir::DestroyOp>(useOp)) {
        if (destroyOp)
          fir::emitFatalError(loc,
                              "multiple DestroyOp's for ac-value expression");
        destroyOp = useOp;
      }

    if (destroyOp)
      destroyOp->erase();

    builder.create<hlfir::YieldElementOp>(loc, elementResult);
  }

  // Override the default, because the context scope must be popped in
  // pushValue().
  virtual void endImpliedDoScope() override { symMap.popImpliedDoBinding(); }

  /// Return the created hlfir.elemental.
  hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
                                        fir::FirOpBuilder &builder) {
    return hlfir::Entity{elementalOp};
  }

private:
  mlir::Value shape;
  llvm::SmallVector<mlir::Value> lengthParams;
  hlfir::ExprType exprType;
  hlfir::ElementalOp elementalOp{};
};

/// Class that implements the "runtime temp strategy" to lower array
/// constructors.
class RuntimeTempStrategy : public StrategyBase {
  /// Name that will be given to the temporary allocation and hlfir.declare in
  /// the IR.
  static constexpr char tempName[] = ".tmp.arrayctor";

public:
  /// Start lowering an array constructor according to the runtime strategy.
  /// The temporary is only created if the extents and length parameters are
  /// already known. Otherwise, the handling of the allocation (and
  /// reallocation) is left up to the runtime.
  /// \p extent is the pre-computed extent of the array constructor, if it could
  /// be pre-computed. It is std::nullopt otherwise.
  /// \p lengths are the pre-computed length parameters of the array
  /// constructor, if they could be precomputed. \p missingLengthParameters is
  /// set to true if the length parameters could not be precomputed.
  RuntimeTempStrategy(mlir::Location loc, fir::FirOpBuilder &builder,
                      Fortran::lower::StatementContext &stmtCtx,
                      Fortran::lower::SymMap &symMap,
                      fir::SequenceType declaredType,
                      std::optional<mlir::Value> extent,
                      llvm::ArrayRef<mlir::Value> lengths,
                      bool missingLengthParameters)
      : StrategyBase{stmtCtx, symMap},
        arrayConstructorElementType{declaredType.getEleTy()} {
    mlir::Type heapType = fir::HeapType::get(declaredType);
    mlir::Type boxType = fir::BoxType::get(heapType);
    allocatableTemp = builder.createTemporary(loc, boxType, tempName);
    mlir::Value initialBoxValue;
    if (extent && !missingLengthParameters) {
      llvm::SmallVector<mlir::Value, 1> extents{*extent};
      mlir::Value tempStorage = builder.createHeapTemporary(
          loc, declaredType, tempName, extents, lengths);
      mlir::Value shape = builder.genShape(loc, extents);
      declare = builder.create<hlfir::DeclareOp>(
          loc, tempStorage, tempName, shape, lengths,
          /*dummy_scope=*/nullptr, fir::FortranVariableFlagsAttr{});
      initialBoxValue =
          builder.createBox(loc, boxType, declare->getOriginalBase(), shape,
                            /*slice=*/mlir::Value{}, lengths, /*tdesc=*/{});
    } else {
      // The runtime will have to do the initial allocation.
      // The declare operation cannot be emitted in this case since the final
      // array constructor has not yet been allocated. Instead, the resulting
      // temporary variable will be extracted from the allocatable descriptor
      // after all the API calls.
      // Prepare the initial state of the allocatable descriptor with a
      // deallocated status and all the available knowledge about the extent
      // and length parameters.
      llvm::SmallVector<mlir::Value> emboxLengths(lengths);
      if (!extent)
        extent = builder.createIntegerConstant(loc, builder.getIndexType(), 0);
      if (missingLengthParameters) {
        if (mlir::isa<fir::CharacterType>(declaredType.getEleTy()))
          emboxLengths.push_back(builder.createIntegerConstant(
              loc, builder.getCharacterLengthType(), 0));
        else
          TODO(loc,
               "parametrized derived type array constructor without type-spec");
      }
      mlir::Value nullAddr = builder.createNullConstant(loc, heapType);
      mlir::Value shape = builder.genShape(loc, {*extent});
      initialBoxValue = builder.createBox(loc, boxType, nullAddr, shape,
                                          /*slice=*/mlir::Value{}, emboxLengths,
                                          /*tdesc=*/{});
    }
    builder.create<fir::StoreOp>(loc, initialBoxValue, allocatableTemp);
    arrayConstructorVector = fir::runtime::genInitArrayConstructorVector(
        loc, builder, allocatableTemp,
        builder.createBool(loc, missingLengthParameters));
  }

  bool useSimplePushRuntime(hlfir::Entity value) {
    return value.isScalar() &&
           !mlir::isa<fir::CharacterType>(arrayConstructorElementType) &&
           !fir::isRecordWithAllocatableMember(arrayConstructorElementType) &&
           !fir::isRecordWithTypeParameters(arrayConstructorElementType);
  }

  /// Push a lowered ac-value into the array constructor vector using
  /// the runtime API.
  void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
                 hlfir::Entity value) {
    if (useSimplePushRuntime(value)) {
      auto [addrExv, cleanUp] = hlfir::convertToAddress(
          loc, builder, value, arrayConstructorElementType);
      mlir::Value addr = fir::getBase(addrExv);
      if (mlir::isa<fir::BaseBoxType>(addr.getType()))
        addr = builder.create<fir::BoxAddrOp>(loc, addr);
      fir::runtime::genPushArrayConstructorSimpleScalar(
          loc, builder, arrayConstructorVector, addr);
      if (cleanUp)
        (*cleanUp)();
      return;
    }
    auto [boxExv, cleanUp] =
        hlfir::convertToBox(loc, builder, value, arrayConstructorElementType);
    fir::runtime::genPushArrayConstructorValue(
        loc, builder, arrayConstructorVector, fir::getBase(boxExv));
    if (cleanUp)
      (*cleanUp)();
  }

  /// Start a fir.do_loop with the control from an implied-do and return
  /// the loop induction variable that is the ac-do-variable value.
  mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
                             mlir::Value lower, mlir::Value upper,
                             mlir::Value stride) {
    auto loop = builder.create<fir::DoLoopOp>(loc, lower, upper, stride,
                                              /*unordered=*/false,
                                              /*finalCount=*/false);
    builder.setInsertionPointToStart(loop.getBody());
    return loop.getInductionVar();
  }

  /// Move the temporary to an hlfir.expr value (array constructors are not
  /// variables and cannot be further modified).
  hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
                                        fir::FirOpBuilder &builder) {
    // Temp is created using createHeapTemporary, or allocated on the heap
    // by the runtime.
    mlir::Value mustFree = builder.createBool(loc, true);
    mlir::Value temp;
    if (declare)
      temp = declare->getBase();
    else
      temp = hlfir::derefPointersAndAllocatables(
          loc, builder, hlfir::Entity{allocatableTemp});
    auto hlfirExpr = builder.create<hlfir::AsExprOp>(loc, temp, mustFree);
    return hlfir::Entity{hlfirExpr};
  }

private:
  /// Element type of the array constructor being built.
  mlir::Type arrayConstructorElementType;
  /// Allocatable descriptor for the storage of the array constructor being
  /// built.
  mlir::Value allocatableTemp;
  /// Structure that allows the runtime API to maintain the status of
  /// of the array constructor being built between two API calls.
  mlir::Value arrayConstructorVector;
  /// DeclareOp for the array constructor storage, if it was possible to
  /// allocate it before any API calls.
  std::optional<hlfir::DeclareOp> declare;
};

/// Wrapper class that dispatch to the selected array constructor lowering
/// strategy and does nothing else.
class ArrayCtorLoweringStrategy {
public:
  template <typename A>
  ArrayCtorLoweringStrategy(A &&impl) : implVariant{std::forward<A>(impl)} {}

  void pushValue(mlir::Location loc, fir::FirOpBuilder &builder,
                 hlfir::Entity value) {
    return Fortran::common::visit(
        [&](auto &impl) { return impl.pushValue(loc, builder, value); },
        implVariant);
  }

  mlir::Value startImpliedDo(mlir::Location loc, fir::FirOpBuilder &builder,
                             mlir::Value lower, mlir::Value upper,
                             mlir::Value stride) {
    return Fortran::common::visit(
        [&](auto &impl) {
          return impl.startImpliedDo(loc, builder, lower, upper, stride);
        },
        implVariant);
  }

  hlfir::Entity finishArrayCtorLowering(mlir::Location loc,
                                        fir::FirOpBuilder &builder) {
    return Fortran::common::visit(
        [&](auto &impl) { return impl.finishArrayCtorLowering(loc, builder); },
        implVariant);
  }

  void startImpliedDoScope(llvm::StringRef doName, mlir::Value indexValue) {
    Fortran::common::visit(
        [&](auto &impl) {
          return impl.startImpliedDoScope(doName, indexValue);
        },
        implVariant);
  }

  void endImpliedDoScope() {
    Fortran::common::visit([&](auto &impl) { return impl.endImpliedDoScope(); },
                           implVariant);
  }

private:
  std::variant<InlinedTempStrategy, LooplessInlinedTempStrategy,
               AsElementalStrategy, RuntimeTempStrategy>
      implVariant;
};
} // namespace

//===----------------------------------------------------------------------===//
//   Definition of selectArrayCtorLoweringStrategy and its helpers.
//   This is the code that analyses the evaluate::ArrayConstructor<T>,
//   pre-lowers the array constructor extent and length parameters if it can,
//   and chooses the lowering strategy.
//===----------------------------------------------------------------------===//

/// Helper to lower a scalar extent expression (like implied-do bounds).
static mlir::Value lowerExtentExpr(mlir::Location loc,
                                   Fortran::lower::AbstractConverter &converter,
                                   Fortran::lower::SymMap &symMap,
                                   Fortran::lower::StatementContext &stmtCtx,
                                   const Fortran::evaluate::ExtentExpr &expr) {
  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
  mlir::IndexType idxTy = builder.getIndexType();
  hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
      loc, converter, toEvExpr(expr), symMap, stmtCtx);
  value = hlfir::loadTrivialScalar(loc, builder, value);
  return builder.createConvert(loc, idxTy, value);
}

namespace {
/// Helper class to lower the array constructor type and its length parameters.
/// The length parameters, if any, are only lowered if this does not require
/// evaluating an ac-value.
template <typename T>
struct LengthAndTypeCollector {
  static mlir::Type collect(mlir::Location,
                            Fortran::lower::AbstractConverter &converter,
                            const Fortran::evaluate::ArrayConstructor<T> &,
                            Fortran::lower::SymMap &,
                            Fortran::lower::StatementContext &,
                            mlir::SmallVectorImpl<mlir::Value> &) {
    // Numerical and Logical types.
    return Fortran::lower::getFIRType(&converter.getMLIRContext(), T::category,
                                      T::kind, /*lenParams*/ {});
  }
};

template <>
struct LengthAndTypeCollector<Fortran::evaluate::SomeDerived> {
  static mlir::Type collect(
      mlir::Location loc, Fortran::lower::AbstractConverter &converter,
      const Fortran::evaluate::ArrayConstructor<Fortran::evaluate::SomeDerived>
          &arrayCtorExpr,
      Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
      mlir::SmallVectorImpl<mlir::Value> &lengths) {
    // Array constructors cannot be unlimited polymorphic (C7113), so there must
    // be a derived type spec available.
    return Fortran::lower::translateDerivedTypeToFIRType(
        converter, arrayCtorExpr.result().derivedTypeSpec());
  }
};

template <int Kind>
using Character =
    Fortran::evaluate::Type<Fortran::common::TypeCategory::Character, Kind>;
template <int Kind>
struct LengthAndTypeCollector<Character<Kind>> {
  static mlir::Type collect(
      mlir::Location loc, Fortran::lower::AbstractConverter &converter,
      const Fortran::evaluate::ArrayConstructor<Character<Kind>> &arrayCtorExpr,
      Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
      mlir::SmallVectorImpl<mlir::Value> &lengths) {
    llvm::SmallVector<Fortran::lower::LenParameterTy> typeLengths;
    if (const Fortran::evaluate::ExtentExpr *lenExpr = arrayCtorExpr.LEN()) {
      lengths.push_back(
          lowerExtentExpr(loc, converter, symMap, stmtCtx, *lenExpr));
      if (std::optional<std::int64_t> cstLen =
              Fortran::evaluate::ToInt64(*lenExpr))
        typeLengths.push_back(*cstLen);
    }
    return Fortran::lower::getFIRType(&converter.getMLIRContext(),
                                      Fortran::common::TypeCategory::Character,
                                      Kind, typeLengths);
  }
};
} // namespace

/// Does the array constructor have length parameters that
/// LengthAndTypeCollector::collect could not lower because this requires
/// lowering an ac-value and must be delayed?
static bool missingLengthParameters(mlir::Type elementType,
                                    llvm::ArrayRef<mlir::Value> lengths) {
  return (mlir::isa<fir::CharacterType>(elementType) ||
          fir::isRecordWithTypeParameters(elementType)) &&
         lengths.empty();
}

namespace {
/// Structure that analyses the ac-value and implied-do of
/// evaluate::ArrayConstructor before they are lowered. It does not generate any
/// IR. The result of this analysis pass is used to select the lowering
/// strategy.
struct ArrayCtorAnalysis {
  template <typename T>
  ArrayCtorAnalysis(
      Fortran::evaluate::FoldingContext &,
      const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr);

  // Can the array constructor easily be rewritten into an hlfir.elemental ?
  bool isSingleImpliedDoWithOneScalarPureExpr() const {
    return !anyArrayExpr && isPerfectLoopNest &&
           innerNumberOfExprIfPrefectNest == 1 && depthIfPerfectLoopNest == 1 &&
           innerExprIsPureIfPerfectNest;
  }

  bool anyImpliedDo = false;
  bool anyArrayExpr = false;
  bool isPerfectLoopNest = true;
  bool innerExprIsPureIfPerfectNest = false;
  std::int64_t innerNumberOfExprIfPrefectNest = 0;
  std::int64_t depthIfPerfectLoopNest = 0;
};
} // namespace

template <typename T>
ArrayCtorAnalysis::ArrayCtorAnalysis(
    Fortran::evaluate::FoldingContext &foldingContext,
    const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr) {
  llvm::SmallVector<const Fortran::evaluate::ArrayConstructorValues<T> *>
      arrayValueListStack{&arrayCtorExpr};
  // Loop through the ac-value-list(s) of the array constructor.
  while (!arrayValueListStack.empty()) {
    std::int64_t localNumberOfImpliedDo = 0;
    std::int64_t localNumberOfExpr = 0;
    // Loop though the ac-value of an ac-value list, and add any nested
    // ac-value-list of ac-implied-do to the stack.
    const Fortran::evaluate::ArrayConstructorValues<T> *currentArrayValueList =
        arrayValueListStack.pop_back_val();
    for (const Fortran::evaluate::ArrayConstructorValue<T> &acValue :
         *currentArrayValueList)
      Fortran::common::visit(
          Fortran::common::visitors{
              [&](const Fortran::evaluate::ImpliedDo<T> &impledDo) {
                arrayValueListStack.push_back(&impledDo.values());
                localNumberOfImpliedDo++;
              },
              [&](const Fortran::evaluate::Expr<T> &expr) {
                localNumberOfExpr++;
                anyArrayExpr = anyArrayExpr || expr.Rank() > 0;
              }},
          acValue.u);
    anyImpliedDo = anyImpliedDo || localNumberOfImpliedDo > 0;

    if (localNumberOfImpliedDo == 0) {
      // Leaf ac-value-list in the array constructor ac-value tree.
      if (isPerfectLoopNest) {
        // This this the only leaf of the array-constructor (the array
        // constructor is a nest of single implied-do with a list of expression
        // in the last deeper implied do). e.g: "[((i+j, i=1,n)j=1,m)]".
        innerNumberOfExprIfPrefectNest = localNumberOfExpr;
        if (localNumberOfExpr == 1)
          innerExprIsPureIfPerfectNest = !Fortran::evaluate::FindImpureCall(
              foldingContext, toEvExpr(std::get<Fortran::evaluate::Expr<T>>(
                                  currentArrayValueList->begin()->u)));
      }
    } else if (localNumberOfImpliedDo == 1 && localNumberOfExpr == 0) {
      // Perfect implied-do nest new level.
      ++depthIfPerfectLoopNest;
    } else {
      // More than one implied-do, or at least one implied-do and an expr
      // at that level. This will not form a perfect nest. Examples:
      // "[a, (i, i=1,n)]" or "[(i, i=1,n), (j, j=1,m)]".
      isPerfectLoopNest = false;
    }
  }
}

/// Does \p expr contain no calls to user function?
static bool isCallFreeExpr(const Fortran::evaluate::ExtentExpr &expr) {
  for (const Fortran::semantics::Symbol &symbol :
       Fortran::evaluate::CollectSymbols(expr))
    if (Fortran::semantics::IsProcedure(symbol))
      return false;
  return true;
}

/// Core function that pre-lowers the extent and length parameters of
/// array constructors if it can, runs the ac-value analysis and
/// select the lowering strategy accordingly.
template <typename T>
static ArrayCtorLoweringStrategy selectArrayCtorLoweringStrategy(
    mlir::Location loc, Fortran::lower::AbstractConverter &converter,
    const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
    Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
  mlir::Type idxType = builder.getIndexType();
  // Try to gather the array constructor extent.
  mlir::Value extent;
  fir::SequenceType::Extent typeExtent = fir::SequenceType::getUnknownExtent();
  auto shapeExpr = Fortran::evaluate::GetContextFreeShape(
      converter.getFoldingContext(), arrayCtorExpr);
  if (shapeExpr && shapeExpr->size() == 1 && (*shapeExpr)[0]) {
    const Fortran::evaluate::ExtentExpr &extentExpr = *(*shapeExpr)[0];
    if (auto constantExtent = Fortran::evaluate::ToInt64(extentExpr)) {
      typeExtent = *constantExtent;
      extent = builder.createIntegerConstant(loc, idxType, typeExtent);
    } else if (isCallFreeExpr(extentExpr)) {
      // The expression built by expression analysis for the array constructor
      // extent does not contain procedure symbols. It is side effect free.
      // This could be relaxed to allow pure procedure, but some care must
      // be taken to not bring in "unmapped" symbols from callee scopes.
      extent = lowerExtentExpr(loc, converter, symMap, stmtCtx, extentExpr);
    }
    // Otherwise, the temporary will have to be built step by step with
    // reallocation and the extent will only be known at the end of the array
    // constructor evaluation.
  }
  // Convert the array constructor type and try to gather its length parameter
  // values, if any.
  mlir::SmallVector<mlir::Value> lengths;
  mlir::Type elementType = LengthAndTypeCollector<T>::collect(
      loc, converter, arrayCtorExpr, symMap, stmtCtx, lengths);
  // Run an analysis of the array constructor ac-value.
  ArrayCtorAnalysis analysis(converter.getFoldingContext(), arrayCtorExpr);
  bool needToEvaluateOneExprToGetLengthParameters =
      missingLengthParameters(elementType, lengths);
  auto declaredType = fir::SequenceType::get({typeExtent}, elementType);

  // Based on what was gathered and the result of the analysis, select and
  // instantiate the right lowering strategy for the array constructor.
  if (!extent || needToEvaluateOneExprToGetLengthParameters ||
      analysis.anyArrayExpr ||
      mlir::isa<fir::RecordType>(declaredType.getEleTy()))
    return RuntimeTempStrategy(
        loc, builder, stmtCtx, symMap, declaredType,
        extent ? std::optional<mlir::Value>(extent) : std::nullopt, lengths,
        needToEvaluateOneExprToGetLengthParameters);
  // Note: the generated hlfir.elemental is always unordered, thus,
  // AsElementalStrategy can only be used for array constructors without
  // impure ac-value expressions. If/when this changes, make sure
  // the 'unordered' attribute is set accordingly for the hlfir.elemental.
  if (analysis.isSingleImpliedDoWithOneScalarPureExpr())
    return AsElementalStrategy(loc, builder, stmtCtx, symMap, declaredType,
                               extent, lengths);

  if (analysis.anyImpliedDo)
    return InlinedTempStrategy(loc, builder, stmtCtx, symMap, declaredType,
                               extent, lengths);

  return LooplessInlinedTempStrategy(loc, builder, stmtCtx, symMap,
                                     declaredType, extent, lengths);
}

/// Lower an ac-value expression \p expr and forward it to the selected
/// lowering strategy \p arrayBuilder,
template <typename T>
static void genAcValue(mlir::Location loc,
                       Fortran::lower::AbstractConverter &converter,
                       const Fortran::evaluate::Expr<T> &expr,
                       Fortran::lower::SymMap &symMap,
                       Fortran::lower::StatementContext &stmtCtx,
                       ArrayCtorLoweringStrategy &arrayBuilder) {
  // TODO: get rid of the toEvExpr indirection.
  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
  hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
      loc, converter, toEvExpr(expr), symMap, stmtCtx);
  value = hlfir::loadTrivialScalar(loc, builder, value);
  arrayBuilder.pushValue(loc, builder, value);
}

/// Lowers an ac-value implied-do \p impledDo according to the selected
/// lowering strategy \p arrayBuilder.
template <typename T>
static void genAcValue(mlir::Location loc,
                       Fortran::lower::AbstractConverter &converter,
                       const Fortran::evaluate::ImpliedDo<T> &impledDo,
                       Fortran::lower::SymMap &symMap,
                       Fortran::lower::StatementContext &stmtCtx,
                       ArrayCtorLoweringStrategy &arrayBuilder) {
  auto lowerIndex =
      [&](const Fortran::evaluate::ExtentExpr expr) -> mlir::Value {
    return lowerExtentExpr(loc, converter, symMap, stmtCtx, expr);
  };
  mlir::Value lower = lowerIndex(impledDo.lower());
  mlir::Value upper = lowerIndex(impledDo.upper());
  mlir::Value stride = lowerIndex(impledDo.stride());
  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
  mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint();
  mlir::Value impliedDoIndexValue =
      arrayBuilder.startImpliedDo(loc, builder, lower, upper, stride);
  arrayBuilder.startImpliedDoScope(toStringRef(impledDo.name()),
                                   impliedDoIndexValue);

  for (const auto &acValue : impledDo.values())
    Fortran::common::visit(
        [&](const auto &x) {
          genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
        },
        acValue.u);

  arrayBuilder.endImpliedDoScope();
  builder.restoreInsertionPoint(insertPt);
}

/// Entry point for evaluate::ArrayConstructor lowering.
template <typename T>
hlfir::EntityWithAttributes Fortran::lower::ArrayConstructorBuilder<T>::gen(
    mlir::Location loc, Fortran::lower::AbstractConverter &converter,
    const Fortran::evaluate::ArrayConstructor<T> &arrayCtorExpr,
    Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) {
  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
  // Select the lowering strategy given the array constructor.
  auto arrayBuilder = selectArrayCtorLoweringStrategy(
      loc, converter, arrayCtorExpr, symMap, stmtCtx);
  // Run the array lowering strategy through the ac-values.
  for (const auto &acValue : arrayCtorExpr)
    Fortran::common::visit(
        [&](const auto &x) {
          genAcValue(loc, converter, x, symMap, stmtCtx, arrayBuilder);
        },
        acValue.u);
  hlfir::Entity hlfirExpr = arrayBuilder.finishArrayCtorLowering(loc, builder);
  // Insert the clean-up for the created hlfir.expr.
  fir::FirOpBuilder *bldr = &builder;
  stmtCtx.attachCleanup(
      [=]() { bldr->create<hlfir::DestroyOp>(loc, hlfirExpr); });
  return hlfir::EntityWithAttributes{hlfirExpr};
}

using namespace Fortran::evaluate;
using namespace Fortran::common;
FOR_EACH_SPECIFIC_TYPE(template class Fortran::lower::ArrayConstructorBuilder, )
