//===-- 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 "mtype.h"
#include "declaration.h"
#include "aggregate.h"

#include "gen/irstate.h"
#include "gen/llvm.h"
#include "gen/tollvm.h"
#include "gen/logger.h"
#include "gen/dvalue.h"
#include "gen/llvmhelpers.h"
#include "gen/abi.h"
#include "gen/abi-win64.h"
#include "gen/abi-generic.h"
#include "ir/irfunction.h"

#include <cassert>
#include <string>
#include <utility>

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

  bool realIs80bits() const { return !isMSVC; }

  // Returns true if the D type is passed byval (the callee getting a pointer
  // to a dedicated hidden copy).
  bool isPassedWithByvalSemantics(Type *t) const {
    return
        // * aggregates which can NOT be rewritten as integers
        //   (size > 8 bytes or not a power of 2)
        (isAggregate(t) && !canRewriteAsInt(t)) ||
        // * 80-bit real and ireal
        (realIs80bits() && (t->ty == Tfloat80 || t->ty == Timaginary80));
  }

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

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

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

    // let LLVM return
    // * magic C++ structs directly as LL aggregate with a single i32/double
    //   element, which LLVM handles as if it was a scalar
    // * 80-bit real/ireal on the x87 stack, for DMD inline asm compliance
    if (isMagicCppStruct(rt) ||
        (realIs80bits() && (rt->ty == Tfloat80 || rt->ty == Timaginary80)))
      return false;

    // force sret for non-POD structs
    const bool excludeStructsWithCtor = (isMSVC && tf->linkage == LINKcpp);
    if (!isPOD(rt, excludeStructsWithCtor))
      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)
    // * all other types are returned via sret
    return isPassedWithByvalSemantics(rt);
  }

  bool passByVal(Type *t) 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(TypeFunction *tf, IrFuncTy &fty) override {
    // return value
    if (!fty.ret->byref && fty.ret->type->toBasetype()->ty != Tvoid) {
      rewriteArgument(fty, *fty.ret);
    }

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

    // extern(D): reverse parameter order for non variadics, for DMD-compliance
    if (tf->linkage == LINKd && tf->varargs != 1 && fty.args.size() > 1) {
      fty.reverseParams = true;
    }
  }

  void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
    Type *t = arg.type->toBasetype();

    if (isMagicCppStruct(t)) {
      // pass directly as LL aggregate
    } else if (isPassedWithByvalSemantics(t)) {
      // these types are passed byval:
      // the caller allocates a copy and then passes a pointer to the copy
      arg.rewrite = &byvalRewrite;

      // the copy is treated as a local variable of the callee
      // hence add the NoAlias and NoCapture attributes
      arg.attrs.clear()
          .add(LLAttribute::NoAlias)
          .add(LLAttribute::NoCapture)
          .addAlignment(byvalRewrite.alignment(arg.type));
    } else if (isAggregate(t) && canRewriteAsInt(t) &&
               !IntegerRewrite::isObsoleteFor(arg.ltype)) {
      arg.rewrite = &integerRewrite;
    }

    if (arg.rewrite) {
      LLType *originalLType = arg.ltype;
      arg.ltype = arg.rewrite->type(arg.type, arg.ltype);

      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; }
