//===-- gen/abi.h - Target ABI description for IR generation ----*- C++ -*-===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// This interface is used by the IR generation code to accomodate any
// additional transformations necessary for the given target ABI (the direct
// LLVM IR representation for C structs unfortunately does not always lead to
// the right ABI, for example on x86_64).
//
//===----------------------------------------------------------------------===//

#pragma once

#include "dmd/globals.h"
#include "gen/dvalue.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/Support/CodeGen.h" // for UWTableKind
#include <vector>

class Type;
class TypeFunction;
class TypeStruct;
class TypeTuple;
struct IrFuncTy;
struct IrFuncTyArg;
class FuncDeclaration;

namespace llvm {
class Type;
class Value;
class FunctionType;
class Function;
}

/// Transforms function arguments and return values.
/// This is needed to implement certain ABI aspects which LLVM doesn't get
/// right by default.
struct ABIRewrite {
  virtual ~ABIRewrite() = default;

  /// Transforms the D argument to a suitable LL argument.
  virtual llvm::Value *put(DValue *v, bool isLValueExp, bool isLastArgExp) = 0;

  /// Transforms the LL parameter back and returns the address for the D
  /// parameter.
  virtual llvm::Value *getLVal(Type *dty, llvm::Value *v) = 0;

  /// Transforms the LL parameter back and returns the value for the D
  /// parameter.
  /// Defaults to loading the lvalue returned by getLVal().
  virtual llvm::Value *getRVal(Type *dty, llvm::Value *v);

  /// Returns the resulting LL type when transforming an argument of the
  /// specified D type.
  virtual llvm::Type *type(Type *t) = 0;

  /// Applies this rewrite to the specified argument, adapting it where
  /// necessary.
  virtual void applyTo(IrFuncTyArg &arg, llvm::Type *finalLType = nullptr);

protected:
  /***** Static Helpers *****/

  /// Returns the address of a D value, storing it to memory first if need be.
  static llvm::Value *getAddressOf(DValue *v);
};

// interface called by codegen
struct TargetABI {
public:
  virtual ~TargetABI() = default;

  /// Returns the ABI for the target we're compiling for
  static TargetABI *getTarget();

  /// Returns the ABI for intrinsics
  static TargetABI *getIntrinsic();

  /// Returns the LLVM calling convention to be used for the given D linkage
  /// type on the target. Defaults to the C calling convention.
  virtual llvm::CallingConv::ID callingConv(LINK) {
    return llvm::CallingConv::C;
  }
  // By default, enforce C calling convention for (non-typesafe) variadics,
  // otherwise forward to LINK overload.
  virtual llvm::CallingConv::ID callingConv(TypeFunction *tf, bool withThisPtr);
  // By default, forward to TypeFunction overload.
  virtual llvm::CallingConv::ID callingConv(FuncDeclaration *fdecl);

  /// Applies any rewrites that might be required to accurately reproduce the
  /// passed function name on LLVM given a specific calling convention.
  ///
  /// Using this function at a stage where the name could be user-visible is
  /// almost certainly a mistake; it is intended to e.g. prepend '\1' where
  /// disabling the LLVM-internal name mangling/postprocessing is required.
  virtual std::string mangleFunctionForLLVM(std::string name, LINK l) {
    return name;
  }

  /// Applies any rewrites that might be required to accurately reproduce the
  /// passed variable name on LLVM given a specific D linkage.
  ///
  /// Using this function at a stage where the name could be user-visible is
  /// almost certainly a mistake; it is intended to e.g. prepend '\1' where
  /// the LLVM-internal postprocessing of prepending a '_' must be disabled.
  virtual std::string mangleVariableForLLVM(std::string name, LINK l) {
    return name;
  }

  /// Returns the default unwind-table kind for all functions.
  /// Analogous to clang's ToolChain::getDefaultUnwindTableLevel().
  virtual llvm::UWTableKind defaultUnwindTableKind() {
    return llvm::UWTableKind::None;
  }

  void setUnwindTableKind(llvm::Function *fn);

  /// Returns true if the target is darwin-based.
  bool isDarwin() {
    return global.params.targetTriple->isOSDarwin();
  }

