//===------- AArch32Tests.cpp - Unit tests for the AArch32 backend --------===//
//
// 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 <llvm/BinaryFormat/ELF.h>
#include <llvm/ExecutionEngine/JITLink/aarch32.h>

#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::jitlink;
using namespace llvm::jitlink::aarch32;
using namespace llvm::support;
using namespace llvm::support::endian;

struct MutableHalfWords {
  MutableHalfWords(HalfWords Preset) : Hi(Preset.Hi), Lo(Preset.Lo) {}

  void patch(HalfWords Value, HalfWords Mask) {
    Hi = (Hi & ~Mask.Hi) | Value.Hi;
    Lo = (Lo & ~Mask.Lo) | Value.Lo;
  }

  uint16_t Hi; // First halfword
  uint16_t Lo; // Second halfword
};

struct MutableWord {
  MutableWord(uint32_t Preset) : Wd(Preset) {}

  void patch(uint32_t Value, uint32_t Mask) { Wd = (Wd & ~Mask) | Value; }

  uint32_t Wd;
};
namespace llvm {
namespace jitlink {

Expected<aarch32::EdgeKind_aarch32>
getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &Cfg);
Expected<uint32_t> getELFRelocationType(Edge::Kind Kind);

} // namespace jitlink
} // namespace llvm

TEST(AArch32_ELF, EdgeKinds) {
  // Fails: Invalid ELF type -> JITLink kind
  aarch32::ArmConfig Cfg;
  Expected<uint32_t> ErrKind = getJITLinkEdgeKind(ELF::R_ARM_ME_TOO, Cfg);
  EXPECT_TRUE(errorToBool(ErrKind.takeError()));

  // Fails: Invalid JITLink kind -> ELF type
  Expected<uint32_t> ErrType = getELFRelocationType(Edge::Invalid);
  EXPECT_TRUE(errorToBool(ErrType.takeError()));

  for (Edge::Kind K = FirstDataRelocation; K < LastThumbRelocation; K += 1) {
    Expected<uint32_t> ELFType = getELFRelocationType(K);
    EXPECT_FALSE(errorToBool(ELFType.takeError()))
        << "Failed to translate JITLink kind -> ELF type";

    Expected<Edge::Kind> JITLinkKind = getJITLinkEdgeKind(*ELFType, Cfg);
    EXPECT_FALSE(errorToBool(JITLinkKind.takeError()))
        << "Failed to translate ELF type -> JITLink kind";

    EXPECT_EQ(*JITLinkKind, K) << "Round-trip value inconsistent?";
  }
}

TEST(AArch32_ELF, DynFixupInfos) {
  // We can do an opcode check for all Arm edges
  for (Edge::Kind K = FirstArmRelocation; K < LastArmRelocation; K += 1) {
    const auto *Info = FixupInfoBase::getDynFixupInfo(K);
    EXPECT_NE(Info, nullptr);
    const auto *InfoArm = static_cast<const FixupInfoArm *>(Info);
    EXPECT_NE(InfoArm->checkOpcode, nullptr);
    EXPECT_FALSE(InfoArm->checkOpcode(0x00000000));
  }
  // We can do an opcode check for all Thumb edges
  for (Edge::Kind K = FirstThumbRelocation; K < LastThumbRelocation; K += 1) {
    const auto *Info = FixupInfoBase::getDynFixupInfo(K);
    EXPECT_NE(Info, nullptr);
    const auto *InfoThumb = static_cast<const FixupInfoThumb *>(Info);
    EXPECT_NE(InfoThumb->checkOpcode, nullptr);
    EXPECT_FALSE(InfoThumb->checkOpcode(0x0000, 0x0000));
  }
  // We cannot do it for Data and generic edges
  EXPECT_EQ(FixupInfoBase::getDynFixupInfo(FirstDataRelocation), nullptr);
  EXPECT_EQ(FixupInfoBase::getDynFixupInfo(Edge::GenericEdgeKind::Invalid),
            nullptr);
}

