#include "CIRGenTypes.h"

#include "CIRGenCXXABI.h"
#include "CIRGenFunctionInfo.h"
#include "CIRGenModule.h"
#include "mlir/IR/BuiltinTypes.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/Type.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"

#include <cassert>

using namespace clang;
using namespace clang::CIRGen;

CIRGenTypes::CIRGenTypes(CIRGenModule &genModule)
    : cgm(genModule), astContext(genModule.getASTContext()),
      builder(cgm.getBuilder()), theCXXABI(cgm.getCXXABI()),
      theABIInfo(cgm.getTargetCIRGenInfo().getABIInfo()) {}

CIRGenTypes::~CIRGenTypes() {
  for (auto i = functionInfos.begin(), e = functionInfos.end(); i != e;)
    delete &*i++;
}

mlir::MLIRContext &CIRGenTypes::getMLIRContext() const {
  return *builder.getContext();
}

/// Return true if the specified type in a function parameter or result position
/// can be converted to a CIR type at this point. This boils down to being
/// whether it is complete, as well as whether we've temporarily deferred
/// expanding the type because we're in a recursive context.
bool CIRGenTypes::isFuncParamTypeConvertible(clang::QualType type) {
  // Some ABIs cannot have their member pointers represented in LLVM IR unless
  // certain circumstances have been reached, but in CIR we represent member
  // pointer types abstractly at this point so they are always convertible.
  if (type->getAs<MemberPointerType>())
    return true;

  // If this isn't a tag type, we can convert it.
  const TagType *tagType = type->getAs<TagType>();
  if (!tagType)
    return true;

  // Function types involving incomplete class types are problematic in MLIR.
  return !tagType->isIncompleteType();
}

/// Code to verify a given function type is complete, i.e. the return type and
/// all of the parameter types are complete. Also check to see if we are in a
/// RS_StructPointer context, and if so whether any struct types have been
/// pended. If so, we don't want to ask the ABI lowering code to handle a type
/// that cannot be converted to a CIR type.
bool CIRGenTypes::isFuncTypeConvertible(const FunctionType *ft) {
  if (!isFuncParamTypeConvertible(ft->getReturnType()))
    return false;

  if (const auto *fpt = dyn_cast<FunctionProtoType>(ft))
    for (unsigned i = 0, e = fpt->getNumParams(); i != e; i++)
      if (!isFuncParamTypeConvertible(fpt->getParamType(i)))
        return false;

  return true;
}

mlir::Type CIRGenTypes::convertFunctionTypeInternal(QualType qft) {
  assert(qft.isCanonical());
  const FunctionType *ft = cast<FunctionType>(qft.getTypePtr());

  // In classic codegen, if the function type depends on an incomplete type
  // (e.g. a struct or enum), it cannot lower the function type due to ABI
  // handling requirements and returns a placeholder. In CIR, ABI handling is
  // deferred until after codegen, and record types are identified by name, so
  // incomplete record type references in the function type will automatically
  // see the complete type once the record is defined. We can always produce a
  // proper function type here.

  const CIRGenFunctionInfo *fi;
  if (const auto *fpt = dyn_cast<FunctionProtoType>(ft)) {
    fi = &arrangeFreeFunctionType(
        CanQual<FunctionProtoType>::CreateUnsafe(QualType(fpt, 0)));
  } else {
    const FunctionNoProtoType *fnpt = cast<FunctionNoProtoType>(ft);
    fi = &arrangeFreeFunctionType(
        CanQual<FunctionNoProtoType>::CreateUnsafe(QualType(fnpt, 0)));
  }

  mlir::Type resultType = getFunctionType(*fi);

  return resultType;
}

