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

#include "Program.h"
#include "ByteCodeStmtGen.h"
#include "Context.h"
#include "Function.h"
#include "Opcode.h"
#include "PrimType.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"

using namespace clang;
using namespace clang::interp;

unsigned Program::createGlobalString(const StringLiteral *S) {
  const size_t CharWidth = S->getCharByteWidth();
  const size_t BitWidth = CharWidth * Ctx.getCharBit();

  PrimType CharType;
  switch (CharWidth) {
  case 1:
    CharType = PT_Sint8;
    break;
  case 2:
    CharType = PT_Uint16;
    break;
  case 4:
    CharType = PT_Uint32;
    break;
  default:
    llvm_unreachable("unsupported character width");
  }

  // Create a descriptor for the string.
  Descriptor *Desc = allocateDescriptor(S, CharType, S->getLength() + 1,
                                        /*isConst=*/true,
                                        /*isTemporary=*/false,
                                        /*isMutable=*/false);

  // Allocate storage for the string.
  // The byte length does not include the null terminator.
  unsigned I = Globals.size();
  unsigned Sz = Desc->getAllocSize();
  auto *G = new (Allocator, Sz) Global(Desc, /*isStatic=*/true,
                                       /*isExtern=*/false);
  Globals.push_back(G);

  // Construct the string in storage.
  const Pointer Ptr(G->block());
  for (unsigned I = 0, N = S->getLength(); I <= N; ++I) {
    Pointer Field = Ptr.atIndex(I).narrow();
    const uint32_t CodePoint = I == N ? 0 : S->getCodeUnit(I);
    switch (CharType) {
      case PT_Sint8: {
        using T = PrimConv<PT_Sint8>::T;
        Field.deref<T>() = T::from(CodePoint, BitWidth);
        break;
      }
      case PT_Uint16: {
        using T = PrimConv<PT_Uint16>::T;
        Field.deref<T>() = T::from(CodePoint, BitWidth);
        break;
      }
      case PT_Uint32: {
        using T = PrimConv<PT_Uint32>::T;
        Field.deref<T>() = T::from(CodePoint, BitWidth);
        break;
      }
      default:
        llvm_unreachable("unsupported character type");
    }
  }
  return I;
}

Pointer Program::getPtrGlobal(unsigned Idx) {
  assert(Idx < Globals.size());
  return Pointer(Globals[Idx]->block());
}

llvm::Optional<unsigned> Program::getGlobal(const ValueDecl *VD) {
  auto It = GlobalIndices.find(VD);
  if (It != GlobalIndices.end())
    return It->second;

  // Find any previous declarations which were aleady evaluated.
  llvm::Optional<unsigned> Index;
  for (const Decl *P = VD; P; P = P->getPreviousDecl()) {
    auto It = GlobalIndices.find(P);
    if (It != GlobalIndices.end()) {
      Index = It->second;
      break;
    }
  }

  // Map the decl to the existing index.
  if (Index) {
    GlobalIndices[VD] = *Index;
    return {};
  }

  return Index;
}

llvm::Optional<unsigned> Program::getOrCreateGlobal(const ValueDecl *VD) {
  if (auto Idx = getGlobal(VD))
    return Idx;

  if (auto Idx = createGlobal(VD)) {
    GlobalIndices[VD] = *Idx;
    return Idx;
  }
  return {};
}

llvm::Optional<unsigned> Program::getOrCreateDummy(const ParmVarDecl *PD) {
  auto &ASTCtx = Ctx.getASTContext();

  // Create a pointer to an incomplete array of the specified elements.
  QualType ElemTy = PD->getType()->getAs<PointerType>()->getPointeeType();
  QualType Ty = ASTCtx.getIncompleteArrayType(ElemTy, ArrayType::Normal, 0);

  // Dedup blocks since they are immutable and pointers cannot be compared.
  auto It = DummyParams.find(PD);
  if (It != DummyParams.end())
    return It->second;

  if (auto Idx = createGlobal(PD, Ty, /*isStatic=*/true, /*isExtern=*/true)) {
    DummyParams[PD] = *Idx;
    return Idx;
  }
  return {};
}

