//===- SystemZ.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 "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.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::support::endian;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;

namespace {
class SystemZ : public TargetInfo {
public:
  SystemZ(Ctx &);
  int getTlsGdRelaxSkip(RelType type) const override;
  RelExpr getRelExpr(RelType type, const Symbol &s,
                     const uint8_t *loc) const override;
  RelType getDynRel(RelType type) const override;
  void writeGotHeader(uint8_t *buf) const override;
  void writeGotPlt(uint8_t *buf, const Symbol &s) const override;
  void writeIgotPlt(uint8_t *buf, const Symbol &s) const override;
  void writePltHeader(uint8_t *buf) const override;
  void addPltHeaderSymbols(InputSection &isd) const override;
  void writePlt(uint8_t *buf, const Symbol &sym,
                uint64_t pltEntryAddr) const override;
  RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
  RelExpr adjustGotPcExpr(RelType type, int64_t addend,
                          const uint8_t *loc) const override;
  bool relaxOnce(int pass) const override;
  void relocate(uint8_t *loc, const Relocation &rel,
                uint64_t val) const override;
  int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;

private:
  void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
  void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
  void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
  void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
};
} // namespace

SystemZ::SystemZ(Ctx &ctx) : TargetInfo(ctx) {
  copyRel = R_390_COPY;
  gotRel = R_390_GLOB_DAT;
  pltRel = R_390_JMP_SLOT;
  relativeRel = R_390_RELATIVE;
  iRelativeRel = R_390_IRELATIVE;
  symbolicRel = R_390_64;
  tlsGotRel = R_390_TLS_TPOFF;
  tlsModuleIndexRel = R_390_TLS_DTPMOD;
  tlsOffsetRel = R_390_TLS_DTPOFF;
  gotHeaderEntriesNum = 3;
  gotPltHeaderEntriesNum = 0;
  gotEntrySize = 8;
  pltHeaderSize = 32;
  pltEntrySize = 32;
  ipltEntrySize = 32;

  // This "trap instruction" is used to fill gaps between sections.
  // On SystemZ, the behavior of the GNU ld is to fill those gaps
  // with nop instructions instead - and unfortunately the default
  // glibc crt object files (used to) rely on that behavior since
  // they use an alignment on the .init section fragments that causes
  // gaps which must be filled with nops as they are being executed.
  // Therefore, we provide a nop instruction as "trapInstr" here.
  trapInstr = {0x07, 0x07, 0x07, 0x07};

  defaultImageBase = 0x1000000;
}

