//===-- irclass.cpp -------------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/ADT/SmallString.h"
#ifndef NDEBUG
#include "llvm/Support/raw_ostream.h"
#endif

#include "aggregate.h"
#include "declaration.h"
#include "hdrgen.h" // for parametersTypeToChars()
#include "mtype.h"
#include "target.h"
#include "gen/funcgenstate.h"
#include "gen/irstate.h"
#include "gen/logger.h"
#include "gen/tollvm.h"
#include "gen/llvmhelpers.h"
#include "gen/arrays.h"
#include "gen/metadata.h"
#include "gen/runtime.h"
#include "gen/functions.h"
#include "gen/abi.h"
#include "gen/mangling.h"

#include "ir/iraggr.h"
#include "ir/irfunction.h"
#include "ir/irtypeclass.h"

//////////////////////////////////////////////////////////////////////////////

extern LLConstant *DtoDefineClassInfo(ClassDeclaration *cd);

//////////////////////////////////////////////////////////////////////////////

LLGlobalVariable *IrAggr::getVtblSymbol() {
  if (vtbl) {
    return vtbl;
  }

  // create the vtblZ symbol
  auto initname = getMangledVTableSymbolName(aggrdecl);

  LLType *vtblTy = stripModifiers(type)->ctype->isClass()->getVtbl();

  vtbl =
      getOrCreateGlobal(aggrdecl->loc, gIR->module, vtblTy, true,
                        llvm::GlobalValue::ExternalLinkage, nullptr, initname);

  return vtbl;
}

//////////////////////////////////////////////////////////////////////////////

LLGlobalVariable *IrAggr::getClassInfoSymbol() {
  if (classInfo) {
    return classInfo;
  }

  // create the ClassZ / InterfaceZ symbol
  std::string initname = getMangledClassInfoSymbolName(aggrdecl);

  // The type is also ClassInfo for interfaces – the actual TypeInfo for them
  // is a TypeInfo_Interface instance that references __ClassZ in its "base"
  // member.
  ClassDeclaration *cinfo = Type::typeinfoclass;
  DtoType(cinfo->type);
  IrTypeClass *tc = stripModifiers(cinfo->type)->ctype->isClass();
  assert(tc && "invalid ClassInfo type");

  // classinfos cannot be constants since they're used as locks for synchronized
  classInfo = getOrCreateGlobal(
      aggrdecl->loc, gIR->module, tc->getMemoryLLType(), false,
      llvm::GlobalValue::ExternalLinkage, nullptr, initname);

  // Generate some metadata on this ClassInfo if it's for a class.
  ClassDeclaration *classdecl = aggrdecl->isClassDeclaration();
  if (classdecl && !aggrdecl->isInterfaceDeclaration()) {
    // Gather information
    LLType *type = DtoType(aggrdecl->type);
    LLType *bodyType = llvm::cast<LLPointerType>(type)->getElementType();
    bool hasDestructor = (classdecl->dtor != nullptr);
    bool hasCustomDelete = (classdecl->aggDelete != nullptr);
// Construct the fields
#if LDC_LLVM_VER >= 306
    llvm::Metadata *mdVals[CD_NumFields];
    mdVals[CD_BodyType] =
        llvm::ConstantAsMetadata::get(llvm::UndefValue::get(bodyType));
    mdVals[CD_Finalize] = llvm::ConstantAsMetadata::get(
        LLConstantInt::get(LLType::getInt1Ty(gIR->context()), hasDestructor));
    mdVals[CD_CustomDelete] = llvm::ConstantAsMetadata::get(
        LLConstantInt::get(LLType::getInt1Ty(gIR->context()), hasCustomDelete));
#else
    MDNodeField *mdVals[CD_NumFields];
    mdVals[CD_BodyType] = llvm::UndefValue::get(bodyType);
    mdVals[CD_Finalize] =
        LLConstantInt::get(LLType::getInt1Ty(gIR->context()), hasDestructor);
    mdVals[CD_CustomDelete] =
        LLConstantInt::get(LLType::getInt1Ty(gIR->context()), hasCustomDelete);
#endif
    // Construct the metadata and insert it into the module.
    llvm::SmallString<64> name;
    llvm::NamedMDNode *node = gIR->module.getOrInsertNamedMetadata(
        llvm::Twine(CD_PREFIX, initname).toStringRef(name));
    node->addOperand(llvm::MDNode::get(
        gIR->context(), llvm::makeArrayRef(mdVals, CD_NumFields)));
  }

  return classInfo;
}

