//===------ 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/Error.h"

namespace llvm {
namespace jitlink {
namespace aarch32 {

/// 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,

  LastDataRelocation = Data_Pointer32,

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

  /// TODO: Arm_Call is here only as a placeholder for now.
  Arm_Call = FirstArmRelocation,

  LastArmRelocation = Arm_Call,

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

  /// Write immediate value for PC-relative branch with link (can bridge between
  /// Arm and Thumb).
  Thumb_Call = FirstThumbRelocation,

  /// Write immediate value for (unconditional) PC-relative branch without link.
  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,

  LastThumbRelocation = Thumb_MovtAbs,
};

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

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

/// Get a human-readable name for the given AArch32 edge kind.
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 StubsFlavor {
  Unsupported = 0,
  Thumbv7,
};

/// JITLink sub-arch configuration for Arm CPU models
struct ArmConfig {
  bool J1J2BranchEncoding = false;
  StubsFlavor Stubs = Unsupported;
};

/// Obtain the sub-arch configuration for a given Arm CPU model.
inline ArmConfig getArmConfigForCPUArch(ARMBuildAttrs::CPUArch CPUArch) {
  ArmConfig ArmCfg;
  switch (CPUArch) {
  case ARMBuildAttrs::v7:
  case ARMBuildAttrs::v8_A:
    ArmCfg.J1J2BranchEncoding = true;
    ArmCfg.Stubs = Thumbv7;
    break;
  default:
    DEBUG_WITH_TYPE("jitlink", {
      dbgs() << "  Warning: ARM config not defined for CPU architecture "
             << getCPUArchName(CPUArch);
    });
    break;
  }
  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
};

/// Collection of named constants per fixup kind. It may contain but is not
/// limited to the following 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
///   ImmMask     - Mask with all bits set that encode the immediate value
///   RegMask     - Mask with all bits set that encode the register
///
template <EdgeKind_aarch32 Kind> struct FixupInfo {};

template <> struct FixupInfo<Thumb_Jump24> {
  static constexpr HalfWords Opcode{0xf000, 0x8000};
  static constexpr HalfWords OpcodeMask{0xf800, 0x8000};
  static constexpr HalfWords ImmMask{0x07ff, 0x2fff};
  static constexpr uint16_t LoBitConditional = 0x1000;
};

template <> struct FixupInfo<Thumb_Call> {
  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;
};

template <> struct FixupInfo<Thumb_MovtAbs> {
  static constexpr HalfWords Opcode{0xf2c0, 0x0000};
  static constexpr HalfWords OpcodeMask{0xfbf0, 0x8000};
  static constexpr HalfWords ImmMask{0x040f, 0x70ff};
  static constexpr HalfWords RegMask{0x0000, 0x0f00};
};

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

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

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

/// Helper function to read the initial addend for Thumb-class relocations.
Expected<int64_t> readAddendThumb(LinkGraph &G, Block &B, const Edge &E,
                                  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, const Edge &E,
                                    const ArmConfig &ArmCfg) {
  Edge::Kind Kind = E.getKind();
  if (Kind <= LastDataRelocation)
    return readAddendData(G, B, E);

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

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

  llvm_unreachable("Relocation must be of class Data, Arm or Thumb");
}

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

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

/// Helper function to apply the fixup for Thumb-class relocations.
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);

  llvm_unreachable("Relocation must be of class Data, Arm or Thumb");
}

/// Stubs builder for a specific StubsFlavor
///
/// Right now we only have one default stub kind, but we want to extend this
/// and allow creation of specific kinds in the future (e.g. branch range
/// extension or interworking).
///
/// Let's keep it simple for the moment and not wire this through a GOT.
///
template <StubsFlavor Flavor>
class StubsManager : public TableManager<StubsManager<Flavor>> {
public:
  StubsManager() = default;

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

  /// Implements link-graph traversal via visitExistingEdges().
  bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
    if (E.getTarget().isDefined())
      return false;

    switch (E.getKind()) {
    case Thumb_Call:
    case Thumb_Jump24: {
      DEBUG_WITH_TYPE("jitlink", {
        dbgs() << "  Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
               << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
               << formatv("{0:x}", E.getOffset()) << ")\n";
      });
      E.setTarget(this->getEntryForTarget(G, E.getTarget()));
      return true;
    }
    }
    return false;
  }

  /// Create a branch range extension stub for the class's flavor.
  Symbol &createEntry(LinkGraph &G, Symbol &Target);

private:
  /// Create a new node in the link-graph for the given stub template.
  template <size_t Size>
  Block &addStub(LinkGraph &G, const uint8_t (&Code)[Size],
                 uint64_t Alignment) {
    ArrayRef<char> Template(reinterpret_cast<const char *>(Code), Size);
    return G.createContentBlock(getStubsSection(G), Template,
                                orc::ExecutorAddr(), Alignment, 0);
  }

  /// Get or create the object file section that will contain all our stubs.
  Section &getStubsSection(LinkGraph &G) {
    if (!StubsSection)
      StubsSection = &G.createSection(getSectionName(),
                                      orc::MemProt::Read | orc::MemProt::Exec);
    return *StubsSection;
  }

  Section *StubsSection = nullptr;
};

/// Create a branch range extension stub with Thumb encoding for v7 CPUs.
template <>
Symbol &StubsManager<Thumbv7>::createEntry(LinkGraph &G, Symbol &Target);

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

#endif // LLVM_EXECUTIONENGINE_JITLINK_AARCH32