RelExpr SystemZ::getRelExpr(RelType type, const Symbol &s,
                            const uint8_t *loc) const {
  switch (type) {
  case R_390_NONE:
    return R_NONE;
  // Relocations targeting the symbol value.
  case R_390_8:
  case R_390_12:
  case R_390_16:
  case R_390_20:
  case R_390_32:
  case R_390_64:
    return R_ABS;
  case R_390_PC16:
  case R_390_PC32:
  case R_390_PC64:
  case R_390_PC12DBL:
  case R_390_PC16DBL:
  case R_390_PC24DBL:
  case R_390_PC32DBL:
    return R_PC;
  case R_390_GOTOFF16:
  case R_390_GOTOFF: // a.k.a. R_390_GOTOFF32
  case R_390_GOTOFF64:
    return R_GOTREL;
  // Relocations targeting the PLT associated with the symbol.
  case R_390_PLT32:
  case R_390_PLT64:
  case R_390_PLT12DBL:
  case R_390_PLT16DBL:
  case R_390_PLT24DBL:
  case R_390_PLT32DBL:
    return R_PLT_PC;
  case R_390_PLTOFF16:
  case R_390_PLTOFF32:
  case R_390_PLTOFF64:
    return R_PLT_GOTREL;
  // Relocations targeting the GOT entry associated with the symbol.
  case R_390_GOTENT:
    return R_GOT_PC;
  case R_390_GOT12:
  case R_390_GOT16:
  case R_390_GOT20:
  case R_390_GOT32:
  case R_390_GOT64:
    return R_GOT_OFF;
  // Relocations targeting the GOTPLT entry associated with the symbol.
  case R_390_GOTPLTENT:
    return R_GOTPLT_PC;
  case R_390_GOTPLT12:
  case R_390_GOTPLT16:
  case R_390_GOTPLT20:
  case R_390_GOTPLT32:
  case R_390_GOTPLT64:
    return R_GOTPLT_GOTREL;
  // Relocations targeting _GLOBAL_OFFSET_TABLE_.
  case R_390_GOTPC:
  case R_390_GOTPCDBL:
    return R_GOTONLY_PC;
  // TLS-related relocations.
  case R_390_TLS_LOAD:
    return R_NONE;
  case R_390_TLS_GDCALL:
    return R_TLSGD_PC;
  case R_390_TLS_LDCALL:
    return R_TLSLD_PC;
  case R_390_TLS_GD32:
  case R_390_TLS_GD64:
    return R_TLSGD_GOT;
  case R_390_TLS_LDM32:
  case R_390_TLS_LDM64:
    return R_TLSLD_GOT;
  case R_390_TLS_LDO32:
  case R_390_TLS_LDO64:
    return R_DTPREL;
  case R_390_TLS_LE32:
  case R_390_TLS_LE64:
    return R_TPREL;
  case R_390_TLS_IE32:
  case R_390_TLS_IE64:
    return R_GOT;
  case R_390_TLS_GOTIE12:
  case R_390_TLS_GOTIE20:
  case R_390_TLS_GOTIE32:
  case R_390_TLS_GOTIE64:
    return R_GOT_OFF;
  case R_390_TLS_IEENT:
    return R_GOT_PC;

  default:
    Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
             << ") against symbol " << &s;
    return R_NONE;
  }
}

void SystemZ::writeGotHeader(uint8_t *buf) const {
  // _GLOBAL_OFFSET_TABLE_[0] holds the value of _DYNAMIC.
  // _GLOBAL_OFFSET_TABLE_[1] and [2] are reserved.
  write64be(buf, ctx.mainPart->dynamic->getVA());
}

void SystemZ::writeGotPlt(uint8_t *buf, const Symbol &s) const {
  write64be(buf, s.getPltVA(ctx) + 14);
}

void SystemZ::writeIgotPlt(uint8_t *buf, const Symbol &s) const {
  if (ctx.arg.writeAddends)
    write64be(buf, s.getVA(ctx));
}

void SystemZ::writePltHeader(uint8_t *buf) const {
  const uint8_t pltData[] = {
      0xe3, 0x10, 0xf0, 0x38, 0x00, 0x24, // stg     %r1,56(%r15)
      0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl    %r1,_GLOBAL_OFFSET_TABLE_
      0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc     48(8,%r15),8(%r1)
      0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg      %r1,16(%r1)
      0x07, 0xf1,                         // br      %r1
      0x07, 0x00,                         // nopr
      0x07, 0x00,                         // nopr
      0x07, 0x00,                         // nopr
  };
  memcpy(buf, pltData, sizeof(pltData));
  uint64_t got = ctx.in.got->getVA();
  uint64_t plt = ctx.in.plt->getVA();
  write32be(buf + 8, (got - plt - 6) >> 1);
}

void SystemZ::addPltHeaderSymbols(InputSection &isec) const {
  // The PLT header needs a reference to _GLOBAL_OFFSET_TABLE_, so we
  // must ensure the .got section is created even if otherwise unused.
  ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
}

