//===- lib/ReaderWriter/ELF/Mips/MipsRelocationHandler.cpp ----------------===//
//
//                             The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "MipsLinkingContext.h"
#include "MipsRelocationHandler.h"
#include "MipsTargetLayout.h"
#include "llvm/Support/Format.h"

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

namespace {
enum class CrossJumpMode {
  None,      // Not a jump or non-isa-cross jump
  ToRegular, // cross isa jump to regular symbol
  ToMicro    // cross isa jump to microMips symbol
};

typedef std::function<std::error_code(int64_t, bool)> OverflowChecker;

static std::error_code dummyCheck(int64_t, bool) {
  return std::error_code();
}

template <int BITS> static std::error_code signedCheck(int64_t res, bool) {
  if (llvm::isInt<BITS>(res))
    return std::error_code();
  return make_out_of_range_reloc_error();
}

template <int BITS>
static std::error_code gpDispCheck(int64_t res, bool isGpDisp) {
  if (!isGpDisp || llvm::isInt<BITS>(res))
    return std::error_code();
  return make_out_of_range_reloc_error();
}

struct MipsRelocationParams {
  uint8_t _size;  // Relocations's size in bytes
  uint64_t _mask; // Read/write mask of relocation
  uint8_t _shift; // Relocation's addendum left shift size
  bool _shuffle;  // Relocation's addendum/result needs to be shuffled
  OverflowChecker _overflow; // Check the relocation result
};

template <class ELFT> class RelocationHandler : public TargetRelocationHandler {
public:
  RelocationHandler(MipsLinkingContext &ctx, MipsTargetLayout<ELFT> &layout)
      : _ctx(ctx), _targetLayout(layout) {}

  std::error_code applyRelocation(ELFWriter &writer,
                                  llvm::FileOutputBuffer &buf,
                                  const AtomLayout &atom,
                                  const Reference &ref) const override;

private:
  MipsLinkingContext &_ctx;
  MipsTargetLayout<ELFT> &_targetLayout;
};
}