// This is CIR's version of CodeGenTypes::addRecordTypeName. It isn't shareable
// because CIR has different uniquing requirements.
std::string CIRGenTypes::getRecordTypeName(const clang::RecordDecl *recordDecl,
                                           StringRef suffix) {
  llvm::SmallString<256> typeName;
  llvm::raw_svector_ostream outStream(typeName);

  PrintingPolicy policy = recordDecl->getASTContext().getPrintingPolicy();
  policy.SuppressInlineNamespace =
      llvm::to_underlying(PrintingPolicy::SuppressInlineNamespaceMode::None);
  policy.AlwaysIncludeTypeForTemplateArgument = true;
  policy.PrintAsCanonical = true;
  policy.SuppressTagKeyword = true;

  if (recordDecl->getIdentifier())
    QualType(astContext.getCanonicalTagType(recordDecl))
        .print(outStream, policy);
  else if (auto *typedefNameDecl = recordDecl->getTypedefNameForAnonDecl())
    typedefNameDecl->printQualifiedName(outStream, policy);
  else
    outStream << builder.getUniqueAnonRecordName();

  if (!suffix.empty())
    outStream << suffix;

  return builder.getUniqueRecordName(std::string(typeName));
}

/// Return true if the specified type is already completely laid out.
bool CIRGenTypes::isRecordLayoutComplete(const Type *ty) const {
  const auto it = recordDeclTypes.find(ty);
  return it != recordDeclTypes.end() && it->second.isComplete();
}

// We have multiple forms of this function that call each other, so we need to
// declare one in advance.
static bool
isSafeToConvert(QualType qt, CIRGenTypes &cgt,
                llvm::SmallPtrSetImpl<const RecordDecl *> &alreadyChecked);

/// Return true if it is safe to convert the specified record decl to CIR and
/// lay it out, false if doing so would cause us to get into a recursive
/// compilation mess.
static bool
isSafeToConvert(const RecordDecl *rd, CIRGenTypes &cgt,
                llvm::SmallPtrSetImpl<const RecordDecl *> &alreadyChecked) {
  // If we have already checked this type (maybe the same type is used by-value
  // multiple times in multiple record fields, don't check again.
  if (!alreadyChecked.insert(rd).second)
    return true;

  assert(rd->isCompleteDefinition() &&
         "Expect RecordDecl to be CompleteDefinition");
  const Type *key = cgt.getASTContext().getCanonicalTagType(rd).getTypePtr();

  // If this type is already laid out, converting it is a noop.
  if (cgt.isRecordLayoutComplete(key))
    return true;

  // Check the cross-call cache. This avoids redundant recursive field walks
  // for the same record types across different convertRecordDeclType calls
  // during a single layout phase.
  if (cgt.isCachedSafeToConvert(key))
    return true;

  // If this type is currently being laid out, we can't recursively compile it.
  if (cgt.isRecordBeingLaidOut(key))
    return false;

  // If this type would require laying out bases that are currently being laid
  // out, don't do it.  This includes virtual base classes which get laid out
  // when a class is translated, even though they aren't embedded by-value into
  // the class.
  if (const CXXRecordDecl *crd = dyn_cast<CXXRecordDecl>(rd)) {
    for (const clang::CXXBaseSpecifier &i : crd->bases())
      if (!isSafeToConvert(i.getType()
                               ->castAs<RecordType>()
                               ->getDecl()
                               ->getDefinitionOrSelf(),
                           cgt, alreadyChecked))
        return false;
  }

  // If this type would require laying out members that are currently being laid
  // out, don't do it.
  for (const FieldDecl *field : rd->fields())
    if (!isSafeToConvert(field->getType(), cgt, alreadyChecked))
      return false;

  // Cache the positive result. This will be cleared when recordsBeingLaidOut
  // changes.
  cgt.cacheSafeToConvert(key);

  // If there are no problems, lets do it.
  return true;
}

/// Return true if it is safe to convert this field type, which requires the
/// record elements contained by-value to all be recursively safe to convert.
static bool
isSafeToConvert(QualType qt, CIRGenTypes &cgt,
                llvm::SmallPtrSetImpl<const RecordDecl *> &alreadyChecked) {
  // Strip off atomic type sugar.
  if (const auto *at = qt->getAs<AtomicType>())
    qt = at->getValueType();

  // If this is a record, check it.
  if (const auto *rd = qt->getAsRecordDecl())
    return isSafeToConvert(rd, cgt, alreadyChecked);

  // If this is an array, check the elements, which are embedded inline.
  if (const auto *at = cgt.getASTContext().getAsArrayType(qt))
    return isSafeToConvert(at->getElementType(), cgt, alreadyChecked);

  // Otherwise, there is no concern about transforming this. We only care about
  // things that are contained by-value in a record that can have another
  // record as a member.
  return true;
}