void SystemZ::writePlt(uint8_t *buf, const Symbol &sym,
                       uint64_t pltEntryAddr) const {
  const uint8_t inst[] = {
      0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl    %r1,<.got.plt slot>
      0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg      %r1,0(%r1)
      0x07, 0xf1,                         // br      %r1
      0x0d, 0x10,                         // basr    %r1,%r0
      0xe3, 0x10, 0x10, 0x0c, 0x00, 0x14, // lgf     %r1,12(%r1)
      0xc0, 0xf4, 0x00, 0x00, 0x00, 0x00, // jg      <plt header>
      0x00, 0x00, 0x00, 0x00,             // <relocation offset>
  };
  memcpy(buf, inst, sizeof(inst));

  write32be(buf + 2, (sym.getGotPltVA(ctx) - pltEntryAddr) >> 1);
  write32be(buf + 24, (ctx.in.plt->getVA() - pltEntryAddr - 22) >> 1);
  write32be(buf + 28, ctx.in.relaPlt->entsize * sym.getPltIdx(ctx));
}

int64_t SystemZ::getImplicitAddend(const uint8_t *buf, RelType type) const {
  switch (type) {
  case R_390_8:
    return SignExtend64<8>(*buf);
  case R_390_16:
  case R_390_PC16:
    return SignExtend64<16>(read16be(buf));
  case R_390_PC16DBL:
    return SignExtend64<16>(read16be(buf)) << 1;
  case R_390_32:
  case R_390_PC32:
    return SignExtend64<32>(read32be(buf));
  case R_390_PC32DBL:
    return SignExtend64<32>(read32be(buf)) << 1;
  case R_390_64:
  case R_390_PC64:
  case R_390_TLS_DTPMOD:
  case R_390_TLS_DTPOFF:
  case R_390_TLS_TPOFF:
  case R_390_GLOB_DAT:
  case R_390_RELATIVE:
  case R_390_IRELATIVE:
    return read64be(buf);
  case R_390_COPY:
  case R_390_JMP_SLOT:
  case R_390_NONE:
    // These relocations are defined as not having an implicit addend.
    return 0;
  default:
    InternalErr(ctx, buf) << "cannot read addend for relocation " << type;
    return 0;
  }
}

RelType SystemZ::getDynRel(RelType type) const {
  if (type == R_390_64 || type == R_390_PC64)
    return type;
  return R_390_NONE;
}

RelExpr SystemZ::adjustTlsExpr(RelType type, RelExpr expr) const {
  if (expr == R_RELAX_TLS_GD_TO_IE)
    return R_RELAX_TLS_GD_TO_IE_GOT_OFF;
  return expr;
}

int SystemZ::getTlsGdRelaxSkip(RelType type) const {
  // A __tls_get_offset call instruction is marked with 2 relocations:
  //
  //   R_390_TLS_GDCALL / R_390_TLS_LDCALL: marker relocation
  //   R_390_PLT32DBL: __tls_get_offset
  //
  // After the relaxation we no longer call __tls_get_offset and should skip
  // both relocations to not create a false dependence on __tls_get_offset
  // being defined.
  //
  // Note that this mechanism only works correctly if the R_390_TLS_[GL]DCALL
  // is seen immediately *before* the R_390_PLT32DBL.  Unfortunately, current
  // compilers on the platform will typically generate the inverse sequence.
  // To fix this, we sort relocations by offset in RelocationScanner::scan;
  // this ensures the correct sequence as the R_390_TLS_[GL]DCALL applies to
  // the first byte of the brasl instruction, while the R_390_PLT32DBL applies
  // to its third byte (the relative displacement).

  if (type == R_390_TLS_GDCALL || type == R_390_TLS_LDCALL)
    return 2;
  return 1;
}

