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

#include "gen/classes.h"

#include "dmd/aggregate.h"
#include "dmd/declaration.h"
#include "dmd/errors.h"
#include "dmd/expression.h"
#include "dmd/identifier.h"
#include "dmd/init.h"
#include "dmd/mtype.h"
#include "dmd/target.h"
#include "gen/arrays.h"
#include "gen/dvalue.h"
#include "gen/functions.h"
#include "gen/irstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/nested.h"
#include "gen/optimizer.h"
#include "gen/runtime.h"
#include "gen/structs.h"
#include "gen/tollvm.h"
#include "ir/iraggr.h"
#include "ir/irdsymbol.h"
#include "ir/irfunction.h"
#include "ir/irtypeclass.h"

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

// FIXME: this needs to be cleaned up

void DtoResolveClass(ClassDeclaration *cd) {
  if (cd->ir->isResolved()) {
    return;
  }
  cd->ir->setResolved();

  IF_LOG Logger::println("DtoResolveClass(%s): %s", cd->toPrettyChars(),
                         cd->loc.toChars());
  LOG_SCOPE;

  // make sure the base classes are processed first
  for (auto bc : *cd->baseclasses) {
    DtoResolveClass(bc->sym);
  }

  // make sure type exists
  DtoType(cd->type);

  // create IrAggr
  getIrAggr(cd, true);

  // make sure all fields really get their ir field
  for (auto vd : cd->fields) {
    IF_LOG {
      if (isIrFieldCreated(vd)) {
        Logger::println("class field already exists");
      }
    }
    getIrField(vd, true);
  }
}

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

DValue *DtoNewClass(Loc loc, TypeClass *tc, NewExp *newexp) {
  // resolve type
  DtoResolveClass(tc->sym);
  const auto irClass = getIrAggr(tc->sym);

  // allocate
  LLValue *mem;
  bool doInit = true;
  if (newexp->placement) {
    mem = DtoLVal(newexp->placement);
  } else if (newexp->onstack) {
    mem = DtoRawAlloca(irClass->getLLStructType(), tc->sym->alignsize,
                       ".newclass_alloca");
  } else if (global.params.ehnogc && newexp->thrownew) {
    // _d_newThrowable template lowering
    assert(newexp->lowering);
    mem = DtoRVal(newexp->lowering);
    doInit = false;
  } else {
    llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_allocclass");
    LLConstant *ci = irClass->getClassInfoSymbol();
    mem = gIR->CreateCallOrInvoke(fn, ci, ".newclass_gc");
  }

  // init
  if (doInit)
    DtoInitClass(tc, mem);

  // init inner-class outer reference
  if (newexp->thisexp) {
    Logger::println("Resolving outer class");
    LOG_SCOPE;
    unsigned idx = getFieldGEPIndex(tc->sym, tc->sym->vthis);
    LLValue *src = DtoRVal(newexp->thisexp);
    LLValue *dst = DtoGEP(irClass->getLLStructType(), mem, 0, idx);
    IF_LOG Logger::cout() << "dst: " << *dst << "\nsrc: " << *src << '\n';
    DtoStore(src, dst);
  }
  // set the context for nested classes
  else if (tc->sym->isNested() && tc->sym->vthis) {
    DtoResolveNestedContext(loc, tc->sym, mem);
  }

  // call constructor
  if (newexp->member) {
    // evaluate argprefix
    if (newexp->argprefix) {
      toElemDtor(newexp->argprefix);
    }

    Logger::println("Calling constructor");
    assert(newexp->arguments != NULL);
    DFuncValue dfn(newexp->member, DtoCallee(newexp->member), mem);
    // ignore ctor return value (C++ ctors on Posix may not return `this`)
    DtoCallFunction(newexp->loc, tc, &dfn, newexp->arguments);
    return new DImValue(tc, mem);
  }

  assert(newexp->argprefix == NULL);

  // return default constructed class
  return new DImValue(tc, mem);
}

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

