//===- bolt/Core/Relocation.h - Object file relocations ---------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the declaration of Relocation class, which represents a
// relocation in an object or a binary file.
//
//===----------------------------------------------------------------------===//

#ifndef BOLT_CORE_RELOCATION_H
#define BOLT_CORE_RELOCATION_H

#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/TargetParser/Triple.h"

namespace llvm {
class MCSymbol;

namespace object {
class RelocationRef;
} // namespace object

class raw_ostream;

namespace ELF {
/// Relocation type mask that was accidentally output by bfd 2.30 linker.
enum { R_X86_64_converted_reloc_bit = 0x80 };
} // namespace ELF

namespace bolt {

/// Relocation class.
class Relocation {
public:
  Relocation(uint64_t Offset, MCSymbol *Symbol, uint32_t Type, uint64_t Addend,
             uint64_t Value, bool IsRELR = false)
      : Offset(Offset), Symbol(Symbol), Type(Type), Optional(false),
        IsRELR(IsRELR), Addend(Addend), Value(Value) {
    assert((isRelative() || !isRELR()) &&
           "Only relative relocations can be relr.");
  }

  Relocation()
      : Offset(0), Symbol(0), Type(0), Optional(0), IsRELR(0), Addend(0),
        Value(0) {}

  static Triple::ArchType Arch; /// set by BinaryContext ctor.

  /// The offset of this relocation in the object it is contained in.
  uint64_t Offset;

  /// The symbol this relocation is referring to.
  MCSymbol *Symbol;

  /// Relocation type.
  uint32_t Type;

private:
  /// Relocations added by optimizations can be optional, meaning they can be
  /// omitted under certain circumstances.
  bool Optional = false;

  /// Track which relocations originate from a relr section. Emit these
  /// exclusively into the relr section and do not accidentally promote relative
  /// rela entries, because that would require growing the relr section.
  bool IsRELR = false;

public:
  /// The offset from the \p Symbol base used to compute the final
  /// value of this relocation.
  uint64_t Addend;

  /// The computed relocation value extracted from the binary file.
  /// Used to validate relocation correctness.
  uint64_t Value;

  /// Return size in bytes of the given relocation \p Type.
  static size_t getSizeForType(uint32_t Type);

  void setOptional() { Optional = true; }

  bool isOptional() { return Optional; }

  bool isRELR() const { return IsRELR; }

  /// Return size of this relocation.
  size_t getSize() const { return getSizeForType(Type); }

  /// Skip relocations that we don't want to handle in BOLT
  static bool skipRelocationType(uint32_t Type);

  /// Adjust value depending on relocation type (make it PC relative or not).
  static uint64_t encodeValue(uint32_t Type, uint64_t Value, uint64_t PC);

  /// Return true if there are enough bits to encode the relocation value.
  static bool canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC);

  /// Extract current relocated value from binary contents. This is used for
  /// RISC architectures where values are encoded in specific bits depending
  /// on the relocation value. For X86, we limit to sign extending the value
  /// if necessary.
  static uint64_t extractValue(uint32_t Type, uint64_t Contents, uint64_t PC);

  /// Return true if relocation type is PC-relative. Return false otherwise.
  static bool isPCRelative(uint32_t Type);

  /// Check if \p Type is a supported relocation type.
  static bool isSupported(uint32_t Type);

  /// Return true if relocation type implies the creation of a GOT entry
  static bool isGOT(uint32_t Type);

  /// Special relocation type that allows the linker to modify the instruction.
  static bool isX86GOTPCRELX(uint32_t Type);
  static bool isX86GOTPC64(uint32_t Type);

  /// Return true if relocation type is NONE
  static bool isNone(uint32_t Type);

  /// Return true if relocation type is RELATIVE
  static bool isRelative(uint32_t Type);

  /// Return true if relocation type is IRELATIVE
  static bool isIRelative(uint32_t Type);

  /// Return true if relocation type is for thread local storage.
  static bool isTLS(uint32_t Type);

  /// Return true of relocation type is for referencing a specific instruction
  /// (as opposed to a function, basic block, etc).
  static bool isInstructionReference(uint32_t Type);

  /// Return the relocation type of \p Rel from llvm::object. It checks for
  /// overflows as BOLT uses 32 bits for the type.
  static uint32_t getType(const object::RelocationRef &Rel);

  /// Return code for a NONE relocation
  static uint32_t getNone();

  /// Return code for a PC-relative 4-byte relocation
  static uint32_t getPC32();

  /// Return code for a PC-relative 8-byte relocation
  static uint32_t getPC64();

  /// Return code for a ABS 8-byte relocation
  static uint32_t getAbs64();

  /// Return code for a RELATIVE relocation
  static uint32_t getRelative();

  /// Return true if this relocation is PC-relative. Return false otherwise.
  bool isPCRelative() const { return isPCRelative(Type); }

  /// Return true if this relocation is R_*_RELATIVE type. Return false
  /// otherwise.
  bool isRelative() const { return isRelative(Type); }

  /// Return true if this relocation is R_*_IRELATIVE type. Return false
  /// otherwise.
  bool isIRelative() const { return isIRelative(Type); }

  /// Emit relocation at a current \p Streamer' position. The caller is
  /// responsible for setting the position correctly.
  size_t emit(MCStreamer *Streamer) const;

  /// Emit a group of composed relocations. All relocations must have the same
  /// offset. If std::distance(Begin, End) == 1, this is equivalent to
  /// Begin->emit(Streamer).
  template <typename RelocIt>
  static size_t emit(RelocIt Begin, RelocIt End, MCStreamer *Streamer) {
    if (Begin == End)
      return 0;

    const MCExpr *Value = nullptr;

    for (auto RI = Begin; RI != End; ++RI) {
      assert(RI->Offset == Begin->Offset &&
             "emitting composed relocations with different offsets");
      Value = RI->createExpr(Streamer, Value);
    }

    assert(Value && "failed to create relocation value");
    auto Size = std::prev(End)->getSize();
    Streamer->emitValue(Value, Size);
    return Size;
  }

  /// Print a relocation to \p OS.
  void print(raw_ostream &OS) const;

private:
  const MCExpr *createExpr(MCStreamer *Streamer) const;
  const MCExpr *createExpr(MCStreamer *Streamer,
                           const MCExpr *RetainedValue) const;
  static MCBinaryExpr::Opcode getComposeOpcodeFor(uint32_t Type);
};

/// Relocation ordering by offset.
inline bool operator<(const Relocation &A, const Relocation &B) {
  return A.Offset < B.Offset;
}

inline bool operator<(const Relocation &A, uint64_t B) { return A.Offset < B; }

inline bool operator<(uint64_t A, const Relocation &B) { return A < B.Offset; }

inline raw_ostream &operator<<(raw_ostream &OS, const Relocation &Rel) {
  Rel.print(OS);
  return OS;
}

} // namespace bolt
} // namespace llvm

#endif