void SystemZ::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
                             uint64_t val) const {
  // The general-dynamic code sequence for a global `x`:
  //
  // Instruction                      Relocation       Symbol
  // ear %rX,%a0
  // sllg %rX,%rX,32
  // ear %rX,%a1
  // larl %r12,_GLOBAL_OFFSET_TABLE_  R_390_GOTPCDBL   _GLOBAL_OFFSET_TABLE_
  // lgrl %r2,.LC0                    R_390_PC32DBL    .LC0
  // brasl %r14,__tls_get_offset@plt  R_390_TLS_GDCALL x
  //            :tls_gdcall:x         R_390_PLT32DBL   __tls_get_offset
  // la %r2,0(%r2,%rX)
  //
  // .LC0:
  // .quad   x@TLSGD                  R_390_TLS_GD64   x
  //
  // Relaxing to initial-exec entails:
  // 1) Replacing the call by a load from the GOT.
  // 2) Replacing the relocation on the constant LC0 by R_390_TLS_GOTIE64.

  switch (rel.type) {
  case R_390_TLS_GDCALL:
    // brasl %r14,__tls_get_offset@plt -> lg %r2,0(%r2,%r12)
    write16be(loc, 0xe322);
    write32be(loc + 2, 0xc0000004);
    break;
  case R_390_TLS_GD64:
    relocateNoSym(loc, R_390_TLS_GOTIE64, val);
    break;
  default:
    llvm_unreachable("unsupported relocation for TLS GD to IE relaxation");
  }
}

void SystemZ::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
                             uint64_t val) const {
  // The general-dynamic code sequence for a global `x`:
  //
  // Instruction                      Relocation       Symbol
  // ear %rX,%a0
  // sllg %rX,%rX,32
  // ear %rX,%a1
  // larl %r12,_GLOBAL_OFFSET_TABLE_  R_390_GOTPCDBL   _GLOBAL_OFFSET_TABLE_
  // lgrl %r2,.LC0                    R_390_PC32DBL    .LC0
  // brasl %r14,__tls_get_offset@plt  R_390_TLS_GDCALL x
  //            :tls_gdcall:x         R_390_PLT32DBL   __tls_get_offset
  // la %r2,0(%r2,%rX)
  //
  // .LC0:
  // .quad   x@tlsgd                  R_390_TLS_GD64   x
  //
  // Relaxing to local-exec entails:
  // 1) Replacing the call by a nop.
  // 2) Replacing the relocation on the constant LC0 by R_390_TLS_LE64.

  switch (rel.type) {
  case R_390_TLS_GDCALL:
    // brasl %r14,__tls_get_offset@plt -> brcl 0,.
    write16be(loc, 0xc004);
    write32be(loc + 2, 0x00000000);
    break;
  case R_390_TLS_GD64:
    relocateNoSym(loc, R_390_TLS_LE64, val);
    break;
  default:
    llvm_unreachable("unsupported relocation for TLS GD to LE relaxation");
  }
}

void SystemZ::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
                             uint64_t val) const {
  // The local-dynamic code sequence for a global `x`:
  //
  // Instruction                      Relocation       Symbol
  // ear %rX,%a0
  // sllg %rX,%rX,32
  // ear %rX,%a1
  // larl %r12,_GLOBAL_OFFSET_TABLE_  R_390_GOTPCDBL   _GLOBAL_OFFSET_TABLE_
  // lgrl %r2,.LC0                    R_390_PC32DBL    .LC0
  // brasl %r14,__tls_get_offset@plt  R_390_TLS_LDCALL <sym>
  //            :tls_ldcall:<sym>     R_390_PLT32DBL   __tls_get_offset
  // la %r2,0(%r2,%rX)
  // lgrl %rY,.LC1                    R_390_PC32DBL    .LC1
  // la %r2,0(%r2,%rY)
  //
  // .LC0:
  // .quad   <sym>@tlsldm             R_390_TLS_LDM64  <sym>
  // .LC1:
  // .quad   x@dtpoff                 R_390_TLS_LDO64  x
  //
  // Relaxing to local-exec entails:
  // 1) Replacing the call by a nop.
  // 2) Replacing the constant LC0 by 0 (i.e. ignoring the relocation).
  // 3) Replacing the relocation on the constant LC1 by R_390_TLS_LE64.

  switch (rel.type) {
  case R_390_TLS_LDCALL:
    // brasl %r14,__tls_get_offset@plt -> brcl 0,.
    write16be(loc, 0xc004);
    write32be(loc + 2, 0x00000000);
    break;
  case R_390_TLS_LDM64:
    break;
  case R_390_TLS_LDO64:
    relocateNoSym(loc, R_390_TLS_LE64, val);
    break;
  default:
    llvm_unreachable("unsupported relocation for TLS LD to LE relaxation");
  }
}