void DtoInitClass(TypeClass *tc, LLValue *dst) {
  DtoResolveClass(tc->sym);

  IrClass *irClass = getIrAggr(tc->sym);
  llvm::StructType *st = irClass->getLLStructType();

  // Set vtable field. Doing this seperately might be optimized better.
  LLValue *tmp = DtoGEP(st, dst, 0u, 0, "vtbl");
  LLValue *val = irClass->getVtblSymbol();
  DtoStore(val, tmp);

  // For D classes, set the monitor field to null.
  const bool isCPPclass = tc->sym->isCPPclass() ? true : false;
  if (!isCPPclass) {
    tmp = DtoGEP(st, dst, 0, 1, "monitor");
    val = LLConstant::getNullValue(st->getElementType(1));
    DtoStore(val, tmp);
  }

  // Copy the rest from the static initializer, if any.
  unsigned const firstDataIdx = isCPPclass ? 1 : 2;
  uint64_t const dataBytes =
      tc->sym->structsize - target.ptrsize * firstDataIdx;
  if (dataBytes == 0) {
    return;
  }

  LLValue *dstarr = DtoGEP(st, dst, 0, firstDataIdx);

  // init symbols might not have valid types
  LLValue *initsym = irClass->getInitSymbol();
  LLValue *srcarr = DtoGEP(st, initsym, 0, firstDataIdx);

  DtoMemCpy(dstarr, srcarr, DtoConstSize_t(dataBytes));
}

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

void DtoFinalizeClass(Loc loc, LLValue *inst) {
  // get runtime function
  llvm::Function *fn =
      getRuntimeFunction(loc, gIR->module, "_d_callfinalizer");

  gIR->CreateCallOrInvoke(fn, inst, "");
}

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

void DtoFinalizeScopeClass(Loc loc, DValue *dval,
                           bool dynTypeMatchesStaticType) {
  llvm::Value *inst = DtoRVal(dval);

  if (!isOptimizationEnabled() || !dynTypeMatchesStaticType) {
    DtoFinalizeClass(loc, inst);
    return;
  }

  bool hasDtor = false;
  const auto cd = dval->type->toBasetype()->isTypeClass()->sym;
  for (auto cd2 = cd; cd2; cd2 = cd2->baseClass) {
    if (cd2->dtor) {
      hasDtor = true;
      break;
    }
  }

  if (hasDtor) {
    DtoFinalizeClass(loc, inst);
    return;
  }

  // no dtors => only finalize (via druntime call) if monitor is set,
  // see https://github.com/ldc-developers/ldc/issues/2515
  llvm::BasicBlock *ifbb = gIR->insertBB("if");
  llvm::BasicBlock *endbb = gIR->insertBBAfter(ifbb, "endif");

  llvm::StructType *st =
      isaStruct(getIrType(cd->type, true)->isClass()->getMemoryLLType());
  const auto monitor =
      DtoLoad(st->getElementType(1), DtoGEP(st, inst, 0, 1), ".monitor");
  const auto hasMonitor =
      gIR->ir->CreateICmp(llvm::CmpInst::ICMP_NE, monitor,
                          getNullValue(monitor->getType()), ".hasMonitor");
  llvm::BranchInst::Create(ifbb, endbb, hasMonitor, gIR->scopebb());

  gIR->ir->SetInsertPoint(ifbb);
  DtoFinalizeClass(loc, inst);
  gIR->ir->CreateBr(endbb);

  gIR->ir->SetInsertPoint(endbb);
}

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