namespace llvm {
namespace jitlink {
namespace aarch32 {

HalfWords encodeImmBT4BlT1BlxT2(int64_t Value);
HalfWords encodeImmBT4BlT1BlxT2_J1J2(int64_t Value);
uint32_t encodeImmBA1BlA1BlxA2(int64_t Value);
HalfWords encodeImmMovtT1MovwT3(uint16_t Value);
HalfWords encodeRegMovtT1MovwT3(int64_t Value);
uint32_t encodeImmMovtA1MovwA2(uint16_t Value);
uint32_t encodeRegMovtA1MovwA2(int64_t Value);

int64_t decodeImmBT4BlT1BlxT2(uint32_t Hi, uint32_t Lo);
int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo);
int64_t decodeImmBA1BlA1BlxA2(int64_t Value);
uint16_t decodeImmMovtT1MovwT3(uint32_t Hi, uint32_t Lo);
int64_t decodeRegMovtT1MovwT3(uint32_t Hi, uint32_t Lo);
uint16_t decodeImmMovtA1MovwA2(uint64_t Value);
int64_t decodeRegMovtA1MovwA2(uint64_t Value);

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

// Big-endian for v7 and v8 (and v6 unless in legacy backwards compatible mode
// be32) have little-endian instructions and big-endian data. In ELF relocatable
// objects big-endian instructions may still be encountered. A be8 supporting
// linker is expected to endian-reverse instructions for the executable.
template <endianness Endian>
static HalfWords makeHalfWords(std::array<uint8_t, 4> Mem) {
  return HalfWords{read16<Endian>(Mem.data()), read16<Endian>(Mem.data() + 2)};
}

/// 25-bit branch with link (with J1J2 range extension)
TEST(AArch32_Relocations, Thumb_Call_J1J2) {
  static_assert(isInt<25>(16777215), "Max value");
  static_assert(isInt<25>(-16777215), "Min value");
  static_assert(!isInt<25>(16777217), "First overflow");
  static_assert(!isInt<25>(-16777217), "First underflow");

  constexpr HalfWords ImmMask = FixupInfo<Thumb_Call>::ImmMask;

  static std::array<HalfWords, 3> MemPresets{
      makeHalfWords<endianness::little>({0xff, 0xf7, 0xfe, 0xef}), // common
      makeHalfWords<endianness::little>({0x00, 0x00, 0x00, 0x00}), // zeros
      makeHalfWords<endianness::little>({0xff, 0xff, 0xff, 0xff}), // ones
  };

  auto EncodeDecode = [ImmMask](int64_t In, MutableHalfWords &Mem) {
    Mem.patch(encodeImmBT4BlT1BlxT2_J1J2(In), ImmMask);
    return decodeImmBT4BlT1BlxT2_J1J2(Mem.Hi, Mem.Lo);
  };

  for (MutableHalfWords Mem : MemPresets) {
    HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo);

    EXPECT_EQ(EncodeDecode(1, Mem), 0);                 // Zero value
    EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40);           // Common value
    EXPECT_EQ(EncodeDecode(16777215, Mem), 16777214);   // Maximum value
    EXPECT_EQ(EncodeDecode(-16777215, Mem), -16777216); // Minimum value
    EXPECT_NE(EncodeDecode(16777217, Mem), 16777217);   // First overflow
    EXPECT_NE(EncodeDecode(-16777217, Mem), -16777217); // First underflow

    EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) &&
                UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo))
        << "Diff outside immediate field";
  }
}

/// 22-bit branch with link (without J1J2 range extension)
TEST(AArch32_Relocations, Thumb_Call_Bare) {
  static_assert(isInt<22>(2097151), "Max value");
  static_assert(isInt<22>(-2097151), "Min value");
  static_assert(!isInt<22>(2097153), "First overflow");
  static_assert(!isInt<22>(-2097153), "First underflow");

  constexpr HalfWords ImmMask = FixupInfo<Thumb_Call>::ImmMask;

  static std::array<HalfWords, 3> MemPresets{
      makeHalfWords<endianness::little>({0xff, 0xf7, 0xfe, 0xef}), // common
      makeHalfWords<endianness::little>({0x00, 0x00, 0x00, 0x00}), // zeros
      makeHalfWords<endianness::little>({0xff, 0xff, 0xff, 0xff}), // ones
  };

  auto EncodeDecode = [ImmMask](int64_t In, MutableHalfWords &Mem) {
    Mem.patch(encodeImmBT4BlT1BlxT2_J1J2(In), ImmMask);
    return decodeImmBT4BlT1BlxT2_J1J2(Mem.Hi, Mem.Lo);
  };

  for (MutableHalfWords Mem : MemPresets) {
    HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo);

    EXPECT_EQ(EncodeDecode(1, Mem), 0);               // Zero value
    EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40);         // Common value
    EXPECT_EQ(EncodeDecode(2097151, Mem), 2097150);   // Maximum value
    EXPECT_EQ(EncodeDecode(-2097151, Mem), -2097152); // Minimum value
    EXPECT_NE(EncodeDecode(2097153, Mem), 2097153);   // First overflow
    EXPECT_NE(EncodeDecode(-2097153, Mem), -2097153); // First underflow

    EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) &&
                UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo))
        << "Diff outside immediate field";
  }
}

/// 26-bit branch with link
TEST(AArch32_Relocations, Arm_Call_Bare) {
  static_assert(isInt<26>(33554430), "Max value");
  static_assert(isInt<26>(-33554432), "Min value");
  static_assert(!isInt<26>(33554432), "First overflow");
  static_assert(!isInt<26>(-33554434), "First underflow");

  constexpr uint32_t ImmMask = FixupInfo<Arm_Call>::ImmMask;

  static std::array<uint32_t, 3> MemPresets{
      0xfeeffff7, // common
      0x00000000, // zeros
      0xffffffff, // ones
  };

  auto EncodeDecode = [=](int64_t In, MutableWord &Mem) {
    Mem.patch(encodeImmBA1BlA1BlxA2(In), ImmMask);
    return decodeImmBA1BlA1BlxA2(Mem.Wd);
  };

  for (MutableWord Mem : MemPresets) {
    MutableWord UnaffectedBits(Mem.Wd & ~ImmMask);

    EXPECT_EQ(EncodeDecode(0, Mem), 0);                 // Zero value
    EXPECT_EQ(EncodeDecode(0x40, Mem), 0x40);           // Common value
    EXPECT_EQ(EncodeDecode(33554428, Mem), 33554428);   // Maximum value
    EXPECT_EQ(EncodeDecode(-33554432, Mem), -33554432); // Minimum value
    EXPECT_NE(EncodeDecode(33554434, Mem), 33554434);   // First overflow
    EXPECT_NE(EncodeDecode(-33554434, Mem), -33554434); // First underflow

    EXPECT_TRUE(UnaffectedBits.Wd == (Mem.Wd & ~ImmMask))
        << "Diff outside immediate field";
  }
}