RelExpr SystemZ::adjustGotPcExpr(RelType type, int64_t addend,
                                 const uint8_t *loc) const {
  // Only R_390_GOTENT with addend 2 can be relaxed.
  if (!ctx.arg.relax || addend != 2 || type != R_390_GOTENT)
    return R_GOT_PC;
  const uint16_t op = read16be(loc - 2);

  // lgrl rx,sym@GOTENT -> larl rx, sym
  // This relaxation is legal if "sym" binds locally (which was already
  // verified by our caller) and is in-range and properly aligned for a
  // LARL instruction.  We cannot verify the latter constraint here, so
  // we assume it is true and revert the decision later on in relaxOnce
  // if necessary.
  if ((op & 0xff0f) == 0xc408)
    return R_RELAX_GOT_PC;

  return R_GOT_PC;
}

bool SystemZ::relaxOnce(int pass) const {
  // If we decided in adjustGotPcExpr to relax a R_390_GOTENT,
  // we need to validate the target symbol is in-range and aligned.
  SmallVector<InputSection *, 0> storage;
  bool changed = false;
  for (OutputSection *osec : ctx.outputSections) {
    if (!(osec->flags & SHF_EXECINSTR))
      continue;
    for (InputSection *sec : getInputSections(*osec, storage)) {
      for (Relocation &rel : sec->relocs()) {
        if (rel.expr != R_RELAX_GOT_PC)
          continue;

        uint64_t v = sec->getRelocTargetVA(
            ctx, rel, sec->getOutputSection()->addr + rel.offset);
        if (isInt<33>(v) && !(v & 1))
          continue;
        if (rel.sym->auxIdx == 0) {
          rel.sym->allocateAux(ctx);
          addGotEntry(ctx, *rel.sym);
          changed = true;
        }
        rel.expr = R_GOT_PC;
      }
    }
  }
  return changed;
}

void SystemZ::relaxGot(uint8_t *loc, const Relocation &rel,
                       uint64_t val) const {
  assert(isInt<33>(val) &&
         "R_390_GOTENT should not have been relaxed if it overflows");
  assert(!(val & 1) &&
         "R_390_GOTENT should not have been relaxed if it is misaligned");
  const uint16_t op = read16be(loc - 2);

  // lgrl rx,sym@GOTENT -> larl rx, sym
  if ((op & 0xff0f) == 0xc408) {
    write16be(loc - 2, 0xc000 | (op & 0x00f0));
    write32be(loc, val >> 1);
  }
}