DValue *DtoCastClass(Loc loc, DValue *val, Type *_to) {
  IF_LOG Logger::println("DtoCastClass(%s, %s)", val->type->toChars(),
                         _to->toChars());
  LOG_SCOPE;

  Type *to = _to->toBasetype();

  // class -> pointer
  if (to->ty == TY::Tpointer) {
    IF_LOG Logger::println("to pointer");
    return new DImValue(_to, DtoRVal(val));
  }
  // class -> bool
  if (to->ty == TY::Tbool) {
    IF_LOG Logger::println("to bool");
    LLValue *llval = DtoRVal(val);
    LLValue *zero = LLConstant::getNullValue(llval->getType());
    return new DImValue(_to, gIR->ir->CreateICmpNE(llval, zero));
  }
  // class -> integer
  if (to->isIntegral()) {
    IF_LOG Logger::println("to %s", to->toChars());

    // get class ptr
    LLValue *v = DtoRVal(val);
    // cast to size_t
    v = gIR->ir->CreatePtrToInt(v, DtoSize_t(), "");
    // cast to the final int type
    DImValue im(Type::tsize_t, v);
    return DtoCastInt(loc, &im, _to);
  }
  // class -> typeof(null)
  if (to->ty == TY::Tnull) {
    IF_LOG Logger::println("to %s", to->toChars());
    return new DImValue(_to, LLConstant::getNullValue(DtoType(_to)));
  }

  // must be class/interface
  assert(to->ty == TY::Tclass);
  TypeClass *tc = static_cast<TypeClass *>(to);

  // from type
  Type *from = val->type->toBasetype();
  TypeClass *fc = static_cast<TypeClass *>(from);

  // copy DMD logic:
  // if to isBaseOf from with offset:   (to ? to + offset : null)
  // else if from is C++ and to is C++:  to
  // else if from is C++ and to is D:    null
  // else if from is interface:          _d_interface_cast(to)
  // else if from is class:              _d_dynamic_cast(to)

  int offset = 0;
  if (tc->sym->isBaseOf(fc->sym, &offset)) {
    Logger::println("static down cast");
    // interface types don't cover the full object in case of multiple inheritence
    //  so GEP on the original type is inappropriate

    // offset pointer
    LLValue *orig = DtoRVal(val);
    LLValue *v = orig;
    if (offset != 0) {
      assert(offset > 0);
      v = DtoGEP1(getI8Type(), v, DtoConstUint(offset));
    }
    IF_LOG {
      Logger::cout() << "V = " << *v << std::endl;
    }

    // Check whether the original value was null, and return null if so.
    // Sure we could have jumped over the code above in this case, but
    // it's just a GEP and (maybe) a pointer-to-pointer BitCast, so it
    // should be pretty cheap and perfectly safe even if the original was
    // null.
    const auto nullPtr = getNullPtr();
    LLValue *isNull = gIR->ir->CreateICmpEQ(orig, nullPtr, ".nullcheck");
    v = gIR->ir->CreateSelect(isNull, nullPtr, v, ".interface");
    // return r-value
    return new DImValue(_to, v);
  }

  if (fc->sym->classKind == ClassKind::cpp) {
    Logger::println("C++ class/interface cast");
    LLValue *v =
        tc->sym->classKind == ClassKind::cpp ? DtoRVal(val) : getNullPtr();
    return new DImValue(_to, v);
  }

  // from interface
  if (fc->sym->isInterfaceDeclaration()) {
    Logger::println("interface cast");
    return DtoDynamicCastInterface(loc, val, _to);
  }
  // from class
  Logger::println("dynamic up cast");
  return DtoDynamicCastObject(loc, val, _to);
}

bool DtoIsObjcLinkage(Type *_to) {
  if (auto to = _to->isTypeClass()) {
    DtoResolveClass(to->sym);
    return to->sym->classKind == ClassKind::objc;
  }
  
  return false;
}

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

static void resolveObjectAndClassInfoClasses() {
  // check declarations in object.d
  getObjectType();
  getClassInfoType();

  DtoResolveClass(ClassDeclaration::object);
  DtoResolveClass(Type::typeinfoclass);
}

DValue *DtoDynamicCastObject(Loc loc, DValue *val, Type *_to) {

  resolveObjectAndClassInfoClasses();

  // Dynamic casting in Objective-C works differently from D.
  // We call objc_opt_isKindOfClass to get a bool defining
  // whether the cast is valid, if it is then we go ahead.
  if (DtoIsObjcLinkage(_to)) {
    llvm::Function *kindOfClassFunc =
      getRuntimeFunction(loc, gIR->module, "objc_opt_isKindOfClass");

    // Get the object.
    LLValue *obj = DtoRVal(val);

    // Get class_t handle
    LLValue *objTy = getNullPtr();
    if (auto thandle = _to->isClassHandle()) {
      objTy = gIR->objc.deref(thandle, getOpaquePtrType());
    }

    // objc_opt_isKindOfClass will check if id is null
    // by itself, so we don't need to add an extra check.
    // objc_opt_isKindOfClass(id) ? id : null
    LLValue *objCastable = gIR->CreateCallOrInvoke(kindOfClassFunc, obj, objTy);
    LLValue *ret = gIR->ir->CreateSelect(objCastable, obj, getNullPtr());
    return new DImValue(_to, ret);
  }

  llvm_unreachable("dynamic cast should have been lowered");
}

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

