//===- AMDGPU.cpp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "InputFiles.h"
#include "Symbols.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"

using namespace llvm;
using namespace llvm::object;
using namespace llvm::support::endian;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;

namespace {
class AMDGPU final : public TargetInfo {
private:
  uint32_t calcEFlagsV3() const;
  uint32_t calcEFlagsV4() const;

public:
  AMDGPU();
  uint32_t calcEFlags() const override;
  void relocate(uint8_t *loc, const Relocation &rel,
                uint64_t val) const override;
  RelExpr getRelExpr(RelType type, const Symbol &s,
                     const uint8_t *loc) const override;
  RelType getDynRel(RelType type) const override;
  int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
};
} // namespace

AMDGPU::AMDGPU() {
  relativeRel = R_AMDGPU_RELATIVE64;
  gotRel = R_AMDGPU_ABS64;
  symbolicRel = R_AMDGPU_ABS64;
}

static uint32_t getEFlags(InputFile *file) {
  return cast<ObjFile<ELF64LE>>(file)->getObj().getHeader().e_flags;
}

uint32_t AMDGPU::calcEFlagsV3() const {
  uint32_t ret = getEFlags(ctx.objectFiles[0]);

  // Verify that all input files have the same e_flags.
  for (InputFile *f : ArrayRef(ctx.objectFiles).slice(1)) {
    if (ret == getEFlags(f))
      continue;
    error("incompatible e_flags: " + toString(f));
    return 0;
  }
  return ret;
}

uint32_t AMDGPU::calcEFlagsV4() const {
  uint32_t retMach = getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_MACH;
  uint32_t retXnack =
      getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_FEATURE_XNACK_V4;
  uint32_t retSramEcc =
      getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_FEATURE_SRAMECC_V4;

  // Verify that all input files have compatible e_flags (same mach, all
  // features in the same category are either ANY, ANY and ON, or ANY and OFF).
  for (InputFile *f : ArrayRef(ctx.objectFiles).slice(1)) {
    if (retMach != (getEFlags(f) & EF_AMDGPU_MACH)) {
      error("incompatible mach: " + toString(f));
      return 0;
    }

    if (retXnack == EF_AMDGPU_FEATURE_XNACK_UNSUPPORTED_V4 ||
        (retXnack != EF_AMDGPU_FEATURE_XNACK_ANY_V4 &&
            (getEFlags(f) & EF_AMDGPU_FEATURE_XNACK_V4)
                != EF_AMDGPU_FEATURE_XNACK_ANY_V4)) {
      if (retXnack != (getEFlags(f) & EF_AMDGPU_FEATURE_XNACK_V4)) {
        error("incompatible xnack: " + toString(f));
        return 0;
      }
    } else {
      if (retXnack == EF_AMDGPU_FEATURE_XNACK_ANY_V4)
        retXnack = getEFlags(f) & EF_AMDGPU_FEATURE_XNACK_V4;
    }

    if (retSramEcc == EF_AMDGPU_FEATURE_SRAMECC_UNSUPPORTED_V4 ||
        (retSramEcc != EF_AMDGPU_FEATURE_SRAMECC_ANY_V4 &&
            (getEFlags(f) & EF_AMDGPU_FEATURE_SRAMECC_V4) !=
                EF_AMDGPU_FEATURE_SRAMECC_ANY_V4)) {
      if (retSramEcc != (getEFlags(f) & EF_AMDGPU_FEATURE_SRAMECC_V4)) {
        error("incompatible sramecc: " + toString(f));
        return 0;
      }
    } else {
      if (retSramEcc == EF_AMDGPU_FEATURE_SRAMECC_ANY_V4)
        retSramEcc = getEFlags(f) & EF_AMDGPU_FEATURE_SRAMECC_V4;
    }
  }

  return retMach | retXnack | retSramEcc;
}

uint32_t AMDGPU::calcEFlags() const {
  if (ctx.objectFiles.empty())
    return 0;

  uint8_t abiVersion = cast<ObjFile<ELF64LE>>(ctx.objectFiles[0])
                           ->getObj()
                           .getHeader()
                           .e_ident[EI_ABIVERSION];
  switch (abiVersion) {
  case ELFABIVERSION_AMDGPU_HSA_V2:
  case ELFABIVERSION_AMDGPU_HSA_V3:
    return calcEFlagsV3();
  case ELFABIVERSION_AMDGPU_HSA_V4:
  case ELFABIVERSION_AMDGPU_HSA_V5:
    return calcEFlagsV4();
  default:
    error("unknown abi version: " + Twine(abiVersion));
    return 0;
  }
}

void AMDGPU::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
  switch (rel.type) {
  case R_AMDGPU_ABS32:
  case R_AMDGPU_GOTPCREL:
  case R_AMDGPU_GOTPCREL32_LO:
  case R_AMDGPU_REL32:
  case R_AMDGPU_REL32_LO:
    write32le(loc, val);
    break;
  case R_AMDGPU_ABS64:
  case R_AMDGPU_REL64:
    write64le(loc, val);
    break;
  case R_AMDGPU_GOTPCREL32_HI:
  case R_AMDGPU_REL32_HI:
    write32le(loc, val >> 32);
    break;
  case R_AMDGPU_REL16: {
    int64_t simm = (static_cast<int64_t>(val) - 4) / 4;
    checkInt(loc, simm, 16, rel);
    write16le(loc, simm);
    break;
  }
  default:
    llvm_unreachable("unknown relocation");
  }
}

RelExpr AMDGPU::getRelExpr(RelType type, const Symbol &s,
                           const uint8_t *loc) const {
  switch (type) {
  case R_AMDGPU_ABS32:
  case R_AMDGPU_ABS64:
    return R_ABS;
  case R_AMDGPU_REL32:
  case R_AMDGPU_REL32_LO:
  case R_AMDGPU_REL32_HI:
  case R_AMDGPU_REL64:
  case R_AMDGPU_REL16:
    return R_PC;
  case R_AMDGPU_GOTPCREL:
  case R_AMDGPU_GOTPCREL32_LO:
  case R_AMDGPU_GOTPCREL32_HI:
    return R_GOT_PC;
  default:
    error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
          ") against symbol " + toString(s));
    return R_NONE;
  }
}

RelType AMDGPU::getDynRel(RelType type) const {
  if (type == R_AMDGPU_ABS64)
    return type;
  return R_AMDGPU_NONE;
}

int64_t AMDGPU::getImplicitAddend(const uint8_t *buf, RelType type) const {
  switch (type) {
  case R_AMDGPU_NONE:
    return 0;
  case R_AMDGPU_ABS64:
  case R_AMDGPU_RELATIVE64:
    return read64(buf);
  default:
    internalLinkerError(getErrorLocation(buf),
                        "cannot read addend for relocation " + toString(type));
    return 0;
  }
}

TargetInfo *elf::getAMDGPUTargetInfo() {
  static AMDGPU target;
  return &target;
}