llvm::Optional<unsigned> Program::createGlobal(const ValueDecl *VD) {
  bool IsStatic, IsExtern;
  if (auto *Var = dyn_cast<VarDecl>(VD)) {
    IsStatic = !Var->hasLocalStorage();
    IsExtern = !Var->getAnyInitializer();
  } else {
    IsStatic = false;
    IsExtern = true;
  }
  if (auto Idx = createGlobal(VD, VD->getType(), IsStatic, IsExtern)) {
    for (const Decl *P = VD; P; P = P->getPreviousDecl())
      GlobalIndices[P] = *Idx;
    return *Idx;
  }
  return {};
}

llvm::Optional<unsigned> Program::createGlobal(const Expr *E) {
  return createGlobal(E, E->getType(), /*isStatic=*/true, /*isExtern=*/false);
}

llvm::Optional<unsigned> Program::createGlobal(const DeclTy &D, QualType Ty,
                                               bool IsStatic, bool IsExtern) {
  // Create a descriptor for the global.
  Descriptor *Desc;
  const bool IsConst = Ty.isConstQualified();
  const bool IsTemporary = D.dyn_cast<const Expr *>();
  if (auto T = Ctx.classify(Ty)) {
    Desc = createDescriptor(D, *T, IsConst, IsTemporary);
  } else {
    Desc = createDescriptor(D, Ty.getTypePtr(), IsConst, IsTemporary);
  }
  if (!Desc)
    return {};

  // Allocate a block for storage.
  unsigned I = Globals.size();

  auto *G = new (Allocator, Desc->getAllocSize())
      Global(getCurrentDecl(), Desc, IsStatic, IsExtern);
  G->block()->invokeCtor();

  Globals.push_back(G);

  return I;
}

Function *Program::getFunction(const FunctionDecl *F) {
  F = F->getDefinition();
  auto It = Funcs.find(F);
  return It == Funcs.end() ? nullptr : It->second.get();
}

llvm::Expected<Function *> Program::getOrCreateFunction(const FunctionDecl *F) {
  if (Function *Func = getFunction(F)) {
    return Func;
  }

  // Try to compile the function if it wasn't compiled yet.
  if (const FunctionDecl *FD = F->getDefinition())
    return ByteCodeStmtGen<ByteCodeEmitter>(Ctx, *this).compileFunc(FD);

  // A relocation which traps if not resolved.
  return nullptr;
}