//////////////////////////////////////////////////////////////////////////////

LLGlobalVariable *IrAggr::getInterfaceArraySymbol() {
  if (classInterfacesArray) {
    return classInterfacesArray;
  }

  ClassDeclaration *cd = aggrdecl->isClassDeclaration();

  size_t n = stripModifiers(type)->ctype->isClass()->getNumInterfaceVtbls();
  assert(n > 0 && "getting ClassInfo.interfaces storage symbol, but we "
                  "don't implement any interfaces");

  LLType *InterfaceTy = DtoType(Type::typeinfoclass->fields[3]->type->nextOf());

  // create Interface[N]
  LLArrayType *array_type = llvm::ArrayType::get(InterfaceTy, n);

  // put it in a global
  std::string name("_D");
  name.append(mangle(cd));
  name.append("16__interfaceInfosZ");

  // We keep this as external for now and only consider template linkage if
  // we emit the initializer later.
  classInterfacesArray =
      getOrCreateGlobal(cd->loc, gIR->module, array_type, true,
                        llvm::GlobalValue::ExternalLinkage, nullptr, name);

  return classInterfacesArray;
}

//////////////////////////////////////////////////////////////////////////////

LLConstant *IrAggr::getVtblInit() {
  if (constVtbl) {
    return constVtbl;
  }

  IF_LOG Logger::println("Building vtbl initializer");
  LOG_SCOPE;

  ClassDeclaration *cd = aggrdecl->isClassDeclaration();
  assert(cd && "not class");

  std::vector<llvm::Constant *> constants;
  constants.reserve(cd->vtbl.dim);

  // start with the classinfo
  llvm::Constant *c;
  if (!cd->isCPPclass()) {
    c = getClassInfoSymbol();
    c = DtoBitCast(c, DtoType(Type::typeinfoclass->type));
    constants.push_back(c);
  }

  // add virtual function pointers
  size_t n = cd->vtbl.dim;
  for (size_t i = cd->vtblOffset(); i < n; i++) {
    Dsymbol *dsym = static_cast<Dsymbol *>(cd->vtbl.data[i]);
    assert(dsym && "null vtbl member");

    FuncDeclaration *fd = dsym->isFuncDeclaration();
    assert(fd && "vtbl entry not a function");

    if (cd->isAbstract() || (fd->isAbstract() && !fd->fbody)) {
      c = getNullValue(getPtrToType(DtoFunctionType(fd)));
    } else {
      DtoResolveFunction(fd);
      assert(isIrFuncCreated(fd) && "invalid vtbl function");
      c = getIrFunc(fd)->func;
      if (cd->isFuncHidden(fd)) {
        // fd is hidden from the view of this class. If fd overlaps with any
        // function in the vtbl[], issue error.
        for (size_t j = 1; j < n; j++) {
          if (j == i) {
            continue;
          }
          auto fd2 =
              static_cast<Dsymbol *>(cd->vtbl.data[j])->isFuncDeclaration();
          if (!fd2->ident->equals(fd->ident)) {
            continue;
          }
          if (fd->leastAsSpecialized(fd2) || fd2->leastAsSpecialized(fd)) {
            TypeFunction *tf = static_cast<TypeFunction *>(fd->type);
            if (tf->ty == Tfunction) {
              cd->error("use of %s%s is hidden by %s; use 'alias %s = %s.%s;' "
                        "to introduce base class overload set",
                        fd->toPrettyChars(),
                        parametersTypeToChars(tf->parameters, tf->varargs),
                        cd->toChars(),

                        fd->toChars(), fd->parent->toChars(), fd->toChars());
            } else {
              cd->error("use of %s is hidden by %s", fd->toPrettyChars(),
                        cd->toChars());
            }
            fatal();
            break;
          }
        }
      }
    }
    constants.push_back(c);
  }

  // build the constant struct
  LLType *vtblTy = stripModifiers(type)->ctype->isClass()->getVtbl();
#ifndef NDEBUG
  size_t nc = constants.size();

  for (size_t i = 0; i < nc; ++i) {
    if (constants[i]->getType() != vtblTy->getContainedType(i)) {
      llvm::errs() << "type mismatch for entry # " << i
                   << " in vtbl initializer\n";

      constants[i]->getType()->dump();
      vtblTy->getContainedType(i)->dump();
    }
  }

#endif
  constVtbl = LLConstantStruct::get(isaStruct(vtblTy), constants);

  assert(constVtbl->getType() ==
             stripModifiers(type)->ctype->isClass()->getVtbl() &&
         "vtbl initializer type mismatch");

  return constVtbl;
}

