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

#include "gen/abi.h"
#include "mars.h"
#include "id.h"
#include "gen/abi-generic.h"
#include "gen/abi-aarch64.h"
#include "gen/abi-arm.h"
#include "gen/abi-mips64.h"
#include "gen/abi-ppc.h"
#include "gen/abi-ppc64le.h"
#include "gen/abi-win64.h"
#include "gen/abi-x86-64.h"
#include "gen/abi-x86.h"
#include "gen/dvalue.h"
#include "gen/irstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/tollvm.h"
#include "ir/irfunction.h"
#include "ir/irfuncty.h"
#include <algorithm>

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

llvm::Value *ABIRewrite::getRVal(Type *dty, LLValue *v) {
  return DtoLoad(DtoBitCast(getLVal(dty, v), DtoType(dty)->getPointerTo()));
}

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

LLValue *ABIRewrite::getAddressOf(DValue *v) {
  if (v->isLVal())
    return DtoLVal(v);
  return DtoAllocaDump(v, ".getAddressOf_dump");
}

LLValue *ABIRewrite::loadFromMemory(LLValue *address, LLType *asType,
                                    const char *name) {
  LLType *pointerType = address->getType();
  assert(pointerType->isPointerTy());
  LLType *pointeeType = pointerType->getPointerElementType();

  if (asType == pointeeType) {
    return DtoLoad(address, name);
  }

  if (getTypeStoreSize(asType) > getTypeAllocSize(pointeeType)) {
    // not enough allocated memory
    LLValue *paddedDump = DtoRawAlloca(asType, 0, ".loadFromMemory_paddedDump");
    DtoMemCpy(paddedDump, address,
              DtoConstSize_t(getTypeAllocSize(pointeeType)));
    return DtoLoad(paddedDump, name);
  }

  address = DtoBitCast(address, getPtrToType(asType),
                       ".loadFromMemory_bitCastAddress");
  return DtoLoad(address, name);
}

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

// A Homogeneous Floating-point Aggregate (HFA) is an ARM/AArch64 concept that
// consists of up to 4 of same floating point type.  D floats of same size are
// considered as same (e.g. ifloat and float are same).  It is the aggregate
// final data layout that matters so nested structs, unions, and sarrays can
// result in an HFA.
//
// simple HFAs: struct F1 {float f;}  struct D4 {double a,b,c,d;}
// interesting HFA: struct {F1[2] vals; float weight;}

namespace {
bool isNestedHFA(const TypeStruct *t, d_uns64 &floatSize, int &num,
                 uinteger_t adim) {
  // Used internally by isHFA() to check struct recursively for HFA-ness.
  // Return true if struct 't' is part of an HFA where 'floatSize' is sizeof
  // the float and 'num' is number of these floats so far.  On return, 'num'
  // is updated to the total number of floats in the HFA.  Set 'floatSize'
  // to 0 discover the sizeof the float.  When struct 't' is part of an
  // sarray, 'adim' is the dimension of that array, otherwise it is 1.
  VarDeclarations fields = t->sym->fields;

  // HFA can't contains an empty struct
  if (fields.dim == 0)
    return false;

  // Accumulate number of floats in HFA
  int n;

  // For unions, need to find field with most floats
  int maxn = num;

  for (size_t i = 0; i < fields.dim; ++i) {
    Type *field = fields[i]->type;

    // reset to initial num floats (all union fields are at offset 0)
    if (fields[i]->offset == 0)
      n = num;

    // reset dim to dimension of sarray we are in (will be 1 if not)
    uinteger_t dim = adim;

    // Field is an array.  Process the arrayof type and multiply dim by
    // array dim.  Note that empty arrays immediately exclude this struct
    // from HFA status.
    if (field->ty == Tsarray) {
      TypeSArray *array = (TypeSArray *)field;
      if (array->dim->toUInteger() == 0)
        return false;
      field = array->nextOf();
      dim *= array->dim->toUInteger();
    }

    if (field->ty == Tstruct) {
      if (!isNestedHFA((TypeStruct *)field, floatSize, n, dim))
        return false;
    } else if (field->isfloating()) {
      d_uns64 sz = field->size();
      n += dim;

      if (field->iscomplex()) {
        sz /= 2; // complex is 2 floats, adjust sz
        n += dim;
      }

      if (floatSize == 0) // discovered floatSize
        floatSize = sz;
      else if (sz != floatSize) // different float size, reject
        return false;

      //if (n > 4)
      //  return false; // too many floats for HFA, reject
    } else {
      return false; // reject all other types
    }

    if (n > maxn)
      maxn = n;
  }

  num = maxn;
  return true;
}
}