static MipsRelocationParams getRelocationParams(uint32_t rType) {
  switch (rType) {
  case R_MIPS_NONE:
    return {4, 0x0, 0, false, dummyCheck};
  case R_MIPS_64:
  case R_MIPS_SUB:
    return {8, 0xffffffffffffffffull, 0, false, dummyCheck};
  case R_MICROMIPS_SUB:
    return {8, 0xffffffffffffffffull, 0, true, dummyCheck};
  case R_MIPS_32:
  case R_MIPS_GPREL32:
  case R_MIPS_REL32:
  case R_MIPS_PC32:
  case R_MIPS_EH:
    return {4, 0xffffffff, 0, false, dummyCheck};
  case LLD_R_MIPS_32_HI16:
    return {4, 0xffff0000, 0, false, dummyCheck};
  case LLD_R_MIPS_64_HI16:
    return {8, 0xffffffffffff0000ull, 0, false, dummyCheck};
  case R_MIPS_26:
  case LLD_R_MIPS_GLOBAL_26:
    return {4, 0x3ffffff, 2, false, dummyCheck};
  case R_MIPS_PC16:
    return {4, 0xffff, 2, false, signedCheck<18>};
  case R_MIPS_PC18_S3:
    return {4, 0x3ffff, 3, false, signedCheck<21>};
  case R_MIPS_PC19_S2:
    return {4, 0x7ffff, 2, false, signedCheck<21>};
  case R_MIPS_PC21_S2:
    return {4, 0x1fffff, 2, false, signedCheck<23>};
  case R_MIPS_PC26_S2:
    return {4, 0x3ffffff, 2, false, signedCheck<28>};
  case R_MIPS_HI16:
    return {4, 0xffff, 0, false, gpDispCheck<16>};
  case R_MIPS_LO16:
  case R_MIPS_HIGHER:
  case R_MIPS_HIGHEST:
    return {4, 0xffff, 0, false, dummyCheck};
  case R_MIPS_16:
  case R_MIPS_PCHI16:
  case R_MIPS_PCLO16:
  case R_MIPS_GOT16:
  case R_MIPS_CALL16:
  case R_MIPS_GOT_DISP:
  case R_MIPS_GOT_PAGE:
  case R_MIPS_GOT_OFST:
  case R_MIPS_GPREL16:
  case R_MIPS_TLS_GD:
  case R_MIPS_TLS_LDM:
  case R_MIPS_TLS_GOTTPREL:
  case R_MIPS_LITERAL:
    return {4, 0xffff, 0, false, signedCheck<16>};
  case R_MIPS_GOT_HI16:
  case R_MIPS_GOT_LO16:
  case R_MIPS_CALL_HI16:
  case R_MIPS_CALL_LO16:
  case R_MIPS_TLS_DTPREL_HI16:
  case R_MIPS_TLS_DTPREL_LO16:
  case R_MIPS_TLS_TPREL_HI16:
  case R_MIPS_TLS_TPREL_LO16:
    return {4, 0xffff, 0, false, dummyCheck};
  case R_MICROMIPS_GPREL16:
  case R_MICROMIPS_LITERAL:
    return {4, 0xffff, 0, true, signedCheck<16>};
  case R_MICROMIPS_GPREL7_S2:
    return {4, 0x7f, 2, false, signedCheck<9>};
  case R_MICROMIPS_GOT_HI16:
  case R_MICROMIPS_GOT_LO16:
  case R_MICROMIPS_CALL_HI16:
  case R_MICROMIPS_CALL_LO16:
  case R_MICROMIPS_TLS_DTPREL_HI16:
  case R_MICROMIPS_TLS_DTPREL_LO16:
  case R_MICROMIPS_TLS_TPREL_HI16:
  case R_MICROMIPS_TLS_TPREL_LO16:
    return {4, 0xffff, 0, true, dummyCheck};
  case R_MICROMIPS_26_S1:
  case LLD_R_MICROMIPS_GLOBAL_26_S1:
    return {4, 0x3ffffff, 1, true, dummyCheck};
  case R_MICROMIPS_HI16:
    return {4, 0xffff, 0, true, gpDispCheck<16>};
  case R_MICROMIPS_LO16:
  case R_MICROMIPS_HI0_LO16:
  case R_MICROMIPS_HIGHER:
  case R_MICROMIPS_HIGHEST:
    return {4, 0xffff, 0, true, dummyCheck};
  case R_MICROMIPS_PC16_S1:
    return {4, 0xffff, 1, true, signedCheck<17>};
  case R_MICROMIPS_PC7_S1:
    return {4, 0x7f, 1, false, signedCheck<8>};
  case R_MICROMIPS_PC10_S1:
    return {4, 0x3ff, 1, false, signedCheck<11>};
  case R_MICROMIPS_PC23_S2:
    return {4, 0x7fffff, 2, true, signedCheck<25>};
  case R_MICROMIPS_PC18_S3:
    return {4, 0x3ffff, 3, true, signedCheck<21>};
  case R_MICROMIPS_PC19_S2:
    return {4, 0x7ffff, 2, true, signedCheck<21>};
  case R_MICROMIPS_PC21_S2:
    return {4, 0x1fffff, 2, true, signedCheck<23>};
  case R_MICROMIPS_PC26_S2:
    return {4, 0x3ffffff, 2, true, signedCheck<28>};
  case R_MICROMIPS_GOT16:
  case R_MICROMIPS_CALL16:
  case R_MICROMIPS_TLS_GD:
  case R_MICROMIPS_TLS_LDM:
  case R_MICROMIPS_TLS_GOTTPREL:
  case R_MICROMIPS_GOT_DISP:
  case R_MICROMIPS_GOT_PAGE:
  case R_MICROMIPS_GOT_OFST:
    return {4, 0xffff, 0, true, signedCheck<16>};
  case R_MIPS_JALR:
    return {4, 0x0, 0, false, dummyCheck};
  case R_MICROMIPS_JALR:
    return {4, 0x0, 0, true, dummyCheck};
  case R_MIPS_JUMP_SLOT:
  case R_MIPS_COPY:
  case R_MIPS_TLS_DTPMOD32:
  case R_MIPS_TLS_DTPREL32:
  case R_MIPS_TLS_TPREL32:
    return {4, 0xffffffff, 0, false, dummyCheck};
  case R_MIPS_TLS_DTPMOD64:
  case R_MIPS_TLS_DTPREL64:
  case R_MIPS_TLS_TPREL64:
    return {8, 0xffffffffffffffffull, 0, false, dummyCheck};
  case LLD_R_MIPS_GLOBAL_GOT:
  case LLD_R_MIPS_STO_PLT:
    // Do nothing.
    return {4, 0x0, 0, false, dummyCheck};
  default:
    llvm_unreachable("Unknown relocation");
  }
}

