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

#include "dmd/compiler.h"
#include "dmd/declaration.h"
#include "dmd/errors.h"
#include "dmd/expression.h"
#include "dmd/id.h"
#include "dmd/mtype.h"
#include "dmd/target.h"
#include "dmd/template.h"
#include "gen/abi/abi.h"
#include "gen/arrays.h"
#include "gen/classes.h"
#include "gen/dvalue.h"
#include "gen/funcgenstate.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/mangling.h"
#include "gen/pragma.h"
#include "gen/tollvm.h"
#include "gen/runtime.h"
#include "ir/irfunction.h"
#include "ir/irtype.h"
#include "llvm/IR/LLVMContext.h"
#include <llvm/IR/DerivedTypes.h>

using namespace dmd;

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

IrFuncTy &DtoIrTypeFunction(DValue *fnval) {
  if (DFuncValue *dfnval = fnval->isFunc()) {
    if (dfnval->func) {
      return getIrFunc(dfnval->func)->irFty;
    }
  }

  return getIrType(fnval->type->toBasetype(), true)->getIrFuncTy();
}

TypeFunction *DtoTypeFunction(DValue *fnval) {
  Type *type = fnval->type->toBasetype();
  if (type->ty == TY::Tfunction) {
    return static_cast<TypeFunction *>(type);
  }
  if (type->ty == TY::Tdelegate) {
    // FIXME: There is really no reason why the function type should be
    // unmerged at this stage, but the frontend still seems to produce such
    // cases; for example for the uint(uint) next type of the return type of
    // (&zero)(), leading to a crash in DtoCallFunction:
    // ---
    // void test8198() {
    //   uint delegate(uint) zero() { return null; }
    //   auto a = (&zero)()(0);
    // }
    // ---
    // Calling merge() here works around the symptoms, but does not fix the
    // root cause.

    Type *next = merge(type->nextOf());
    assert(next->ty == TY::Tfunction);
    return static_cast<TypeFunction *>(next);
  }

  llvm_unreachable("Cannot get TypeFunction* from non lazy/function/delegate");
}

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

static void addExplicitArguments(std::vector<LLValue *> &args, AttrSet &attrs,
                                 IrFuncTy &irFty, LLFunctionType *calleeType,
                                 Expressions &argexps,
                                 Parameters *formalParams) {
  // Number of arguments added to the LLVM type that are implicit on the
  // frontend side of things (this, context pointers, etc.)
  const size_t implicitLLArgCount = args.size();

  // Number of formal arguments in the LLVM type (i.e. excluding varargs).
  const size_t formalLLArgCount = irFty.args.size();

  // Number of formal arguments in the D call expression (excluding varargs).
  const size_t formalDArgCount = Parameter::dim(formalParams);

  // The number of explicit arguments in the D call expression (including
  // varargs), not all of which necessarily generate a LLVM argument.
  const size_t explicitDArgCount = argexps.size();

  // construct and initialize an IrFuncTyArg object for each vararg
  std::vector<IrFuncTyArg *> optionalIrArgs;
  for (size_t i = formalDArgCount; i < explicitDArgCount; i++) {
    Type *argType = argexps[i]->type;
    bool passByVal = gABI->passByVal(irFty.type, argType);

    llvm::AttrBuilder initialAttrs(getGlobalContext());
    if (passByVal) {
      initialAttrs.addByValAttr(DtoType(argType));
      if (auto alignment = DtoAlignment(argType))
        initialAttrs.addAlignmentAttr(alignment);
    } else {
      DtoAddExtendAttr(argType, initialAttrs);
    }

    optionalIrArgs.push_back(new IrFuncTyArg(argType, passByVal, std::move(initialAttrs)));
    optionalIrArgs.back()->parametersIdx = i;
  }

  // let the ABI rewrite the IrFuncTyArg objects
  gABI->rewriteVarargs(irFty, optionalIrArgs);

  const size_t explicitLLArgCount = formalLLArgCount + optionalIrArgs.size();
  args.resize(implicitLLArgCount + explicitLLArgCount,
              static_cast<llvm::Value *>(nullptr));

  size_t dArgIndex = 0;
  for (size_t i = 0; i < explicitLLArgCount; ++i, ++dArgIndex) {
    const bool isVararg = (i >= formalLLArgCount);
    IrFuncTyArg *irArg = nullptr;
    if (isVararg) {
      irArg = optionalIrArgs[i - formalLLArgCount];
    } else {
      irArg = irFty.args[i];
    }

    // Make sure to evaluate argument expressions for which there's no LL
    // parameter (e.g., empty structs for some ABIs).
    if (irArg->parametersIdx < formalDArgCount) {
      for (; dArgIndex < irArg->parametersIdx; ++dArgIndex) {
        toElem(argexps[dArgIndex]);
      }
    }

    Expression *const argexp = argexps[dArgIndex];
    Parameter *const formalParam =
        isVararg ? nullptr : Parameter::getNth(formalParams, dArgIndex);

    // evaluate argument expression
    DValue *const dval = DtoArgument(formalParam, argexp);

    // load from lvalue/let TargetABI rewrite it/...
    bool isLValueExp = argexp->isLvalue();
    // regard temporaries as rvalues here
    if (isLValueExp) {
      auto ae = argexp;
      if (auto ce = ae->isCommaExp())
        ae = ce->getTail();
      if (auto ve = ae->isVarExp()) {
        if (auto vd = ve->var->isVarDeclaration()) {
          if (vd->storage_class & STCtemp)
            isLValueExp = false;
        }
      }
    }
    llvm::Value *llVal = irFty.putArg(*irArg, dval, isLValueExp,
                                      dArgIndex == explicitDArgCount - 1);

    const size_t llArgIdx = implicitLLArgCount + i;
    llvm::Type *const paramType =
        (isVararg ? nullptr : calleeType->getParamType(llArgIdx));

    // Hack around LDC assuming structs and static arrays are in memory:
    // If the function wants a struct, and the argument value is a
    // pointer to a struct, load from it before passing it in.
    if (isaPointer(llVal) && DtoIsInMemoryOnly(argexp->type) &&
        ((!isVararg && !isaPointer(paramType)) ||
         (isVararg && !irArg->byref && !irArg->isByVal()))) {
      Logger::println("Loading struct type for function argument");
      llVal = DtoLoad(DtoType(dval->type), llVal);
    }

    // parameter type mismatch, this is hard to get rid of
    if (!isVararg && llVal->getType() != paramType) {
      IF_LOG {
        Logger::cout() << "arg:     " << *llVal << '\n';
        Logger::cout() << "expects: " << *paramType << '\n';
      }
      if (isaStruct(llVal)) {
        llVal = DtoSlicePaint(llVal, paramType);
      } else {
        llVal = DtoBitCast(llVal, paramType);
      }
    }

    args[llArgIdx] = llVal;
    attrs.addToParam(llArgIdx, irArg->attrs);

    if (isVararg) {
      delete irArg;
    }
  }

  for (; dArgIndex < explicitDArgCount; ++dArgIndex) {
    toElem(argexps[dArgIndex]);
  }
}

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