//////////////////////////////////////////////////////////////////////////////

LLConstant *IrAggr::getClassInfoInit() {
  if (constClassInfo) {
    return constClassInfo;
  }
  constClassInfo = DtoDefineClassInfo(aggrdecl->isClassDeclaration());
  return constClassInfo;
}

//////////////////////////////////////////////////////////////////////////////

llvm::GlobalVariable *IrAggr::getInterfaceVtbl(BaseClass *b, bool new_instance,
                                               size_t interfaces_index) {
  auto it = interfaceVtblMap.find({b->sym, interfaces_index});
  if (it != interfaceVtblMap.end()) {
    return it->second;
  }

  IF_LOG Logger::println(
      "Building vtbl for implementation of interface %s in class %s",
      b->sym->toPrettyChars(), aggrdecl->toPrettyChars());
  LOG_SCOPE;

  ClassDeclaration *cd = aggrdecl->isClassDeclaration();
  assert(cd && "not a class aggregate");

  FuncDeclarations vtbl_array;
  b->fillVtbl(cd, &vtbl_array, new_instance);

  std::vector<llvm::Constant *> constants;
  constants.reserve(vtbl_array.dim);

  if (!b->sym->isCPPinterface()) { // skip interface info for CPP interfaces
    // index into the interfaces array
    llvm::Constant *idxs[2] = {DtoConstSize_t(0),
                               DtoConstSize_t(interfaces_index)};

    llvm::GlobalVariable *interfaceInfosZ = getInterfaceArraySymbol();
    llvm::Constant *c = llvm::ConstantExpr::getGetElementPtr(
#if LDC_LLVM_VER >= 307
        isaPointer(interfaceInfosZ)->getElementType(),
#endif
        interfaceInfosZ, idxs, true);

    constants.push_back(c);
  }

  // Thunk prefix
  char thunkPrefix[16];
  int thunkLen = sprintf(thunkPrefix, "Thn%d_", b->offset);
  char thunkPrefixLen[16];
  sprintf(thunkPrefixLen, "%d", thunkLen);

  // add virtual function pointers
  size_t n = vtbl_array.dim;
  for (size_t i = b->sym->vtblOffset(); i < n; i++) {
    Dsymbol *dsym = static_cast<Dsymbol *>(vtbl_array.data[i]);
    if (dsym == nullptr) {
      // FIXME
      // why is this null?
      // happens for mini/s.d
      constants.push_back(getNullValue(getVoidPtrType()));
      continue;
    }

    FuncDeclaration *fd = dsym->isFuncDeclaration();
    assert(fd && "vtbl entry not a function");

    assert((!fd->isAbstract() || fd->fbody) &&
           "null symbol in interface implementation vtable");

    DtoResolveFunction(fd);
    assert(isIrFuncCreated(fd) && "invalid vtbl function");

    IrFunction *irFunc = getIrFunc(fd);

    assert(irFunc->irFty.arg_this);

    int thunkOffset = b->offset;
    if (fd->interfaceVirtual)
      thunkOffset -= fd->interfaceVirtual->offset;
    if (thunkOffset == 0) {
      constants.push_back(irFunc->func);
      continue;
    }

    // Create the thunk function if it does not already exist in this
    // module.
    OutBuffer nameBuf;
    const auto mangledTargetName = mangleExact(fd);
    nameBuf.write(mangledTargetName, 2);
    nameBuf.writestring(thunkPrefix);
    nameBuf.writestring(mangledTargetName + 2);
    const char *thunkName = nameBuf.extractString();
    llvm::Function *thunk = gIR->module.getFunction(thunkName);
    if (!thunk) {
      const LinkageWithCOMDAT lwc(LLGlobalValue::LinkOnceODRLinkage,
                                  supportsCOMDAT());
      thunk = LLFunction::Create(
          isaFunction(irFunc->func->getType()->getContainedType(0)), lwc.first,
          thunkName, &gIR->module);
      setLinkage(lwc, thunk);
      thunk->copyAttributesFrom(irFunc->func);

// Thunks themselves don't have an identity, only the target
// function has.
#if LDC_LLVM_VER >= 309
      thunk->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
#else
      thunk->setUnnamedAddr(true);
#endif

#if LDC_LLVM_VER >= 307
      // thunks don't need exception handling themselves
      thunk->setPersonalityFn(nullptr);
#endif

      // It is necessary to add debug information to the thunk in case it is
      // subject to inlining. See https://llvm.org/bugs/show_bug.cgi?id=26833
      IF_LOG Logger::println("Doing function body for thunk to: %s",
                             fd->toChars());

      // Create a dummy FuncDeclaration with enough information to satisfy the
      // DIBuilder
      FuncDeclaration *thunkFd = reinterpret_cast<FuncDeclaration *>(
          memcpy(new char[sizeof(FuncDeclaration)], (void *)fd,
                 sizeof(FuncDeclaration)));
      thunkFd->ir = new IrDsymbol();
      auto thunkFunc = getIrFunc(thunkFd, true); // create the IrFunction
      thunkFunc->func = thunk;
      thunkFunc->type = irFunc->type;
      gIR->funcGenStates.emplace_back(new FuncGenState(*thunkFunc, *gIR));

      // debug info
      thunkFunc->diSubprogram = gIR->DBuilder.EmitThunk(thunk, thunkFd);

      // create entry and end blocks
      llvm::BasicBlock *beginbb =
          llvm::BasicBlock::Create(gIR->context(), "", thunk);
      gIR->scopes.push_back(IRScope(beginbb));

      gIR->DBuilder.EmitFuncStart(thunkFd);

      // Copy the function parameters, so later we can pass them to the
      // real function and set their names from the original function (the
      // latter being just for IR readablilty).
      std::vector<LLValue *> args;
      llvm::Function::arg_iterator thunkArg = thunk->arg_begin();
      llvm::Function::arg_iterator origArg = irFunc->func->arg_begin();
      for (; thunkArg != thunk->arg_end(); ++thunkArg, ++origArg) {
        thunkArg->setName(origArg->getName());
        args.push_back(&(*thunkArg));
      }

      // cast 'this' to Object
      const int thisArgIndex =
          (!irFunc->irFty.arg_sret || gABI->passThisBeforeSret(irFunc->type))
              ? 0
              : 1;
      LLValue *&thisArg = args[thisArgIndex];
      LLType *targetThisType = thisArg->getType();
      thisArg = DtoBitCast(thisArg, getVoidPtrType());
      thisArg = DtoGEP1(thisArg, DtoConstInt(-thunkOffset), true);
      thisArg = DtoBitCast(thisArg, targetThisType);

      // all calls that might be subject to inlining into a caller with debug
      // info should have debug info, too
      gIR->DBuilder.EmitStopPoint(fd->loc);

      // call the real vtbl function.
      llvm::CallInst *call = gIR->ir->CreateCall(irFunc->func, args);
      call->setCallingConv(irFunc->func->getCallingConv());
      call->setTailCallKind(llvm::CallInst::TCK_Tail);

      // return from the thunk
      if (thunk->getReturnType() == LLType::getVoidTy(gIR->context())) {
        llvm::ReturnInst::Create(gIR->context(), beginbb);
      } else {
        llvm::ReturnInst::Create(gIR->context(), call, beginbb);
      }

      gIR->DBuilder.EmitFuncEnd(thunkFd);

      // clean up
      gIR->scopes.pop_back();

      gIR->funcGenStates.pop_back();
    }

    constants.push_back(thunk);
  }

  // build the vtbl constant
  llvm::Constant *vtbl_constant =
      LLConstantStruct::getAnon(gIR->context(), constants, false);

  std::string mangledName("_D");
  mangledName.append(mangle(cd));
  mangledName.append("11__interface");
  mangledName.append(mangle(b->sym));
  mangledName.append(thunkPrefixLen);
  mangledName.append(thunkPrefix);
  mangledName.append("6__vtblZ");

  const auto lwc = DtoLinkage(cd);
  LLGlobalVariable *GV =
      getOrCreateGlobal(cd->loc, gIR->module, vtbl_constant->getType(), true,
                        lwc.first, vtbl_constant, mangledName);
  setLinkage(lwc, GV);

  // insert into the vtbl map
  interfaceVtblMap.insert({{b->sym, interfaces_index}, GV});

  return GV;
}