// Return true if it is safe to convert the specified record decl to CIR and lay
// it out, false if doing so would cause us to get into a recursive compilation
// mess.
static bool isSafeToConvert(const RecordDecl *rd, CIRGenTypes &cgt) {
  // If no records are being laid out, we can certainly do this one.
  if (cgt.noRecordsBeingLaidOut())
    return true;

  llvm::SmallPtrSet<const RecordDecl *, 16> alreadyChecked;
  return isSafeToConvert(rd, cgt, alreadyChecked);
}

bool CIRGenModule::isPaddedAtomicType(QualType type) {
  return isPaddedAtomicType(type->castAs<AtomicType>());
}

bool CIRGenModule::isPaddedAtomicType(const AtomicType *type) {
  return astContext.getTypeSize(type) !=
         astContext.getTypeSize(type->getValueType());
}

/// Lay out a tagged decl type like struct or union.
mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *rd) {
  // TagDecl's are not necessarily unique, instead use the (clang) type
  // connected to the decl.
  const Type *key = astContext.getCanonicalTagType(rd).getTypePtr();
  cir::RecordType entry = recordDeclTypes[key];

  // If we don't have an entry for this record yet, create one.
  // We create an incomplete type initially. If `rd` is complete, we will
  // add the members below.
  if (!entry) {
    auto name = getRecordTypeName(rd, "");
    entry = builder.getIncompleteRecordTy(name, rd);
    recordDeclTypes[key] = entry;
  }

  rd = rd->getDefinition();
  if (!rd || !rd->isCompleteDefinition() || entry.isComplete())
    return entry;

  // If converting this type would cause us to infinitely loop, don't do it!
  if (!isSafeToConvert(rd, *this)) {
    deferredRecords.push_back(rd);
    return entry;
  }

  // Okay, this is a definition of a type. Compile the implementation now.
  bool insertResult = recordsBeingLaidOut.insert(key).second;
  (void)insertResult;
  assert(insertResult && "isSafeToCovert() should have caught this.");

  // Invalidate the safety cache since recordsBeingLaidOut changed.
  safeToConvertCache.clear();

  // Force conversion of non-virtual base classes recursively.
  if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(rd)) {
    for (const auto &base : cxxRecordDecl->bases()) {
      if (base.isVirtual())
        continue;
      convertRecordDeclType(base.getType()->castAsRecordDecl());
    }
  }

  // Layout fields.
  std::unique_ptr<CIRGenRecordLayout> layout = computeRecordLayout(rd, &entry);
  recordDeclTypes[key] = entry;
  cirGenRecordLayouts[key] = std::move(layout);

  // We're done laying out this record.
  bool eraseResult = recordsBeingLaidOut.erase(key);
  (void)eraseResult;
  assert(eraseResult && "record not in RecordsBeingLaidOut set?");

  // Invalidate the safety cache since recordsBeingLaidOut changed.
  safeToConvertCache.clear();

  // If we're done converting the outer-most record, then convert any deferred
  // records as well.
  if (recordsBeingLaidOut.empty())
    while (!deferredRecords.empty())
      convertRecordDeclType(deferredRecords.pop_back_val());

  return entry;
}

