//===------ aarch32.h - Generic JITLink arm/thumb utilities -----*- 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
//
//===----------------------------------------------------------------------===//
//
// Generic utilities for graphs representing arm/thumb objects.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_EXECUTIONENGINE_JITLINK_AARCH32
#define LLVM_EXECUTIONENGINE_JITLINK_AARCH32

#include "TableManager.h"
#include "llvm/ExecutionEngine/JITLink/JITLink.h"
#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h"
#include "llvm/Support/ARMBuildAttributes.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Error.h"

namespace llvm {
namespace jitlink {
namespace aarch32 {

/// Check whether the given target flags are set for this Symbol.
LLVM_ABI bool hasTargetFlags(Symbol &Sym, TargetFlagsType Flags);

/// JITLink-internal AArch32 fixup kinds
enum EdgeKind_aarch32 : Edge::Kind {

  ///
  /// Relocations of class Data respect target endianness (unless otherwise
  /// specified)
  ///
  FirstDataRelocation = Edge::FirstRelocation,

  /// Relative 32-bit value relocation
  Data_Delta32 = FirstDataRelocation,

  /// Absolute 32-bit value relocation
  Data_Pointer32,

  /// Relative 31-bit value relocation that preserves the most-significant bit
  Data_PRel31,

  /// Create GOT entry and store offset
  Data_RequestGOTAndTransformToDelta32,

  LastDataRelocation = Data_RequestGOTAndTransformToDelta32,

  ///
  /// Relocations of class Arm (covers fixed-width 4-byte instruction subset)
  ///
  FirstArmRelocation,

  /// Write immediate value for unconditional PC-relative branch with link.
  /// We patch the instruction opcode to account for an instruction-set state
  /// switch: we use the bl instruction to stay in ARM and the blx instruction
  /// to switch to Thumb.
  Arm_Call = FirstArmRelocation,

  /// Write immediate value for conditional PC-relative branch without link.
  /// If the branch target is not ARM, we are forced to generate an explicit
  /// interworking stub.
  Arm_Jump24,

  /// Write immediate value to the lower halfword of the destination register
  Arm_MovwAbsNC,

  /// Write immediate value to the top halfword of the destination register
  Arm_MovtAbs,

  LastArmRelocation = Arm_MovtAbs,

  ///
  /// Relocations of class Thumb16 and Thumb32 (covers Thumb instruction subset)
  ///
  FirstThumbRelocation,

  /// Write immediate value for unconditional PC-relative branch with link.
  /// We patch the instruction opcode to account for an instruction-set state
  /// switch: we use the bl instruction to stay in Thumb and the blx instruction
  /// to switch to ARM.
  Thumb_Call = FirstThumbRelocation,

  /// Write immediate value for PC-relative branch without link. The instruction
  /// can be made conditional by an IT block. If the branch target is not ARM,
  /// we are forced to generate an explicit interworking stub.
  Thumb_Jump24,

  /// Write immediate value to the lower halfword of the destination register
  Thumb_MovwAbsNC,

  /// Write immediate value to the top halfword of the destination register
  Thumb_MovtAbs,

  /// Write PC-relative immediate value to the lower halfword of the destination
  /// register
  Thumb_MovwPrelNC,

  /// Write PC-relative immediate value to the top halfword of the destination
  /// register
  Thumb_MovtPrel,

  LastThumbRelocation = Thumb_MovtPrel,

  /// No-op relocation
  None,