bool IrAggr::isPacked() const {
  return static_cast<IrTypeAggr *>(type->ctype)->packed;
}

//////////////////////////////////////////////////////////////////////////////

LLConstant *IrAggr::getClassInfoInterfaces() {
  IF_LOG Logger::println("Building ClassInfo.interfaces");
  LOG_SCOPE;

  ClassDeclaration *cd = aggrdecl->isClassDeclaration();
  assert(cd);

  size_t n = interfacesWithVtbls.size();
  assert(stripModifiers(type)->ctype->isClass()->getNumInterfaceVtbls() == n &&
         "inconsistent number of interface vtables in this class");

  VarDeclaration *interfaces_idx = Type::typeinfoclass->fields[3];

  if (n == 0) {
    return getNullValue(DtoType(interfaces_idx->type));
  }

  // Build array of:
  //
  // struct Interface
  // {
  //     ClassInfo   classinfo;
  //     void*[]     vtbl;
  //     ptrdiff_t   offset;
  // }

  LLSmallVector<LLConstant *, 6> constants;
  constants.reserve(cd->vtblInterfaces->dim);

  LLType *classinfo_type = DtoType(Type::typeinfoclass->type);
  LLType *voidptrptr_type = DtoType(Type::tvoid->pointerTo()->pointerTo());
  VarDeclaration *idx = Type::typeinfoclass->fields[3];
  LLStructType *interface_type = isaStruct(DtoType(idx->type->nextOf()));
  assert(interface_type);

  for (size_t i = 0; i < n; ++i) {
    BaseClass *it = interfacesWithVtbls[i];

    IF_LOG Logger::println("Adding interface %s", it->sym->toPrettyChars());

    IrAggr *irinter = getIrAggr(it->sym);
    assert(irinter && "interface has null IrStruct");
    IrTypeClass *itc = stripModifiers(irinter->type)->ctype->isClass();
    assert(itc && "null interface IrTypeClass");

    // classinfo
    LLConstant *ci = irinter->getClassInfoSymbol();
    ci = DtoBitCast(ci, classinfo_type);

    // vtbl
    LLConstant *vtb;
    // interface get a null
    if (cd->isInterfaceDeclaration()) {
      vtb = DtoConstSlice(DtoConstSize_t(0), getNullValue(voidptrptr_type));
    } else {
      auto itv = interfaceVtblMap.find({it->sym, i});
      assert(itv != interfaceVtblMap.end() && "interface vtbl not found");
      vtb = itv->second;
      vtb = DtoBitCast(vtb, voidptrptr_type);
      vtb = DtoConstSlice(DtoConstSize_t(itc->getVtblSize()), vtb);
    }

    // offset
    LLConstant *off = DtoConstSize_t(it->offset);

    // create Interface struct
    LLConstant *inits[3] = {ci, vtb, off};
    LLConstant *entry =
        LLConstantStruct::get(interface_type, llvm::makeArrayRef(inits, 3));
    constants.push_back(entry);
  }

  // create Interface[N]
  LLArrayType *array_type = llvm::ArrayType::get(interface_type, n);

  // create and apply initializer
  LLConstant *arr = LLConstantArray::get(array_type, constants);
  auto ciarr = getInterfaceArraySymbol();
  ciarr->setInitializer(arr);
  setLinkage(cd, ciarr);

  // return null, only baseclass provide interfaces
  if (cd->vtblInterfaces->dim == 0) {
    return getNullValue(DtoType(interfaces_idx->type));
  }

  // only the interface explicitly implemented by this class
  // (not super classes) should show in ClassInfo
  LLConstant *idxs[2] = {DtoConstSize_t(0),
                         DtoConstSize_t(n - cd->vtblInterfaces->dim)};

  LLConstant *ptr = llvm::ConstantExpr::getGetElementPtr(
#if LDC_LLVM_VER >= 307
      isaPointer(ciarr)->getElementType(),
#endif
      ciarr, idxs, true);

  // return as a slice
  return DtoConstSlice(DtoConstSize_t(cd->vtblInterfaces->dim), ptr);
}

//////////////////////////////////////////////////////////////////////////////

void IrAggr::initializeInterface() {
  InterfaceDeclaration *base = aggrdecl->isInterfaceDeclaration();
  assert(base && "not interface");

  // has interface vtbls?
  if (!base->vtblInterfaces) {
    return;
  }

  for (auto bc : *base->vtblInterfaces) {
    // add to the interface list
    interfacesWithVtbls.push_back(bc);
  }
}

//////////////////////////////////////////////////////////////////////////////
