//===-- abi-win64.cpp -----------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// extern(C) implements the C calling convention for x86-64 on Windows, see
// http://msdn.microsoft.com/en-us/library/7kcdt6fy%28v=vs.110%29.aspx
//
//===----------------------------------------------------------------------===//

#include "gen/abi-win64.h"

#include "dmd/mtype.h"
#include "dmd/declaration.h"
#include "dmd/aggregate.h"
#include "gen/abi.h"
#include "gen/abi-generic.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 <cassert>
#include <string>
#include <utility>

struct Win64TargetABI : TargetABI {
private:
  const bool isMSVC;
  IndirectByvalRewrite byvalRewrite;
  IntegerRewrite integerRewrite;

  bool isX87(Type *t) const {
    return !isMSVC // 64-bit reals for MSVC targets
           && (t->ty == Tfloat80 || t->ty == Timaginary80);
  }

  bool passPointerToHiddenCopy(Type *t, bool isReturnValue, LINK linkage) const {
    // 80-bit real/ireal:
    // * returned on the x87 stack (for DMD inline asm compliance and what LLVM
    //   defaults to)
    // * passed by ref to hidden copy
    if (isX87(t))
      return !isReturnValue;

    const bool isMSVCpp = isMSVC && linkage == LINKcpp;

    if (isReturnValue) {
      // MSVC++ enforces sret for non-PODs, incl. aggregates with ctors
      // (which by itself doesn't make it a non-POD for D).
      const bool excludeStructsWithCtor = isMSVCpp;
      if (!isPOD(t, excludeStructsWithCtor))
        return true;
    } else {
      // Contrary to return values, POD-ness is ignored for arguments.
      // MSVC++ seems to enforce by-ref passing only for aggregates with
      // copy ctor (incl. `= delete`).
      if (isMSVCpp && t->ty == Tstruct) {
        StructDeclaration *sd = static_cast<TypeStruct *>(t)->sym;
        assert(sd);
        if (sd->postblit || sd->hasCopyCtor)
          return true;
      }
    }

    // Remaining aggregates which can NOT be rewritten as integers (size > 8
    // bytes or not a power of 2) are passed by ref to hidden copy.
    return isAggregate(t) && !canRewriteAsInt(t);
  }

public:
  Win64TargetABI()
      : isMSVC(global.params.targetTriple->isWindowsMSVCEnvironment()) {}

  llvm::CallingConv::ID callingConv(LINK l, TypeFunction *tf = nullptr,
                                    FuncDeclaration *fd = nullptr) override {
    // Use the vector calling convention for extern(D) (except for variadics)
    // => let LLVM pass vectors in registers instead of passing a ref to a
    // hidden copy (both cases handled by LLVM automatically for LL vectors
    // which we don't rewrite).
    return l == LINKd && !(tf && tf->parameterList.varargs == VARARGvariadic)
               ? llvm::CallingConv::X86_VectorCall
               : llvm::CallingConv::C;
  }

  std::string mangleFunctionForLLVM(std::string name, LINK l) override {
    if (l == LINKd) {
      // Prepend a 0x1 byte to prevent LLVM from applying vectorcall/stdcall
      // mangling: _D… => _D…@<paramssize>
      name.insert(name.begin(), '\1');
    }
    return name;
  }

  bool returnInArg(TypeFunction *tf, bool needsThis) override {
    if (tf->isref())
      return false;

    Type *rt = tf->next->toBasetype();

    // for non-static member functions, MSVC++ enforces sret for all structs
    if (isMSVC && tf->linkage == LINKcpp && needsThis && rt->ty == Tstruct) {
      return true;
    }

    // * all POD types of a power-of-2 size <= 8 bytes (incl. 2x32-bit cfloat)
    //   are returned in a register (RAX, or XMM0 for single float/ifloat/
    //   double/idouble)
    // * 80-bit real/ireal are returned on the x87 stack
    // * all other types are returned via sret
    return passPointerToHiddenCopy(rt, /*isReturnValue=*/true, tf->linkage);
  }

  bool passByVal(TypeFunction *, Type *) override {
    // LLVM's byval attribute is not compatible with the Win64 ABI
    return false;
  }

  bool passThisBeforeSret(TypeFunction *tf) override {
    // required by MSVC++
    return tf->linkage == LINKcpp;
  }

  void rewriteFunctionType(IrFuncTy &fty) override {
    // return value
    const auto rt = fty.ret->type->toBasetype();
    if (!fty.ret->byref && rt->ty != Tvoid) {
      rewrite(fty, *fty.ret, /*isReturnValue=*/true);
    }

    // explicit parameters
    for (auto arg : fty.args) {
      if (!arg->byref) {
        rewriteArgument(fty, *arg);
      }
    }
  }

  void rewriteVarargs(IrFuncTy &fty,
                      std::vector<IrFuncTyArg *> &args) override {
    for (auto arg : args) {
      rewriteArgument(fty, *arg);

      if (arg->rewrite == &byvalRewrite) {
        // mark the vararg as being passed byref to prevent DtoCall() from
        // passing the dereferenced pointer, i.e., just pass the pointer
        arg->byref = true;
      }
    }
  }

  void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
    rewrite(fty, arg, /*isReturnValue=*/false);
  }

  void rewrite(IrFuncTy &fty, IrFuncTyArg &arg, bool isReturnValue) {
    Type *t = arg.type->toBasetype();
    LLType *originalLType = arg.ltype;

    if (passPointerToHiddenCopy(t, isReturnValue, fty.type->linkage)) {
      // the caller allocates a hidden copy and passes a pointer to that copy
      byvalRewrite.applyTo(arg);
    } else if (isAggregate(t) && canRewriteAsInt(t)) {
      integerRewrite.applyToIfNotObsolete(arg);
    }

    if (arg.rewrite) {
      IF_LOG {
        Logger::println("Rewriting argument type %s", t->toChars());
        LOG_SCOPE;
        Logger::cout() << *originalLType << " => " << *arg.ltype << '\n';
      }
    }
  }
};

// The public getter for abi.cpp
TargetABI *getWin64TargetABI() { return new Win64TargetABI; }