  LastRelocation = None,
};

/// Flags enum for AArch32-specific symbol properties
enum TargetFlags_aarch32 : TargetFlagsType {
  ThumbSymbol = 1 << 0,
};

/// Human-readable name for a given CPU architecture kind
LLVM_ABI const char *getCPUArchName(ARMBuildAttrs::CPUArch K);

/// Get a human-readable name for the given AArch32 edge kind.
LLVM_ABI const char *getEdgeKindName(Edge::Kind K);

/// AArch32 uses stubs for a number of purposes, like branch range extension
/// or interworking between Arm and Thumb instruction subsets.
///
/// Stub implementations vary depending on CPU architecture (v4, v6, v7),
/// instruction subset and branch type (absolute/PC-relative).
///
/// For each kind of stub, the StubsFlavor defines one concrete form that is
/// used throughout the LinkGraph.
///
/// Stubs are often called "veneers" in the official docs and online.
///
enum class StubsFlavor {
  Undefined = 0,
  pre_v7,
  v7,
};

/// JITLink sub-arch configuration for Arm CPU models
struct ArmConfig {
  bool J1J2BranchEncoding = false;
  StubsFlavor Stubs = StubsFlavor::Undefined;
  // In the long term, we might want a linker switch like --target1-rel
  bool Target1Rel = false;
};

/// Obtain the sub-arch configuration for a given Arm CPU model.
inline ArmConfig getArmConfigForCPUArch(ARMBuildAttrs::CPUArch CPUArch) {
  ArmConfig ArmCfg;
  if (CPUArch == ARMBuildAttrs::v7 || CPUArch >= ARMBuildAttrs::v7E_M) {
    ArmCfg.J1J2BranchEncoding = true;
    ArmCfg.Stubs = StubsFlavor::v7;
  } else {
    ArmCfg.J1J2BranchEncoding = false;
    ArmCfg.Stubs = StubsFlavor::pre_v7;
  }
  return ArmCfg;
}

/// Immutable pair of halfwords, Hi and Lo, with overflow check
struct HalfWords {
  constexpr HalfWords() : Hi(0), Lo(0) {}
  constexpr HalfWords(uint32_t Hi, uint32_t Lo) : Hi(Hi), Lo(Lo) {
    assert(isUInt<16>(Hi) && "Overflow in first half-word");
    assert(isUInt<16>(Lo) && "Overflow in second half-word");
  }
  const uint16_t Hi; // First halfword
  const uint16_t Lo; // Second halfword
};

/// FixupInfo base class is required for dynamic lookups.
struct FixupInfoBase {
  LLVM_ABI static const FixupInfoBase *getDynFixupInfo(Edge::Kind K);
  virtual ~FixupInfoBase() = default;
};

/// FixupInfo checks for Arm edge kinds work on 32-bit words
struct FixupInfoArm : public FixupInfoBase {
  bool (*checkOpcode)(uint32_t Wd) = nullptr;
};

/// FixupInfo check for Thumb32 edge kinds work on a pair of 16-bit halfwords
struct FixupInfoThumb : public FixupInfoBase {
  bool (*checkOpcode)(uint16_t Hi, uint16_t Lo) = nullptr;
};

/// Collection of named constants per fixup kind
///
/// Mandatory entries:
///   Opcode      - Values of the op-code bits in the instruction, with
///                 unaffected bits nulled
///   OpcodeMask  - Mask with all bits set that encode the op-code
///
/// Other common entries:
///   ImmMask     - Mask with all bits set that encode the immediate value
///   RegMask     - Mask with all bits set that encode the register
///
/// Specializations can add further custom fields without restrictions.
///
template <EdgeKind_aarch32 Kind> struct FixupInfo {};

struct FixupInfoArmBranch : public FixupInfoArm {
  static constexpr uint32_t Opcode = 0x0a000000;
  static constexpr uint32_t ImmMask = 0x00ffffff;
};

template <> struct FixupInfo<Arm_Jump24> : public FixupInfoArmBranch {
  static constexpr uint32_t OpcodeMask = 0x0f000000;
};

template <> struct FixupInfo<Arm_Call> : public FixupInfoArmBranch {
  static constexpr uint32_t OpcodeMask = 0x0e000000;
  static constexpr uint32_t CondMask = 0xe0000000; // excluding BLX bit
  static constexpr uint32_t Unconditional = 0xe0000000;
  static constexpr uint32_t BitH = 0x01000000;
  static constexpr uint32_t BitBlx = 0x10000000;
};

struct FixupInfoArmMov : public FixupInfoArm {
  static constexpr uint32_t OpcodeMask = 0x0ff00000;
  static constexpr uint32_t ImmMask = 0x000f0fff;
  static constexpr uint32_t RegMask = 0x0000f000;
};

template <> struct FixupInfo<Arm_MovtAbs> : public FixupInfoArmMov {
  static constexpr uint32_t Opcode = 0x03400000;
};

template <> struct FixupInfo<Arm_MovwAbsNC> : public FixupInfoArmMov {
  static constexpr uint32_t Opcode = 0x03000000;
};

template <> struct FixupInfo<Thumb_Jump24> : public FixupInfoThumb {
  static constexpr HalfWords Opcode{0xf000, 0x9000};
  static constexpr HalfWords OpcodeMask{0xf800, 0x9000};
  static constexpr HalfWords ImmMask{0x07ff, 0x2fff};
};

template <> struct FixupInfo<Thumb_Call> : public FixupInfoThumb {
  static constexpr HalfWords Opcode{0xf000, 0xc000};
  static constexpr HalfWords OpcodeMask{0xf800, 0xc000};
  static constexpr HalfWords ImmMask{0x07ff, 0x2fff};
  static constexpr uint16_t LoBitH = 0x0001;
  static constexpr uint16_t LoBitNoBlx = 0x1000;
};

struct FixupInfoThumbMov : public FixupInfoThumb {
  static constexpr HalfWords OpcodeMask{0xfbf0, 0x8000};
  static constexpr HalfWords ImmMask{0x040f, 0x70ff};
  static constexpr HalfWords RegMask{0x0000, 0x0f00};
};

template <> struct FixupInfo<Thumb_MovtAbs> : public FixupInfoThumbMov {
  static constexpr HalfWords Opcode{0xf2c0, 0x0000};
};

template <> struct FixupInfo<Thumb_MovtPrel> : public FixupInfoThumbMov {
  static constexpr HalfWords Opcode{0xf2c0, 0x0000};
};

template <> struct FixupInfo<Thumb_MovwAbsNC> : public FixupInfoThumbMov {
  static constexpr HalfWords Opcode{0xf240, 0x0000};
};

template <> struct FixupInfo<Thumb_MovwPrelNC> : public FixupInfoThumbMov {
  static constexpr HalfWords Opcode{0xf240, 0x0000};
};

/// Helper function to read the initial addend for Data-class relocations.
LLVM_ABI Expected<int64_t>
readAddendData(LinkGraph &G, Block &B, Edge::OffsetT Offset, Edge::Kind Kind);

/// Helper function to read the initial addend for Arm-class relocations.
LLVM_ABI Expected<int64_t> readAddendArm(LinkGraph &G, Block &B,
                                         Edge::OffsetT Offset, Edge::Kind Kind);

/// Helper function to read the initial addend for Thumb-class relocations.
LLVM_ABI Expected<int64_t> readAddendThumb(LinkGraph &G, Block &B,
                                           Edge::OffsetT Offset,
                                           Edge::Kind Kind,
                                           const ArmConfig &ArmCfg);

/// Read the initial addend for a REL-type relocation. It's the value encoded
/// in the immediate field of the fixup location by the compiler.
inline Expected<int64_t> readAddend(LinkGraph &G, Block &B,
                                    Edge::OffsetT Offset, Edge::Kind Kind,
                                    const ArmConfig &ArmCfg) {
  if (Kind <= LastDataRelocation)
    return readAddendData(G, B, Offset, Kind);

  if (Kind <= LastArmRelocation)
    return readAddendArm(G, B, Offset, Kind);

  if (Kind <= LastThumbRelocation)
    return readAddendThumb(G, B, Offset, Kind, ArmCfg);

  assert(Kind == None && "Not associated with a relocation class");
  return 0;
}

/// Helper function to apply the fixup for Data-class relocations.
LLVM_ABI Error applyFixupData(LinkGraph &G, Block &B, const Edge &E);

/// Helper function to apply the fixup for Arm-class relocations.
LLVM_ABI Error applyFixupArm(LinkGraph &G, Block &B, const Edge &E);

/// Helper function to apply the fixup for Thumb-class relocations.
LLVM_ABI Error applyFixupThumb(LinkGraph &G, Block &B, const Edge &E,
                               const ArmConfig &ArmCfg);

/// Apply fixup expression for edge to block content.
inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
                        const ArmConfig &ArmCfg) {
  Edge::Kind Kind = E.getKind();

  if (Kind <= LastDataRelocation)
    return applyFixupData(G, B, E);

  if (Kind <= LastArmRelocation)
    return applyFixupArm(G, B, E);

  if (Kind <= LastThumbRelocation)
    return applyFixupThumb(G, B, E, ArmCfg);

  assert(Kind == None && "Not associated with a relocation class");
  return Error::success();
}

/// Populate a Global Offset Table from edges that request it.
class GOTBuilder : public TableManager<GOTBuilder> {
public:
  static StringRef getSectionName() { return "$__GOT"; }

