//===-- gen/abi-loongarch64.cpp - LoongArch64 ABI description -----------*- C++
//-*-===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// ABI spec:
// https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc
//
//===----------------------------------------------------------------------===//

#include "gen/abi/abi.h"
#include "gen/abi/generic.h"
#include "gen/dvalue.h"
#include "gen/irstate.h"
#include "gen/llvmhelpers.h"
#include "gen/tollvm.h"

using namespace dmd;

namespace {
struct Integer2Rewrite : BaseBitcastABIRewrite {
  LLType *type(Type *t) override {
    return LLStructType::get(gIR->context(),
                             {DtoType(Type::tint64), DtoType(Type::tint64)});
  }
};

struct FlattenedFields {
  Type *fields[2];
  int length = 0; // use -1 to represent "no need to rewrite" condition
};

FlattenedFields visitStructFields(Type *ty, unsigned baseOffset) {
  // recursively visit a POD struct to flatten it
  FlattenedFields result;
  if (auto ts = ty->isTypeStruct()) {
    for (auto fi : ts->sym->fields) {
      auto sub =
          visitStructFields(fi->type->toBasetype(), baseOffset + fi->offset);
      if (sub.length == -1 || result.length + sub.length > 2) {
        result.length = -1;
        return result;
      }
      for (unsigned i = 0; i < (unsigned)sub.length; ++i) {
        result.fields[result.length++] = sub.fields[i];
      }
    }
    return result;
  }
  switch (ty->ty) {
  case TY::Tcomplex32: // treat it as {float32, float32}
    result.fields[0] = pointerTo(Type::tfloat32);
    result.fields[1] = pointerTo(Type::tfloat32);
    result.length = 2;
    break;
  case TY::Tcomplex64: // treat it as {float64, float64}
    result.fields[0] = pointerTo(Type::tfloat64);
    result.fields[1] = pointerTo(Type::tfloat64);
    result.length = 2;
    break;
  default:
    if (size(ty) > 8) {
      // field larger than GRLEN and FRLEN
      result.length = -1;
      break;
    }
    result.fields[0] = ty;
    result.length = 1;
    break;
  }
  return result;
}

struct HardfloatRewrite : BaseBitcastABIRewrite {
  LLType *type(Type *ty, const FlattenedFields &flat) {
    if (flat.length == 1) {
      return LLStructType::get(gIR->context(), {DtoType(flat.fields[0])},
                               false);
    }
    assert(flat.length == 2);
    LLType *t[2];
    for (unsigned i = 0; i < 2; ++i) {
      t[i] =
          flat.fields[i]->isFloating()
              ? DtoType(flat.fields[i])
              : LLIntegerType::get(gIR->context(), size(flat.fields[i]) * 8);
    }
    return LLStructType::get(gIR->context(), {t[0], t[1]}, false);
  }
  LLType *type(Type *ty) override {
    return type(ty, visitStructFields(ty->toBasetype(), 0));
  }
};
} // anonymous namespace

struct LoongArch64TargetABI : TargetABI {
private:
  HardfloatRewrite hardfloatRewrite;
  Integer2Rewrite integer2Rewrite;
  IntegerRewrite integerRewrite;

  bool requireHardfloatRewrite(Type *ty) {
    if (!isPOD(ty) || !ty->toBasetype()->isTypeStruct())
      return false;
    auto result = visitStructFields(ty->toBasetype(), 0);
    if (result.length <= 0)
      return false;
    if (result.length == 1)
      return result.fields[0]->isFloating();
    return result.fields[0]->isFloating() || result.fields[1]->isFloating();
  }

public:
  bool passByVal(TypeFunction *, Type *t) override {
    return isPOD(t) && size(t) > 16;
  }

  void rewriteVarargs(IrFuncTy &fty,
                      std::vector<IrFuncTyArg *> &args) override {
    for (auto arg : args) {
      if (!arg->byref)
        rewriteArgument(fty, *arg, /*isVararg=*/true);
    }
  }

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

  void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg, bool isVararg) {
    TargetABI::rewriteArgument(fty, arg);
    if (arg.rewrite)
      return;

    if (!isVararg && requireHardfloatRewrite(arg.type)) {
      hardfloatRewrite.applyTo(arg);
      return;
    }

    Type *ty = arg.type->toBasetype();
    if (ty->isIntegral() && (ty->ty == TY::Tint32 || ty->ty == TY::Tuns32 ||
                             ty->ty == TY::Tdchar)) {
      // In the LP64D ABI, both int32 and unsigned int32 are stored in
      // general-purpose registers as proper sign extensions of their
      // 32-bit values. So, the native ABI function's int32 arguments and
      // return values should have the `signext` attribute.
      // C example: https://godbolt.org/z/vcjErxj76
      arg.attrs.addAttribute(LLAttribute::SExt);
    } else if (isAggregate(ty) && size(ty) && size(ty) <= 16) {
      if (size(ty) > 8 && DtoAlignment(ty) < 16) {
        // pass the aggregate as {int64, int64} to avoid wrong alignment
        integer2Rewrite.applyToIfNotObsolete(arg);
      } else {
        integerRewrite.applyToIfNotObsolete(arg);
      }
    }
  }
};

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