mlir::Type CIRGenTypes::convertType(QualType type) {
  type = astContext.getCanonicalType(type);
  const Type *ty = type.getTypePtr();

  if (astContext.getLangOpts().CUDAIsDevice) {
    if (type->isCUDADeviceBuiltinSurfaceType()) {
      if (mlir::Type ty =
              cgm.getTargetCIRGenInfo().getCUDADeviceBuiltinSurfaceDeviceType())
        return ty;
    } else if (type->isCUDADeviceBuiltinTextureType()) {
      assert(!cir::MissingFeatures::cudaTextureType());
    }
  }

  // Process record types before the type cache lookup.
  if (const auto *recordType = dyn_cast<RecordType>(type))
    return convertRecordDeclType(recordType->getDecl()->getDefinitionOrSelf());

  // Has the type already been processed?
  TypeCacheTy::iterator tci = typeCache.find(ty);
  if (tci != typeCache.end())
    return tci->second;

  // For types that haven't been implemented yet or are otherwise unsupported,
  // report an error and return 'int'.

  mlir::Type resultType = nullptr;
  switch (ty->getTypeClass()) {
  case Type::Record:
    llvm_unreachable("Should have been handled above");

  case Type::Builtin: {
    switch (cast<BuiltinType>(ty)->getKind()) {
    // void
    case BuiltinType::Void:
      resultType = cgm.voidTy;
      break;

    // bool
    case BuiltinType::Bool:
      resultType = cir::BoolType::get(&getMLIRContext());
      break;

    // Signed integral types.
    case BuiltinType::Char_S:
    case BuiltinType::Int:
    case BuiltinType::Int128:
    case BuiltinType::Long:
    case BuiltinType::LongLong:
    case BuiltinType::SChar:
    case BuiltinType::Short:
    case BuiltinType::WChar_S:
    case BuiltinType::Accum:
    case BuiltinType::Fract:
    case BuiltinType::LongAccum:
    case BuiltinType::LongFract:
    case BuiltinType::ShortAccum:
    case BuiltinType::ShortFract:
    // Saturated signed types.
    case BuiltinType::SatAccum:
    case BuiltinType::SatFract:
    case BuiltinType::SatLongAccum:
    case BuiltinType::SatLongFract:
    case BuiltinType::SatShortAccum:
    case BuiltinType::SatShortFract:
      resultType =
          cir::IntType::get(&getMLIRContext(), astContext.getTypeSize(ty),
                            /*isSigned=*/true);
      break;

    // SVE types
    case BuiltinType::SveInt8:
      resultType =
          cir::VectorType::get(builder.getSInt8Ty(), 16, /*is_scalable=*/true);
      break;
    case BuiltinType::SveUint8:
      resultType =
          cir::VectorType::get(builder.getUInt8Ty(), 16, /*is_scalable=*/true);
      break;
    case BuiltinType::SveInt16:
      resultType =
          cir::VectorType::get(builder.getSInt16Ty(), 8, /*is_scalable=*/true);
      break;
    case BuiltinType::SveUint16:
      resultType =
          cir::VectorType::get(builder.getUInt16Ty(), 8, /*is_scalable=*/true);
      break;
    case BuiltinType::SveFloat16:
      resultType = cir::VectorType::get(builder.getFp16Ty(), 8,
                                        /*is_scalable=*/true);
      break;
    case BuiltinType::SveBFloat16:
      resultType = cir::VectorType::get(builder.getFp16Ty(), 8,
                                        /*is_scalable=*/true);
      break;
    case BuiltinType::SveInt32:
      resultType =
          cir::VectorType::get(builder.getSInt32Ty(), 4, /*is_scalable=*/true);
      break;
    case BuiltinType::SveUint32:
      resultType =
          cir::VectorType::get(builder.getUInt32Ty(), 4, /*is_scalable=*/true);
      break;
    case BuiltinType::SveFloat32:
      resultType = cir::VectorType::get(builder.getSingleTy(), 4,
                                        /*is_scalable=*/true);
      break;
    case BuiltinType::SveInt64:
      resultType =
          cir::VectorType::get(builder.getSInt64Ty(), 2, /*is_scalable=*/true);
      break;
    case BuiltinType::SveUint64:
      resultType =
          cir::VectorType::get(builder.getUInt64Ty(), 2, /*is_scalable=*/true);
      break;
    case BuiltinType::SveFloat64:
      resultType = cir::VectorType::get(builder.getDoubleTy(), 2,
                                        /*is_scalable=*/true);
      break;
    case BuiltinType::SveBool:
      resultType = cir::VectorType::get(builder.getUIntNTy(1), 16,
                                        /*is_scalable=*/true);
      break;

    // Unsigned integral types.
    case BuiltinType::Char8:
    case BuiltinType::Char16:
    case BuiltinType::Char32:
    case BuiltinType::Char_U:
    case BuiltinType::UChar:
    case BuiltinType::UInt:
    case BuiltinType::UInt128:
    case BuiltinType::ULong:
    case BuiltinType::ULongLong:
    case BuiltinType::UShort:
    case BuiltinType::WChar_U:
    case BuiltinType::UAccum:
    case BuiltinType::UFract:
    case BuiltinType::ULongAccum:
    case BuiltinType::ULongFract:
    case BuiltinType::UShortAccum:
    case BuiltinType::UShortFract:
    // Saturated unsigned types.
    case BuiltinType::SatUAccum:
    case BuiltinType::SatUFract:
    case BuiltinType::SatULongAccum:
    case BuiltinType::SatULongFract:
    case BuiltinType::SatUShortAccum:
    case BuiltinType::SatUShortFract:
      resultType =
          cir::IntType::get(&getMLIRContext(), astContext.getTypeSize(ty),
                            /*isSigned=*/false);
      break;

    // Floating-point types
    case BuiltinType::Float16:
      resultType = cgm.fP16Ty;
      break;
    case BuiltinType::Half:
      resultType = cgm.fP16Ty;
      break;
    case BuiltinType::BFloat16:
      resultType = cgm.bFloat16Ty;
      break;
    case BuiltinType::MFloat8:
      resultType = cgm.uInt8Ty;
      break;
    case BuiltinType::Float:
      assert(&astContext.getFloatTypeSemantics(type) ==
                 &llvm::APFloat::IEEEsingle() &&
             "ClangIR NYI: 'float' in a format other than IEEE 32-bit");
      resultType = cgm.floatTy;
      break;
    case BuiltinType::Double:
      assert(&astContext.getFloatTypeSemantics(type) ==
                 &llvm::APFloat::IEEEdouble() &&
             "ClangIR NYI: 'double' in a format other than IEEE 64-bit");
      resultType = cgm.doubleTy;
      break;
    case BuiltinType::LongDouble:
      resultType =
          builder.getLongDoubleTy(astContext.getFloatTypeSemantics(type));
      break;
    case BuiltinType::Float128:
      resultType = cgm.fP128Ty;
      break;
    case BuiltinType::Ibm128:
      cgm.errorNYI(SourceLocation(), "processing of built-in type", type);
      resultType = cgm.sInt32Ty;
      break;

    case BuiltinType::NullPtr:
      // Add proper CIR type for it? this looks mostly useful for sema related
      // things (like for overloads accepting void), for now, given that
      // `sizeof(std::nullptr_t)` is equal to `sizeof(void *)`, model
      // std::nullptr_t as !cir.ptr<!void>
      resultType = builder.getVoidPtrTy();
      break;

#define AMDGPU_OPAQUE_PTR_TYPE(Name, Id, SingletonId, Width, Align, AS)        \
  case BuiltinType::Id: {                                                      \
    if (BuiltinType::Id == BuiltinType::AMDGPUTexture) {                       \
      resultType = cir::VectorType::get(builder.getSInt32Ty(), 8);             \
    } else {                                                                   \
      resultType = builder.getPointerTo(cgm.voidTy);                           \
    }                                                                          \
    break;                                                                     \
  }
#define AMDGPU_NAMED_BARRIER_TYPE(Name, Id, SingletonId, Width, Align, Scope)  \
  case BuiltinType::Id:                                                        \
    llvm_unreachable("NYI");
#define AMDGPU_TYPE(Name, Id, SingletonId, Width, Align)                       \
  case BuiltinType::Id:                                                        \
    llvm_unreachable("NYI");
#include "clang/Basic/AMDGPUTypes.def"

    default:
      cgm.errorNYI(SourceLocation(), "processing of built-in type", type);
      resultType = cgm.sInt32Ty;
      break;
    }
    break;
  }

  case Type::Complex: {
    const auto *ct = cast<clang::ComplexType>(ty);
    mlir::Type elementTy = convertType(ct->getElementType());
    resultType = cir::ComplexType::get(elementTy);
    break;
  }

  case Type::LValueReference:
  case Type::RValueReference: {
    const ReferenceType *refTy = cast<ReferenceType>(ty);
    QualType elemTy = refTy->getPointeeType();
    auto pointeeType = convertTypeForMem(elemTy);
    resultType = builder.getPointerTo(pointeeType, elemTy.getAddressSpace());
    assert(resultType && "Cannot get pointer type?");
    break;
  }

  case Type::Pointer: {
    const PointerType *ptrTy = cast<PointerType>(ty);
    QualType elemTy = ptrTy->getPointeeType();
    assert(!elemTy->isConstantMatrixType() && "not implemented");

    mlir::Type pointeeType = convertType(elemTy);

    resultType = builder.getPointerTo(pointeeType, elemTy.getAddressSpace());
    break;
  }

  case Type::VariableArray: {
    const VariableArrayType *a = cast<VariableArrayType>(ty);
    if (a->getIndexTypeCVRQualifiers() != 0)
      cgm.errorNYI(SourceLocation(), "non trivial array types", type);
    // VLAs resolve to the innermost element type; this matches
    // the return of alloca, and there isn't any obviously better choice.
    resultType = convertTypeForMem(a->getElementType());
    break;
  }

  case Type::IncompleteArray: {
    const IncompleteArrayType *arrTy = cast<IncompleteArrayType>(ty);
    if (arrTy->getIndexTypeCVRQualifiers() != 0)
      cgm.errorNYI(SourceLocation(), "non trivial array types", type);

    mlir::Type elemTy = convertTypeForMem(arrTy->getElementType());
    // int X[] -> [0 x int], unless the element type is not sized.  If it is
    // unsized (e.g. an incomplete record) just use [0 x i8].
    if (!cir::isSized(elemTy)) {
      elemTy = cgm.sInt8Ty;
    }

    resultType = cir::ArrayType::get(elemTy, 0);
    break;
  }

  case Type::ConstantArray: {
    const ConstantArrayType *arrTy = cast<ConstantArrayType>(ty);
    mlir::Type elemTy = convertTypeForMem(arrTy->getElementType());
    // In classic codegen, arrays of unsized types which it assumes are "arrays
    // of undefined struct type" are lowered to arrays of i8 "just to have a
    // concrete type", but in CIR, we can get here with abstract types like
    // !cir.method and !cir.data_member, so we just create an array of the type
    // and handle it during lowering if we still don't have a sized type.
    resultType = cir::ArrayType::get(elemTy, arrTy->getSize().getZExtValue());
    break;
  }

  case Type::ExtVector:
  case Type::Vector: {
    const VectorType *vec = cast<VectorType>(ty);
    const mlir::Type elemTy = convertType(vec->getElementType());
    resultType = cir::VectorType::get(elemTy, vec->getNumElements());
    break;
  }

  case Type::Enum: {
    const auto *ed = ty->castAsEnumDecl();
    if (auto integerType = ed->getIntegerType(); !integerType.isNull())
      return convertType(integerType);
    // Return a placeholder 'i32' type.  This can be changed later when the
    // type is defined (see UpdateCompletedType), but is likely to be the
    // "right" answer.
    resultType = cgm.uInt32Ty;
    break;
  }

  case Type::MemberPointer: {
    const auto *mpt = cast<MemberPointerType>(ty);

    NestedNameSpecifier mptNNS = mpt->getQualifier();
    auto clsTy = mlir::cast<cir::RecordType>(
        convertType(QualType(mptNNS.getAsType(), 0)));
    if (mpt->isMemberDataPointer()) {
      mlir::Type memberTy = convertType(mpt->getPointeeType());
      resultType = cir::DataMemberType::get(memberTy, clsTy);
    } else {
      auto memberFuncTy = getFunctionType(cgm.getTypes().arrangeCXXMethodType(
          mptNNS.getAsRecordDecl(),
          mpt->getPointeeType()->getAs<clang::FunctionProtoType>(),
          /*methodDecl=*/nullptr));
      resultType = cir::MethodType::get(memberFuncTy, clsTy);
    }
    break;
  }

  case Type::FunctionNoProto:
  case Type::FunctionProto:
    resultType = convertFunctionTypeInternal(type);
    break;

  case Type::BitInt: {
    const auto *bitIntTy = cast<BitIntType>(type);
    unsigned numBits = bitIntTy->getNumBits();
    assert(numBits <= cir::IntType::maxBitwidth() &&
           "_BitInt width exceeds CIR IntType maximum");
    resultType =
        cir::IntType::get(&getMLIRContext(), numBits, bitIntTy->isSigned(),
                          /*isBitInt=*/true);
    break;
  }

  case Type::Atomic: {
    QualType valueType = cast<AtomicType>(ty)->getValueType();
    resultType = convertTypeForMem(valueType);

    // Pad out to the inflated size if necessary.
    uint64_t valueSize = astContext.getTypeSize(valueType);
    uint64_t atomicSize = astContext.getTypeSize(ty);
    if (valueSize != atomicSize) {
      assert(valueSize < atomicSize);
      auto paddingArray =
          cir::ArrayType::get(cgm.sInt8Ty, (atomicSize - valueSize) / 8);
      mlir::Type elements[] = {resultType, paddingArray};
      resultType = cir::StructType::get(&getMLIRContext(), /*members=*/elements,
                                        /*packed=*/false, /*padded=*/false,
                                        /*is_class=*/false);
    }

    break;
  }

  default:
    cgm.errorNYI(SourceLocation(), "processing of type",
                 type->getTypeClassName());
    resultType = cgm.sInt32Ty;
    break;
  }

  assert(resultType && "Type conversion not yet implemented");

  typeCache[ty] = resultType;
  return resultType;
}