static int64_t getHi16(int64_t value) {
  return ((value + 0x8000) >> 16) & 0xffff;
}

static int64_t getHigher16(int64_t value) {
  return ((value + 0x80008000ull) >> 32) & 0xffff;
}

static int64_t getHighest16(int64_t value) {
  return ((value + 0x800080008000ull) >> 48) & 0xffff;
}

static int64_t maskLow16(int64_t value) {
  return (value + 0x8000) & ~0xffff;
}

/// \brief R_MIPS_26, R_MICROMIPS_26_S1
/// local   : ((A | ((P + 4) & 0x3F000000)) + S) >> 2
static int32_t reloc26loc(uint64_t P, uint64_t S, int32_t A, uint32_t shift) {
  return (A | ((P + 4) & (0xfc000000 << shift))) + S;
}

/// \brief LLD_R_MIPS_GLOBAL_26, LLD_R_MICROMIPS_GLOBAL_26_S1
/// external: (sign-extend(A) + S) >> 2
static int32_t reloc26ext(uint64_t S, int32_t A, uint32_t shift) {
  A = shift == 1 ? llvm::SignExtend32<27>(A) : llvm::SignExtend32<28>(A);
  return A + S;
}

/// \brief R_MIPS_HI16, R_MIPS_TLS_DTPREL_HI16, R_MIPS_TLS_TPREL_HI16,
/// R_MICROMIPS_HI16, R_MICROMIPS_TLS_DTPREL_HI16, R_MICROMIPS_TLS_TPREL_HI16
/// local/external: hi16 (AHL + S) - (short)(AHL + S) (truncate)
/// _gp_disp      : hi16 (AHL + GP - P) - (short)(AHL + GP - P) (verify)
static int32_t relocHi16(uint64_t P, uint64_t S, int64_t AHL, bool isGPDisp) {
  return getHi16(isGPDisp ? AHL + S - P : AHL + S);
}

/// \brief R_MIPS_LO16, R_MIPS_TLS_DTPREL_LO16, R_MIPS_TLS_TPREL_LO16,
/// R_MICROMIPS_LO16, R_MICROMIPS_HI0_LO16,
/// R_MICROMIPS_TLS_DTPREL_LO16, R_MICROMIPS_TLS_TPREL_LO16
/// local/external: lo16 AHL + S (truncate)
/// _gp_disp      : lo16 AHL + GP - P + 4 (verify)
static int32_t relocLo16(uint64_t P, uint64_t S, int64_t AHL, bool isGPDisp,
                         bool micro) {
  return isGPDisp ? AHL + S - P + (micro ? 3 : 4) : AHL + S;
}

/// R_MIPS_GOT_OFST, R_MICROMIPS_GOT_OFST
/// rel16 offset of (S+A) from the page pointer (verify)
static int32_t relocGOTOfst(uint64_t S, int64_t A) {
  int64_t page = maskLow16(S + A);
  return S + A - page;
}

/// \brief R_MIPS_PC16
/// local/external: (S + A - P) >> 2
static ErrorOr<int64_t> relocPc16(uint64_t P, uint64_t S, int64_t A) {
  A = llvm::SignExtend32<18>(A);
  if ((S + A) & 3)
    return make_unaligned_range_reloc_error();
  return S + A - P;
}

/// \brief R_MIPS_PC18_S3, R_MICROMIPS_PC18_S3
/// local/external: (S + A - P) >> 3 (P with cleared 3 less significant bits)
static ErrorOr<int64_t> relocPc18(uint64_t P, uint64_t S, int64_t A) {
  A = llvm::SignExtend32<21>(A);
  if ((S + A) & 6)
    return make_unaligned_range_reloc_error();
  return S + A - ((P | 7) ^ 7);
}

/// \brief R_MIPS_PC19_S2, R_MICROMIPS_PC19_S2
/// local/external: (S + A - P) >> 2
static ErrorOr<int64_t> relocPc19(uint64_t P, uint64_t S, int64_t A) {
  A = llvm::SignExtend32<21>(A);
  if ((S + A) & 2)
    return make_unaligned_range_reloc_error();
  return S + A - P;
}

/// \brief R_MIPS_PC21_S2, R_MICROMIPS_PC21_S2
/// local/external: (S + A - P) >> 2
static ErrorOr<int64_t> relocPc21(uint64_t P, uint64_t S, int64_t A) {
  A = llvm::SignExtend32<23>(A);
  if ((S + A) & 2)
    return make_unaligned_range_reloc_error();
  return S + A - P;
}