static LLValue *getTypeinfoArrayArgumentForDVarArg(Expressions *argexps,
                                                   int begin) {
  IF_LOG Logger::println("doing d-style variadic arguments");
  LOG_SCOPE

  // number of non variadic args
  IF_LOG Logger::println("num non vararg params = %d", begin);

  const size_t numArgExps = argexps ? argexps->size() : 0;
  const size_t numVariadicArgs = numArgExps - begin;

  // build type info array
  LLType *typeinfotype = DtoType(getTypeInfoType());
  LLArrayType *typeinfoarraytype =
      LLArrayType::get(typeinfotype, numVariadicArgs);

  auto typeinfomem = new llvm::GlobalVariable(
      gIR->module, typeinfoarraytype, true, llvm::GlobalValue::InternalLinkage,
      nullptr, "._arguments.storage");
  IF_LOG Logger::cout() << "_arguments storage: " << *typeinfomem << '\n';

  std::vector<LLConstant *> vtypeinfos;
  vtypeinfos.reserve(numVariadicArgs);
  for (size_t i = begin; i < numArgExps; i++) {
    Expression *argExp = (*argexps)[i];
    vtypeinfos.push_back(DtoTypeInfoOf(argExp->loc, argExp->type));
  }

  // apply initializer
  LLConstant *tiinits = LLConstantArray::get(typeinfoarraytype, vtypeinfos);
  typeinfomem->setInitializer(tiinits);

  return DtoConstSlice(DtoConstSize_t(numVariadicArgs), typeinfomem);
}

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

static LLType *getAtomicType(LLType *type) {
  switch (const size_t N = getTypeBitSize(type)) {
  case 8:
  case 16:
  case 32:
  case 64:
  case 128:
    return llvm::IntegerType::get(gIR->context(), static_cast<unsigned>(N));
  default:
    return nullptr;
  }
}

bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e,
                            DValue *&result) {
  // va_start instruction
  if (fndecl->llvmInternal == LLVMva_start) {
    if (e->arguments->length < 1 || e->arguments->length > 2) {
      error(e->loc, "`va_start` instruction expects 1 (or 2) arguments");
      fatal();
    }
    DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
    assert(ap);
    // variadic extern(D) function with implicit _argptr?
    if (LLValue *argptrMem = p->func()->_argptr) {
      // then va_copy the _argptr
      DLValue argptr(ap->type, argptrMem);
      gABI->vaCopy(ap, &argptr);
    } else {
      LLValue *llAp = gABI->prepareVaStart(ap);
      p->ir->CreateCall(GET_INTRINSIC_DECL(vastart, llAp->getType()), llAp, "");
    }
    result = nullptr;
    return true;
  }

  // va_copy instruction
  if (fndecl->llvmInternal == LLVMva_copy) {
    if (e->arguments->length != 2) {
      error(e->loc, "`va_copy` instruction expects 2 arguments");
      fatal();
    }
    DLValue *dest = toElem((*e->arguments)[0])->isLVal(); // va_list
    assert(dest);
    DValue *src = toElem((*e->arguments)[1]);             // va_list
    gABI->vaCopy(dest, src);
    result = nullptr;
    return true;
  }

  // va_arg instruction
  if (fndecl->llvmInternal == LLVMva_arg) {
    if (e->arguments->length != 1) {
      error(e->loc, "`va_arg` instruction expects 1 argument");
      fatal();
    }
    if (DtoIsInMemoryOnly(e->type)) {
      error(e->loc,
            "`va_arg` instruction does not support structs and static arrays");
      fatal();
    }
    DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
    assert(ap);
    LLValue *llAp = gABI->prepareVaArg(ap);
    LLType *llType = DtoType(e->type);
    result = new DImValue(e->type, p->ir->CreateVAArg(llAp, llType));
    return true;
  }

  // va_end instruction
  if (fndecl->llvmInternal == LLVMva_end) {
    if (e->arguments->length != 1) {
      error(e->loc, "`va_end` instruction expects 1 argument");
      fatal();
    }
    DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
    assert(ap);
    LLValue *llAp = gABI->prepareVaArg(ap);
    p->ir->CreateCall(GET_INTRINSIC_DECL(vaend, llAp->getType()), llAp);
    result = nullptr;
    return true;
  }

  // C alloca
  if (fndecl->llvmInternal == LLVMalloca) {
    if (e->arguments->length != 1) {
      error(e->loc, "`alloca` expects 1 argument");
      fatal();
    }
    Expression *exp = (*e->arguments)[0];
    DValue *expv = toElem(exp);
    if (expv->type->toBasetype()->ty != TY::Tint32) {
      expv = DtoCast(e->loc, expv, Type::tint32);
    }
    result = new DImValue(e->type,
                          p->ir->CreateAlloca(LLType::getInt8Ty(p->context()),
                                              DtoRVal(expv), ".alloca"));
    return true;
  }

  // fence instruction
  if (fndecl->llvmInternal == LLVMfence) {
    if (e->arguments->length < 1 || e->arguments->length > 2) {
      error(e->loc, "`fence` instruction expects 1 (or 2) arguments");
      fatal();
    }
    auto atomicOrdering =
        static_cast<llvm::AtomicOrdering>((*e->arguments)[0]->toInteger());
    llvm::SyncScope::ID scope = llvm::SyncScope::System;
    if (e->arguments->length == 2) {
      scope = static_cast<llvm::SyncScope::ID>((*e->arguments)[1]->toInteger());
    }
    // orderings below acquire are invalid; like clang, don't emit any
    // instruction in those cases
    if (static_cast<int>(atomicOrdering) >
        static_cast<int>(llvm::AtomicOrdering::Monotonic)) {
      p->ir->CreateFence(atomicOrdering, scope);
    }
    return true;
  }

  // atomic store instruction
  if (fndecl->llvmInternal == LLVMatomic_store) {
    if (e->arguments->length != 3) {
      error(e->loc, "atomic store instruction expects 3 arguments");
      fatal();
    }
    Expression *exp1 = (*e->arguments)[0];
    Expression *exp2 = (*e->arguments)[1];
    int atomicOrdering = (*e->arguments)[2]->toInteger();

    DValue *dval = toElem(exp1);
    LLValue *ptr = DtoRVal(exp2);
    LLType *pointeeType = DtoType(exp2->type->isTypePointer()->nextOf());

    LLValue *val = nullptr;
    if (pointeeType->isIntegerTy()) {
      val = DtoRVal(dval);
    } else if (auto atype = getAtomicType(pointeeType)) {
      auto lval = makeLValue(exp1->loc, dval);
      val = DtoLoad(atype, lval);
    } else {
      error(e->loc,
          "atomic store only supports types of size 1/2/4/8/16 bytes, not `%s`",
          exp1->type->toChars());
      fatal();
    }

    llvm::StoreInst *ret = p->ir->CreateStore(val, ptr);
    ret->setAtomic(llvm::AtomicOrdering(atomicOrdering));
    if (auto alignment = getTypeAllocSize(val->getType())) {
      ret->setAlignment(llvm::Align(alignment));
    }
    return true;
  }

  // atomic load instruction
  if (fndecl->llvmInternal == LLVMatomic_load) {
    if (e->arguments->length != 2) {
      error(e->loc, "atomic load instruction expects 2 arguments");
      fatal();
    }

    Expression *exp = (*e->arguments)[0];
    int atomicOrdering = (*e->arguments)[1]->toInteger();

    LLValue *ptr = DtoRVal(exp);
    LLType *pointeeType = DtoType(e->type);
    LLType *loadedType  = pointeeType;
    Type *retType = exp->type->nextOf();

    if (!pointeeType->isIntegerTy()) {
      if (auto atype = getAtomicType(pointeeType)) {
        loadedType = atype;
      } else {
        error(e->loc,
              "atomic load only supports types of size 1/2/4/8/16 bytes, "
              "not `%s`",
              retType->toChars());
        fatal();
      }
    }

    llvm::LoadInst *load = p->ir->CreateLoad(loadedType, ptr);
    if (auto alignment = getTypeAllocSize(loadedType)) {
      load->setAlignment(llvm::Align(alignment));
    }
    load->setAtomic(llvm::AtomicOrdering(atomicOrdering));
    llvm::Value *val = load;
    if (loadedType != pointeeType) {
      val = DtoAllocaDump(val, retType);
      result = new DLValue(retType, val);
    } else {
      result = new DImValue(retType, val);
    }
    return true;
  }

  // cmpxchg instruction
  if (fndecl->llvmInternal == LLVMatomic_cmp_xchg) {
    if (e->arguments->length != 6) {
      error(e->loc, "`cmpxchg` instruction expects 6 arguments");
      fatal();
    }
    if (e->type->ty != TY::Tstruct) {
      error(e->loc, "`cmpxchg` instruction returns a struct");
      fatal();
    }
    Expression *exp1 = (*e->arguments)[0];
    Expression *exp2 = (*e->arguments)[1];
    Expression *exp3 = (*e->arguments)[2];
    const auto successOrdering =
        llvm::AtomicOrdering((*e->arguments)[3]->toInteger());
    const auto failureOrdering =
        llvm::AtomicOrdering((*e->arguments)[4]->toInteger());
    const bool isWeak = (*e->arguments)[5]->toInteger() != 0;

    LLValue *ptr = DtoRVal(exp1);
    LLType *pointeeType = DtoType(exp1->type->isTypePointer()->nextOf());
    DValue *dcmp = toElem(exp2);
    DValue *dval = toElem(exp3);

    LLValue *cmp = nullptr;
    LLValue *val = nullptr;
    if (pointeeType->isIntegerTy()) {
      cmp = DtoRVal(dcmp);
      val = DtoRVal(dval);
    } else if (auto atype = getAtomicType(pointeeType)) {
      auto cmpLVal = makeLValue(exp2->loc, dcmp);
      cmp = DtoLoad(atype, cmpLVal);
      auto lval = makeLValue(exp3->loc, dval);
      val = DtoLoad(atype, lval);
    } else {
      error(e->loc,
            "`cmpxchg` only supports types of size 1/2/4/8/16 bytes, not `%s`",
            exp2->type->toChars());
      fatal();
    }

    auto ret =
        p->ir->CreateAtomicCmpXchg(ptr, cmp, val,
                                   llvm::MaybeAlign(), // default alignment
                                   successOrdering, failureOrdering);
    ret->setWeak(isWeak);

    // we return a struct; allocate on stack and store to both fields manually
    // (avoiding DtoAllocaDump() due to bad optimized codegen, most likely
    // because of i1)
    auto mem = DtoAlloca(e->type);
    llvm::Type* memty = DtoType(e->type);
    DtoStore(p->ir->CreateExtractValue(ret, 0), DtoGEP(memty, mem, 0u, 0));
    DtoStoreZextI8(p->ir->CreateExtractValue(ret, 1), DtoGEP(memty, mem, 0, 1));

    result = new DLValue(e->type, mem);
    return true;
  }

  // atomicrmw instruction
  if (fndecl->llvmInternal == LLVMatomic_rmw) {
    if (e->arguments->length != 3) {
      error(e->loc, "`atomicrmw` instruction expects 3 arguments");
      fatal();
    }

    TemplateInstance *ti = fndecl->parent->isTemplateInstance();
    assert(ti);
    const char *opString = ti->tempdecl->isTemplateDeclaration()->intrinsicName;
    assert(opString);

    static const char *ops[] = {"xchg", "add", "sub", "and",  "nand", "or",
                                "xor",  "max", "min", "umax", "umin", nullptr};

    int op = 0;
    for (;; ++op) {
      if (ops[op] == nullptr) {
        error(e->loc, "unknown `atomicrmw` operation `%s`", opString);
        fatal();
      }
      if (strcmp(opString, ops[op]) == 0) {
        break;
      }
    }

    Expression *exp1 = (*e->arguments)[0];
    Expression *exp2 = (*e->arguments)[1];
    int atomicOrdering = (*e->arguments)[2]->toInteger();
    LLValue *ptr = DtoRVal(exp1);
    LLValue *val = DtoRVal(exp2);
    LLValue *ret =
        p->ir->CreateAtomicRMW(llvm::AtomicRMWInst::BinOp(op), ptr, val,
                               llvm::MaybeAlign(), // default alignment
                               llvm::AtomicOrdering(atomicOrdering));
    result = new DImValue(exp2->type, ret);
    return true;
  }

  // bitop
  if (fndecl->llvmInternal == LLVMbitop_bt ||
      fndecl->llvmInternal == LLVMbitop_btr ||
      fndecl->llvmInternal == LLVMbitop_btc ||
      fndecl->llvmInternal == LLVMbitop_bts) {
    if (e->arguments->length != 2) {
      error(e->loc, "bitop intrinsic expects 2 arguments");
      fatal();
    }

    Expression *exp1 = (*e->arguments)[0];
    Expression *exp2 = (*e->arguments)[1];
    LLValue *ptr = DtoRVal(exp1);
    LLValue *bitnum = DtoRVal(exp2);

    unsigned bitmask = DtoSize_t()->getBitWidth() - 1;
    assert(bitmask == 31 || bitmask == 63);
    // auto q = cast(size_t*)ptr + (bitnum >> (64bit ? 6 : 5));
    LLValue *q =
        DtoGEP1(DtoSize_t(), ptr,
                p->ir->CreateLShr(bitnum, bitmask == 63 ? 6 : 5), "bitop.q");

    // auto mask = 1 << (bitnum & bitmask);
    LLValue *mask =
        p->ir->CreateAnd(bitnum, DtoConstSize_t(bitmask), "bitop.tmp");
    mask = p->ir->CreateShl(DtoConstSize_t(1), mask, "bitop.mask");

    // auto result = (*q & mask) ? -1 : 0;
    LLValue *val =
        p->ir->CreateZExt(DtoLoad(DtoSize_t(), q, "bitop.tmp"), DtoSize_t(), "bitop.val");
    LLValue *ret = p->ir->CreateAnd(val, mask, "bitop.tmp");
    ret = p->ir->CreateICmpNE(ret, DtoConstSize_t(0), "bitop.tmp");
    ret = p->ir->CreateSelect(ret, DtoConstInt(-1), DtoConstInt(0),
                              "bitop.result");

    if (fndecl->llvmInternal != LLVMbitop_bt) {
      llvm::Instruction::BinaryOps op;
      if (fndecl->llvmInternal == LLVMbitop_btc) {
        // *q ^= mask;
        op = llvm::Instruction::Xor;
      } else if (fndecl->llvmInternal == LLVMbitop_btr) {
        // *q &= ~mask;
        mask = p->ir->CreateNot(mask);
        op = llvm::Instruction::And;
      } else if (fndecl->llvmInternal == LLVMbitop_bts) {
        // *q |= mask;
        op = llvm::Instruction::Or;
      } else {
        llvm_unreachable("Unrecognized bitop intrinsic.");
      }

      LLValue *newVal = p->ir->CreateBinOp(op, val, mask, "bitop.new_val");
      newVal = p->ir->CreateTrunc(newVal, DtoSize_t(), "bitop.tmp");
      DtoStore(newVal, q);
    }

    result = new DImValue(e->type, ret);
    return true;
  }

  if (fndecl->llvmInternal == LLVMbitop_vld) {
    if (e->arguments->length != 1) {
      error(e->loc, "`bitop.vld` intrinsic expects 1 argument");
      fatal();
    }
    // TODO: Check types

    Expression *exp1 = (*e->arguments)[0];
    LLValue *ptr = DtoRVal(exp1);
    result = new DImValue(e->type, DtoVolatileLoad(DtoType(e->type), ptr));
    return true;
  }

  if (fndecl->llvmInternal == LLVMbitop_vst) {
    if (e->arguments->length != 2) {
      error(e->loc, "`bitop.vst` intrinsic expects 2 arguments");
      fatal();
    }
    // TODO: Check types

    Expression *exp1 = (*e->arguments)[0];
    Expression *exp2 = (*e->arguments)[1];
    LLValue *ptr = DtoRVal(exp1);
    LLValue *val = DtoRVal(exp2);
    DtoVolatileStore(val, ptr);
    return true;
  }

  return false;
}

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