mlir::Type CIRGenTypes::convertTypeForMem(clang::QualType qualType,
                                          bool forBitField) {
  if (qualType->isConstantMatrixType()) {
    cgm.errorNYI("Matrix type conversion");
    return cgm.sInt32Ty;
  }

  mlir::Type convertedType = convertType(qualType);

  assert(!forBitField && "Bit fields NYI");

  // If this is a bit-precise integer type in a bitfield representation, map
  // this integer to the target-specified size.
  if (forBitField && qualType->isBitIntType())
    assert(!qualType->isBitIntType() && "Bit field with type _BitInt NYI");

  return convertedType;
}

/// Return record layout info for the given record decl.
const CIRGenRecordLayout &
CIRGenTypes::getCIRGenRecordLayout(const RecordDecl *rd) {
  const auto *key = astContext.getCanonicalTagType(rd).getTypePtr();

  // If we have already computed the layout, return it.
  auto it = cirGenRecordLayouts.find(key);
  if (it != cirGenRecordLayouts.end())
    return *it->second;

  // Compute the type information.
  convertRecordDeclType(rd);

  // Now try again.
  it = cirGenRecordLayouts.find(key);

  assert(it != cirGenRecordLayouts.end() &&
         "Unable to find record layout information for type");
  return *it->second;
}

