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

#include "gen/dvalue.h"

#include "dmd/declaration.h"
#include "gen/irstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/optimizer.h"
#include "gen/tollvm.h"
#include "llvm/IR/MDBuilder.h"

namespace {
bool isDefinedInFuncEntryBB(LLValue *v) {
  auto instr = llvm::dyn_cast<llvm::Instruction>(v);
  if (!instr) {
    // Global, constant, ...
    return true;
  }

  auto bb = instr->getParent();
  if (bb != &(bb->getParent()->getEntryBlock())) {
    return false;
  }

  // An invoke instruction in the entry BB does not necessarily dominate the
  // rest of the function because of the failure path.
  return !llvm::isa<llvm::InvokeInst>(instr);
}
}

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

LLValue *DtoLVal(DValue *v) {
  auto lval = v->isLVal();
  assert(lval);
  return lval->getLVal()->val;
}

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

DValue::DValue(Type *t, LLValue *v) : type(t), val(v) {
  assert(type);
  assert(val);
}

bool DValue::definedInFuncEntryBB() { return isDefinedInFuncEntryBB(val); }

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

DRValue::DRValue(Type *t, LLValue *v) : DValue(t, v) {
  assert(!DtoIsInMemoryOnly(t) &&
         "Cannot represent memory-only type as DRValue");
}

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

DImValue::DImValue(Type *t, llvm::Value *v) : DRValue(t, v) {
  // TODO: get rid of Tfunction exception
  // v may be an addrspace qualified pointer so strip it before doing a pointer
  // equality check.
  assert(t->toBasetype()->ty == TY::Tfunction ||
         stripAddrSpaces(v->getType()) == DtoType(t));
  assert(t->toBasetype()->ty != TY::Tarray &&
         "use DSliceValue for dynamic arrays");
}

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

DConstValue::DConstValue(Type *t, LLConstant *con) : DRValue(t, con) {
  assert(con->getType() == DtoType(t));
}

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

DSliceValue::DSliceValue(Type *t, LLValue *pair, LLValue *length, LLValue *ptr)
    : DRValue(t, pair), length(length), ptr(ptr) {
  assert(t->toBasetype()->ty == TY::Tarray);
  // v may be an addrspace qualified pointer so strip it before doing a pointer
  // equality check.
  assert(stripAddrSpaces(pair->getType()) == DtoType(t));
}

DSliceValue::DSliceValue(Type *t, LLValue *pair)
    : DSliceValue(t, pair, nullptr, nullptr) {}

DSliceValue::DSliceValue(Type *t, LLValue *length, LLValue *ptr)
    : DSliceValue(t, DtoAggrPair(length, ptr), length, ptr) {}

LLValue *DSliceValue::getLength() {
  return length ? length : DtoExtractValue(val, 0, ".len");
}

LLValue *DSliceValue::getPtr() {
  return ptr ? ptr : DtoExtractValue(val, 1, ".ptr");
}

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

DFuncValue::DFuncValue(Type *t, FuncDeclaration *fd, LLValue *v, LLValue *vt,
                       LLValue *vtable)
    : DRValue(t, v), func(fd), vthis(vt), vtable(vtable) {}

DFuncValue::DFuncValue(FuncDeclaration *fd, LLValue *v, LLValue *vt,
                       LLValue *vtable)
    : DFuncValue(fd->type, fd, v, vt, vtable) {}

bool DFuncValue::definedInFuncEntryBB() {
  return isDefinedInFuncEntryBB(val) &&
         (!vthis || isDefinedInFuncEntryBB(vthis));
}

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

DLValue::DLValue(Type *t, LLValue *v) : DValue(t, v) {
  assert(t->toBasetype()->ty == TY::Ttuple || v->getType()->isPointerTy());
}

DRValue *DLValue::getRVal() {
  if (DtoIsInMemoryOnly(type)) {
    llvm_unreachable("getRVal() for memory-only type");
    return nullptr;
  }

  LLValue *rval = DtoLoad(DtoMemType(type), val);

  const auto ty = type->toBasetype()->ty;
  if (ty == TY::Tbool) {
    assert(rval->getType() == llvm::Type::getInt8Ty(gIR->context()));

    if (isOptimizationEnabled()) {
      // attach range metadata for i8 being loaded: [0, 2)
      llvm::MDBuilder mdBuilder(gIR->context());
      llvm::cast<llvm::LoadInst>(rval)->setMetadata(
          llvm::LLVMContext::MD_range,
          mdBuilder.createRange(llvm::APInt(8, 0), llvm::APInt(8, 2)));
    }

    // truncate to i1
    rval = gIR->ir->CreateTrunc(rval, llvm::Type::getInt1Ty(gIR->context()));
  } else if (ty == TY::Tarray) {
    return new DSliceValue(type, rval);
  }

  return new DImValue(type, rval);
}

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

