//===-- Target.cpp --------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//

#include "flang/Optimizer/CodeGen/Target.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Dialect/Support/KindMapping.h"
#include "flang/Optimizer/Support/FatalError.h"
#include "flang/Optimizer/Support/Utils.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/TypeRange.h"
#include "llvm/ADT/TypeSwitch.h"

#define DEBUG_TYPE "flang-codegen-target"

using namespace fir;

namespace fir::details {
llvm::StringRef Attributes::getIntExtensionAttrName() const {
  // The attribute names are available via LLVM dialect interfaces
  // like getZExtAttrName(), getByValAttrName(), etc., so we'd better
  // use them than literals.
  if (isZeroExt())
    return "llvm.zeroext";
  else if (isSignExt())
    return "llvm.signext";
  return {};
}
} // namespace fir::details

// Reduce a REAL/float type to the floating point semantics.
static const llvm::fltSemantics &floatToSemantics(const KindMapping &kindMap,
                                                  mlir::Type type) {
  assert(isa_real(type));
  return mlir::cast<mlir::FloatType>(type).getFloatSemantics();
}

static void typeTodo(const llvm::fltSemantics *sem, mlir::Location loc,
                     const std::string &context) {
  if (sem == &llvm::APFloat::IEEEhalf()) {
    TODO(loc, "COMPLEX(KIND=2): for " + context + " type");
  } else if (sem == &llvm::APFloat::BFloat()) {
    TODO(loc, "COMPLEX(KIND=3): " + context + " type");
  } else if (sem == &llvm::APFloat::x87DoubleExtended()) {
    TODO(loc, "COMPLEX(KIND=10): " + context + " type");
  } else {
    TODO(loc, "complex for this precision for " + context + " type");
  }
}

namespace {
template <typename S>
struct GenericTarget : public CodeGenSpecifics {
  using CodeGenSpecifics::CodeGenSpecifics;
  using AT = CodeGenSpecifics::Attributes;

  mlir::Type complexMemoryType(mlir::Type eleTy) const override {
    assert(fir::isa_real(eleTy));
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 eleTy
    return mlir::TupleType::get(eleTy.getContext(),
                                mlir::TypeRange{eleTy, eleTy});
  }

  mlir::Type boxcharMemoryType(mlir::Type eleTy) const override {
    auto idxTy = mlir::IntegerType::get(eleTy.getContext(), S::defaultWidth);
    auto ptrTy = fir::ReferenceType::get(eleTy);
    // Use a type that will be translated into LLVM as:
    // { t*, index }
    return mlir::TupleType::get(eleTy.getContext(),
                                mlir::TypeRange{ptrTy, idxTy});
  }

  Marshalling boxcharArgumentType(mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    auto idxTy = mlir::IntegerType::get(eleTy.getContext(), S::defaultWidth);
    auto ptrTy = fir::ReferenceType::get(eleTy);
    marshal.emplace_back(ptrTy, AT{});
    // Characters are passed in a split format with all pointers first (in the
    // declared position) and all LEN arguments appended after all of the dummy
    // arguments.
    // NB: Other conventions/ABIs can/should be supported via options.
    marshal.emplace_back(idxTy, AT{/*alignment=*/0, /*byval=*/false,
                                   /*sret=*/false, /*append=*/true});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType,
                     const Marshalling &) const override {
    TODO(loc, "passing VALUE BIND(C) derived type for this target");
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType ty) const override {
    TODO(loc, "returning BIND(C) derived type for this target");
  }

  CodeGenSpecifics::Marshalling
  integerArgumentType(mlir::Location loc,
                      mlir::IntegerType argTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    AT::IntegerExtension intExt = AT::IntegerExtension::None;
    if (argTy.getWidth() < getCIntTypeWidth()) {
      // isSigned() and isUnsigned() branches below are dead code currently.
      // If needed, we can generate calls with signed/unsigned argument types
      // to more precisely match C side (e.g. for Fortran runtime functions
      // with 'unsigned short' arguments).
      if (argTy.isSigned())
        intExt = AT::IntegerExtension::Sign;
      else if (argTy.isUnsigned())
        intExt = AT::IntegerExtension::Zero;
      else if (argTy.isSignless()) {
        // Zero extend for 'i1' and sign extend for other types.
        if (argTy.getWidth() == 1)
          intExt = AT::IntegerExtension::Zero;
        else
          intExt = AT::IntegerExtension::Sign;
      }
    }

    marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false,
                                   /*sret=*/false, /*append=*/false,
                                   /*intExt=*/intExt});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  integerReturnType(mlir::Location loc,
                    mlir::IntegerType argTy) const override {
    return integerArgumentType(loc, argTy);
  }

  // Width of 'int' type is 32-bits for almost all targets, except
  // for AVR and MSP430 (see TargetInfo initializations
  // in clang/lib/Basic/Targets).
  unsigned char getCIntTypeWidth() const override { return 32; }
};
} // namespace