  LLVM_ABI bool visitEdge(LinkGraph &G, Block *B, Edge &E);
  LLVM_ABI Symbol &createEntry(LinkGraph &G, Symbol &Target);

private:
  Section *GOTSection = nullptr;
};

/// Stubs builder emits non-position-independent Arm stubs for pre-v7 CPUs.
/// These architectures have no MovT/MovW instructions and don't support Thumb2.
/// BL is the only Thumb instruction that can generate stubs and they can always
/// be transformed into BLX.
class StubsManager_prev7 {
public:
  StubsManager_prev7() = default;

  /// Name of the object file section that will contain all our stubs.
  static StringRef getSectionName() {
    return "__llvm_jitlink_aarch32_STUBS_prev7";
  }

  /// Implements link-graph traversal via visitExistingEdges()
  LLVM_ABI bool visitEdge(LinkGraph &G, Block *B, Edge &E);

private:
  // Each stub uses a single block that can have 2 entryponts, one for Arm and
  // one for Thumb
  struct StubMapEntry {
    Block *B = nullptr;
    Symbol *ArmEntry = nullptr;
    Symbol *ThumbEntry = nullptr;
  };

  std::pair<StubMapEntry *, bool> getStubMapSlot(StringRef Name) {
    auto &&[Stubs, NewStub] = StubMap.try_emplace(Name);
    return std::make_pair(&Stubs->second, NewStub);
  }

