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

#include "gen/nested.h"

#include "dmd/errors.h"
#include "dmd/target.h"
#include "gen/dvalue.h"
#include "gen/funcgenstate.h"
#include "gen/functions.h"
#include "gen/irstate.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/runtime.h"
#include "gen/tollvm.h"
#include "ir/irfunction.h"
#include "ir/irtypeaggr.h"
#include "llvm/Analysis/ValueTracking.h"

using namespace dmd;

namespace {
unsigned getVthisIdx(AggregateDeclaration *ad) {
  return getFieldGEPIndex(ad, ad->vthis);
}

bool isNRVOVar(VarDeclaration *vd) {
  if (auto fd = vd->toParent2()->isFuncDeclaration())
    return fd->isNRVO() && vd == fd->nrvo_var && !fd->needsClosure();
  return false;
}

bool captureByRef(VarDeclaration *vd) {
  return vd->isReference() || isNRVOVar(vd);
}
LLValue *loadThisPtr(AggregateDeclaration *ad, IrFunction &irfunc) {
  if (ad->isClassDeclaration()) {
      return DtoLoad(DtoType(irfunc.irFty.arg_this->type),
                     irfunc.thisArg);
  }

  return irfunc.thisArg;
}

LLValue *indexVThis(AggregateDeclaration *ad,  LLValue* val) {
  DtoResolveDsymbol(ad);
  llvm::StructType *st = getIrAggr(ad)->getLLStructType();
  unsigned idx = getVthisIdx(ad);
  return DtoLoad(st->getElementType(idx),
                 DtoGEP(st, val, 0, idx, ".vthis"));
}

} // anonymous namespace

static void DtoCreateNestedContextType(FuncDeclaration *fd);