//===----------------------------------------------------------------------===//
// i386 (x86 32 bit) linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetI386 : public GenericTarget<TargetI386> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 32;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location, mlir::Type eleTy) const override {
    assert(fir::isa_real(eleTy));
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 eleTy, byval, align 4
    auto structTy =
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy});
    marshal.emplace_back(fir::ReferenceType::get(structTy),
                         AT{/*alignment=*/4, /*byval=*/true});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    assert(fir::isa_real(eleTy));
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // i64   pack both floats in a 64-bit GPR
      marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64),
                           AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy, sret, align 4
      auto structTy = mlir::TupleType::get(eleTy.getContext(),
                                           mlir::TypeRange{eleTy, eleTy});
      marshal.emplace_back(fir::ReferenceType::get(structTy),
                           AT{/*alignment=*/4, /*byval=*/false, /*sret=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// i386 (x86 32 bit) Windows target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetI386Win : public GenericTarget<TargetI386Win> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 32;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 eleTy, byval, align 4
    auto structTy =
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy});
    marshal.emplace_back(fir::ReferenceType::get(structTy),
                         AT{/*align=*/4, /*byval=*/true});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // i64   pack both floats in a 64-bit GPR
      marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64),
                           AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { double, double }   struct of 2 double, sret, align 8
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/8, /*byval=*/false, /*sret=*/true});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, sret, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/false, /*sret=*/true});
    } else if (sem == &llvm::APFloat::x87DoubleExtended()) {
      // Use a type that will be translated into LLVM as:
      // { x86_fp80, x86_fp80 }   struct of 2 x86_fp80, sret, align 4
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/4, /*byval=*/false, /*sret=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// x86_64 (x86 64 bit) linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetX86_64 : public GenericTarget<TargetX86_64> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // <2 x t>   vector of 2 eleTy
      marshal.emplace_back(fir::VectorType::get(2, eleTy), AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // FIXME: In case of SSE register exhaustion, the ABI here may be
      // incorrect since LLVM may pass the real via register and the imaginary
      // part via the stack while the ABI it should be all in register or all
      // in memory. Register occupancy must be analyzed here.
      // two distinct double arguments
      marshal.emplace_back(eleTy, AT{});
      marshal.emplace_back(eleTy, AT{});
    } else if (sem == &llvm::APFloat::x87DoubleExtended()) {
      // Use a type that will be translated into LLVM as:
      // { x86_fp80, x86_fp80 }  struct of 2 fp128, byval, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/true});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, byval, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/true});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // <2 x t>   vector of 2 eleTy
      marshal.emplace_back(fir::VectorType::get(2, eleTy), AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { double, double }   struct of 2 double
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{});
    } else if (sem == &llvm::APFloat::x87DoubleExtended()) {
      // { x86_fp80, x86_fp80 }
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, sret, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/false, /*sret=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }

  /// X86-64 argument classes from System V ABI version 1.0 section 3.2.3.
  enum ArgClass {
    Integer = 0,
    SSE,
    SSEUp,
    X87,
    X87Up,
    ComplexX87,
    NoClass,
    Memory
  };

  /// Classify an argument type or a field of an aggregate type argument.
  /// See System V ABI version 1.0 section 3.2.3.
  /// The Lo and Hi class are set to the class of the lower eight eightbytes
  /// and upper eight eightbytes on return.
  /// If this is called for an aggregate field, the caller is responsible to
  /// do the post-merge.
  void classify(mlir::Location loc, mlir::Type type, std::uint64_t byteOffset,
                ArgClass &Lo, ArgClass &Hi) const {
    Hi = Lo = ArgClass::NoClass;
    ArgClass &current = byteOffset < 8 ? Lo : Hi;
    // System V AMD64 ABI 3.2.3. version 1.0
    llvm::TypeSwitch<mlir::Type>(type)
        .template Case<mlir::IntegerType>([&](mlir::IntegerType intTy) {
          if (intTy.getWidth() == 128)
            Hi = Lo = ArgClass::Integer;
          else
            current = ArgClass::Integer;
        })
        .template Case<mlir::FloatType>([&](mlir::Type floatTy) {
          const auto *sem = &floatToSemantics(kindMap, floatTy);
          if (sem == &llvm::APFloat::x87DoubleExtended()) {
            Lo = ArgClass::X87;
            Hi = ArgClass::X87Up;
          } else if (sem == &llvm::APFloat::IEEEquad()) {
            Lo = ArgClass::SSE;
            Hi = ArgClass::SSEUp;
          } else {
            current = ArgClass::SSE;
          }
        })
        .template Case<mlir::ComplexType>([&](mlir::ComplexType cmplx) {
          const auto *sem = &floatToSemantics(kindMap, cmplx.getElementType());
          if (sem == &llvm::APFloat::x87DoubleExtended()) {
            current = ArgClass::ComplexX87;
          } else {
            fir::SequenceType::Shape shape{2};
            classifyArray(loc,
                          fir::SequenceType::get(shape, cmplx.getElementType()),
                          byteOffset, Lo, Hi);
          }
        })
        .template Case<fir::LogicalType>([&](fir::LogicalType logical) {
          if (kindMap.getLogicalBitsize(logical.getFKind()) == 128)
            Hi = Lo = ArgClass::Integer;
          else
            current = ArgClass::Integer;
        })
        .template Case<fir::CharacterType>(
            [&](fir::CharacterType character) { current = ArgClass::Integer; })
        .template Case<fir::SequenceType>([&](fir::SequenceType seqTy) {
          // Array component.
          classifyArray(loc, seqTy, byteOffset, Lo, Hi);
        })
        .template Case<fir::RecordType>([&](fir::RecordType recTy) {
          // Component that is a derived type.
          classifyStruct(loc, recTy, byteOffset, Lo, Hi);
        })
        .template Case<fir::VectorType>([&](fir::VectorType vecTy) {
          // Previously marshalled SSE eight byte for a previous struct
          // argument.
          auto *sem = fir::isa_real(vecTy.getEleTy())
                          ? &floatToSemantics(kindMap, vecTy.getEleTy())
                          : nullptr;
          // Not expecting to hit this todo in standard code (it would
          // require some vector type extension).
          if (!(sem == &llvm::APFloat::IEEEsingle() && vecTy.getLen() <= 2) &&
              !(sem == &llvm::APFloat::IEEEhalf() && vecTy.getLen() <= 4))
            TODO(loc, "passing vector argument to C by value");
          current = SSE;
        })
        .Default([&](mlir::Type ty) {
          if (fir::conformsWithPassByRef(ty))
            current = ArgClass::Integer; // Pointers.
          else
            TODO(loc, "unsupported component type for BIND(C), VALUE derived "
                      "type argument");
        });
  }

  // Classify fields of a derived type starting at \p offset. Returns the new
  // offset. Post-merge is left to the caller.
  std::uint64_t classifyStruct(mlir::Location loc, fir::RecordType recTy,
                               std::uint64_t byteOffset, ArgClass &Lo,
                               ArgClass &Hi) const {
    for (auto component : recTy.getTypeList()) {
      if (byteOffset > 16) {
        // See 3.2.3 p. 1 and note 15. Note that when the offset is bigger
        // than 16 bytes here, it is not a single _m256 and or _m512 entity
        // that could fit in AVX registers.
        Lo = Hi = ArgClass::Memory;
        return byteOffset;
      }
      mlir::Type compType = component.second;
      auto [compSize, compAlign] = fir::getTypeSizeAndAlignmentOrCrash(
          loc, compType, getDataLayout(), kindMap);
      byteOffset = llvm::alignTo(byteOffset, compAlign);
      ArgClass LoComp, HiComp;
      classify(loc, compType, byteOffset, LoComp, HiComp);
      Lo = mergeClass(Lo, LoComp);
      Hi = mergeClass(Hi, HiComp);
      byteOffset = byteOffset + llvm::alignTo(compSize, compAlign);
      if (Lo == ArgClass::Memory || Hi == ArgClass::Memory)
        return byteOffset;
    }
    return byteOffset;
  }

  // Classify fields of a constant size array type starting at \p offset.
  // Returns the new offset. Post-merge is left to the caller.
  void classifyArray(mlir::Location loc, fir::SequenceType seqTy,
                     std::uint64_t byteOffset, ArgClass &Lo,
                     ArgClass &Hi) const {
    mlir::Type eleTy = seqTy.getEleTy();
    const std::uint64_t arraySize = seqTy.getConstantArraySize();
    auto [eleSize, eleAlign] = fir::getTypeSizeAndAlignmentOrCrash(
        loc, eleTy, getDataLayout(), kindMap);
    std::uint64_t eleStorageSize = llvm::alignTo(eleSize, eleAlign);
    for (std::uint64_t i = 0; i < arraySize; ++i) {
      byteOffset = llvm::alignTo(byteOffset, eleAlign);
      if (byteOffset > 16) {
        // See 3.2.3 p. 1 and note 15. Same as in classifyStruct.
        Lo = Hi = ArgClass::Memory;
        return;
      }
      ArgClass LoComp, HiComp;
      classify(loc, eleTy, byteOffset, LoComp, HiComp);
      Lo = mergeClass(Lo, LoComp);
      Hi = mergeClass(Hi, HiComp);
      byteOffset = byteOffset + eleStorageSize;
      if (Lo == ArgClass::Memory || Hi == ArgClass::Memory)
        return;
    }
  }

  // Goes through the previously marshalled arguments and count the
  // register occupancy to check if there are enough registers left.
  bool hasEnoughRegisters(mlir::Location loc, int neededIntRegisters,
                          int neededSSERegisters,
                          const Marshalling &previousArguments) const {
    int availIntRegisters = 6;
    int availSSERegisters = 8;
    for (auto typeAndAttr : previousArguments) {
      const auto &attr = std::get<Attributes>(typeAndAttr);
      if (attr.isByVal())
        continue; // Previous argument passed on the stack.
      ArgClass Lo, Hi;
      Lo = Hi = ArgClass::NoClass;
      classify(loc, std::get<mlir::Type>(typeAndAttr), 0, Lo, Hi);
      // post merge is not needed here since previous aggregate arguments
      // were marshalled into simpler arguments.
      if (Lo == ArgClass::Integer)
        --availIntRegisters;
      else if (Lo == SSE)
        --availSSERegisters;
      if (Hi == ArgClass::Integer)
        --availIntRegisters;
      else if (Hi == ArgClass::SSE)
        --availSSERegisters;
    }
    return availSSERegisters >= neededSSERegisters &&
           availIntRegisters >= neededIntRegisters;
  }

  /// Argument class merging as described in System V ABI 3.2.3 point 4.
  ArgClass mergeClass(ArgClass accum, ArgClass field) const {
    assert((accum != ArgClass::Memory && accum != ArgClass::ComplexX87) &&
           "Invalid accumulated classification during merge.");
    if (accum == field || field == NoClass)
      return accum;
    if (field == ArgClass::Memory)
      return ArgClass::Memory;
    if (accum == NoClass)
      return field;
    if (accum == Integer || field == Integer)
      return ArgClass::Integer;
    if (field == ArgClass::X87 || field == ArgClass::X87Up ||
        field == ArgClass::ComplexX87 || accum == ArgClass::X87 ||
        accum == ArgClass::X87Up)
      return Memory;
    return SSE;
  }

  /// Argument class post merging as described in System V ABI 3.2.3 point 5.
  void postMerge(std::uint64_t byteSize, ArgClass &Lo, ArgClass &Hi) const {
    if (Hi == ArgClass::Memory)
      Lo = ArgClass::Memory;
    if (Hi == ArgClass::X87Up && Lo != ArgClass::X87)
      Lo = ArgClass::Memory;
    if (byteSize > 16 && (Lo != ArgClass::SSE || Hi != ArgClass::SSEUp))
      Lo = ArgClass::Memory;
    if (Hi == ArgClass::SSEUp && Lo != ArgClass::SSE)
      Hi = SSE;
  }

  /// When \p recTy is a one field record type that can be passed
  /// like the field on its own, returns the field type. Returns
  /// a null type otherwise.
  mlir::Type passAsFieldIfOneFieldStruct(fir::RecordType recTy,
                                         bool allowComplex = false) const {
    auto typeList = recTy.getTypeList();
    if (typeList.size() != 1)
      return {};
    mlir::Type fieldType = typeList[0].second;
    if (mlir::isa<mlir::FloatType, mlir::IntegerType, fir::LogicalType>(
            fieldType))
      return fieldType;
    if (allowComplex && mlir::isa<mlir::ComplexType>(fieldType))
      return fieldType;
    if (mlir::isa<fir::CharacterType>(fieldType)) {
      // Only CHARACTER(1) are expected in BIND(C) contexts, which is the only
      // contexts where derived type may be passed in registers.
      assert(mlir::cast<fir::CharacterType>(fieldType).getLen() == 1 &&
             "fir.type value arg character components must have length 1");
      return fieldType;
    }
    // Complex field that needs to be split, or array.
    return {};
  }

  mlir::Type pickLLVMArgType(mlir::Location loc, mlir::MLIRContext *context,
                             ArgClass argClass,
                             std::uint64_t partByteSize) const {
    if (argClass == ArgClass::SSE) {
      if (partByteSize > 16)
        TODO(loc, "passing struct as a real > 128 bits in register");
      // Clang uses vector type when several fp fields are marshalled
      // into a single SSE register (like  <n x smallest fp field> ).
      // It should make no difference from an ABI point of view to just
      // select an fp type of the right size, and it makes things simpler
      // here.
      if (partByteSize > 8)
        return mlir::Float128Type::get(context);
      if (partByteSize > 4)
        return mlir::Float64Type::get(context);
      if (partByteSize > 2)
        return mlir::Float32Type::get(context);
      return mlir::Float16Type::get(context);
    }
    assert(partByteSize <= 8 &&
           "expect integer part of aggregate argument to fit into eight bytes");
    if (partByteSize > 4)
      return mlir::IntegerType::get(context, 64);
    if (partByteSize > 2)
      return mlir::IntegerType::get(context, 32);
    if (partByteSize > 1)
      return mlir::IntegerType::get(context, 16);
    return mlir::IntegerType::get(context, 8);
  }

  /// Marshal a derived type passed by value like a C struct.
  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType recTy,
                     const Marshalling &previousArguments) const override {
    std::uint64_t byteOffset = 0;
    ArgClass Lo, Hi;
    Lo = Hi = ArgClass::NoClass;
    byteOffset = classifyStruct(loc, recTy, byteOffset, Lo, Hi);
    postMerge(byteOffset, Lo, Hi);
    if (Lo == ArgClass::Memory || Lo == ArgClass::X87 ||
        Lo == ArgClass::ComplexX87)
      return passOnTheStack(loc, recTy, /*isResult=*/false);
    int neededIntRegisters = 0;
    int neededSSERegisters = 0;
    if (Lo == ArgClass::SSE)
      ++neededSSERegisters;
    else if (Lo == ArgClass::Integer)
      ++neededIntRegisters;
    if (Hi == ArgClass::SSE)
      ++neededSSERegisters;
    else if (Hi == ArgClass::Integer)
      ++neededIntRegisters;
    // C struct should not be split into LLVM registers if LLVM codegen is not
    // able to later assign actual registers to all of them (struct passing is
    // all in registers or all on the stack).
    if (!hasEnoughRegisters(loc, neededIntRegisters, neededSSERegisters,
                            previousArguments))
      return passOnTheStack(loc, recTy, /*isResult=*/false);

    if (auto fieldType = passAsFieldIfOneFieldStruct(recTy)) {
      CodeGenSpecifics::Marshalling marshal;
      marshal.emplace_back(fieldType, AT{});
      return marshal;
    }
    if (Hi == ArgClass::NoClass || Hi == ArgClass::SSEUp) {
      // Pass a single integer or floating point argument.
      mlir::Type lowType =
          pickLLVMArgType(loc, recTy.getContext(), Lo, byteOffset);
      CodeGenSpecifics::Marshalling marshal;
      marshal.emplace_back(lowType, AT{});
      return marshal;
    }
    // Split into two integer or floating point arguments.
    // Note that for the first argument, this will always pick i64 or f64 which
    // may be bigger than needed if some struct padding ends the first eight
    // byte (e.g. for `{i32, f64}`). It is valid from an X86-64 ABI and
    // semantic point of view, but it may not match the LLVM IR interface clang
    // would produce for the equivalent C code (the assembly will still be
    // compatible).  This allows keeping the logic simpler here since it
    // avoids computing the "data" size of the Lo part.
    mlir::Type lowType = pickLLVMArgType(loc, recTy.getContext(), Lo, 8u);
    mlir::Type hiType =
        pickLLVMArgType(loc, recTy.getContext(), Hi, byteOffset - 8u);
    CodeGenSpecifics::Marshalling marshal;
    marshal.emplace_back(lowType, AT{});
    marshal.emplace_back(hiType, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType recTy) const override {
    std::uint64_t byteOffset = 0;
    ArgClass Lo, Hi;
    Lo = Hi = ArgClass::NoClass;
    byteOffset = classifyStruct(loc, recTy, byteOffset, Lo, Hi);
    mlir::MLIRContext *context = recTy.getContext();
    postMerge(byteOffset, Lo, Hi);
    if (Lo == ArgClass::Memory)
      return passOnTheStack(loc, recTy, /*isResult=*/true);

    // Note that X87/ComplexX87 are passed in memory, but returned via %st0
    // %st1 registers. Here, they are returned as fp80 or {fp80, fp80} by
    // passAsFieldIfOneFieldStruct, and LLVM will use the expected registers.

    // Note that {_Complex long double} is not 100% clear from an ABI
    // perspective because the aggregate post merger rules say it should be
    // passed in memory because it is bigger than 2 eight bytes. This has the
    // funny effect of
    // {_Complex long double} return to be dealt with differently than
    // _Complex long double.

    if (auto fieldType =
            passAsFieldIfOneFieldStruct(recTy, /*allowComplex=*/true)) {
      if (auto complexType = mlir::dyn_cast<mlir::ComplexType>(fieldType))
        return complexReturnType(loc, complexType.getElementType());
      CodeGenSpecifics::Marshalling marshal;
      marshal.emplace_back(fieldType, AT{});
      return marshal;
    }

    if (Hi == ArgClass::NoClass || Hi == ArgClass::SSEUp) {
      // Return a single integer or floating point argument.
      mlir::Type lowType = pickLLVMArgType(loc, context, Lo, byteOffset);
      CodeGenSpecifics::Marshalling marshal;
      marshal.emplace_back(lowType, AT{});
      return marshal;
    }
    // Will be returned in two different registers. Generate {lowTy, HiTy} for
    // the LLVM IR result type.
    CodeGenSpecifics::Marshalling marshal;
    mlir::Type lowType = pickLLVMArgType(loc, context, Lo, 8u);
    mlir::Type hiType = pickLLVMArgType(loc, context, Hi, byteOffset - 8u);
    marshal.emplace_back(mlir::TupleType::get(context, {lowType, hiType}),
                         AT{});
    return marshal;
  }

  /// Marshal an argument that must be passed on the stack.
  CodeGenSpecifics::Marshalling
  passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const {
    CodeGenSpecifics::Marshalling marshal;
    auto sizeAndAlign =
        fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap);
    // The stack is always 8 byte aligned (note 14 in 3.2.3).
    unsigned short align =
        std::max(sizeAndAlign.second, static_cast<unsigned short>(8));
    marshal.emplace_back(fir::ReferenceType::get(ty),
                         AT{align, /*byval=*/!isResult, /*sret=*/isResult});
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// x86_64 (x86 64 bit) Windows target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetX86_64Win : public GenericTarget<TargetX86_64Win> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // i64   pack both floats in a 64-bit GPR
      marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64),
                           AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { double, double }   struct of 2 double, byval, align 8
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/8, /*byval=*/true});
    } else if (sem == &llvm::APFloat::IEEEquad() ||
               sem == &llvm::APFloat::x87DoubleExtended()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy, byval, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/true});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // i64   pack both floats in a 64-bit GPR
      marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64),
                           AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { double, double }   struct of 2 double, sret, align 8
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/8, /*byval=*/false, /*sret=*/true});
    } else if (sem == &llvm::APFloat::IEEEquad() ||
               sem == &llvm::APFloat::x87DoubleExtended()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy, sret, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/false, /*sret=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// AArch64 target specifics.
//===----------------------------------------------------------------------===//

namespace {
// AArch64 procedure call standard:
// https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#parameter-passing
struct TargetAArch64 : public GenericTarget<TargetAArch64> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble() ||
        sem == &llvm::APFloat::IEEEquad()) {
      // [2 x t]   array of 2 eleTy
      marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  integerArgumentType(mlir::Location loc,
                      mlir::IntegerType argTy) const override {
    if (argTy.getWidth() < getCIntTypeWidth() && argTy.isSignless()) {
      AT::IntegerExtension intExt;
      if (argTy.getWidth() == 1) {
        // Zero extend for 'i1'.
        intExt = AT::IntegerExtension::Zero;
      } else {
        if (triple.isOSDarwin()) {
          // On Darwin, sign extend. The apple developer guide specifies this as
          // a divergence from the AArch64PCS:
          // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly
          intExt = AT::IntegerExtension::Sign;
        } else {
          // On linux, pass directly and do not extend.
          intExt = AT::IntegerExtension::None;
        }
      }
      CodeGenSpecifics::Marshalling marshal;
      marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false,
                                     /*sret=*/false, /*append=*/false,
                                     /*intExt=*/intExt});
      return marshal;
    }
    return GenericTarget::integerArgumentType(loc, argTy);
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble() ||
        sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }

  // Flatten a RecordType::TypeList containing more record types or array type
  static std::optional<std::vector<mlir::Type>>
  flattenTypeList(const RecordType::TypeList &types) {
    std::vector<mlir::Type> flatTypes;
    // The flat list will be at least the same size as the non-flat list.
    flatTypes.reserve(types.size());
    for (auto [c, type] : types) {
      // Flatten record type
      if (auto recTy = mlir::dyn_cast<RecordType>(type)) {
        auto subTypeList = flattenTypeList(recTy.getTypeList());
        if (!subTypeList)
          return std::nullopt;
        llvm::copy(*subTypeList, std::back_inserter(flatTypes));
        continue;
      }

      // Flatten array type
      if (auto seqTy = mlir::dyn_cast<SequenceType>(type)) {
        if (seqTy.hasDynamicExtents())
          return std::nullopt;
        std::size_t n = seqTy.getConstantArraySize();
        auto eleTy = seqTy.getElementType();
        // Flatten array of record types
        if (auto recTy = mlir::dyn_cast<RecordType>(eleTy)) {
          auto subTypeList = flattenTypeList(recTy.getTypeList());
          if (!subTypeList)
            return std::nullopt;
          for (std::size_t i = 0; i < n; ++i)
            llvm::copy(*subTypeList, std::back_inserter(flatTypes));
        } else {
          std::fill_n(std::back_inserter(flatTypes),
                      seqTy.getConstantArraySize(), eleTy);
        }
        continue;
      }

      // Other types are already flat
      flatTypes.push_back(type);
    }
    return flatTypes;
  }

  // Determine if the type is a Homogenous Floating-point Aggregate (HFA). An
  // HFA is a record type with up to 4 floating-point members of the same type.
  static std::optional<int> usedRegsForHFA(fir::RecordType ty) {
    RecordType::TypeList types = ty.getTypeList();
    if (types.empty() || types.size() > 4)
      return std::nullopt;

    std::optional<std::vector<mlir::Type>> flatTypes = flattenTypeList(types);
    if (!flatTypes || flatTypes->size() > 4) {
      return std::nullopt;
    }

    if (!isa_real(flatTypes->front())) {
      return std::nullopt;
    }

    return llvm::all_equal(*flatTypes) ? std::optional<int>{flatTypes->size()}
                                       : std::nullopt;
  }

  struct NRegs {
    int n{0};
    bool isSimd{false};
  };

  NRegs usedRegsForRecordType(mlir::Location loc, fir::RecordType type) const {
    if (std::optional<int> size = usedRegsForHFA(type))
      return {*size, true};

    auto [size, align] = fir::getTypeSizeAndAlignmentOrCrash(
        loc, type, getDataLayout(), kindMap);

    if (size <= 16)
      return {static_cast<int>((size + 7) / 8), false};

    // Pass on the stack, i.e. no registers used
    return {};
  }

  NRegs usedRegsForType(mlir::Location loc, mlir::Type type) const {
    return llvm::TypeSwitch<mlir::Type, NRegs>(type)
        .Case<mlir::IntegerType>([&](auto intTy) {
          return intTy.getWidth() == 128 ? NRegs{2, false} : NRegs{1, false};
        })
        .Case<mlir::FloatType>([&](auto) { return NRegs{1, true}; })
        .Case<mlir::ComplexType>([&](auto) { return NRegs{2, true}; })
        .Case<fir::LogicalType>([&](auto) { return NRegs{1, false}; })
        .Case<fir::CharacterType>([&](auto) { return NRegs{1, false}; })
        .Case<fir::SequenceType>([&](auto ty) {
          assert(ty.getShape().size() == 1 &&
                 "invalid array dimensions in BIND(C)");
          NRegs nregs = usedRegsForType(loc, ty.getEleTy());
          nregs.n *= ty.getShape()[0];
          return nregs;
        })
        .Case<fir::RecordType>(
            [&](auto ty) { return usedRegsForRecordType(loc, ty); })
        .Case<fir::VectorType>([&](auto) {
          TODO(loc, "passing vector argument to C by value is not supported");
          return NRegs{};
        })
        .Default([&](auto ty) {
          if (fir::conformsWithPassByRef(ty))
            return NRegs{1, false}; // Pointers take 1 integer register
          TODO(loc, "unsupported component type for BIND(C), VALUE derived "
                    "type argument");
          return NRegs{};
        });
  }

  bool hasEnoughRegisters(mlir::Location loc, fir::RecordType type,
                          const Marshalling &previousArguments) const {
    int availIntRegisters = 8;
    int availSIMDRegisters = 8;

    // Check previous arguments to see how many registers are used already
    for (auto [type, attr] : previousArguments) {
      if (availIntRegisters <= 0 || availSIMDRegisters <= 0)
        break;

      if (attr.isByVal())
        continue; // Previous argument passed on the stack

      NRegs nregs = usedRegsForType(loc, type);
      if (nregs.isSimd)
        availSIMDRegisters -= nregs.n;
      else
        availIntRegisters -= nregs.n;
    }

    NRegs nregs = usedRegsForRecordType(loc, type);

    if (nregs.isSimd)
      return nregs.n <= availSIMDRegisters;

    return nregs.n <= availIntRegisters;
  }

  CodeGenSpecifics::Marshalling
  passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const {
    CodeGenSpecifics::Marshalling marshal;
    auto sizeAndAlign =
        fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap);
    // The stack is always 8 byte aligned
    unsigned short align =
        std::max(sizeAndAlign.second, static_cast<unsigned short>(8));
    marshal.emplace_back(fir::ReferenceType::get(ty),
                         AT{align, /*byval=*/!isResult, /*sret=*/isResult});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structType(mlir::Location loc, fir::RecordType type, bool isResult) const {
    NRegs nregs = usedRegsForRecordType(loc, type);

    // If the type needs no registers it must need to be passed on the stack
    if (nregs.n == 0)
      return passOnTheStack(loc, type, isResult);

    CodeGenSpecifics::Marshalling marshal;

    mlir::Type pcsType;
    if (nregs.isSimd) {
      pcsType = type;
    } else {
      pcsType = fir::SequenceType::get(
          nregs.n, mlir::IntegerType::get(type.getContext(), 64));
    }

    marshal.emplace_back(pcsType, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType ty,
                     const Marshalling &previousArguments) const override {
    if (!hasEnoughRegisters(loc, ty, previousArguments)) {
      return passOnTheStack(loc, ty, /*isResult=*/false);
    }

    return structType(loc, ty, /*isResult=*/false);
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType ty) const override {
    return structType(loc, ty, /*isResult=*/true);
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// PPC (AIX 32 bit) target specifics.
//===----------------------------------------------------------------------===//
namespace {
struct TargetPPC : public GenericTarget<TargetPPC> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 32;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // two distinct element type arguments (re, im)
    marshal.emplace_back(eleTy, AT{});
    marshal.emplace_back(eleTy, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 element type
    marshal.emplace_back(
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}),
        AT{});
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// PPC64 (AIX 64 bit) target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetPPC64 : public GenericTarget<TargetPPC64> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // two distinct element type arguments (re, im)
    marshal.emplace_back(eleTy, AT{});
    marshal.emplace_back(eleTy, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 element type
    marshal.emplace_back(
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}),
        AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structType(mlir::Location loc, fir::RecordType ty, bool isResult) const {
    CodeGenSpecifics::Marshalling marshal;
    auto sizeAndAlign{
        fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)};
    unsigned short align{
        std::max(sizeAndAlign.second, static_cast<unsigned short>(8))};
    marshal.emplace_back(fir::ReferenceType::get(ty),
                         AT{align, /*byval*/ !isResult, /*sret*/ isResult});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType ty,
                     const Marshalling &previousArguments) const override {
    return structType(loc, ty, false);
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType ty) const override {
    return structType(loc, ty, true);
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// PPC64le linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetPPC64le : public GenericTarget<TargetPPC64le> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth{64};

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // two distinct element type arguments (re, im)
    marshal.emplace_back(eleTy, AT{});
    marshal.emplace_back(eleTy, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 element type
    marshal.emplace_back(
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}),
        AT{});
    return marshal;
  }

  unsigned getElemWidth(mlir::Type ty) const {
    unsigned width{};
    llvm::TypeSwitch<mlir::Type>(ty)
        .template Case<mlir::ComplexType>([&](mlir::ComplexType cmplx) {
          auto elemType{
              mlir::dyn_cast<mlir::FloatType>(cmplx.getElementType())};
          width = elemType.getWidth();
        })
        .template Case<mlir::FloatType>(
            [&](mlir::FloatType real) { width = real.getWidth(); });
    return width;
  }

  // Determine if all derived types components are of the same float type with
  // the same width. Complex(4) is considered 2 floats and complex(8) 2 doubles.
  bool hasSameFloatAndWidth(
      fir::RecordType recTy,
      std::pair<mlir::Type, unsigned> &firstTypeAndWidth) const {
    for (auto comp : recTy.getTypeList()) {
      mlir::Type compType{comp.second};
      if (mlir::isa<fir::RecordType>(compType)) {
        auto rc{hasSameFloatAndWidth(mlir::cast<fir::RecordType>(compType),
                                     firstTypeAndWidth)};
        if (!rc)
          return false;
      } else {
        mlir::Type ty;
        bool isFloatType{false};
        if (mlir::isa<mlir::FloatType, mlir::ComplexType>(compType)) {
          ty = compType;
          isFloatType = true;
        } else if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(compType)) {
          ty = seqTy.getEleTy();
          isFloatType = mlir::isa<mlir::FloatType, mlir::ComplexType>(ty);
        }

        if (!isFloatType) {
          return false;
        }
        auto width{getElemWidth(ty)};
        if (firstTypeAndWidth.first == nullptr) {
          firstTypeAndWidth.first = ty;
          firstTypeAndWidth.second = width;
        } else if (width != firstTypeAndWidth.second) {
          return false;
        }
      }
    }
    return true;
  }

  CodeGenSpecifics::Marshalling
  passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const {
    CodeGenSpecifics::Marshalling marshal;
    auto sizeAndAlign{
        fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)};
    unsigned short align{
        std::max(sizeAndAlign.second, static_cast<unsigned short>(8))};
    marshal.emplace_back(fir::ReferenceType::get(ty),
                         AT{align, /*byval=*/!isResult, /*sret=*/isResult});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structType(mlir::Location loc, fir::RecordType recTy, bool isResult) const {
    CodeGenSpecifics::Marshalling marshal;
    auto sizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash(
        loc, recTy, getDataLayout(), kindMap)};
    auto recordTypeSize{sizeAndAlign.first};
    mlir::Type seqTy;
    std::pair<mlir::Type, unsigned> firstTyAndWidth{nullptr, 0};

    // If there are less than or equal to 8 floats, the structure is flatten as
    // an array of floats.
    constexpr uint64_t maxNoOfFloats{8};

    // i64 type
    mlir::Type elemTy{mlir::IntegerType::get(recTy.getContext(), defaultWidth)};
    uint64_t nElem{static_cast<uint64_t>(
        std::ceil(static_cast<float>(recordTypeSize * 8) / defaultWidth))};

    // If the derived type components contains are all floats with the same
    // width, the argument is passed as an array of floats.
    if (hasSameFloatAndWidth(recTy, firstTyAndWidth)) {
      uint64_t n{};
      auto firstType{firstTyAndWidth.first};

      // Type is either float or complex
      if (auto cmplx = mlir::dyn_cast<mlir::ComplexType>(firstType)) {
        auto fltType{mlir::dyn_cast<mlir::FloatType>(cmplx.getElementType())};
        n = static_cast<uint64_t>(8 * recordTypeSize / fltType.getWidth());
        if (n <= maxNoOfFloats) {
          nElem = n;
          elemTy = fltType;
        }
      } else if (mlir::isa<mlir::FloatType>(firstType)) {
        auto elemSizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash(
            loc, firstType, getDataLayout(), kindMap)};
        n = static_cast<uint64_t>(recordTypeSize / elemSizeAndAlign.first);
        if (n <= maxNoOfFloats) {
          nElem = n;
          elemTy = firstType;
        }
      }
      // Neither float nor complex
      assert(n > 0 && "unexpected type");
    }

    // For function returns, only flattened if there are less than 8
    // floats in total.
    if (isResult &&
        ((mlir::isa<mlir::FloatType>(elemTy) && nElem > maxNoOfFloats) ||
         !mlir::isa<mlir::FloatType>(elemTy))) {
      return passOnTheStack(loc, recTy, isResult);
    }

    seqTy = fir::SequenceType::get(nElem, elemTy);
    marshal.emplace_back(seqTy, AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType recType,
                     const Marshalling &previousArguments) const override {
    auto sizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash(
        loc, recType, getDataLayout(), kindMap)};
    if (sizeAndAlign.first > 64) {
      return passOnTheStack(loc, recType, false);
    }
    return structType(loc, recType, false);
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType recType) const override {
    return structType(loc, recType, true);
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// sparc (sparc 32 bit) target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetSparc : public GenericTarget<TargetSparc> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 32;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location, mlir::Type eleTy) const override {
    assert(fir::isa_real(eleTy));
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 eleTy
    auto structTy =
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy});
    marshal.emplace_back(fir::ReferenceType::get(structTy), AT{});
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    assert(fir::isa_real(eleTy));
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { t, t }   struct of 2 eleTy, byval
    auto structTy =
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy});
    marshal.emplace_back(fir::ReferenceType::get(structTy),
                         AT{/*alignment=*/0, /*byval=*/true});
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// sparcv9 (sparc 64 bit) target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetSparcV9 : public GenericTarget<TargetSparcV9> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble()) {
      // two distinct float, double arguments
      marshal.emplace_back(eleTy, AT{});
      marshal.emplace_back(eleTy, AT{});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, byval, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/true});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    // Use a type that will be translated into LLVM as:
    // { eleTy, eleTy }   struct of 2 eleTy
    marshal.emplace_back(
        mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}),
        AT{});
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// RISCV64 linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetRISCV64 : public GenericTarget<TargetRISCV64> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble()) {
      // Two distinct element type arguments (re, im)
      marshal.emplace_back(eleTy, AT{});
      marshal.emplace_back(eleTy, AT{});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy, byVal
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{/*alignment=*/0, /*byval=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// AMDGPU linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetAMDGPU : public GenericTarget<TargetAMDGPU> {
  using GenericTarget::GenericTarget;

  // Default size (in bits) of the index type for strings.
  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // Lower COMPLEX(KIND=4) as an array of two element values.
      marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Pass COMPLEX(KIND=8) as two separate arguments.
      marshal.emplace_back(eleTy, AT{});
      marshal.emplace_back(eleTy, AT{});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle()) {
      // Return COMPLEX(KIND=4) as an array of two elements.
      marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{});
    } else if (sem == &llvm::APFloat::IEEEdouble()) {
      // Return COMPLEX(KIND=8) via an aggregate with two fields.
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// NVPTX linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetNVPTX : public GenericTarget<TargetNVPTX> {
  using GenericTarget::GenericTarget;

  // Default size (in bits) of the index type for strings.
  static constexpr int defaultWidth = 64;

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    TODO(loc, "handle complex argument types");
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    TODO(loc, "handle complex return types");
    return marshal;
  }
};
} // namespace

//===----------------------------------------------------------------------===//
// LoongArch64 linux target specifics.
//===----------------------------------------------------------------------===//

namespace {
struct TargetLoongArch64 : public GenericTarget<TargetLoongArch64> {
  using GenericTarget::GenericTarget;

  static constexpr int defaultWidth = 64;
  static constexpr int GRLen = defaultWidth; /* eight bytes */
  static constexpr int GRLenInChar = GRLen / 8;
  static constexpr int FRLen = defaultWidth; /* eight bytes */

  CodeGenSpecifics::Marshalling
  complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble()) {
      // Two distinct element type arguments (re, im)
      marshal.emplace_back(eleTy, AT{});
      marshal.emplace_back(eleTy, AT{});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, byval
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/true});
    } else {
      typeTodo(sem, loc, "argument");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
    CodeGenSpecifics::Marshalling marshal;
    const auto *sem = &floatToSemantics(kindMap, eleTy);
    if (sem == &llvm::APFloat::IEEEsingle() ||
        sem == &llvm::APFloat::IEEEdouble()) {
      // Use a type that will be translated into LLVM as:
      // { t, t }   struct of 2 eleTy, byVal
      marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(),
                                                mlir::TypeRange{eleTy, eleTy}),
                           AT{/*alignment=*/0, /*byval=*/true});
    } else if (sem == &llvm::APFloat::IEEEquad()) {
      // Use a type that will be translated into LLVM as:
      // { fp128, fp128 }   struct of 2 fp128, sret, align 16
      marshal.emplace_back(
          fir::ReferenceType::get(mlir::TupleType::get(
              eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})),
          AT{/*align=*/16, /*byval=*/false, /*sret=*/true});
    } else {
      typeTodo(sem, loc, "return");
    }
    return marshal;
  }

  CodeGenSpecifics::Marshalling
  integerArgumentType(mlir::Location loc,
                      mlir::IntegerType argTy) const override {
    if (argTy.getWidth() == 32) {
      // LA64 LP64D ABI requires unsigned 32 bit integers to be sign extended.
      // Therefore, Flang also follows it if a function needs to be
      // interoperable with C.
      //
      // Currently, it only adds `signext` attribute to the dummy arguments and
      // return values in the function signatures, but it does not add the
      // corresponding attribute to the actual arguments and return values in
      // `fir.call` instruction. Thanks to LLVM's integration of all these
      // attributes, the modification is still effective.
      CodeGenSpecifics::Marshalling marshal;
      AT::IntegerExtension intExt = AT::IntegerExtension::Sign;
      marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false,
                                     /*sret=*/false, /*append=*/false,
                                     /*intExt=*/intExt});
      return marshal;
    }

    return GenericTarget::integerArgumentType(loc, argTy);
  }

  /// Flatten non-basic types, resulting in an array of types containing only
  /// `IntegerType` and `FloatType`.
  llvm::SmallVector<mlir::Type> flattenTypeList(mlir::Location loc,
                                                const mlir::Type type) const {
    llvm::SmallVector<mlir::Type> flatTypes;

    llvm::TypeSwitch<mlir::Type>(type)
        .template Case<mlir::IntegerType>([&](mlir::IntegerType intTy) {
          if (intTy.getWidth() != 0)
            flatTypes.push_back(intTy);
        })
        .template Case<mlir::FloatType>([&](mlir::FloatType floatTy) {
          if (floatTy.getWidth() != 0)
            flatTypes.push_back(floatTy);
        })
        .template Case<mlir::ComplexType>([&](mlir::ComplexType cmplx) {
          const auto *sem = &floatToSemantics(kindMap, cmplx.getElementType());
          if (sem == &llvm::APFloat::IEEEsingle() ||
              sem == &llvm::APFloat::IEEEdouble() ||
              sem == &llvm::APFloat::IEEEquad())
            std::fill_n(std::back_inserter(flatTypes), 2,
                        cmplx.getElementType());
          else
            TODO(loc, "unsupported complex type(not IEEEsingle, IEEEdouble, "
                      "IEEEquad) as a structure component for BIND(C), "
                      "VALUE derived type argument and type return");
        })
        .template Case<fir::LogicalType>([&](fir::LogicalType logicalTy) {
          const unsigned width =
              kindMap.getLogicalBitsize(logicalTy.getFKind());
          if (width != 0)
            flatTypes.push_back(
                mlir::IntegerType::get(type.getContext(), width));
        })
        .template Case<fir::CharacterType>([&](fir::CharacterType charTy) {
          assert(kindMap.getCharacterBitsize(charTy.getFKind()) <= 8 &&
                 "the bit size of characterType as an interoperable type must "
                 "not exceed 8");
          for (unsigned i = 0; i < charTy.getLen(); ++i)
            flatTypes.push_back(mlir::IntegerType::get(type.getContext(), 8));
        })
        .template Case<fir::SequenceType>([&](fir::SequenceType seqTy) {
          if (!seqTy.hasDynamicExtents()) {
            const std::uint64_t numOfEle = seqTy.getConstantArraySize();
            mlir::Type eleTy = seqTy.getEleTy();
            if (!mlir::isa<mlir::IntegerType, mlir::FloatType>(eleTy)) {
              llvm::SmallVector<mlir::Type> subTypeList =
                  flattenTypeList(loc, eleTy);
              if (subTypeList.size() != 0)
                for (std::uint64_t i = 0; i < numOfEle; ++i)
                  llvm::copy(subTypeList, std::back_inserter(flatTypes));
            } else {
              std::fill_n(std::back_inserter(flatTypes), numOfEle, eleTy);
            }
          } else
            TODO(loc, "unsupported dynamic extent sequence type as a structure "
                      "component for BIND(C), "
                      "VALUE derived type argument and type return");
        })
        .template Case<fir::RecordType>([&](fir::RecordType recTy) {
          for (auto &component : recTy.getTypeList()) {
            mlir::Type eleTy = component.second;
            llvm::SmallVector<mlir::Type> subTypeList =
                flattenTypeList(loc, eleTy);
            if (subTypeList.size() != 0)
              llvm::copy(subTypeList, std::back_inserter(flatTypes));
          }
        })
        .template Case<fir::VectorType>([&](fir::VectorType vecTy) {
          auto sizeAndAlign = fir::getTypeSizeAndAlignmentOrCrash(
              loc, vecTy, getDataLayout(), kindMap);
          if (sizeAndAlign.first == 2 * GRLenInChar)
            flatTypes.push_back(
                mlir::IntegerType::get(type.getContext(), 2 * GRLen));
          else
            TODO(loc, "unsupported vector width(must be 128 bits)");
        })
        .Default([&](mlir::Type ty) {
          if (fir::conformsWithPassByRef(ty))
            flatTypes.push_back(
                mlir::IntegerType::get(type.getContext(), GRLen));
          else
            TODO(loc, "unsupported component type for BIND(C), VALUE derived "
                      "type argument and type return");
        });

    return flatTypes;
  }

  /// Determine if a struct is eligible to be passed in FARs (and GARs) (i.e.,
  /// when flattened it contains a single fp value, fp+fp, or int+fp of
  /// appropriate size).
  bool detectFARsEligibleStruct(mlir::Location loc, fir::RecordType recTy,
                                mlir::Type &field1Ty,
                                mlir::Type &field2Ty) const {
    field1Ty = field2Ty = nullptr;
    llvm::SmallVector<mlir::Type> flatTypes = flattenTypeList(loc, recTy);
    size_t flatSize = flatTypes.size();

    // Cannot be eligible if the number of flattened types is equal to 0 or
    // greater than 2.
    if (flatSize == 0 || flatSize > 2)
      return false;

    bool isFirstAvaliableFloat = false;

    assert((mlir::isa<mlir::IntegerType, mlir::FloatType>(flatTypes[0])) &&
           "Type must be integerType or floatType after flattening");
    if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(flatTypes[0])) {
      const unsigned Size = floatTy.getWidth();
      // Can't be eligible if larger than the FP registers. Half precision isn't
      // currently supported on LoongArch and the ABI hasn't been confirmed, so
      // default to the integer ABI in that case.
      if (Size > FRLen || Size < 32)
        return false;
      isFirstAvaliableFloat = true;
      field1Ty = floatTy;
    } else if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(flatTypes[0])) {
      if (intTy.getWidth() > GRLen)
        return false;
      field1Ty = intTy;
    }

    // flatTypes has two elements
    if (flatSize == 2) {
      assert((mlir::isa<mlir::IntegerType, mlir::FloatType>(flatTypes[1])) &&
             "Type must be integerType or floatType after flattening");
      if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(flatTypes[1])) {
        const unsigned Size = floatTy.getWidth();
        if (Size > FRLen || Size < 32)
          return false;
        field2Ty = floatTy;
        return true;
      } else if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(flatTypes[1])) {
        // Can't be eligible if an integer type was already found (int+int pairs
        // are not eligible).
        if (!isFirstAvaliableFloat)
          return false;
        if (intTy.getWidth() > GRLen)
          return false;
        field2Ty = intTy;
        return true;
      }
    }

    // return isFirstAvaliableFloat if flatTypes only has one element
    return isFirstAvaliableFloat;
  }

  bool checkTypeHasEnoughRegs(mlir::Location loc, int &GARsLeft, int &FARsLeft,
                              const mlir::Type type) const {
    if (!type)
      return true;

    llvm::TypeSwitch<mlir::Type>(type)
        .template Case<mlir::IntegerType>([&](mlir::IntegerType intTy) {
          const unsigned width = intTy.getWidth();
          if (width > 128)
            TODO(loc,
                 "integerType with width exceeding 128 bits is unsupported");
          if (width == 0)
            return;
          if (width <= GRLen)
            --GARsLeft;
          else if (width <= 2 * GRLen)
            GARsLeft = GARsLeft - 2;
        })
        .template Case<mlir::FloatType>([&](mlir::FloatType floatTy) {
          const unsigned width = floatTy.getWidth();
          if (width > 128)
            TODO(loc, "floatType with width exceeding 128 bits is unsupported");
          if (width == 0)
            return;
          if (width == 32 || width == 64)
            --FARsLeft;
          else if (width <= GRLen)
            --GARsLeft;
          else if (width <= 2 * GRLen)
            GARsLeft = GARsLeft - 2;
        })
        .Default([&](mlir::Type ty) {
          if (fir::conformsWithPassByRef(ty))
            --GARsLeft; // Pointers.
          else
            TODO(loc, "unsupported component type for BIND(C), VALUE derived "
                      "type argument and type return");
        });

    return GARsLeft >= 0 && FARsLeft >= 0;
  }

  bool hasEnoughRegisters(mlir::Location loc, int GARsLeft, int FARsLeft,
                          const Marshalling &previousArguments,
                          const mlir::Type &field1Ty,
                          const mlir::Type &field2Ty) const {
    for (auto &typeAndAttr : previousArguments) {
      const auto &attr = std::get<Attributes>(typeAndAttr);
      if (attr.isByVal()) {
        // Previous argument passed on the stack, and its address is passed in
        // GAR.
        --GARsLeft;
        continue;
      }

      // Previous aggregate arguments were marshalled into simpler arguments.
      const auto &type = std::get<mlir::Type>(typeAndAttr);
      llvm::SmallVector<mlir::Type> flatTypes = flattenTypeList(loc, type);

      for (auto &flatTy : flatTypes) {
        if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, flatTy))
          return false;
      }
    }

    if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, field1Ty))
      return false;
    if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, field2Ty))
      return false;
    return true;
  }

  /// LoongArch64 subroutine calling sequence ABI in:
  /// https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc#subroutine-calling-sequence
  CodeGenSpecifics::Marshalling
  classifyStruct(mlir::Location loc, fir::RecordType recTy, int GARsLeft,
                 int FARsLeft, bool isResult,
                 const Marshalling &previousArguments) const {
    CodeGenSpecifics::Marshalling marshal;

    auto [recSize, recAlign] = fir::getTypeSizeAndAlignmentOrCrash(
        loc, recTy, getDataLayout(), kindMap);
    mlir::MLIRContext *context = recTy.getContext();

    if (recSize == 0) {
      TODO(loc, "unsupported empty struct type for BIND(C), "
                "VALUE derived type argument and type return");
    }

    if (recSize > 2 * GRLenInChar) {
      marshal.emplace_back(
          fir::ReferenceType::get(recTy),
          AT{recAlign, /*byval=*/!isResult, /*sret=*/isResult});
      return marshal;
    }

    // Pass by FARs(and GARs)
    mlir::Type field1Ty = nullptr, field2Ty = nullptr;
    if (detectFARsEligibleStruct(loc, recTy, field1Ty, field2Ty) &&
        hasEnoughRegisters(loc, GARsLeft, FARsLeft, previousArguments, field1Ty,
                           field2Ty)) {
      if (!isResult) {
        if (field1Ty)
          marshal.emplace_back(field1Ty, AT{});
        if (field2Ty)
          marshal.emplace_back(field2Ty, AT{});
      } else {
        // field1Ty is always preferred over field2Ty for assignment, so there
        // will never be a case where field1Ty == nullptr and field2Ty !=
        // nullptr.
        if (field1Ty && !field2Ty)
          marshal.emplace_back(field1Ty, AT{});
        else if (field1Ty && field2Ty)
          marshal.emplace_back(
              mlir::TupleType::get(context,
                                   mlir::TypeRange{field1Ty, field2Ty}),
              AT{/*alignment=*/0, /*byval=*/true});
      }
      return marshal;
    }

    if (recSize <= GRLenInChar) {
      marshal.emplace_back(mlir::IntegerType::get(context, GRLen), AT{});
      return marshal;
    }

    if (recAlign == 2 * GRLenInChar) {
      marshal.emplace_back(mlir::IntegerType::get(context, 2 * GRLen), AT{});
      return marshal;
    }

    // recSize > GRLenInChar && recSize <= 2 * GRLenInChar
    marshal.emplace_back(
        fir::SequenceType::get({2}, mlir::IntegerType::get(context, GRLen)),
        AT{});
    return marshal;
  }

  /// Marshal a derived type passed by value like a C struct.
  CodeGenSpecifics::Marshalling
  structArgumentType(mlir::Location loc, fir::RecordType recTy,
                     const Marshalling &previousArguments) const override {
    int GARsLeft = 8;
    int FARsLeft = FRLen ? 8 : 0;

    return classifyStruct(loc, recTy, GARsLeft, FARsLeft, /*isResult=*/false,
                          previousArguments);
  }

  CodeGenSpecifics::Marshalling
  structReturnType(mlir::Location loc, fir::RecordType recTy) const override {
    // The rules for return and argument types are the same.
    int GARsLeft = 2;
    int FARsLeft = FRLen ? 2 : 0;
    return classifyStruct(loc, recTy, GARsLeft, FARsLeft, /*isResult=*/true,
                          {});
  }
};
} // namespace