  Symbol *getOrCreateSlotEntrypoint(LinkGraph &G, StubMapEntry &Slot,
                                    bool Thumb);

  DenseMap<StringRef, StubMapEntry> StubMap;
  Section *StubsSection = nullptr;
};

/// Stubs builder for v7 emits non-position-independent Arm and Thumb stubs.
class StubsManager_v7 {
public:
  StubsManager_v7() = default;

  /// Name of the object file section that will contain all our stubs.
  static StringRef getSectionName() {
    return "__llvm_jitlink_aarch32_STUBS_v7";
  }

  /// Implements link-graph traversal via visitExistingEdges().
  LLVM_ABI bool visitEdge(LinkGraph &G, Block *B, Edge &E);

private:
  // Two slots per external: Arm and Thumb
  using StubMapEntry = std::tuple<Symbol *, Symbol *>;

  Symbol *&getStubSymbolSlot(StringRef Name, bool Thumb) {
    StubMapEntry &Stubs = StubMap[Name];
    if (Thumb)
      return std::get<1>(Stubs);
    return std::get<0>(Stubs);
  }

  DenseMap<StringRef, StubMapEntry> StubMap;
  Section *StubsSection = nullptr;
};

} // namespace aarch32
} // namespace jitlink
} // namespace llvm

#endif // LLVM_EXECUTIONENGINE_JITLINK_AARCH32