Record *Program::getOrCreateRecord(const RecordDecl *RD) {
  // Use the actual definition as a key.
  RD = RD->getDefinition();
  if (!RD)
    return nullptr;

  // Deduplicate records.
  auto It = Records.find(RD);
  if (It != Records.end()) {
    return It->second;
  }

  // Number of bytes required by fields and base classes.
  unsigned Size = 0;
  // Number of bytes required by virtual base.
  unsigned VirtSize = 0;

  // Helper to get a base descriptor.
  auto GetBaseDesc = [this](const RecordDecl *BD, Record *BR) -> Descriptor * {
    if (!BR)
      return nullptr;
    return allocateDescriptor(BD, BR, /*isConst=*/false,
                              /*isTemporary=*/false,
                              /*isMutable=*/false);
  };

  // Reserve space for base classes.
  Record::BaseList Bases;
  Record::VirtualBaseList VirtBases;
  if (auto *CD = dyn_cast<CXXRecordDecl>(RD)) {
    for (const CXXBaseSpecifier &Spec : CD->bases()) {
      if (Spec.isVirtual())
        continue;

      const RecordDecl *BD = Spec.getType()->getAs<RecordType>()->getDecl();
      Record *BR = getOrCreateRecord(BD);
      if (Descriptor *Desc = GetBaseDesc(BD, BR)) {
        Size += align(sizeof(InlineDescriptor));
        Bases.push_back({BD, Size, Desc, BR});
        Size += align(BR->getSize());
        continue;
      }
      return nullptr;
    }

    for (const CXXBaseSpecifier &Spec : CD->vbases()) {
      const RecordDecl *BD = Spec.getType()->getAs<RecordType>()->getDecl();
      Record *BR = getOrCreateRecord(BD);

      if (Descriptor *Desc = GetBaseDesc(BD, BR)) {
        VirtSize += align(sizeof(InlineDescriptor));
        VirtBases.push_back({BD, VirtSize, Desc, BR});
        VirtSize += align(BR->getSize());
        continue;
      }
      return nullptr;
    }
  }

  // Reserve space for fields.
  Record::FieldList Fields;
  for (const FieldDecl *FD : RD->fields()) {
    // Reserve space for the field's descriptor and the offset.
    Size += align(sizeof(InlineDescriptor));

    // Classify the field and add its metadata.
    QualType FT = FD->getType();
    const bool IsConst = FT.isConstQualified();
    const bool IsMutable = FD->isMutable();
    Descriptor *Desc;
    if (llvm::Optional<PrimType> T = Ctx.classify(FT)) {
      Desc = createDescriptor(FD, *T, IsConst, /*isTemporary=*/false,
                              IsMutable);
    } else {
      Desc = createDescriptor(FD, FT.getTypePtr(), IsConst,
                              /*isTemporary=*/false, IsMutable);
    }
    if (!Desc)
      return nullptr;
    Fields.push_back({FD, Size, Desc});
    Size += align(Desc->getAllocSize());
  }

  Record *R = new (Allocator) Record(RD, std::move(Bases), std::move(Fields),
                                     std::move(VirtBases), VirtSize, Size);
  Records.insert({RD, R});
  return R;
}

Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty,
                                      bool IsConst, bool IsTemporary,
                                      bool IsMutable) {
  // Classes and structures.
  if (auto *RT = Ty->getAs<RecordType>()) {
    if (auto *Record = getOrCreateRecord(RT->getDecl()))
      return allocateDescriptor(D, Record, IsConst, IsTemporary, IsMutable);
  }

  // Arrays.
  if (auto ArrayType = Ty->getAsArrayTypeUnsafe()) {
    QualType ElemTy = ArrayType->getElementType();
    // Array of well-known bounds.
    if (auto CAT = dyn_cast<ConstantArrayType>(ArrayType)) {
      size_t NumElems = CAT->getSize().getZExtValue();
      if (llvm::Optional<PrimType> T = Ctx.classify(ElemTy)) {
        // Arrays of primitives.
        unsigned ElemSize = primSize(*T);
        if (std::numeric_limits<unsigned>::max() / ElemSize <= NumElems) {
          return {};
        }
        return allocateDescriptor(D, *T, NumElems, IsConst, IsTemporary,
                                  IsMutable);
      } else {
        // Arrays of composites. In this case, the array is a list of pointers,
        // followed by the actual elements.
        Descriptor *Desc =
            createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary);
        if (!Desc)
          return nullptr;
        InterpSize ElemSize = Desc->getAllocSize() + sizeof(InlineDescriptor);
        if (std::numeric_limits<unsigned>::max() / ElemSize <= NumElems)
          return {};
        return allocateDescriptor(D, Desc, NumElems, IsConst, IsTemporary,
                                  IsMutable);
      }
    }

    // Array of unknown bounds - cannot be accessed and pointer arithmetic
    // is forbidden on pointers to such objects.
    if (isa<IncompleteArrayType>(ArrayType)) {
      if (llvm::Optional<PrimType> T = Ctx.classify(ElemTy)) {
        return allocateDescriptor(D, *T, IsTemporary,
                                  Descriptor::UnknownSize{});
      } else {
        Descriptor *Desc =
            createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary);
        if (!Desc)
          return nullptr;
        return allocateDescriptor(D, Desc, IsTemporary,
                                  Descriptor::UnknownSize{});
      }
    }
  }

  // Atomic types.
  if (auto *AT = Ty->getAs<AtomicType>()) {
    const Type *InnerTy = AT->getValueType().getTypePtr();
    return createDescriptor(D, InnerTy, IsConst, IsTemporary, IsMutable);
  }

  // Complex types - represented as arrays of elements.
  if (auto *CT = Ty->getAs<ComplexType>()) {
    PrimType ElemTy = *Ctx.classify(CT->getElementType());
    return allocateDescriptor(D, ElemTy, 2, IsConst, IsTemporary, IsMutable);
  }

  return nullptr;
}