DValue *DtoNestedVariable(const Loc &loc, Type *astype, VarDeclaration *vd,
                          bool byref) {
  IF_LOG Logger::println("DtoNestedVariable for %s @ %s", vd->toChars(),
                         loc.toChars());
  LOG_SCOPE;

  ////////////////////////////////////
  // Locate context value

  Dsymbol *vdparent = vd->toParent2();
  assert(vdparent);

  IrFunction *irfunc = gIR->func();

  // Check whether we can access the needed frame
  FuncDeclaration *fd = irfunc->decl;
  while (fd && fd != vdparent) {
    fd = getParentFunc(fd);
  }
  if (!fd) {
    error(loc, "function `%s` cannot access frame of function `%s`",
          irfunc->decl->toPrettyChars(), vdparent->toPrettyChars());
    return new DLValue(astype, llvm::UndefValue::get(getOpaquePtrType()));
  }

  // is the nested variable in this scope?
  if (vdparent == irfunc->decl) {
    return makeVarDValue(astype, vd);
  }

  // get the nested context
  LLValue *ctx = nullptr;
  bool skipDIDeclaration = false;
  auto currentCtx = gIR->funcGen().nestedVar;
  if (currentCtx) {
    Logger::println("Using own nested context of current function");
    ctx = currentCtx;
  } else if (AggregateDeclaration *ad = irfunc->decl->isMember2()) {
    Logger::println(
        "Current function is member of nested class, loading vthis");
    LLValue *val = loadThisPtr(ad, *irfunc);

    for (; ad; ad = ad->toParent2()->isAggregateDeclaration()) {
      assert(ad->vthis);
      val = indexVThis(ad, val);
    }
    ctx = val;
    skipDIDeclaration = true;
  } else {
    Logger::println("Regular nested function, using context arg");
    ctx = irfunc->nestArg;
  }

  assert(ctx);
  IF_LOG { Logger::cout() << "Context: " << *ctx << '\n'; }

  DtoCreateNestedContextType(vdparent->isFuncDeclaration());

  assert(isIrLocalCreated(vd));
  IrLocal *const irLocal = getIrLocal(vd);

  // The variable may not actually be nested in a speculative context (e.g.,
  // with `-allinst`, see https://github.com/ldc-developers/ldc/issues/2932).
  // Use an invalid null storage in that case, so that accessing the var at
  // runtime will cause a segfault.
  if (irLocal->nestedIndex == -1) {
    Logger::println(
        "WARNING: isn't actually nested, using invalid null storage");
    return makeVarDValue(astype, vd, getNullPtr());
  }

  ////////////////////////////////////
  // Extract variable from nested context

  assert(irfunc->frameType);
  IF_LOG { Logger::cout() << "casting to: " << *irfunc->frameType << '\n'; }
  LLValue *val = ctx;
  llvm::StructType *currFrame = irfunc->frameType;
  // Make the DWARF variable address relative to the context pointer (ctx);
  // register all ops (offsetting, dereferencing) required to get there in the
  // following list.
  LLSmallVector<uint64_t, 4> dwarfAddrOps;

  const auto offsetToNthField = [&val, &dwarfAddrOps, &currFrame](unsigned fieldIndex,
                                                      const char *name = "") {
    gIR->DBuilder.OpOffset(dwarfAddrOps, currFrame, fieldIndex);
    val = DtoGEP(currFrame, val, 0, fieldIndex, name);
  };

  const auto vardepth = irLocal->nestedDepth;
  const auto funcdepth = irfunc->depth;

  IF_LOG {
    Logger::cout() << "Variable: " << vd->toChars() << '\n';
    Logger::cout() << "Variable depth: " << vardepth << '\n';
    Logger::cout() << "Function: " << irfunc->decl->toChars() << '\n';
    Logger::cout() << "Function depth: " << funcdepth << '\n';
  }

  if (vardepth == funcdepth) {
    // This is not always handled above because functions without
    // variables accessed by nested functions don't create new frames.
    IF_LOG Logger::println("Same depth");
  } else {
    // Load frame pointer and index that...
    IF_LOG Logger::println("Lower depth");
    offsetToNthField(vardepth);
    IF_LOG Logger::cout() << "Frame index: " << *val << '\n';
    currFrame = getIrFunc(fd)->frameType;
    gIR->DBuilder.OpDeref(dwarfAddrOps);
    val = DtoAlignedLoad(currFrame->getPointerTo(), val,
                         (std::string(".frame.") + vdparent->toChars()).c_str());
    IF_LOG Logger::cout() << "Frame: " << *val << '\n';
  }

  offsetToNthField(irLocal->nestedIndex, vd->toChars());
  IF_LOG {
    Logger::cout() << "Addr: " << *val << '\n';
    Logger::cout() << "of type: " << *val->getType() << '\n';
  }
  if (isSpecialRefVar(vd)) {
    // Handled appropriately by makeVarDValue() and EmitLocalVariable(), pass
    // storage of pointer (reference lvalue).
  } else if (byref || captureByRef(vd)) {
    val = DtoAlignedLoad(irLocal->value->getType(), val);
    // ref/out variables get a reference-debuginfo-type in EmitLocalVariable()
    // => don't dereference, use reference lvalue as address
    if (!vd->isReference())
      gIR->DBuilder.OpDeref(dwarfAddrOps);
    IF_LOG {
      Logger::cout() << "Was byref, now: " << *irLocal->value << '\n';
      Logger::cout() << "of type: " << *irLocal->value->getType() << '\n';
    }
  }

  if (!skipDIDeclaration && global.params.symdebug) {
    gIR->DBuilder.EmitLocalVariable(ctx, vd, nullptr, false,
                                    /*forceAsLocal=*/true, false, dwarfAddrOps);
  }

  return makeVarDValue(astype, vd, val);
}

void DtoResolveNestedContext(const Loc &loc, AggregateDeclaration *decl,
                             LLValue *value) {
  IF_LOG Logger::println("Resolving nested context");
  LOG_SCOPE;

  // get context
  LLValue *nest = DtoNestedContext(loc, decl);

  // store into right location
  if (!llvm::dyn_cast<llvm::UndefValue>(nest)) {
    // Need to make sure the declaration has already been resolved, because
    // when multiple source files are specified on the command line, the
    // frontend sometimes adds "nested" (i.e. a template in module B
    // instantiated from module A with a type from module A instantiates
    // another template from module B) into the wrong module, messing up
    // our codegen order.
    DtoResolveDsymbol(decl);

    unsigned idx = getVthisIdx(decl);
    llvm::StructType *st = getIrAggr(decl)->getLLStructType();
    LLValue *gep = DtoGEP(st, value, 0, idx, ".vthis");
    DtoStore(nest, gep);
  }
}