class ImplicitArgumentsBuilder {
public:
  ImplicitArgumentsBuilder(std::vector<LLValue *> &args, AttrSet &attrs,
                           const Loc &loc, DValue *fnval,
                           LLFunctionType *llCalleeType, Expressions *argexps,
                           Type *resulttype, LLValue *sretPointer)
      : args(args), attrs(attrs), loc(loc), fnval(fnval), argexps(argexps),
        resulttype(resulttype), sretPointer(sretPointer),
        // computed:
        isDelegateCall(fnval->type->toBasetype()->ty == TY::Tdelegate),
        dfnval(fnval->isFunc()), irFty(DtoIrTypeFunction(fnval)),
        tf(DtoTypeFunction(fnval)),
        llArgTypesBegin(llCalleeType->param_begin()) {}

  void addImplicitArgs(bool directcall) {
    if (gABI->passThisBeforeSret(tf)) {
      addContext(directcall);
      addSret();
    } else {
      addSret();
      addContext(directcall);
    }

    addArguments();
  }

  bool hasContext = false; // set after addImplicitArgs invocation

private:
  // passed:
  std::vector<LLValue *> &args;
  AttrSet &attrs;
  const Loc &loc;
  DValue *const fnval;
  Expressions *const argexps;
  Type *const resulttype;
  LLValue *const sretPointer;