bool TargetABI::isHFA(TypeStruct *t, llvm::Type **rewriteType, const int maxFloats) {
  d_uns64 floatSize = 0;
  int num = 0;

  if (isNestedHFA(t, floatSize, num, 1)) {
    if (num <= maxFloats) {
      if (rewriteType) {
        llvm::Type *floatType = nullptr;
        switch (floatSize) {
          case 4:
            floatType = llvm::Type::getFloatTy(gIR->context());
            break;
          case 8:
            floatType = llvm::Type::getDoubleTy(gIR->context());
            break;
          case 16:
            floatType = llvm::Type::getFP128Ty(gIR->context());
            break;
          default:
            llvm_unreachable("Unexpected size for float type");
        }
        *rewriteType = LLArrayType::get(floatType, num);
      }
      return true;
    }
  }
  return false;
}

bool TargetABI::isAggregate(Type *t) {
  TY ty = t->toBasetype()->ty;
  // FIXME: dynamic arrays can currently not be rewritten as they are used
  //        by runtime functions, for which we don't apply the rewrites yet
  //        when calling them
  return ty == Tstruct || ty == Tsarray ||
         /*ty == Tarray ||*/ ty == Tdelegate || t->iscomplex();
}

bool TargetABI::isMagicCppStruct(Type *t) {
  t = t->toBasetype();
  if (t->ty != Tstruct) {
    return false;
  }

  Identifier *id = static_cast<TypeStruct *>(t)->sym->ident;
  return (id == Id::__c_long) || (id == Id::__c_ulong) ||
         (id == Id::__c_long_double);
}

namespace {
bool hasCtor(StructDeclaration *s) {
  if (s->ctor)
    return true;
  for (size_t i = 0; i < s->fields.dim; i++) {
    Type *tf = s->fields[i]->type->baseElemOf();
    if (tf->ty == Tstruct) {
      if (hasCtor(static_cast<TypeStruct *>(tf)->sym))
        return true;
    }
  }
  return false;
}
}

bool TargetABI::isPOD(Type *t, bool excludeStructsWithCtor) {
  t = t->toBasetype();
  if (t->ty != Tstruct)
    return true;
  StructDeclaration *sd = static_cast<TypeStruct *>(t)->sym;
  return sd->isPOD() && !(excludeStructsWithCtor && hasCtor(sd));
}

bool TargetABI::canRewriteAsInt(Type *t, bool include64bit) {
  auto size = t->toBasetype()->size();
  return size == 1 || size == 2 || size == 4 || (include64bit && size == 8);
}

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

void TargetABI::rewriteVarargs(IrFuncTy &fty,
                               std::vector<IrFuncTyArg *> &args) {
  for (auto arg : args) {
    if (!arg->byref) { // don't rewrite ByVal arguments
      rewriteArgument(fty, *arg);
    }
  }
}

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

LLValue *TargetABI::prepareVaStart(DLValue *ap) {
  // pass a i8* pointer to ap to LLVM's va_start intrinsic
  return DtoBitCast(DtoLVal(ap), getVoidPtrType());
}

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

void TargetABI::vaCopy(DLValue *dest, DValue *src) {
  LLValue *llDest = DtoLVal(dest);
  if (src->isLVal()) {
    DtoMemCpy(llDest, DtoLVal(src));
  } else {
    DtoStore(DtoRVal(src), llDest);
  }
}

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

LLValue *TargetABI::prepareVaArg(DLValue *ap) {
  // pass a i8* pointer to ap to LLVM's va_arg intrinsic
  return DtoBitCast(DtoLVal(ap), getVoidPtrType());
}

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