LLValue *DtoNestedContext(const Loc &loc, Dsymbol *sym) {
  IF_LOG Logger::println("DtoNestedContext for %s", sym->toPrettyChars());
  LOG_SCOPE;

  // Exit quickly for functions that accept a context pointer for ABI purposes,
  // but do not actually read from it.
  //
  // null is used instead of LLVM's undef to not break bitwise comparison,
  // for instances of nested struct types which don't have any nested
  // references, or for delegates to nested functions with an empty context.
  //
  // We cannot simply fall back to retuning null once we discover that we
  // don't actually have a context to pass, because we sadly also need to
  // catch invalid code here in the glue layer (see error() below).
  if (FuncDeclaration *symfd = sym->isFuncDeclaration()) {
    // Make sure we've had a chance to analyze nested context usage
    DtoCreateNestedContextType(symfd);

    int depth = getIrFunc(symfd)->depth;
    Logger::println("for function of depth %d", depth);
    if (depth == -1 || (depth == 0 && !symfd->closureVars.empty())) {
      Logger::println("function does not have context or creates its own "
                      "from scratch, returning null");
      return getNullPtr();
    }
  }

  // The function we are currently in, and the constructed object/called
  // function might inherit a context pointer from.
  auto &funcGen = gIR->funcGen();
  auto &irFunc = funcGen.irFunc;

  bool fromParent = true;

  LLValue *val;
  if (funcGen.nestedVar) {
    // if this func has its own vars that are accessed by nested funcs
    // use its own context
    val = funcGen.nestedVar;
    fromParent = false;
  } else if (irFunc.nestArg) {
    // otherwise, it may have gotten a context from the caller
    val = irFunc.nestArg;
  } else if (irFunc.thisArg) {
    // or just have a this argument
    AggregateDeclaration *ad = irFunc.decl->isMember2();
    val = loadThisPtr(ad, irFunc);
    if (!ad->vthis) {
      // This is just a plain 'outer' reference of a class nested in a
      // function (but without any variables in the nested context).
      return val;
    }
    val = indexVThis(ad, val);
  } else {
    if (sym->isFuncDeclaration()) {
      // If we are here, the function actually needs its nested context
      // and we cannot provide one. Thus, it's invalid code that is
      // unfortunately not caught in the frontend (e.g. a function literal
      // tries to call a nested function from the parent scope).
      error(
          loc,
          "function `%s` is a nested function and cannot be accessed from `%s`",
          sym->toPrettyChars(), irFunc.decl->toPrettyChars());
      fatal();
    }
    return getNullPtr();
  }

  // The symbol may need a parent context of the current function.
  if (FuncDeclaration *frameToPass = getParentFunc(sym)) {
    IF_LOG Logger::println("Parent frame is from %s", frameToPass->toChars());
    FuncDeclaration *ctxfd = irFunc.decl;
    IF_LOG Logger::println("Current function is %s", ctxfd->toChars());
    if (fromParent) {
      ctxfd = getParentFunc(ctxfd);

      /*
       The following can happen with
       class Outer {
           class Inner {
               auto opSlice() {
                   struct Range {
                       void foo() {}
                   }
                   return Range();
               }
           }
       }
       see https://github.com/ldc-developers/ldc/issues/4130
       */
      if (!ctxfd)
        ctxfd = irFunc.decl;
    }
    IF_LOG Logger::println("Context is from %s", ctxfd->toChars());

    unsigned neededDepth = getIrFunc(frameToPass)->depth;
    unsigned ctxDepth = getIrFunc(ctxfd)->depth;

    IF_LOG {
      Logger::cout() << "Needed depth: " << neededDepth << '\n';
      Logger::cout() << "Context depth: " << ctxDepth << '\n';
    }

    if (neededDepth >= ctxDepth) {
      // assert(neededDepth <= ctxDepth + 1 && "How are we going more than one
      // nesting level up?");
      // fd needs the same context as we do, so all is well
      IF_LOG Logger::println(
          "Calling sibling function or directly nested function");
    } else {
      llvm::StructType *type = getIrFunc(ctxfd)->frameType;
      val = DtoGEP(type, val, 0, neededDepth);
      val = DtoAlignedLoad(type->getElementType(neededDepth),
          val, (std::string(".frame.") + frameToPass->toChars()).c_str());
    }
  }

  IF_LOG {
    Logger::cout() << "result = " << *val << '\n';
    Logger::cout() << "of type " << *val->getType() << '\n';
  }
  return val;
}