/// \brief R_MIPS_PC26_S2, R_MICROMIPS_PC26_S2
/// local/external: (S + A - P) >> 2
static ErrorOr<int64_t> relocPc26(uint64_t P, uint64_t S, int64_t A) {
  A = llvm::SignExtend32<28>(A);
  if ((S + A) & 2)
    return make_unaligned_range_reloc_error();
  return S + A - P;
}

static int64_t relocRel32(int64_t A) {
  // If output relocation format is REL and the input one is RELA, the only
  // method to transfer the relocation addend from the input relocation
  // to the output dynamic relocation is to save this addend to the location
  // modified by R_MIPS_REL32.
  return A;
}

static std::error_code adjustJumpOpCode(uint64_t &ins, uint64_t tgt,
                                        CrossJumpMode mode) {
  if (mode == CrossJumpMode::None)
    return std::error_code();

  bool toMicro = mode == CrossJumpMode::ToMicro;
  uint32_t opNative = toMicro ? 0x03 : 0x3d;
  uint32_t opCross = toMicro ? 0x1d : 0x3c;

  if ((tgt & 1) != toMicro)
    return make_dynamic_error_code("Incorrect bit 0 for the jalx target");

  if (tgt & 2)
    return make_dynamic_error_code(Twine("The jalx target 0x") +
                                   Twine::utohexstr(tgt) +
                                   " is not word-aligned");
  uint8_t op = ins >> 26;
  if (op != opNative && op != opCross)
    return make_dynamic_error_code(Twine("Unsupported jump opcode (0x") +
                                   Twine::utohexstr(op) +
                                   ") for ISA modes cross call");

  ins = (ins & ~(0x3f << 26)) | (opCross << 26);
  return std::error_code();
}

static bool isMicroMipsAtom(const Atom *a) {
  if (const auto *da = dyn_cast<DefinedAtom>(a))
    return da->codeModel() == DefinedAtom::codeMipsMicro ||
           da->codeModel() == DefinedAtom::codeMipsMicroPIC;
  return false;
}

static CrossJumpMode getCrossJumpMode(const Reference &ref) {
  if (!isa<DefinedAtom>(ref.target()))
    return CrossJumpMode::None;
  bool isTgtMicro = isMicroMipsAtom(ref.target());
  switch (ref.kindValue()) {
  case R_MIPS_26:
  case LLD_R_MIPS_GLOBAL_26:
    return isTgtMicro ? CrossJumpMode::ToMicro : CrossJumpMode::None;
  case R_MICROMIPS_26_S1:
  case LLD_R_MICROMIPS_GLOBAL_26_S1:
    return isTgtMicro ? CrossJumpMode::None : CrossJumpMode::ToRegular;
  default:
    return CrossJumpMode::None;
  }
}