DValue *DtoDynamicCastInterface(Loc loc, DValue *val, Type *_to) {

  resolveObjectAndClassInfoClasses();

  // Dynamic casting in Objective-C works differently from D.
  // In this case we want to call the Objective-C runtime to first
  // get a Class object from the `id`.
  // Then check if class_conformsToProtocol returns true,
  // if it does, then we can cast and return the casted value, 
  // otherwise return null.
  if (DtoIsObjcLinkage(_to)) {
    llvm::Function *getClassFunc =
      getRuntimeFunction(loc, gIR->module, "object_getClass");

    llvm::Function *kindOfProtocolFunc =
      getRuntimeFunction(loc, gIR->module, "class_conformsToProtocol");

    // id -> Class 
    LLValue *obj = DtoRVal(val);
    LLValue *objClass = gIR->CreateCallOrInvoke(getClassFunc, obj);
    
    // Get prototype_t handle
    LLValue *protoTy = getNullPtr();
    if (auto ifhndl = _to->isClassHandle()->isInterfaceDeclaration()) {
      protoTy = gIR->objc.deref(ifhndl, getOpaquePtrType());
    }

    // Class && kindOfProtocolFunc(Class) ? id : null
    LLValue *ret = gIR->ir->CreateSelect(
      gIR->CreateCallOrInvoke(kindOfProtocolFunc, objClass, protoTy),
      obj, 
      getNullPtr()
    );
    return new DImValue(_to, ret);
  }

  llvm_unreachable("interface cast should have been lowered");
}

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

std::pair<llvm::Value *, llvm::Value *>
DtoVirtualFunctionPointer(DValue *inst, FuncDeclaration *fdecl) {
  // sanity checks
  assert(fdecl->isVirtual());
  assert(!fdecl->isFinalFunc());
  TypeClass *tc = inst->type->toBasetype()->isTypeClass();
  assert(tc);
  // slot 0 is always ClassInfo/Interface* unless it is a CPP class
  assert(fdecl->vtblIndex > 0 ||
         (fdecl->vtblIndex == 0 &&
          inst->type->toBasetype()->isTypeClass()->sym->isCPPclass()));

  // get instance
  LLValue *vthis = DtoRVal(inst);
  IF_LOG Logger::cout() << "vthis: " << *vthis << '\n';

  const auto irtc = getIrType(tc->sym->type, true)->isClass();
  const auto vtblType = irtc->getVtblType();

  LLValue *vtable = vthis;
  // get the vtbl for objects
  vtable = DtoGEP(irtc->getMemoryLLType(), vthis, 0u, 0);
  // load vtbl ptr
  vtable = DtoLoad(LLPointerType::get(getGlobalContext(), 0), vtable);
  // index vtbl
  const std::string name = fdecl->toChars();
  const auto vtblname = name + "@vtbl";
  LLValue *funcval =
      DtoGEP(vtblType, vtable, 0, fdecl->vtblIndex, vtblname.c_str());
  // load opaque pointer.
  funcval = DtoAlignedLoad(vtblType->getElementType(), funcval);
  // Because vtables are immutable, LLVM's !invariant.load
  // can be applied (helps with devirtualization).
  llvm::cast<llvm::LoadInst>(funcval)->setMetadata(
      "invariant.load", llvm::MDNode::get(gIR->context(), {}));

  IF_LOG Logger::cout() << "funcval: " << *funcval << '\n';

  // postpone naming until after casting to get the name in call instructions
  funcval->setName(name);

  IF_LOG Logger::cout() << "funcval casted: " << *funcval << '\n';

  return std::make_pair(funcval, vtable);
}