static void DtoCreateNestedContextType(FuncDeclaration *fd) {
  IF_LOG Logger::println("DtoCreateNestedContextType for %s",
                         fd->toPrettyChars());
  LOG_SCOPE

  FuncDeclaration *parentFunc = getParentFunc(fd);
  // Make sure the parent has already been analyzed.
  if (parentFunc) {
    DtoCreateNestedContextType(parentFunc);
  }

  DtoDeclareFunction(fd);

  IrFunction &irFunc = *getIrFunc(fd);

  if (irFunc.nestedContextCreated) {
    Logger::println("already done");
    return;
  }
  irFunc.nestedContextCreated = true;

  if (fd->closureVars.length == 0) {
    // No local variables of this function are captured.
    if (parentFunc) {
      // Propagate context arg properties if the context arg is passed on
      // unmodified.
      IrFunction &parentIrFunc = *getIrFunc(parentFunc);
      irFunc.frameType = parentIrFunc.frameType;
      irFunc.frameTypeAlignment = parentIrFunc.frameTypeAlignment;
      irFunc.depth = parentIrFunc.depth;
    }
    return;
  }

  Logger::println("has nested frame");

  // construct nested variables array
  // start with adding all enclosing parent frames until a static parent is
  // reached

  LLStructType *innerFrameType = nullptr;
  unsigned depth = 0;

  if (parentFunc) {
    IrFunction &parentIrFunc = *getIrFunc(parentFunc);
    innerFrameType = parentIrFunc.frameType;
    if (innerFrameType) {
      depth = parentIrFunc.depth + 1;
    }
  }

  irFunc.depth = depth;

  IF_LOG Logger::cout() << "Function " << fd->toChars() << " has depth "
                        << depth << '\n';

  AggrTypeBuilder builder;

  if (depth != 0) {
    assert(innerFrameType);
    // Add frame pointer types for all but last frame
    for (unsigned i = 0; i < (depth - 1); ++i) {
      builder.addType(innerFrameType->getElementType(i), target.ptrsize);
    }
    // Add frame pointer type for last frame
    builder.addType(LLPointerType::getUnqual(innerFrameType), target.ptrsize);
  }

  // Add the direct nested variables of this function, and update their
  // indices to match.
  // TODO: optimize ordering for minimal space usage?
  unsigned maxAlignment = 1;
  for (auto vd : fd->closureVars) {
    const bool isParam = vd->isParameter();

    LLType *t = nullptr;
    unsigned alignment = 0;
    if (captureByRef(vd)) {
      t = DtoType(pointerTo(vd->type));
      alignment = target.ptrsize;
    } else if (isParam && (vd->storage_class & STClazy)) {
      // the type is a delegate (LL struct)
      auto tf = TypeFunction::create(nullptr, vd->type, VARARGnone, LINK::d);
      auto td = TypeDelegate::create(tf);
      t = DtoType(td);
      alignment = target.ptrsize;
    } else {
      t = DtoMemType(vd->type);
      alignment = DtoAlignment(vd);
    }

    if (alignment > 1) {
      builder.alignCurrentOffset(alignment);
      maxAlignment = std::max(maxAlignment, alignment);
    }

    IrLocal &irLocal =
        *(isParam ? getIrParameter(vd, true) : getIrLocal(vd, true));
    irLocal.nestedIndex = builder.currentFieldIndex();
    irLocal.nestedDepth = depth;

    builder.addType(t, getTypeAllocSize(t));

    IF_LOG Logger::cout() << "Nested var '" << vd->toChars() << "' of type "
                          << *t << "\n";
  }

  LLStructType *frameType = LLStructType::create(
      gIR->context(), builder.defaultTypes(),
      std::string("nest.") + fd->toChars(), builder.isPacked());

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

  // Store type in IrFunction
  irFunc.frameType = frameType;
  irFunc.frameTypeAlignment = maxAlignment;
}