static ErrorOr<int64_t> calculateRelocation(Reference::KindValue kind,
                                            Reference::Addend addend,
                                            uint64_t tgtAddr, uint64_t relAddr,
                                            uint64_t gpAddr, bool isGP,
                                            bool isCrossJump, bool isDynamic) {
  switch (kind) {
  case R_MIPS_NONE:
    return 0;
  case R_MIPS_16:
    return tgtAddr + llvm::SignExtend32<16>(addend);
  case R_MIPS_32:
  case R_MIPS_64:
    return tgtAddr + addend;
  case R_MIPS_SUB:
  case R_MICROMIPS_SUB:
    return tgtAddr - addend;
  case R_MIPS_26:
    return reloc26loc(relAddr, tgtAddr, addend, 2);
  case R_MICROMIPS_26_S1:
    return reloc26loc(relAddr, tgtAddr, addend, isCrossJump ? 2 : 1);
  case R_MIPS_HI16:
  case R_MICROMIPS_HI16:
    return relocHi16(relAddr, tgtAddr, addend, isGP);
  case R_MIPS_PCHI16:
    return getHi16(tgtAddr + addend - relAddr);
  case R_MIPS_LO16:
    return relocLo16(relAddr, tgtAddr, addend, isGP, false);
  case R_MIPS_PCLO16:
    return tgtAddr + addend - relAddr;
  case R_MICROMIPS_LO16:
  case R_MICROMIPS_HI0_LO16:
    return relocLo16(relAddr, tgtAddr, addend, isGP, true);
  case R_MIPS_GOT_LO16:
  case R_MIPS_CALL_LO16:
  case R_MICROMIPS_GOT_LO16:
  case R_MICROMIPS_CALL_LO16:
    return tgtAddr - gpAddr;
  case R_MIPS_GOT_HI16:
  case R_MIPS_CALL_HI16:
  case R_MICROMIPS_GOT_HI16:
  case R_MICROMIPS_CALL_HI16:
    return getHi16(tgtAddr - gpAddr);
  case R_MIPS_HIGHER:
  case R_MICROMIPS_HIGHER:
    return getHigher16(tgtAddr + addend);
  case R_MIPS_HIGHEST:
  case R_MICROMIPS_HIGHEST:
    return getHighest16(tgtAddr + addend);
  case R_MIPS_EH:
  case R_MIPS_GOT16:
  case R_MIPS_CALL16:
  case R_MIPS_GOT_DISP:
  case R_MIPS_GOT_PAGE:
  case R_MICROMIPS_GOT_DISP:
  case R_MICROMIPS_GOT_PAGE:
  case R_MICROMIPS_GOT16:
  case R_MICROMIPS_CALL16:
  case R_MIPS_TLS_GD:
  case R_MIPS_TLS_LDM:
  case R_MIPS_TLS_GOTTPREL:
  case R_MICROMIPS_TLS_GD:
  case R_MICROMIPS_TLS_LDM:
  case R_MICROMIPS_TLS_GOTTPREL:
    return tgtAddr - gpAddr;
  case R_MIPS_GOT_OFST:
  case R_MICROMIPS_GOT_OFST:
    return relocGOTOfst(tgtAddr, addend);
  case R_MIPS_PC16:
    return relocPc16(relAddr, tgtAddr, addend);
  case R_MIPS_PC18_S3:
  case R_MICROMIPS_PC18_S3:
    return relocPc18(relAddr, tgtAddr, addend);
  case R_MIPS_PC19_S2:
  case R_MICROMIPS_PC19_S2:
    return relocPc19(relAddr, tgtAddr, addend);
  case R_MIPS_PC21_S2:
  case R_MICROMIPS_PC21_S2:
    return relocPc21(relAddr, tgtAddr, addend);
  case R_MIPS_PC26_S2:
  case R_MICROMIPS_PC26_S2:
    return relocPc26(relAddr, tgtAddr, addend);
  case R_MICROMIPS_PC7_S1:
  case R_MICROMIPS_PC10_S1:
  case R_MICROMIPS_PC16_S1:
  case R_MICROMIPS_PC23_S2:
    return tgtAddr + addend - relAddr;
  case R_MIPS_TLS_DTPREL_HI16:
  case R_MIPS_TLS_TPREL_HI16:
  case R_MICROMIPS_TLS_DTPREL_HI16:
  case R_MICROMIPS_TLS_TPREL_HI16:
    return relocHi16(0, tgtAddr, addend, false);
  case R_MIPS_TLS_DTPREL_LO16:
  case R_MIPS_TLS_TPREL_LO16:
    return relocLo16(0, tgtAddr, addend, false, false);
  case R_MICROMIPS_TLS_DTPREL_LO16:
  case R_MICROMIPS_TLS_TPREL_LO16:
    return relocLo16(0, tgtAddr, addend, false, true);
  case R_MIPS_GPREL16:
  case R_MIPS_GPREL32:
  case R_MIPS_LITERAL:
  case R_MICROMIPS_GPREL16:
  case R_MICROMIPS_GPREL7_S2:
  case R_MICROMIPS_LITERAL:
    return tgtAddr + addend - gpAddr;
  case R_MIPS_JALR:
  case R_MICROMIPS_JALR:
    // We do not do JALR optimization now.
    return 0;
  case R_MIPS_REL32:
    return relocRel32(addend);
  case R_MIPS_JUMP_SLOT:
  case R_MIPS_COPY:
    // Ignore runtime relocations.
    return 0;
  case R_MIPS_TLS_DTPMOD32:
  case R_MIPS_TLS_DTPMOD64:
    return isDynamic ? 0 : 1;
  case R_MIPS_TLS_DTPREL32:
  case R_MIPS_TLS_DTPREL64:
    return isDynamic ? 0 : tgtAddr + addend - 0x8000;
  case R_MIPS_TLS_TPREL32:
  case R_MIPS_TLS_TPREL64:
    return isDynamic ? 0 : tgtAddr + addend - 0x7000;
  case R_MIPS_PC32:
    return tgtAddr + addend - relAddr;
  case LLD_R_MIPS_32_HI16:
  case LLD_R_MIPS_64_HI16:
    return maskLow16(tgtAddr + addend);
  case LLD_R_MIPS_GLOBAL_26:
    return reloc26ext(tgtAddr, addend, 2);
  case LLD_R_MICROMIPS_GLOBAL_26_S1:
    return reloc26ext(tgtAddr, addend, isCrossJump ? 2 : 1);
  case LLD_R_MIPS_STO_PLT:
  case LLD_R_MIPS_GLOBAL_GOT:
    // Do nothing.
    return 0;
  default:
    return make_unhandled_reloc_error();
  }
}

