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

#include "ir/iraggr.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/irstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/mangling.h"
#include "gen/pragma.h"
#include "gen/tollvm.h"
#include "ir/irdsymbol.h"
#include "ir/irtypeclass.h"
#include "ir/irtypestruct.h"
#include <algorithm>
#include <unordered_map>

using namespace dmd;

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

llvm::StructType *IrAggr::getLLStructType() {
  if (llStructType)
    return llStructType;

  LLType *llType = DtoType(type);
  if (auto irClassType = getIrType(type)->isClass())
    llType = irClassType->getMemoryLLType();

  llStructType = llvm::dyn_cast<LLStructType>(llType);

  return llStructType;
}

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

bool IrAggr::suppressTypeInfo() const {
  return !global.params.useTypeInfo || !Type::dtypeinfo ||
         aggrdecl->llvmInternal == LLVMno_typeinfo;
}

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

bool IrAggr::useDLLImport() const {
  return dllimportDataSymbol(aggrdecl);
}

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

LLGlobalVariable *IrAggr::getInitSymbol(bool define) {
#if LDC_LLVM_VER >= 1800
  #define startswith starts_with
#endif

  if (!init) {
    const auto irMangle = getIRMangledInitSymbolName(aggrdecl);

    // Init symbols of built-in TypeInfo classes (in rt.util.typeinfo) are
    // special.
    auto cd = aggrdecl->isClassDeclaration();
    const bool isBuiltinTypeInfo =
        cd && llvm::StringRef(cd->ident->toChars()).startswith("TypeInfo_");

    // Only declare the symbol if it isn't yet, otherwise the init symbol of
    // built-in TypeInfos may clash with an existing base-typed forward
    // declaration when compiling the rt.util.typeinfo unittests.
    init = gIR->module.getGlobalVariable(irMangle);
    if (init) {
      assert(!init->hasInitializer() &&
             "existing init symbol not expected to be defined");
      assert((isBuiltinTypeInfo || init->getValueType() == getLLStructType()) &&
             "type of existing init symbol declaration doesn't match");
    } else {
      // Init symbols of built-in TypeInfos need to be kept mutable as the type
      // is not declared as immutable on the D side, and e.g. synchronized() can
      // be used on the implicit monitor.
      const bool isConstant = !isBuiltinTypeInfo;
      init = declareGlobal(aggrdecl->loc, gIR->module, getLLStructType(),
                           irMangle, isConstant, false, useDLLImport());
    }

    init->setAlignment(llvm::MaybeAlign(DtoAlignment(type)));

    if (!define)
      define = defineOnDeclare(aggrdecl, /*isFunction=*/false);
  }

  if (define) {
    auto initConstant = getDefaultInit();
    if (!init->hasInitializer()) {
      init = gIR->setGlobalVarInitializer(init, initConstant, aggrdecl);
    }
  }

  return init;

#if LDC_LLVM_VER >= 1800
  #undef startswith
#endif
}

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

llvm::Constant *IrAggr::getDefaultInit() {
  if (constInit) {
    return constInit;
  }

  IF_LOG Logger::println("Building default initializer for %s",
                         aggrdecl->toPrettyChars());
  LOG_SCOPE;

  VarInitMap noExplicitInitializers;
  constInit = createInitializerConstant(noExplicitInitializers);
  return constInit;
}

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

// helper function that adds zero bytes to a vector of constants
// FIXME A similar function is in ir/irtypeaggr.cpp
static inline size_t
add_zeros(llvm::SmallVectorImpl<llvm::Constant *> &constants,
          size_t startOffset, size_t endOffset) {
  assert(startOffset <= endOffset);
  const size_t paddingSize = endOffset - startOffset;
  if (paddingSize) {
    llvm::ArrayType *pad = llvm::ArrayType::get(
        llvm::Type::getInt8Ty(gIR->context()), paddingSize);
    constants.push_back(llvm::Constant::getNullValue(pad));
  }
  return paddingSize ? 1 : 0;
}

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

LLConstant *IrAggr::getDefaultInitializer(VarDeclaration *field) {
  // Issue 9057 workaround caused by issue 14666 fix, see DMD upstream
  // commit 069f570005.
  if (field->_init && field->semanticRun() < PASS::semantic2done &&
      field->_scope) {
    semantic2(field, field->_scope);
  }

  return DtoConstInitializer(field->_init ? field->_init->loc : field->loc,
                             field->type, field->_init, field->isCsymbol());
}