/// Write immediate value to the top halfword of the destination register
TEST(AArch32_Relocations, Thumb_MovtAbs) {
  static_assert(isUInt<16>(65535), "Max value");
  static_assert(!isUInt<16>(65536), "First overflow");

  constexpr HalfWords ImmMask = FixupInfo<Thumb_MovtAbs>::ImmMask;
  constexpr HalfWords RegMask = FixupInfo<Thumb_MovtAbs>::RegMask;

  static std::array<uint8_t, 3> Registers{0, 5, 12};
  static std::array<HalfWords, 3> MemPresets{
      makeHalfWords<endianness::little>({0xff, 0xf7, 0xfe, 0xef}), // common
      makeHalfWords<endianness::little>({0x00, 0x00, 0x00, 0x00}), // zeros
      makeHalfWords<endianness::little>({0xff, 0xff, 0xff, 0xff}), // ones
  };

  auto EncodeDecode = [ImmMask](uint32_t In, MutableHalfWords &Mem) {
    Mem.patch(encodeImmMovtT1MovwT3(In), ImmMask);
    return decodeImmMovtT1MovwT3(Mem.Hi, Mem.Lo);
  };

  for (MutableHalfWords Mem : MemPresets) {
    for (uint8_t Reg : Registers) {
      HalfWords UnaffectedBits(Mem.Hi & ~(ImmMask.Hi | RegMask.Hi),
                               Mem.Lo & ~(ImmMask.Lo | RegMask.Lo));

      Mem.patch(encodeRegMovtT1MovwT3(Reg), RegMask);
      EXPECT_EQ(EncodeDecode(0x76bb, Mem), 0x76bb);   // Common value
      EXPECT_EQ(EncodeDecode(0, Mem), 0);             // Minimum value
      EXPECT_EQ(EncodeDecode(0xffff, Mem), 0xffff);   // Maximum value
      EXPECT_NE(EncodeDecode(0x10000, Mem), 0x10000); // First overflow

      // Destination register as well as unaffected bits should be intact
      EXPECT_EQ(decodeRegMovtT1MovwT3(Mem.Hi, Mem.Lo), Reg);
      EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~(ImmMask.Hi | RegMask.Hi)) &&
                  UnaffectedBits.Lo == (Mem.Lo & ~(ImmMask.Lo | RegMask.Lo)))
          << "Diff outside immediate/register field";
    }
  }
}

/// Write immediate value to the top halfword of the destination register
TEST(AArch32_Relocations, Arm_MovtAbs) {
  static_assert(isUInt<16>(65535), "Max value");
  static_assert(!isUInt<16>(65536), "First overflow");

  constexpr uint32_t ImmMask = FixupInfo<Arm_MovtAbs>::ImmMask;
  constexpr uint32_t RegMask = FixupInfo<Arm_MovtAbs>::RegMask;

  static std::array<uint8_t, 3> Registers{0, 5, 12};
  static std::array<uint32_t, 3> MemPresets{
      0xfeeffff7, // common
      0x00000000, // zeros
      0xffffffff, // ones
  };

  auto EncodeDecode = [=](uint64_t In, MutableWord &Mem) {
    Mem.patch(encodeImmMovtA1MovwA2(In), ImmMask);
    return decodeImmMovtA1MovwA2(Mem.Wd);
  };

  for (MutableWord Mem : MemPresets) {
    for (uint8_t Reg : Registers) {
      MutableWord UnaffectedBits(Mem.Wd & ~(ImmMask | RegMask));

      Mem.patch(encodeRegMovtA1MovwA2(Reg), RegMask);
      EXPECT_EQ(EncodeDecode(0x76bb, Mem), 0x76bb);   // Common value
      EXPECT_EQ(EncodeDecode(0, Mem), 0);             // Minimum value
      EXPECT_EQ(EncodeDecode(0xffff, Mem), 0xffff);   // Maximum value
      EXPECT_NE(EncodeDecode(0x10000, Mem), 0x10000); // First overflow

      // Destination register as well as unaffected bits should be intact
      EXPECT_EQ(decodeRegMovtA1MovwA2(Mem.Wd), Reg);
      EXPECT_TRUE(UnaffectedBits.Wd == (Mem.Wd & ~(ImmMask | RegMask)))
          << "Diff outside immediate/register field";
    }
  }
}