void DtoCreateNestedContext(FuncGenState &funcGen) {
  const auto fd = funcGen.irFunc.decl;
  IF_LOG Logger::println("DtoCreateNestedContext for %s", fd->toPrettyChars());
  LOG_SCOPE

  DtoCreateNestedContextType(fd);

  // construct nested variables array
  if (fd->closureVars.length > 0) {
    auto &irFunc = funcGen.irFunc;
    unsigned depth = irFunc.depth;
    LLStructType *frameType = irFunc.frameType;
    const unsigned frameAlignment =
        std::max(getABITypeAlign(frameType), irFunc.frameTypeAlignment);

    // Create frame for current function and append to frames list
    LLValue *frame = nullptr;
    bool needsClosure = fd->needsClosure();
    IF_LOG Logger::println("Needs closure (GC) flag: %d", (int)needsClosure);
    if (needsClosure) {
      LLFunction *fn =
          getRuntimeFunction(fd->loc, gIR->module, "_d_allocmemory");
      auto size = getTypeAllocSize(frameType);
      if (frameAlignment > 16) // GC guarantees an alignment of 16
        size += frameAlignment - 16;
      LLValue *mem =
          gIR->CreateCallOrInvoke(fn, DtoConstSize_t(size), ".gc_frame");
      if (frameAlignment <= 16) {
        frame = mem;
      } else {
        const uint64_t mask = frameAlignment - 1;
        mem = gIR->ir->CreatePtrToInt(mem, DtoSize_t());
        mem = gIR->ir->CreateAdd(mem, DtoConstSize_t(mask));
        mem = gIR->ir->CreateAnd(mem, DtoConstSize_t(~mask));
        frame =
            gIR->ir->CreateIntToPtr(mem, frameType->getPointerTo(), ".frame");
      }
    } else {
      frame = DtoRawAlloca(frameType, frameAlignment, ".frame");
    }

    // copy parent frames into beginning
    if (depth != 0) {
      LLValue *src = irFunc.nestArg;
      if (!src) {
        assert(irFunc.thisArg);
        AggregateDeclaration *ad = fd->isMember2();
        assert(ad);
        assert(ad->vthis);
        LLValue *thisptr = loadThisPtr(ad, irFunc);
        IF_LOG Logger::println("Indexing to 'this'");
        src = indexVThis(ad, thisptr);
      }
      if (depth > 1) {
        DtoMemCpy(frame, src, DtoConstSize_t((depth - 1) * target.ptrsize),
                  getABITypeAlign(getOpaquePtrType()));
      }
      // Copy nestArg into framelist; the outer frame is not in the list of
      // pointers
      LLValue *gep = DtoGEP(frameType, frame, 0, depth - 1);
      DtoAlignedStore(src, gep);
    }

    funcGen.nestedVar = frame;

    // go through all nested vars and assign addresses where possible.
    for (auto vd : fd->closureVars) {
      if (needsClosure && vd->needsScopeDtor()) {
        // This should really be a front-end, not a glue layer error,
        // but we need to fix this in DMD too.
        error(vd->loc, "%s `%s` has scoped destruction, cannot build closure",
              vd->kind(), vd->toPrettyChars());
      }

      IrLocal *irLocal = getIrLocal(vd);
      LLValue *gep = DtoGEP(frameType, frame, 0, irLocal->nestedIndex, vd->toChars());
      if (vd->isParameter()) {
        IF_LOG Logger::println("nested param: %s", vd->toChars());
        LOG_SCOPE
        IrParameter *parm = getIrParameter(vd);
        assert(parm->value);
        assert(parm->value->getType()->isPointerTy());

        if (vd->isReference()) {
          Logger::println(
              "Captured by reference, copying pointer to nested frame");
          DtoAlignedStore(parm->value, gep);
          // pass GEP as reference lvalue to EmitLocalVariable()
        } else {
          Logger::println("Moving to nested frame");
          // The parameter value is an alloca'd stack slot.
          // Copy to the nesting frame and leave the alloca for
          // the optimizers to clean up.
          DtoMemCpy(frameType->getContainedType(irLocal->nestedIndex), gep, parm->value);
          gep->takeName(parm->value);
          parm->value = gep; // update variable lvalue
        }
      } else if (isNRVOVar(vd)) {
        IF_LOG Logger::println(
            "nested NRVO var: %s, copying pointer to nested frame",
            vd->toChars());
        assert(irFunc.sretArg);
        DtoAlignedStore(irFunc.sretArg, gep);
        assert(!irLocal->value);
        irLocal->value = irFunc.sretArg;
        gep = irFunc.sretArg; // lvalue for debuginfo
      } else {
        IF_LOG Logger::println("nested var: %s, allocating in nested frame",
                               vd->toChars());
        assert(!irLocal->value);
        irLocal->value = gep;
      }

      gIR->DBuilder.EmitLocalVariable(gep, vd);
    }
  }
}