Type *TargetABI::vaListType() {
  // char* is used by default in druntime.
  return Type::tchar->pointerTo();
}

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

const char *TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty) {
  llvm_unreachable("Unknown Objective-C ABI");
}

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

// Some reasonable defaults for when we don't know what ABI to use.
struct UnknownTargetABI : TargetABI {
  bool returnInArg(TypeFunction *tf) override {
    if (tf->isref) {
      return false;
    }

    // Return structs and static arrays on the stack. The latter is needed
    // because otherwise LLVM tries to actually return the array in a number
    // of physical registers, which leads, depending on the target, to
    // either horrendous codegen or backend crashes.
    Type *rt = tf->next->toBasetype();
    return (rt->ty == Tstruct || rt->ty == Tsarray);
  }

  bool passByVal(Type *t) override { return t->toBasetype()->ty == Tstruct; }

  void rewriteFunctionType(TypeFunction *t, IrFuncTy &fty) override {
    // why?
  }
};

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

TargetABI *TargetABI::getTarget() {
  switch (global.params.targetTriple->getArch()) {
  case llvm::Triple::x86:
    return getX86TargetABI();
  case llvm::Triple::x86_64:
    if (global.params.targetTriple->isOSWindows()) {
      return getWin64TargetABI();
    } else {
      return getX86_64TargetABI();
    }
  case llvm::Triple::mips:
  case llvm::Triple::mipsel:
  case llvm::Triple::mips64:
  case llvm::Triple::mips64el:
    return getMIPS64TargetABI(global.params.is64bit);
  case llvm::Triple::ppc:
  case llvm::Triple::ppc64:
    return getPPCTargetABI(global.params.targetTriple->isArch64Bit());
  case llvm::Triple::ppc64le:
    return getPPC64LETargetABI();
#if LDC_LLVM_VER == 305
  case llvm::Triple::arm64:
  case llvm::Triple::arm64_be:
#endif
  case llvm::Triple::aarch64:
  case llvm::Triple::aarch64_be:
    return getAArch64TargetABI();
  case llvm::Triple::arm:
  case llvm::Triple::armeb:
  case llvm::Triple::thumb:
  case llvm::Triple::thumbeb:
    return getArmTargetABI();
  default:
    Logger::cout() << "WARNING: Unknown ABI, guessing...\n";
    return new UnknownTargetABI;
  }
}

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

// A simple ABI for LLVM intrinsics.
struct IntrinsicABI : TargetABI {
  RemoveStructPadding remove_padding;

  bool returnInArg(TypeFunction *tf) override { return false; }

  bool passByVal(Type *t) override { return false; }

  void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
    Type *ty = arg.type->toBasetype();
    if (ty->ty != Tstruct) {
      return;
    }
    // TODO: Check that no unions are passed in or returned.

    LLType *abiTy = DtoUnpaddedStructType(arg.type);

    if (abiTy && abiTy != arg.ltype) {
      arg.ltype = abiTy;
      arg.rewrite = &remove_padding;
    }
  }

  void rewriteFunctionType(TypeFunction *tf, IrFuncTy &fty) override {
    if (!fty.arg_sret) {
      Type *rt = fty.ret->type->toBasetype();
      if (rt->ty == Tstruct) {
        Logger::println("Intrinsic ABI: Transforming return type");
        rewriteArgument(fty, *fty.ret);
      }
    }

    Logger::println("Intrinsic ABI: Transforming arguments");
    LOG_SCOPE;

    for (auto arg : fty.args) {
      IF_LOG Logger::cout() << "Arg: " << arg->type->toChars() << '\n';

      // Arguments that are in memory are of no interest to us.
      if (arg->byref) {
        continue;
      }

      rewriteArgument(fty, *arg);

      IF_LOG Logger::cout() << "New arg type: " << *arg->ltype << '\n';
    }
  }
};

TargetABI *TargetABI::getIntrinsic() {
  static IntrinsicABI iabi;
  return &iabi;
}