// return a constant array of type arrTypeD initialized with a constant value,
// or that constant value
static llvm::Constant *FillSArrayDims(Type *arrTypeD, llvm::Constant *init) {
  // Check whether we actually need to expand anything.
  // KLUDGE: We don't have the initializer type here, so we can only check
  // the size without doing an expensive recursive D <-> LLVM type comparison.
  // The better way to solve this would be to just fix the initializer
  // codegen in any place where a scalar initializer might still be generated.
  if (gDataLayout->getTypeAllocSize(init->getType()) >= size(arrTypeD)) {
    return init;
  }

  if (arrTypeD->ty == TY::Tsarray) {
    init = FillSArrayDims(arrTypeD->nextOf(), init);
    size_t dim = static_cast<TypeSArray *>(arrTypeD)->dim->toUInteger();
    llvm::ArrayType *arrty = llvm::ArrayType::get(init->getType(), dim);
    return llvm::ConstantArray::get(arrty,
                                    std::vector<llvm::Constant *>(dim, init));
  }
  return init;
}

llvm::Constant *
IrAggr::createInitializerConstant(const VarInitMap &explicitInitializers) {
  IF_LOG Logger::println("Creating initializer constant for %s",
                         aggrdecl->toChars());
  LOG_SCOPE;

  llvm::SmallVector<llvm::Constant *, 16> constants;
  unsigned offset = 0;

  auto cd = aggrdecl->isClassDeclaration();
  IrClass *irClass = cd ? static_cast<IrClass *>(this) : nullptr;
  if (irClass) {
    // add vtbl
    constants.push_back(irClass->getVtblSymbol());
    offset += target.ptrsize;

    // add monitor (except for C++ classes)
    if (!cd->isCPPclass()) {
      constants.push_back(getNullPtr());
      offset += target.ptrsize;
    }
  }

  // Add the initializers for the member fields.
  unsigned dummy = 0;
  bool isPacked = false;
  addFieldInitializers(constants, explicitInitializers, aggrdecl, offset, dummy,
                       isPacked);

  // tail padding?
  const size_t structsize = size(aggrdecl, Loc());
  if (offset < structsize) {
    add_zeros(constants, offset, structsize);
  } else if (offset > structsize) {
    error(Loc(), "ICE: IR aggregate constant size exceeds the frontend size");
    fatal();
  }

  // get LL field types
  llvm::SmallVector<llvm::Type *, 16> types;
  types.reserve(constants.size());
  for (auto c : constants)
    types.push_back(c->getType());

  const auto llStructType = getLLStructType();
  bool isCompatible = (types.size() == llStructType->getNumElements());
  if (isCompatible) {
    for (size_t i = 0; i < types.size(); i++) {
      if (types[i] != llStructType->getElementType(i)) {
        isCompatible = false;
        break;
      }
    }
  }

  // build constant
  LLStructType *llType = llStructType;
  if (!isCompatible) {
    // Note: isPacked only reflects whether there are misaligned IR fields,
    // not checking whether the aggregate contains appropriate tail padding.
    // So the resulting constant might contain more (implicit) tail padding than
    // the actual type, which should be harmless.
    llType = LLStructType::get(gIR->context(), types, isPacked);
    assert(getTypeAllocSize(llType) >= structsize);
  }

  llvm::Constant *c = LLConstantStruct::get(llType, constants);
  IF_LOG Logger::cout() << "final initializer: " << *c << std::endl;
  return c;
}