bool CIRGenTypes::isZeroInitializable(clang::QualType t) {
  if (t->getAs<PointerType>())
    return astContext.getTargetNullPointerValue(t) == 0;

  if (const auto *at = astContext.getAsArrayType(t)) {
    if (isa<IncompleteArrayType>(at))
      return true;

    if (const auto *cat = dyn_cast<ConstantArrayType>(at))
      if (astContext.getConstantArrayElementCount(cat) == 0)
        return true;
  }

  if (const auto *rd = t->getAsRecordDecl())
    return isZeroInitializable(rd);

  if (const auto *mpt = t->getAs<MemberPointerType>())
    return theCXXABI.isZeroInitializable(mpt);

  if (t->getAs<HLSLInlineSpirvType>())
    cgm.errorNYI(SourceLocation(),
                 "isZeroInitializable for HLSLInlineSpirvType");

  return true;
}

bool CIRGenTypes::isZeroInitializable(const RecordDecl *rd) {
  return getCIRGenRecordLayout(rd).isZeroInitializable();
}

const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo(
    CanQualType returnType, bool isInstanceMethod,
    llvm::ArrayRef<CanQualType> argTypes, FunctionType::ExtInfo info,
    RequiredArgs required) {
  assert(llvm::all_of(argTypes,
                      [](CanQualType t) { return t.isCanonicalAsParam(); }));
  // Lookup or create unique function info.
  llvm::FoldingSetNodeID id;
  CIRGenFunctionInfo::Profile(id, isInstanceMethod, info, required, returnType,
                              argTypes);

  void *insertPos = nullptr;
  CIRGenFunctionInfo *fi = functionInfos.FindNodeOrInsertPos(id, insertPos);
  if (fi) {
    // We found a matching function info based on id. These asserts verify that
    // it really is a match.
    assert(
        fi->getReturnType() == returnType &&
        std::equal(fi->argTypesBegin(), fi->argTypesEnd(), argTypes.begin()) &&
        "Bad match based on CIRGenFunctionInfo folding set id");
    return *fi;
  }

  assert(!cir::MissingFeatures::opCallCallConv());

  // Construction the function info. We co-allocate the ArgInfos.
  fi = CIRGenFunctionInfo::create(info, isInstanceMethod, returnType, argTypes,
                                  required);
  functionInfos.InsertNode(fi, insertPos);

  return *fi;
}