DSpecialRefValue::DSpecialRefValue(Type *t, LLValue *v) : DLValue(v, t) {
  assert(v->getType()->isPointerTy());
}

DRValue *DSpecialRefValue::getRVal() {
  return DLValue(type, DtoLoad(getOpaquePtrType(), val)).getRVal();
}

DLValue *DSpecialRefValue::getLVal() {
  return new DLValue(type, DtoLoad(getOpaquePtrType(), val));
}

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

DBitFieldLValue::DBitFieldLValue(Type *t, LLValue *ptr, BitFieldDeclaration *bf)
    : DValue(t, ptr), bf(bf),
      intType(LLIntegerType::get(
          gIR->context(), (bf->bitOffset + bf->fieldWidth + 7) / 8 * 8)) {
  assert(ptr->getType()->isPointerTy());
}

DRValue *DBitFieldLValue::getRVal() {
  const auto sizeInBits = intType->getBitWidth();
  const auto ptr = val;
  LLValue *v = gIR->ir->CreateAlignedLoad(intType, ptr, llvm::MaybeAlign(1));
  // TODO: byte-swap v for big-endian targets?

  if (bf->type->isunsigned()) {
    if (auto n = bf->bitOffset)
      v = gIR->ir->CreateLShr(v, n);
    const auto mask = llvm::APInt::getLowBitsSet(sizeInBits, bf->fieldWidth);
    v = gIR->ir->CreateAnd(v, mask);
    v = gIR->ir->CreateZExtOrTrunc(v, DtoType(bf->type));
  } else {
    // shift-left to make the MSB the sign bit
    if (auto n = sizeInBits - (bf->bitOffset + bf->fieldWidth))
      v = gIR->ir->CreateShl(v, n);
    // then arithmetic-shift-right
    if (auto n = sizeInBits - bf->fieldWidth)
      v = gIR->ir->CreateAShr(v, n);
    v = gIR->ir->CreateSExtOrTrunc(v, DtoType(bf->type));
  }

  return new DImValue(type, v);
}

void DBitFieldLValue::store(LLValue *value) {
  assert(value->getType()->isIntegerTy());

  const auto ptr = val;

  const auto mask =
      llvm::APInt::getLowBitsSet(intType->getBitWidth(), bf->fieldWidth);
  const auto oldVal =
      gIR->ir->CreateAlignedLoad(intType, ptr, llvm::MaybeAlign(1));
  // TODO: byte-swap oldVal for big-endian targets?
  const auto maskedOldVal =
      gIR->ir->CreateAnd(oldVal, ~(mask << bf->bitOffset));

  auto bfVal = gIR->ir->CreateZExtOrTrunc(value, intType);
  bfVal = gIR->ir->CreateAnd(bfVal, mask);
  if (auto n = bf->bitOffset)
    bfVal = gIR->ir->CreateShl(bfVal, n);

  const auto newVal = gIR->ir->CreateOr(maskedOldVal, bfVal);
  // TODO: byte-swap newVal for big-endian targets?
  gIR->ir->CreateAlignedStore(newVal, ptr, llvm::MaybeAlign(1));
}

DDcomputeLValue::DDcomputeLValue(Type *t, llvm::Type * llt, LLValue *v) : DLValue(t, v) {
    lltype = llt;
}
DRValue *DDcomputeLValue::getRVal() {
  if (DtoIsInMemoryOnly(type)) {
    llvm_unreachable("getRVal() for memory-only type");
    return nullptr;
  }

  LLValue *rval = DtoLoad(lltype, val);
  
  const auto ty = type->toBasetype()->ty;
  if (ty == TY::Tbool) {
    assert(rval->getType() == llvm::Type::getInt8Ty(gIR->context()));
  
    if (isOptimizationEnabled()) {
      // attach range metadata for i8 being loaded: [0, 2)
      llvm::MDBuilder mdBuilder(gIR->context());
      llvm::cast<llvm::LoadInst>(rval)->setMetadata(
          llvm::LLVMContext::MD_range,
          mdBuilder.createRange(llvm::APInt(8, 0), llvm::APInt(8, 2)));
    }
  
    // truncate to i1
    rval = gIR->ir->CreateTrunc(rval, llvm::Type::getInt1Ty(gIR->context()));
  } else if (ty == TY::Tarray) {
    return new DSliceValue(type, rval);
  }
  
  return new DImValue(type, rval);
}