template <class ELFT>
static uint64_t relocRead(const MipsRelocationParams &params,
                          const uint8_t *loc) {
  assert((params._size == 4 || params._size == 8) && "Unexpected size");
  uint64_t data;
  memcpy(&data, loc, params._size);
  if (params._shuffle) {
    using namespace endian;
    auto p = reinterpret_cast<const uint8_t *>(&data);
    uint32_t a = readNext<uint16_t, ELFT::TargetEndianness, unaligned>(p);
    uint32_t b = read<uint16_t, ELFT::TargetEndianness, unaligned>(p);
    write<uint32_t, ELFT::TargetEndianness, unaligned>(&data, a << 16 | b);
  }
  switch (params._size) {
  case 4:
    return endian::read<uint32_t, ELFT::TargetEndianness, unaligned>(&data);
  case 8:
    return endian::read<uint64_t, ELFT::TargetEndianness, unaligned>(&data);
  default:
    llvm_unreachable("Unexpected size");
  }
}

template <class ELFT>
static void relocWrite(uint64_t data, const MipsRelocationParams &params,
                       uint8_t *loc) {
  switch (params._size) {
  case 4:
    endian::write<uint32_t, ELFT::TargetEndianness, unaligned>(loc, data);
    break;
  case 8:
    endian::write<uint64_t, ELFT::TargetEndianness, unaligned>(loc, data);
    break;
  default:
    llvm_unreachable("Unexpected size");
  }
  if (params._shuffle) {
    uint32_t v = endian::read<uint32_t, ELFT::TargetEndianness, unaligned>(loc);
    uint16_t a = v >> 16;
    uint16_t b = v & 0xffff;
    endian::write<uint16_t, ELFT::TargetEndianness, unaligned>(loc, a);
    endian::write<uint16_t, ELFT::TargetEndianness, unaligned>(loc + 2, b);
  }
}

static uint32_t getRelKind(const Reference &ref, size_t num) {
  if (num == 0)
    return ref.kindValue();
  if (num > 2)
    return R_MIPS_NONE;
  return (ref.tag() >> (8 * (num - 1))) & 0xff;
}

static uint8_t getRelShift(Reference::KindValue kind,
                           const MipsRelocationParams &params,
                           bool isCrossJump) {
  uint8_t shift = params._shift;
  if (isCrossJump &&
      (kind == R_MICROMIPS_26_S1 || kind == LLD_R_MICROMIPS_GLOBAL_26_S1))
    return 2;
  return shift;
}