void IrAggr::addFieldInitializers(
    llvm::SmallVectorImpl<llvm::Constant *> &constants,
    const VarInitMap &explicitInitializers, AggregateDeclaration *decl,
    unsigned &offset, unsigned &interfaceVtblIndex, bool &isPacked) {

  using llvm::APInt;

  if (ClassDeclaration *cd = decl->isClassDeclaration()) {
    if (cd->baseClass) {
      addFieldInitializers(constants, explicitInitializers, cd->baseClass,
                           offset, interfaceVtblIndex, isPacked);
    }

    // has interface vtbls?
    if (cd->vtblInterfaces && cd->vtblInterfaces->length > 0) {
      // Align interface infos to pointer size.
      unsigned aligned =
          (offset + target.ptrsize - 1) & ~(target.ptrsize - 1);
      if (offset < aligned) {
        add_zeros(constants, offset, aligned);
        offset = aligned;
      }

      IrClass *irClass = static_cast<IrClass *>(this);
      for (auto bc : *cd->vtblInterfaces) {
        constants.push_back(
            irClass->getInterfaceVtblSymbol(bc, interfaceVtblIndex));
        offset += target.ptrsize;
        ++interfaceVtblIndex;
      }
    }
  }

  AggrTypeBuilder b(offset);
  b.addAggregate(decl,
                 explicitInitializers.empty() ? nullptr : &explicitInitializers,
                 AggrTypeBuilder::Aliases::Skip);
  offset = b.currentOffset();
  if (!isPacked)
    isPacked = b.isPacked();

  const size_t baseLLFieldIndex = constants.size();
  const size_t numNewLLFields = b.defaultTypes().size();
  constants.resize(constants.size() + numNewLLFields, nullptr);

  const auto getFieldInit = [&explicitInitializers](VarDeclaration *field) {
    const auto explicitIt = explicitInitializers.find(field);
    return explicitIt != explicitInitializers.end()
               ? explicitIt->second
               : getDefaultInitializer(field);
  };

  // IR field index => APInt for bitfield group
  std::unordered_map<unsigned, APInt> bitFieldGroupConstants;

  const auto addToBitFieldGroup = [&](BitFieldDeclaration *bf,
                                      unsigned fieldIndex, unsigned bitOffset) {
    LLConstant *init = getFieldInit(bf);
    if (init->isNullValue()) {
      // no bits to set
      return;
    }

    if (bitFieldGroupConstants.find(fieldIndex) ==
        bitFieldGroupConstants.end()) {
      const auto fieldType =
          llvm::cast<LLArrayType>(b.defaultTypes()[fieldIndex]); // an i8 array
      const auto bitFieldSize = fieldType->getNumElements();
      bitFieldGroupConstants.emplace(fieldIndex, APInt(bitFieldSize * 8, 0));
    }

    APInt &constant = bitFieldGroupConstants[fieldIndex];
    const auto intSizeInBits = constant.getBitWidth();

    const APInt oldVal = constant;
    const APInt bfVal = init->getUniqueInteger().zextOrTrunc(intSizeInBits);
    const APInt mask = APInt::getLowBitsSet(intSizeInBits, bf->fieldWidth)
                       << bitOffset;
    assert(!oldVal.intersects(mask) && "has masked bits set already");

    constant = oldVal | ((bfVal << bitOffset) & mask);
  };

  // add explicit and non-overlapping implicit initializers
  const auto &gepIndices = b.varGEPIndices();
  for (const auto &pair : gepIndices) {
    const auto field = pair.first;
    const auto fieldIndex = pair.second;

    if (auto bf = field->isBitFieldDeclaration()) {
      // multiple bit fields can map to a single IR field for the whole group
      addToBitFieldGroup(bf, fieldIndex, bf->bitOffset);
    } else {
      LLConstant *&constant = constants[baseLLFieldIndex + fieldIndex];
      assert(!constant);
      constant = FillSArrayDims(field->type, getFieldInit(field));
    }
  }

  // add extra bit field members (greater byte offset than the group's first
  // member)
  for (const auto &pair : b.extraBitFieldMembers()) {
    const auto bf = pair.first;
    const auto primary = pair.second;

    const auto fieldIndexIt = gepIndices.find(primary);
    assert(fieldIndexIt != gepIndices.end());

    assert(bf->offset > primary->offset);
    addToBitFieldGroup(bf, fieldIndexIt->second,
                       (bf->offset - primary->offset) * 8 + bf->bitOffset);
  }

  for (const auto &pair : bitFieldGroupConstants) {
    const unsigned fieldIndex = pair.first;
    const APInt intValue = pair.second;

    LLConstant *&constant = constants[baseLLFieldIndex + fieldIndex];
    assert(!constant && "already have a constant for a bitfield group?");

    // convert APInt to i8 array
    const auto i8Type = getI8Type();
    const auto numBytes = intValue.getBitWidth() / 8;
    llvm::SmallVector<LLConstant *, 8> bytes;
    for (unsigned i = 0; i < numBytes; ++i) {
      APInt byteVal = intValue.extractBits(8, i * 8);
      bytes.push_back(LLConstant::getIntegerValue(i8Type, byteVal));
    }

    constant = llvm::ConstantArray::get(LLArrayType::get(i8Type, numBytes), bytes);
  }

  // TODO: sanity check that all explicit initializers have been dealt with?
  //       (potential issue for bitfields in unions...)

  // zero out remaining fields (padding and/or zero-initialized bit fields)
  for (size_t i = 0; i < numNewLLFields; i++) {
    auto &init = constants[baseLLFieldIndex + i];
    if (!init)
      init = llvm::Constant::getNullValue(b.defaultTypes()[i]);
  }
}

IrAggr *getIrAggr(AggregateDeclaration *decl, bool create) {
  if (!isIrAggrCreated(decl) && create) {
    assert(decl->ir->irAggr == nullptr);
    if (auto cd = decl->isClassDeclaration()) {
      decl->ir->irAggr = new IrClass(cd);
    } else {
      decl->ir->irAggr = new IrStruct(decl->isStructDeclaration());
    }
    decl->ir->m_type = IrDsymbol::AggrType;
  }
  assert(decl->ir->irAggr != nullptr);
  return decl->ir->irAggr;
}

IrStruct *getIrAggr(StructDeclaration *sd, bool create) {
  return static_cast<IrStruct *>(
      getIrAggr(static_cast<AggregateDeclaration *>(sd), create));
}

IrClass *getIrAggr(ClassDeclaration *cd, bool create) {
  return static_cast<IrClass *>(
      getIrAggr(static_cast<AggregateDeclaration *>(cd), create));
}

bool isIrAggrCreated(AggregateDeclaration *decl) {
  int t = decl->ir->type();
  assert(t == IrDsymbol::AggrType || t == IrDsymbol::NotSet);
  return t == IrDsymbol::AggrType;
}