  // computed:
  const bool isDelegateCall;
  DFuncValue *const dfnval;
  IrFuncTy &irFty;
  TypeFunction *const tf;
  LLFunctionType::param_iterator llArgTypesBegin;

  // Adds an optional sret pointer argument.
  void addSret() {
    if (!irFty.arg_sret) {
      return;
    }

    size_t index = args.size();
    LLValue *pointer = sretPointer;
    if (!pointer) {
      pointer = DtoRawAlloca(DtoType(tf->nextOf()),
                             DtoAlignment(resulttype), ".sret_tmp");
    }

    args.push_back(pointer);
    attrs.addToParam(index, irFty.arg_sret->attrs);

    // verify that sret and/or inreg attributes are set
    const auto &sretAttrs = irFty.arg_sret->attrs;
    (void)sretAttrs;
    assert((sretAttrs.contains(LLAttribute::StructRet) ||
            sretAttrs.contains(LLAttribute::InReg)) &&
           "Sret arg not sret or inreg?");
  }

  // Adds an optional context/this pointer argument and sets hasContext.
  void addContext(bool directcall) {
    const bool thiscall = irFty.arg_this;
    const bool nestedcall = irFty.arg_nest;
    const bool objccall = irFty.type->linkage == LINK::objc;

    hasContext = thiscall || nestedcall || isDelegateCall || objccall;
    if (!hasContext)
      return;

    size_t index = args.size();
    auto argtype = *(llArgTypesBegin + index);

    if (dfnval && (dfnval->func->ident == Id::ensure ||
                   dfnval->func->ident == Id::require)) {
      // can be the this "context" argument for a contract invocation
      // (pass a pointer to the aggregate `this` pointer, which can naturally be
      // used as the contract's parent context in case the contract features
      // nested functions capturing `this` from the contract's parent)
      LLValue *thisptrLval = gIR->func()->thisArg;
      if (auto parentfd = dfnval->func->parent->isFuncDeclaration()) {
        if (auto iface = parentfd->parent->isInterfaceDeclaration()) {
          // an interface contract expects the interface pointer, not the
          //  class pointer
          Type *thistype = gIR->func()->decl->vthis->type;
          if (thistype != iface->type) {
            DImValue *dthis = new DImValue(thistype, DtoLoad(DtoType(thistype),thisptrLval));
            thisptrLval = DtoAllocaDump(DtoCastClass(loc, dthis, iface->type));
          }
        }
      }
      args.push_back(thisptrLval);
    } else if (thiscall && dfnval && dfnval->vthis) {
      
      if (objccall && directcall) {

        // ... or a Objective-c direct call argument
        if (auto func = dfnval->func->isFuncDeclaration()) {
          if (auto klass = func->isThis()->isClassDeclaration()) {

            // Create obj_super struct with (this, <class ref>)
            auto obj_super = DtoAggrPair(
              DtoBitCast(dfnval->vthis, argtype),
              gIR->objc.deref(klass, getOpaquePtrType()),
              "super"
            );


            // Allocate and store obj_super struct into a new variable.
            auto clsaddr = DtoRawAlloca(obj_super->getType(), 16, "super");
            DtoStore(obj_super, clsaddr);

            args.push_back(clsaddr);
          }
        }
      } else {

        // ... or a normal 'this' argument
        args.push_back(DtoBitCast(dfnval->vthis, argtype));
      }
    } else if (isDelegateCall) {
      // ... or a delegate context arg
      LLValue *ctxarg;
      if (fnval->isLVal()) {
        ctxarg = DtoLoad(getOpaquePtrType(),
                         DtoGEP(DtoType(fnval->type), DtoLVal(fnval), 0u, 0),
                         ".ptr");
      } else {
        ctxarg = gIR->ir->CreateExtractValue(DtoRVal(fnval), 0, ".ptr");
      }
      args.push_back(ctxarg);
    } else if (nestedcall) {
      // ... or a nested function context arg
      if (dfnval) {
        LLValue *contextptr = DtoNestedContext(loc, dfnval->func);
        args.push_back(contextptr);
      } else {
        args.push_back(llvm::UndefValue::get(getOpaquePtrType()));
      }
    } else {
      error(loc, "Context argument required but none given");
      fatal();
    }

    // add attributes
    if (irFty.arg_this) {
      attrs.addToParam(index, irFty.arg_this->attrs);
    } else if (irFty.arg_nest) {
      attrs.addToParam(index, irFty.arg_nest->attrs);
    }

    if (irFty.arg_objcSelector) {
      assert(dfnval);

      auto methodptr = gIR->objc.getMethod(dfnval->func)->selector;
      args.push_back(DtoLoad(methodptr->getType(), methodptr));
    }
  }