// Instantiate the overloaded target instance based on the triple value.
// TODO: Add other targets to this file as needed.
std::unique_ptr<fir::CodeGenSpecifics>
fir::CodeGenSpecifics::get(mlir::MLIRContext *ctx, llvm::Triple &&trp,
                           KindMapping &&kindMap, llvm::StringRef targetCPU,
                           mlir::LLVM::TargetFeaturesAttr targetFeatures,
                           const mlir::DataLayout &dl) {
  switch (trp.getArch()) {
  default:
    break;
  case llvm::Triple::ArchType::x86:
    if (trp.isOSWindows())
      return std::make_unique<TargetI386Win>(ctx, std::move(trp),
                                             std::move(kindMap), targetCPU,
                                             targetFeatures, dl);
    else
      return std::make_unique<TargetI386>(ctx, std::move(trp),
                                          std::move(kindMap), targetCPU,
                                          targetFeatures, dl);
  case llvm::Triple::ArchType::x86_64:
    if (trp.isOSWindows())
      return std::make_unique<TargetX86_64Win>(ctx, std::move(trp),
                                               std::move(kindMap), targetCPU,
                                               targetFeatures, dl);
    else
      return std::make_unique<TargetX86_64>(ctx, std::move(trp),
                                            std::move(kindMap), targetCPU,
                                            targetFeatures, dl);
  case llvm::Triple::ArchType::aarch64:
    return std::make_unique<TargetAArch64>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::ppc:
    return std::make_unique<TargetPPC>(ctx, std::move(trp), std::move(kindMap),
                                       targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::ppc64:
    return std::make_unique<TargetPPC64>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::ppc64le:
    return std::make_unique<TargetPPC64le>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::sparc:
    return std::make_unique<TargetSparc>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::sparcv9:
    return std::make_unique<TargetSparcV9>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::riscv64:
    return std::make_unique<TargetRISCV64>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::amdgcn:
    return std::make_unique<TargetAMDGPU>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::nvptx64:
    return std::make_unique<TargetNVPTX>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  case llvm::Triple::ArchType::loongarch64:
    return std::make_unique<TargetLoongArch64>(
        ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);
  }
  TODO(mlir::UnknownLoc::get(ctx), "target not implemented");
}

std::unique_ptr<fir::CodeGenSpecifics> fir::CodeGenSpecifics::get(
    mlir::MLIRContext *ctx, llvm::Triple &&trp, KindMapping &&kindMap,
    llvm::StringRef targetCPU, mlir::LLVM::TargetFeaturesAttr targetFeatures,
    const mlir::DataLayout &dl, llvm::StringRef tuneCPU) {
  std::unique_ptr<fir::CodeGenSpecifics> CGS = fir::CodeGenSpecifics::get(
      ctx, std::move(trp), std::move(kindMap), targetCPU, targetFeatures, dl);

  CGS->tuneCPU = tuneCPU;
  return CGS;
}