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

#include "gen/irstate.h"

#include "dmd/declaration.h"
#include "dmd/expression.h"
#include "dmd/identifier.h"
#include "dmd/mtype.h"
#include "dmd/statement.h"
#include "gen/funcgenstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/tollvm.h"
#include "ir/irfunction.h"
#include "llvm/IR/InlineAsm.h"
#include <cstdarg>

IRState *gIR = nullptr;
llvm::TargetMachine *gTargetMachine = nullptr;
const llvm::DataLayout *gDataLayout = nullptr;
TargetABI *gABI = nullptr;

////////////////////////////////////////////////////////////////////////////////
IRState::IRState(const char *name, llvm::LLVMContext &context)
    : builder(context), module(name, context), objc(module), DBuilder(this) {
  ir.state = this;
  mem.addRange(&inlineAsmLocs, sizeof(inlineAsmLocs));
}

IRState::~IRState() { mem.removeRange(&inlineAsmLocs); }

FuncGenState &IRState::funcGen() {
  assert(!funcGenStates.empty() && "Function stack is empty!");
  return *funcGenStates.back();
}

IrFunction *IRState::func() { return &funcGen().irFunc; }

llvm::Function *IRState::topfunc() { return func()->getLLVMFunc(); }

#if LDC_LLVM_VER >= 1900
llvm::BasicBlock::iterator IRState::nextAllocaPos() {
#else
llvm::BasicBlock *IRState::nextAllocaPos() {
#endif
  return funcGen()
      .allocasBlock
#if LDC_LLVM_VER >= 1900
      ->end()
#endif
      ;
}

std::unique_ptr<IRBuilderScope> IRState::setInsertPoint(llvm::BasicBlock *bb) {
  auto savedScope = std::make_unique<IRBuilderScope>(builder);
  builder.SetInsertPoint(bb);
  return savedScope;
}

std::unique_ptr<llvm::IRBuilderBase::InsertPointGuard>
IRState::saveInsertPoint() {
  return std::make_unique<llvm::IRBuilderBase::InsertPointGuard>(builder);
}

bool IRState::scopereturned() {
  auto bb = scopebb();
  return !bb->empty() && bb->back().isTerminator();
}

llvm::BasicBlock *IRState::insertBBBefore(llvm::BasicBlock *successor,
                                          const llvm::Twine &name) {
  return llvm::BasicBlock::Create(context(), name, topfunc(), successor);
}

llvm::BasicBlock *IRState::insertBBAfter(llvm::BasicBlock *predecessor,
                                         const llvm::Twine &name) {
  auto bb = llvm::BasicBlock::Create(context(), name, topfunc());
  bb->moveAfter(predecessor);
  return bb;
}

llvm::BasicBlock *IRState::insertBB(const llvm::Twine &name) {
  return insertBBAfter(scopebb(), name);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               const char *Name) {
  return CreateCallOrInvoke(Callee, {}, Name);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               llvm::ArrayRef<LLValue *> Args,
                                               const char *Name,
                                               bool isNothrow) {
  return funcGen().callOrInvoke(Callee, Callee->getFunctionType(), Args, Name,
                                isNothrow);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               LLValue *Arg1,
                                               const char *Name) {
  return CreateCallOrInvoke(Callee, llvm::ArrayRef<LLValue *>(Arg1), Name);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               LLValue *Arg1, LLValue *Arg2,
                                               const char *Name) {
  return CreateCallOrInvoke(Callee, {Arg1, Arg2}, Name);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               LLValue *Arg1, LLValue *Arg2,
                                               LLValue *Arg3,
                                               const char *Name) {
  return CreateCallOrInvoke(Callee, {Arg1, Arg2, Arg3}, Name);
}

llvm::Instruction *IRState::CreateCallOrInvoke(LLFunction *Callee,
                                               LLValue *Arg1, LLValue *Arg2,
                                               LLValue *Arg3, LLValue *Arg4,
                                               const char *Name) {
  return CreateCallOrInvoke(Callee, {Arg1, Arg2, Arg3, Arg4}, Name);
}

bool IRState::emitArrayBoundsChecks() {
  if (global.params.useArrayBounds != CHECKENABLEsafeonly) {
    return global.params.useArrayBounds == CHECKENABLEon;
  }

  // Safe functions only.
  if (funcGenStates.empty()) {
    return false;
  }

  Type *t = func()->decl->type;
  return t->ty == TY::Tfunction && ((TypeFunction *)t)->trust == TRUST::safe;
}

LLGlobalVariable *
IRState::setGlobalVarInitializer(LLGlobalVariable *globalVar,
                                 LLConstant *initializer,
                                 Dsymbol *symbolForLinkageAndVisibility) {
  if (initializer->getType() == globalVar->getValueType()) {
    defineGlobal(globalVar, initializer, symbolForLinkageAndVisibility);
    return globalVar;
  }

  // Create the global helper variable matching the initializer type.
  // It inherits most properties from the existing globalVar.
  auto globalHelperVar = new LLGlobalVariable(
      module, initializer->getType(), globalVar->isConstant(),
      globalVar->getLinkage(), nullptr, "", nullptr,
      globalVar->getThreadLocalMode());
  globalHelperVar->setAlignment(llvm::MaybeAlign(globalVar->getAlignment()));
  globalHelperVar->setComdat(globalVar->getComdat());
  globalHelperVar->setDLLStorageClass(globalVar->getDLLStorageClass());
  globalHelperVar->setSection(globalVar->getSection());
  globalHelperVar->takeName(globalVar);

  defineGlobal(globalHelperVar, initializer, symbolForLinkageAndVisibility);

  // Replace all existing uses of globalVar by the helper variable.
  globalVar->replaceAllUsesWith(globalHelperVar);

  // Register replacement for later occurrences of the original globalVar.
  globalsToReplace.emplace_back(globalVar, globalHelperVar);

  return globalHelperVar;
}

void IRState::replaceGlobals() {
  for (const auto &pair : globalsToReplace) {
    pair.first->replaceAllUsesWith(pair.second);
    pair.first->eraseFromParent();
  }

  globalsToReplace.resize(0);
}

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

LLGlobalVariable *IRState::getStructLiteralGlobal(StructLiteralExp *sle) const {
  return structLiteralGlobals.lookup(sle->origin);
}

void IRState::setStructLiteralGlobal(StructLiteralExp *sle,
                                     LLGlobalVariable *global) {
  structLiteralGlobals[sle->origin] = global;
}

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

namespace {
template <typename F>
LLGlobalVariable *
getCachedStringLiteralImpl(llvm::Module &module,
                           llvm::StringMap<LLGlobalVariable *> &cache,
                           llvm::StringRef key,
#if LDC_LLVM_VER >= 1700
                           std::optional< unsigned > addrspace,
#endif
                           F initFactory) {
  auto iter = cache.find(key);
  if (iter != cache.end()) {
    return iter->second;
  }

  LLConstant *constant = initFactory();

  auto gvar =
      new LLGlobalVariable(module, constant->getType(), true,
                           LLGlobalValue::PrivateLinkage, constant,
                           ".str", nullptr, llvm::GlobalValue::NotThreadLocal
#if LDC_LLVM_VER >= 1700
                           , addrspace
#endif
                           );
  gvar->setUnnamedAddr(LLGlobalValue::UnnamedAddr::Global);

  cache[key] = gvar;

  return gvar;
}
}

LLGlobalVariable *IRState::getCachedStringLiteral(StringExp *se) {
  // special case for ulong[]-typed hex strings: not cached, and not
  // null-terminated either
  if (se->sz == 8) {
    auto constant = buildStringLiteralConstant(se, se->len);
    auto gvar =
        new LLGlobalVariable(module, constant->getType(), true,
                             LLGlobalValue::PrivateLinkage, constant, ".lstr");
    gvar->setUnnamedAddr(LLGlobalValue::UnnamedAddr::Global);
    return gvar;
  }

  llvm::StringMap<LLGlobalVariable *> *cache;
  switch (se->sz) {
  default:
    llvm_unreachable("Unknown char type");
  case 1:
    cache = &cachedStringLiterals;
    break;
  case 2:
    cache = &cachedWstringLiterals;
    break;
  case 4:
    cache = &cachedDstringLiterals;
    break;
  }

  const DArray<const unsigned char> keyData = se->peekData();
  const llvm::StringRef key(reinterpret_cast<const char *>(keyData.ptr),
                            keyData.length);

  return getCachedStringLiteralImpl(module, *cache, key,
#if LDC_LLVM_VER >= 1700
                                    std::nullopt,
#endif
                                    [se]() {
    // null-terminate
    return buildStringLiteralConstant(se, se->len + 1);
  });
}

LLGlobalVariable *IRState::getCachedStringLiteral(llvm::StringRef s
#if LDC_LLVM_VER >= 1700
                                                  ,std::optional< unsigned > addrspace
#endif
                                                  ) {
  return getCachedStringLiteralImpl(module,
                                    cachedStringLiterals,
                                    s,
#if LDC_LLVM_VER >= 1700
                                    addrspace,
#endif
                                    [&]() {
    return llvm::ConstantDataArray::getString(context(), s, true);
  });
}

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

void IRState::addLinkerOption(llvm::ArrayRef<llvm::StringRef> options) {
  llvm::SmallVector<llvm::Metadata *, 2> mdStrings;
  mdStrings.reserve(options.size());
  for (const auto &s : options)
    mdStrings.push_back(llvm::MDString::get(context(), s));
  linkerOptions.push_back(llvm::MDNode::get(context(), mdStrings));
}

void IRState::addLinkerDependentLib(llvm::StringRef libraryName) {
  auto n = llvm::MDString::get(context(), libraryName);
  linkerDependentLibs.push_back(llvm::MDNode::get(context(), n));
}

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

llvm::CallInst *
IRState::createInlineAsmCall(Loc loc, llvm::InlineAsm *ia,
                             llvm::ArrayRef<llvm::Value *> args,
                             llvm::ArrayRef<llvm::Type *> indirectTypes) {
  llvm::CallInst *call = ir->CreateCall(ia, args);
  addInlineAsmSrcLoc(loc, call);

  // a non-indirect output constraint (=> return value of call) shifts the
  // constraint/argument index mapping
  ptrdiff_t i = call->getType()->isVoidTy() ? 0 : -1;
  size_t indirectIdx = 0;

  for (const auto &constraintInfo : ia->ParseConstraints()) {
    if (constraintInfo.isIndirect) {
      call->addParamAttr(i, llvm::Attribute::get(
                                context(),
                                llvm::Attribute::ElementType,
                                indirectTypes[indirectIdx]));
      ++indirectIdx;
    }
    ++i;
  }

  return call;
}

void IRState::addInlineAsmSrcLoc(Loc loc, llvm::CallInst *inlineAsmCall) {
  // Simply use a stack of Loc* per IR module, and use index+1 as 32-bit
  // cookie to be mapped back by the InlineAsmDiagnosticHandler.
  // 0 is not a valid cookie.
  inlineAsmLocs.push_back(loc);
  auto srcLocCookie = static_cast<unsigned>(inlineAsmLocs.size());

  auto constant =
      LLConstantInt::get(LLType::getInt32Ty(context()), srcLocCookie);
  inlineAsmCall->setMetadata(
      "srcloc",
      llvm::MDNode::get(context(), llvm::ConstantAsMetadata::get(constant)));
}

Loc IRState::getInlineAsmSrcLoc(unsigned srcLocCookie) const {
  assert(srcLocCookie > 0 && srcLocCookie <= inlineAsmLocs.size());
  return inlineAsmLocs[srcLocCookie - 1];
}

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

IRBuilder<> *IRBuilderHelper::operator->() {
  IRBuilder<> &b = state->builder;
  assert(b.GetInsertBlock());
  return &b;
}

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

bool useMSVCEH() {
  return global.params.targetTriple->isWindowsMSVCEnvironment();
}