  // D vararg functions need a "TypeInfo[] _arguments" argument.
  void addArguments() {
    if (!irFty.arg_arguments) {
      return;
    }

    int numFormalParams = tf->parameterList.length();
    LLValue *argumentsArg =
        getTypeinfoArrayArgumentForDVarArg(argexps, numFormalParams);

    args.push_back(argumentsArg);
    attrs.addToParam(args.size() - 1, irFty.arg_arguments->attrs);
  }
};

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

static LLValue *DtoCallableValue(DValue *fn) {
  Type *type = fn->type->toBasetype();
  if (type->ty == TY::Tfunction) {
    return DtoRVal(fn);
  }
  if (type->ty == TY::Tdelegate) {
    if (fn->isLVal()) {
      LLValue *dg = DtoLVal(fn);
      llvm::StructType *st = isaStruct(DtoType(fn->type));
      LLValue *funcptr = DtoGEP(st, dg, 0, 1);
      return DtoLoad(st->getElementType(1), funcptr, ".funcptr");
    }
    LLValue *dg = DtoRVal(fn);
    assert(isaStruct(dg));
    return gIR->ir->CreateExtractValue(dg, 1, ".funcptr");
  }

  llvm_unreachable("Not a callable type.");
}

// FIXME: this function is a mess !
DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval,
                        Expressions *arguments, LLValue *sretPointer, bool directcall) {
  IF_LOG Logger::println("DtoCallFunction()");
  LOG_SCOPE

  // make sure the D callee type has been processed
  DtoType(fnval->type);

  // get func value if any
  DFuncValue *const dfnval = fnval->isFunc();

  // get function type info
  IrFuncTy &irFty = DtoIrTypeFunction(fnval);
  TypeFunction *const tf = DtoTypeFunction(fnval);
  Type *const returntype = tf->next;
  const TY returnTy = returntype->toBasetype()->ty;

  if (resulttype == nullptr) {
    resulttype = returntype;
  }

  // get callee llvm value
  LLValue *callable = DtoCallableValue(fnval);
  LLFunctionType *callableTy = irFty.funcType;
  if (dfnval && dfnval->func->isCsymbol()) {
    // See note in DtoDeclareFunction about K&R foward declared (void) functions
    // later defined as (...) functions. We want to use the non-variadic one.
    if (irFty.funcType->getNumParams() == 0 && irFty.funcType->isVarArg())
      callableTy = gIR->module.getFunction(getIRMangledName(dfnval->func, LINK::c))
                                ->getFunctionType();
  }

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

  // parameter attributes
  AttrSet attrs;

  // return attrs
  attrs.addToReturn(irFty.ret->attrs);

  std::vector<LLValue *> args;
  args.reserve(irFty.args.size());

  // handle implicit arguments (sret, context/this, _arguments)
  ImplicitArgumentsBuilder iab(args, attrs, loc, fnval, callableTy, arguments,
                               resulttype, sretPointer);
  iab.addImplicitArgs(directcall);

  // handle explicit arguments

  Logger::println("doing normal arguments");
  IF_LOG {
    Logger::println("Arguments so far: (%d)", static_cast<int>(args.size()));
    Logger::indent();
    for (auto &arg : args) {
      Logger::cout() << *arg << '\n';
    }
    Logger::undent();
    Logger::cout() << "Function type: " << tf->toChars() << '\n';
    // Logger::cout() << "LLVM functype: " << *callable->getType() << '\n';
  }

  if (arguments) {
    addExplicitArguments(args, attrs, irFty, callableTy, *arguments,
                         tf->parameterList.parameters);
  }

  if (irFty.arg_objcSelector) {

    // Use runtime msgSend function bitcasted as original call
    const char *msgSend = gABI->objcMsgSendFunc(resulttype, irFty, directcall);
    auto t = callable->getType();
    callable = getRuntimeFunction(loc, gIR->module, msgSend);
    callable = DtoBitCast(callable, t);
  }

  // call the function
  llvm::CallBase *call = gIR->funcGen().callOrInvoke(callable, callableTy, args,
                                                     "", tf->isnothrow());

  // PGO: Insert instrumentation or attach profile metadata at indirect call
  // sites.
  if (!call->getCalledFunction()) {
    auto &PGO = gIR->funcGen().pgo;
    PGO.emitIndirectCallPGO(call, callable);
  }

  // get return value
  const int sretArgIndex =
      (irFty.arg_sret && irFty.arg_this && gABI->passThisBeforeSret(tf) ? 1
                                                                        : 0);
  LLValue *retllval = irFty.arg_sret ? args[sretArgIndex]
                                     : static_cast<llvm::Instruction *>(call);
  bool retValIsLVal =
      (tf->isref() && returnTy != TY::Tvoid) || (irFty.arg_sret != nullptr);

  if (!retValIsLVal) {
    // let the ABI transform the return value back
    if (DtoIsInMemoryOnly(returntype)) {
      retllval = irFty.getRetLVal(returntype, retllval);
      retValIsLVal = true;
    } else {
      retllval = irFty.getRetRVal(returntype, retllval);
    }
  }

  // repaint the type if necessary
  Type *rbase = stripModifiers(resulttype->toBasetype(), true);
  Type *nextbase = stripModifiers(returntype->toBasetype(), true);
  if (!rbase->equals(nextbase)) {
    IF_LOG Logger::println("repainting return value from '%s' to '%s'",
                           returntype->toChars(), rbase->toChars());
    switch (rbase->ty) {
    case TY::Tarray:
      if (tf->isref()) {
        retllval = DtoBitCast(retllval, DtoType(pointerTo(rbase)));
      } else {
        retllval = DtoSlicePaint(retllval, DtoType(rbase));
      }
      break;

    case TY::Tsarray:
      if (nextbase->ty == TY::Tvector && !tf->isref()) {
        if (!retValIsLVal) {
          // static arrays need to be dumped to memory; use vector alignment
          retllval =
              DtoAllocaDump(retllval, DtoType(rbase), DtoAlignment(nextbase),
                            ".vector_to_sarray_tmp");
          retValIsLVal = true;
        }
        break;
      }
      goto unknownMismatch;

    case TY::Tclass:
    case TY::Taarray:
    case TY::Tpointer:
      if (tf->isref()) {
        retllval = DtoBitCast(retllval, DtoType(pointerTo(rbase)));
      } else {
        retllval = DtoBitCast(retllval, DtoType(rbase));
      }
      break;

    case TY::Tstruct:
      if (nextbase->ty == TY::Taarray && !tf->isref()) {
        // In the D2 frontend, the associative array type and its
        // object.AssociativeArray representation are used
        // interchangably in some places. However, AAs are returned
        // by value and not in an sret argument, so if the struct
        // type will be used, give the return value storage here
        // so that we get the right amount of indirections.
        LLValue *val =
            DtoInsertValue(llvm::UndefValue::get(DtoType(rbase)), retllval, 0);
        retllval = DtoAllocaDump(val, rbase, ".aalvaluetmp");
        retValIsLVal = true;
        break;
      }
      goto unknownMismatch;

    default:
    unknownMismatch:
      // Unfortunately, DMD has quirks resp. bugs with regard to name
      // mangling: For voldemort-type functions which return a nested
      // struct, the mangled name of the return type changes during
      // semantic analysis.
      //
      // (When the function deco is first computed as part of
      // determining the return type deco, its return type part is
      // left off to avoid cycles. If mangle/toDecoBuffer is then
      // called again for the type, it will pick up the previous
      // result and return the full deco string for the nested struct
      // type, consisting of both the full mangled function name, and
      // the struct identifier.)
      //
      // Thus, the type merging in stripModifiers does not work
      // reliably, and the equality check above can fail even if the
      // types only differ in a qualifier.
      //
      // Because a proper fix for this in the frontend is hard, we
      // just carry on and hope that the frontend didn't mess up,
      // i.e. that the LLVM types really match up.
      //
      // An example situation where this case occurs is:
      // ---
      // auto iota() {
      //     static struct Result {
      //         this(int) {}
      //         inout(Result) test() inout { return cast(inout)Result(0); }
      //     }
      //     return Result.init;
      // }
      // void main() { auto r = iota(); }
      // ---
      Logger::println("Unknown return mismatch type, ignoring.");
      break;
    }
    IF_LOG Logger::cout() << "final return value: " << *retllval << '\n';
  }

  // set calling convention and parameter attributes
  LLAttributeList &attrlist = attrs;
  if (auto cf = call->getCalledFunction()) {
    call->setCallingConv(cf->getCallingConv());
    if (cf->isIntrinsic()) { // override intrinsic attrs
      attrlist =
          llvm::Intrinsic::getAttributes(gIR->context(), cf->getIntrinsicID());
    }
  } else if (dfnval) {
    call->setCallingConv(getCallingConvention(dfnval->func));
  } else {
    call->setCallingConv(gABI->callingConv(tf, iab.hasContext));
  }
  // merge in function attributes set in callOrInvoke
  auto attrbuildattribs = call->getAttributes().getFnAttrs();
  attrlist = attrlist.addFnAttributes(
      gIR->context(), llvm::AttrBuilder(gIR->context(), attrbuildattribs));
  call->setAttributes(attrlist);

  // Special case for struct constructor calls: For temporaries, using the
  // this pointer value returned from the constructor instead of the alloca
  // passed as a parameter (which has the same value anyway) might lead to
  // instruction dominance issues because of the way it interacts with the
  // cleanups (see struct ctor hack in ToElemVisitor::visit(CallExp *)).
  if (dfnval && dfnval->func && dfnval->func->isCtorDeclaration() &&
      dfnval->func->isMember2()->isStructDeclaration()) {
    return new DLValue(resulttype, dfnval->vthis);
  }

  if (retValIsLVal) {
    return new DLValue(resulttype, retllval);
  }

  if (rbase->ty == TY::Tarray) {
    return new DSliceValue(resulttype, retllval);
  }

  return new DImValue(resulttype, retllval);
}