const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl gd) {
  assert(!dyn_cast<ObjCMethodDecl>(gd.getDecl()) &&
         "This is reported as a FIXME in LLVM codegen");
  const auto *fd = cast<FunctionDecl>(gd.getDecl());

  if (isa<CXXConstructorDecl>(gd.getDecl()) ||
      isa<CXXDestructorDecl>(gd.getDecl()))
    return arrangeCXXStructorDeclaration(gd);

  return arrangeFunctionDeclaration(fd);
}

// When we find the full definition for a TagDecl, replace the 'opaque' type we
// previously made for it if applicable.
void CIRGenTypes::updateCompletedType(const TagDecl *td) {
  // If this is an enum being completed, then we flush all non-struct types
  // from the cache. This allows function types and other things that may be
  // derived from the enum to be recomputed.
  if ([[maybe_unused]] const auto *ed = dyn_cast<EnumDecl>(td)) {
    // Classic codegen clears the type cache if it contains an entry for this
    // enum type that doesn't use i32 as the underlying type, but I can't find
    // a test case that meets that condition. C++ doesn't allow forward
    // declaration of enums, and C doesn't allow an incomplete forward
    // declaration with a non-default type.
    assert(
        !typeCache.count(
            ed->getASTContext().getCanonicalTagType(ed)->getTypePtr()) ||
        (convertType(ed->getIntegerType()) ==
         typeCache[ed->getASTContext().getCanonicalTagType(ed)->getTypePtr()]));
    // If necessary, provide the full definition of a type only used with a
    // declaration so far.
    assert(!cir::MissingFeatures::generateDebugInfo());
    return;
  }

  // If we completed a RecordDecl that we previously used and converted to an
  // anonymous type, then go ahead and complete it now.
  const auto *rd = cast<RecordDecl>(td);
  if (rd->isDependentType())
    return;

  // Only complete if we converted it already. If we haven't converted it yet,
  // we'll just do it lazily.
  if (recordDeclTypes.count(astContext.getCanonicalTagType(rd).getTypePtr()))
    convertRecordDeclType(rd);

  // If necessary, provide the full definition of a type only used with a
  // declaration so far.
  assert(!cir::MissingFeatures::generateDebugInfo());
}

unsigned CIRGenTypes::getTargetAddressSpace(QualType ty) const {
  // Return the address space for the type. If the type is a
  // function type without an address space qualifier, the
  // program address space is used. Otherwise, the target picks
  // the best address space based on the type information
  return ty->isFunctionType() && !ty.hasAddressSpace()
             ? cgm.getDataLayout().getProgramAddressSpace()
             : getASTContext().getTargetAddressSpace(ty.getAddressSpace());
}