void SystemZ::relocate(uint8_t *loc, const Relocation &rel,
                       uint64_t val) const {
  switch (rel.expr) {
  case R_RELAX_GOT_PC:
    return relaxGot(loc, rel, val);
  case R_RELAX_TLS_GD_TO_IE_GOT_OFF:
    return relaxTlsGdToIe(loc, rel, val);
  case R_RELAX_TLS_GD_TO_LE:
    return relaxTlsGdToLe(loc, rel, val);
  case R_RELAX_TLS_LD_TO_LE:
    return relaxTlsLdToLe(loc, rel, val);
  default:
    break;
  }
  switch (rel.type) {
  case R_390_8:
    checkIntUInt(ctx, loc, val, 8, rel);
    *loc = val;
    break;
  case R_390_12:
  case R_390_GOT12:
  case R_390_GOTPLT12:
  case R_390_TLS_GOTIE12:
    checkUInt(ctx, loc, val, 12, rel);
    write16be(loc, (read16be(loc) & 0xF000) | val);
    break;
  case R_390_PC12DBL:
  case R_390_PLT12DBL:
    checkInt(ctx, loc, val, 13, rel);
    checkAlignment(ctx, loc, val, 2, rel);
    write16be(loc, (read16be(loc) & 0xF000) | ((val >> 1) & 0x0FFF));
    break;
  case R_390_16:
  case R_390_GOT16:
  case R_390_GOTPLT16:
  case R_390_GOTOFF16:
  case R_390_PLTOFF16:
    checkIntUInt(ctx, loc, val, 16, rel);
    write16be(loc, val);
    break;
  case R_390_PC16:
    checkInt(ctx, loc, val, 16, rel);
    write16be(loc, val);
    break;
  case R_390_PC16DBL:
  case R_390_PLT16DBL:
    checkInt(ctx, loc, val, 17, rel);
    checkAlignment(ctx, loc, val, 2, rel);
    write16be(loc, val >> 1);
    break;
  case R_390_20:
  case R_390_GOT20:
  case R_390_GOTPLT20:
  case R_390_TLS_GOTIE20:
    checkInt(ctx, loc, val, 20, rel);
    write32be(loc, (read32be(loc) & 0xF00000FF) | ((val & 0xFFF) << 16) |
                       ((val & 0xFF000) >> 4));
    break;
  case R_390_PC24DBL:
  case R_390_PLT24DBL:
    checkInt(ctx, loc, val, 25, rel);
    checkAlignment(ctx, loc, val, 2, rel);
    loc[0] = val >> 17;
    loc[1] = val >> 9;
    loc[2] = val >> 1;
    break;
  case R_390_32:
  case R_390_GOT32:
  case R_390_GOTPLT32:
  case R_390_GOTOFF:
  case R_390_PLTOFF32:
  case R_390_TLS_IE32:
  case R_390_TLS_GOTIE32:
  case R_390_TLS_GD32:
  case R_390_TLS_LDM32:
  case R_390_TLS_LDO32:
  case R_390_TLS_LE32:
    checkIntUInt(ctx, loc, val, 32, rel);
    write32be(loc, val);
    break;
  case R_390_PC32:
  case R_390_PLT32:
    checkInt(ctx, loc, val, 32, rel);
    write32be(loc, val);
    break;
  case R_390_PC32DBL:
  case R_390_PLT32DBL:
  case R_390_GOTPCDBL:
  case R_390_GOTENT:
  case R_390_GOTPLTENT:
  case R_390_TLS_IEENT:
    checkInt(ctx, loc, val, 33, rel);
    checkAlignment(ctx, loc, val, 2, rel);
    write32be(loc, val >> 1);
    break;
  case R_390_64:
  case R_390_PC64:
  case R_390_PLT64:
  case R_390_GOT64:
  case R_390_GOTPLT64:
  case R_390_GOTOFF64:
  case R_390_PLTOFF64:
  case R_390_GOTPC:
  case R_390_TLS_IE64:
  case R_390_TLS_GOTIE64:
  case R_390_TLS_GD64:
  case R_390_TLS_LDM64:
  case R_390_TLS_LDO64:
  case R_390_TLS_LE64:
  case R_390_TLS_DTPMOD:
  case R_390_TLS_DTPOFF:
  case R_390_TLS_TPOFF:
    write64be(loc, val);
    break;
  case R_390_TLS_LOAD:
  case R_390_TLS_GDCALL:
  case R_390_TLS_LDCALL:
    break;
  default:
    llvm_unreachable("unknown relocation");
  }
}

void elf::setSystemZTargetInfo(Ctx &ctx) { ctx.target.reset(new SystemZ(ctx)); }