  /// Returns true if the D function uses sret (struct return).
  /// `needsThis` is true if the function type is for a non-static member
  /// function.
  ///
  /// A LL sret function doesn't really return a struct (in fact, it returns
  /// void); it merely just sets a struct which has been pre-allocated by the
  /// caller.
  /// The address is passed as additional function parameter using the StructRet
  /// attribute.
  virtual bool returnInArg(TypeFunction *tf, bool needsThis);

  /// Returns true if the specified parameter type (a POD) should be passed by
  /// ref for `in` params with -preview=in.
  virtual bool preferPassByRef(Type *t);

  /// Returns true if the D type is passed using the LLVM ByVal attribute.
  ///
  /// ByVal arguments are bitcopied to the callee's function parameters stack in
  /// memory.
  /// For the LL callee, a ByVal parameter is an implicit pointer to the
  /// bitcopy; the pointer is computed by LLVM and not passed as an explicit
  /// parameter.
  /// The LL caller needs to pass a pointer to the original argument (the memcpy
  /// source).
  virtual bool passByVal(TypeFunction *tf, Type *t);

  /// Returns true if the 'this' argument is to be passed before the 'sret'
  /// argument.
  virtual bool passThisBeforeSret(TypeFunction *tf) { return false; }

  /// Called to give ABI the chance to rewrite the types
  virtual void rewriteFunctionType(IrFuncTy &fty);
  virtual void rewriteVarargs(IrFuncTy &fty, std::vector<IrFuncTyArg *> &args);
  virtual void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg);

  /// Prepares a va_start intrinsic call by transforming the D argument (of type
  /// va_list) to a low-level value (of type i8*) to be passed to LLVM's
  /// va_start intrinsic.
  virtual llvm::Value *prepareVaStart(DLValue *ap);

  /// Implements the va_copy intrinsic.
  virtual void vaCopy(DLValue *dest, DValue *src);

  /// Prepares a va_arg intrinsic call by transforming the D argument (of type
  /// va_list) to a low-level value (of type i8*) to be passed to LLVM's
  /// va_arg intrinsic.
  virtual llvm::Value *prepareVaArg(DLValue *ap);

  /// Returns the D type to be used for va_list.
  ///
  /// Must match the alias in druntime.
  virtual Type *vaListType();

  /// Returns Objective-C message send function
  virtual const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall);

  /***** Static Helpers *****/

  /// Check if `t` is a Homogeneous Floating-point Aggregate (HFA) or
  /// Homogeneous Vector Aggregate (HVA). If so, optionally produce the
  /// rewriteType: an array of its fundamental type.
  static bool isHFVA(Type *t, int maxNumElements,
                     llvm::Type **hfvaType = nullptr);

  /// Check if `t` is a Homogeneous Vector Aggregate (HVA). If so, optionally
  /// produce the rewriteType: an array of its fundamental type.
  static bool isHVA(Type *t, int maxNumElements,
                    llvm::Type **hvaType = nullptr);

  /// Uses the front-end toArgTypes* machinery and returns an appropriate LL
  /// type if arguments of the specified D type are to be rewritten in order to
  /// be passed correctly in registers.
  static llvm::Type *getRewrittenArgType(Type *t);

protected:

  /// Returns true if the D type is an aggregate:
  /// * struct
  /// * static/dynamic array
  /// * delegate
  /// * complex number
  static bool isAggregate(Type *t);

  /// Returns true if the D type is a Plain-Old-Datatype, optionally excluding
  /// structs with constructors from that definition.
  static bool isPOD(Type *t, bool excludeStructsWithCtor = false);

  /// Returns true if the D type can be bit-cast to an integer of the same size.
  static bool canRewriteAsInt(Type *t, bool include64bit = true);

  /// Returns true if the D function type uses extern(D) linkage *and* isn't a
  /// D-style variadic function.
  static bool isExternD(TypeFunction *tf);

  /// Returns the type tuple produced by the front-end's toArgTypes* machinery.
  static TypeTuple *getArgTypes(Type *t);

  static llvm::Type *getRewrittenArgType(Type *t, TypeTuple *argTypes);

  static bool skipReturnValueRewrite(IrFuncTy &fty);
};