template <class ELFT>
std::error_code RelocationHandler<ELFT>::applyRelocation(
    ELFWriter &writer, llvm::FileOutputBuffer &buf, const AtomLayout &atom,
    const Reference &ref) const {
  if (ref.kindNamespace() != Reference::KindNamespace::ELF)
    return std::error_code();
  assert(ref.kindArch() == Reference::KindArch::Mips);

  uint64_t gpAddr = _targetLayout.getGPAddr();
  bool isGpDisp = ref.target()->name() == "_gp_disp";

  uint8_t *atomContent = buf.getBufferStart() + atom._fileOffset;
  uint8_t *location = atomContent + ref.offsetInAtom();
  uint64_t tgtAddr = writer.addressOfAtom(ref.target());
  uint64_t relAddr = atom._virtualAddr + ref.offsetInAtom();

  if (isMicroMipsAtom(ref.target()))
    tgtAddr |= 1;

  CrossJumpMode jumpMode = getCrossJumpMode(ref);
  bool isCrossJump = jumpMode != CrossJumpMode::None;

  uint64_t sym = tgtAddr;
  ErrorOr<int64_t> res = ref.addend();
  Reference::KindValue lastRel = R_MIPS_NONE;

  for (size_t relNum = 0; relNum < 3; ++relNum) {
    Reference::KindValue kind = getRelKind(ref, relNum);
    if (kind == R_MIPS_NONE)
      break;
    auto params = getRelocationParams(kind);
    res = calculateRelocation(kind, *res, sym, relAddr, gpAddr, isGpDisp,
                              isCrossJump, _ctx.isDynamic());
    if (auto ec = res.getError())
      return ec;
    // Check result for the last relocation only.
    if (getRelKind(ref, relNum + 1) == R_MIPS_NONE) {
      if (auto ec = params._overflow(*res, isGpDisp))
        return ec;
    }
    res = *res >> getRelShift(kind, params, isCrossJump);
    // FIXME (simon): Handle r_ssym value.
    sym = 0;
    isGpDisp = false;
    isCrossJump = false;
    lastRel = kind;
  }

  auto params = getRelocationParams(lastRel);
  uint64_t ins = relocRead<ELFT>(params, location);
  if (auto ec = adjustJumpOpCode(ins, tgtAddr, jumpMode))
    return ec;

  ins = (ins & ~params._mask) | (*res & params._mask);
  relocWrite<ELFT>(ins, params, location);

  return std::error_code();
}

namespace lld {
namespace elf {

template <>
std::unique_ptr<TargetRelocationHandler>
createMipsRelocationHandler<ELF32BE>(MipsLinkingContext &ctx,
                                     MipsTargetLayout<ELF32BE> &layout) {
  return llvm::make_unique<RelocationHandler<ELF32BE>>(ctx, layout);
}

template <>
std::unique_ptr<TargetRelocationHandler>
createMipsRelocationHandler<ELF32LE>(MipsLinkingContext &ctx,
                                     MipsTargetLayout<ELF32LE> &layout) {
  return llvm::make_unique<RelocationHandler<ELF32LE>>(ctx, layout);
}

template <>
std::unique_ptr<TargetRelocationHandler>
createMipsRelocationHandler<ELF64BE>(MipsLinkingContext &ctx,
                                     MipsTargetLayout<ELF64BE> &layout) {
  return llvm::make_unique<RelocationHandler<ELF64BE>>(ctx, layout);
}

template <>
std::unique_ptr<TargetRelocationHandler>
createMipsRelocationHandler<ELF64LE>(MipsLinkingContext &ctx,
                                     MipsTargetLayout<ELF64LE> &layout) {
  return llvm::make_unique<RelocationHandler<ELF64LE>>(ctx, layout);
}

template <class ELFT>
Reference::Addend readMipsRelocAddend(Reference::KindValue kind,
                                      const uint8_t *content) {
  auto params = getRelocationParams(kind);
  uint64_t ins = relocRead<ELFT>(params, content);
  int64_t res = (ins & params._mask) << params._shift;
  switch (kind) {
  case R_MIPS_GPREL16:
  case R_MICROMIPS_GPREL16:
  case R_MIPS_PCLO16:
  case R_MIPS_LITERAL:
  case R_MICROMIPS_LITERAL:
    return llvm::SignExtend32<16>(res);
  case R_MICROMIPS_GPREL7_S2:
    return llvm::SignExtend32<9>(res);
  case R_MICROMIPS_PC7_S1:
    return llvm::SignExtend32<8>(res);
  case R_MICROMIPS_PC10_S1:
    return llvm::SignExtend32<11>(res);
  case R_MICROMIPS_PC16_S1:
    return llvm::SignExtend32<17>(res);
  case R_MICROMIPS_PC23_S2:
    return llvm::SignExtend32<25>(res);
  default:
    // Nothing to do
    break;
  }
  return res;
}

template
Reference::Addend readMipsRelocAddend<ELF32BE>(Reference::KindValue kind,
                                               const uint8_t *content);
template
Reference::Addend readMipsRelocAddend<ELF32LE>(Reference::KindValue kind,
                                               const uint8_t *content);
template
Reference::Addend readMipsRelocAddend<ELF64BE>(Reference::KindValue kind,
                                               const uint8_t *content);
template
Reference::Addend readMipsRelocAddend<ELF64LE>(Reference::KindValue kind,
                                               const uint8_t *content);

} // elf
} // lld
