/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "linker/relative_patcher_test.h"
#include "linker/arm/relative_patcher_thumb2.h"
#include "oat_quick_method_header.h"

namespace art {
namespace linker {

class Thumb2RelativePatcherTest : public RelativePatcherTest {
 public:
  Thumb2RelativePatcherTest() : RelativePatcherTest(kThumb2, "default") { }

 protected:
  static const uint8_t kCallRawCode[];
  static const ArrayRef<const uint8_t> kCallCode;
  static const uint8_t kNopRawCode[];
  static const ArrayRef<const uint8_t> kNopCode;
  static const uint8_t kUnpatchedPcRelativeRawCode[];
  static const ArrayRef<const uint8_t> kUnpatchedPcRelativeCode;
  static const uint32_t kPcInsnOffset;

  // Branches within range [-256, 256) can be created from these by adding the low 8 bits.
  static constexpr uint32_t kBlPlus0 = 0xf000f800;
  static constexpr uint32_t kBlMinus256 = 0xf7ffff00;

  // Special BL values.
  static constexpr uint32_t kBlPlusMax = 0xf3ffd7ff;
  static constexpr uint32_t kBlMinusMax = 0xf400d000;

  bool Create2MethodsWithGap(const ArrayRef<const uint8_t>& method1_code,
                             const ArrayRef<const LinkerPatch>& method1_patches,
                             const ArrayRef<const uint8_t>& method3_code,
                             const ArrayRef<const LinkerPatch>& method3_patches,
                             uint32_t distance_without_thunks) {
    CHECK_EQ(distance_without_thunks % kArmAlignment, 0u);
    uint32_t method1_offset =
        kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader);
    AddCompiledMethod(MethodRef(1u), method1_code, method1_patches);

    // We want to put the method3 at a very precise offset.
    const uint32_t method3_offset = method1_offset + distance_without_thunks;
    CHECK_ALIGNED(method3_offset, kArmAlignment);

    // Calculate size of method2 so that we put method3 at the correct place.
    const uint32_t method1_end = method1_offset + method1_code.size();
    const uint32_t method2_offset =
        method1_end + CodeAlignmentSize(method1_end) + sizeof(OatQuickMethodHeader);
    const uint32_t method2_size = (method3_offset - sizeof(OatQuickMethodHeader) - method2_offset);
    std::vector<uint8_t> method2_raw_code(method2_size);
    ArrayRef<const uint8_t> method2_code(method2_raw_code);
    AddCompiledMethod(MethodRef(2u), method2_code);

    AddCompiledMethod(MethodRef(3u), method3_code, method3_patches);

    Link();

    // Check assumptions.
    CHECK_EQ(GetMethodOffset(1), method1_offset);
    CHECK_EQ(GetMethodOffset(2), method2_offset);
    auto result3 = method_offset_map_.FindMethodOffset(MethodRef(3));
    CHECK(result3.first);
    // There may be a thunk before method2.
    if (result3.second == method3_offset + 1 /* thumb mode */) {
      return false;  // No thunk.
    } else {
      uint32_t thunk_end =
          CompiledCode::AlignCode(method3_offset - sizeof(OatQuickMethodHeader), kThumb2) +
          MethodCallThunkSize();
      uint32_t header_offset = thunk_end + CodeAlignmentSize(thunk_end);
      CHECK_EQ(result3.second, header_offset + sizeof(OatQuickMethodHeader) + 1 /* thumb mode */);
      return true;   // Thunk present.
    }
  }

  uint32_t GetMethodOffset(uint32_t method_idx) {
    auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx));
    CHECK(result.first);
    CHECK_NE(result.second & 1u, 0u);
    return result.second - 1 /* thumb mode */;
  }

  std::vector<uint8_t> CompileMethodCallThunk() {
    ArmBaseRelativePatcher::ThunkKey key(
        ArmBaseRelativePatcher::ThunkType::kMethodCall,
        ArmBaseRelativePatcher::ThunkParams{{ 0, 0 }});  // NOLINT(whitespace/braces)
    return static_cast<Thumb2RelativePatcher*>(patcher_.get())->CompileThunk(key);
  }

  uint32_t MethodCallThunkSize() {
    return CompileMethodCallThunk().size();
  }

  bool CheckThunk(uint32_t thunk_offset) {
    const std::vector<uint8_t> expected_code = CompileMethodCallThunk();
    if (output_.size() < thunk_offset + expected_code.size()) {
      LOG(ERROR) << "output_.size() == " << output_.size() << " < "
          << "thunk_offset + expected_code.size() == " << (thunk_offset + expected_code.size());
      return false;
    }
    ArrayRef<const uint8_t> linked_code(&output_[thunk_offset], expected_code.size());
    if (linked_code == ArrayRef<const uint8_t>(expected_code)) {
      return true;
    }
    // Log failure info.
    DumpDiff(ArrayRef<const uint8_t>(expected_code), linked_code);
    return false;
  }

  std::vector<uint8_t> GenNopsAndBl(size_t num_nops, uint32_t bl) {
    std::vector<uint8_t> result;
    result.reserve(num_nops * 2u + 4u);
    for (size_t i = 0; i != num_nops; ++i) {
      result.push_back(0x00);
      result.push_back(0xbf);
    }
    result.push_back(static_cast<uint8_t>(bl >> 16));
    result.push_back(static_cast<uint8_t>(bl >> 24));
    result.push_back(static_cast<uint8_t>(bl));
    result.push_back(static_cast<uint8_t>(bl >> 8));
    return result;
  }

  void TestDexCacheReference(uint32_t dex_cache_arrays_begin, uint32_t element_offset);
  void TestStringReference(uint32_t string_offset);
  void CheckPcRelativePatch(const ArrayRef<const LinkerPatch>& patches, uint32_t target_offset);
};

const uint8_t Thumb2RelativePatcherTest::kCallRawCode[] = {
    0x00, 0xf0, 0x00, 0xf8
};

const ArrayRef<const uint8_t> Thumb2RelativePatcherTest::kCallCode(kCallRawCode);

const uint8_t Thumb2RelativePatcherTest::kNopRawCode[] = {
    0x00, 0xbf
};

const ArrayRef<const uint8_t> Thumb2RelativePatcherTest::kNopCode(kNopRawCode);

const uint8_t Thumb2RelativePatcherTest::kUnpatchedPcRelativeRawCode[] = {
    0x40, 0xf2, 0x00, 0x00,   // MOVW r0, #0 (placeholder)
    0xc0, 0xf2, 0x00, 0x00,   // MOVT r0, #0 (placeholder)
    0x78, 0x44,               // ADD r0, pc
};
const ArrayRef<const uint8_t> Thumb2RelativePatcherTest::kUnpatchedPcRelativeCode(
    kUnpatchedPcRelativeRawCode);
const uint32_t Thumb2RelativePatcherTest::kPcInsnOffset = 8u;

void Thumb2RelativePatcherTest::TestDexCacheReference(uint32_t dex_cache_arrays_begin,
                                                      uint32_t element_offset) {
  dex_cache_arrays_begin_ = dex_cache_arrays_begin;
  LinkerPatch patches[] = {
      LinkerPatch::DexCacheArrayPatch(0u, nullptr, kPcInsnOffset, element_offset),
      LinkerPatch::DexCacheArrayPatch(4u, nullptr, kPcInsnOffset, element_offset),
  };
  CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches),
                       dex_cache_arrays_begin_ + element_offset);
}

void Thumb2RelativePatcherTest::TestStringReference(uint32_t string_offset) {
  constexpr uint32_t kStringIndex = 1u;
  string_index_to_offset_map_.Put(kStringIndex, string_offset);
  LinkerPatch patches[] = {
      LinkerPatch::RelativeStringPatch(0u, nullptr, kPcInsnOffset, kStringIndex),
      LinkerPatch::RelativeStringPatch(4u, nullptr, kPcInsnOffset, kStringIndex),
  };
  CheckPcRelativePatch(ArrayRef<const LinkerPatch>(patches), string_offset);
}

void Thumb2RelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const LinkerPatch>& patches,
                                                     uint32_t target_offset) {
  AddCompiledMethod(MethodRef(1u), kUnpatchedPcRelativeCode, ArrayRef<const LinkerPatch>(patches));
  Link();

  uint32_t method1_offset = GetMethodOffset(1u);
  uint32_t pc_base_offset = method1_offset + kPcInsnOffset + 4u /* PC adjustment */;
  uint32_t diff = target_offset - pc_base_offset;
  // Distribute the bits of the diff between the MOVW and MOVT:
  uint32_t diffw = diff & 0xffffu;
  uint32_t difft = diff >> 16;
  uint32_t movw = 0xf2400000u |           // MOVW r0, #0 (placeholder),
      ((diffw & 0xf000u) << (16 - 12)) |  // move imm4 from bits 12-15 to bits 16-19,
      ((diffw & 0x0800u) << (26 - 11)) |  // move imm from bit 11 to bit 26,
      ((diffw & 0x0700u) << (12 - 8)) |   // move imm3 from bits 8-10 to bits 12-14,
      ((diffw & 0x00ffu));                // keep imm8 at bits 0-7.
  uint32_t movt = 0xf2c00000u |           // MOVT r0, #0 (placeholder),
      ((difft & 0xf000u) << (16 - 12)) |  // move imm4 from bits 12-15 to bits 16-19,
      ((difft & 0x0800u) << (26 - 11)) |  // move imm from bit 11 to bit 26,
      ((difft & 0x0700u) << (12 - 8)) |   // move imm3 from bits 8-10 to bits 12-14,
      ((difft & 0x00ffu));                // keep imm8 at bits 0-7.
  const uint8_t expected_code[] = {
      static_cast<uint8_t>(movw >> 16), static_cast<uint8_t>(movw >> 24),
      static_cast<uint8_t>(movw >> 0), static_cast<uint8_t>(movw >> 8),
      static_cast<uint8_t>(movt >> 16), static_cast<uint8_t>(movt >> 24),
      static_cast<uint8_t>(movt >> 0), static_cast<uint8_t>(movt >> 8),
      0x78, 0x44,
  };
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallSelf) {
  LinkerPatch patches[] = {
      LinkerPatch::RelativeCodePatch(0u, nullptr, 1u),
  };
  AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(patches));
  Link();

  static const uint8_t expected_code[] = {
      0xff, 0xf7, 0xfe, 0xff
  };
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallOther) {
  LinkerPatch method1_patches[] = {
      LinkerPatch::RelativeCodePatch(0u, nullptr, 2u),
  };
  AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(method1_patches));
  LinkerPatch method2_patches[] = {
      LinkerPatch::RelativeCodePatch(0u, nullptr, 1u),
  };
  AddCompiledMethod(MethodRef(2u), kCallCode, ArrayRef<const LinkerPatch>(method2_patches));
  Link();

  uint32_t method1_offset = GetMethodOffset(1u);
  uint32_t method2_offset = GetMethodOffset(2u);
  uint32_t diff_after = method2_offset - (method1_offset + 4u /* PC adjustment */);
  ASSERT_EQ(diff_after & 1u, 0u);
  ASSERT_LT(diff_after >> 1, 1u << 8);  // Simple encoding, (diff_after >> 1) fits into 8 bits.
  static const uint8_t method1_expected_code[] = {
      0x00, 0xf0, static_cast<uint8_t>(diff_after >> 1), 0xf8
  };
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code)));
  uint32_t diff_before = method1_offset - (method2_offset + 4u /* PC adjustment */);
  ASSERT_EQ(diff_before & 1u, 0u);
  ASSERT_GE(diff_before, -1u << 9);  // Simple encoding, -256 <= (diff >> 1) < 0.
  auto method2_expected_code = GenNopsAndBl(0u, kBlMinus256 | ((diff_before >> 1) & 0xffu));
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallTrampoline) {
  LinkerPatch patches[] = {
      LinkerPatch::RelativeCodePatch(0u, nullptr, 2u),
  };
  AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(patches));
  Link();

  uint32_t method1_offset = GetMethodOffset(1u);
  uint32_t diff = kTrampolineOffset - (method1_offset + 4u);
  ASSERT_EQ(diff & 1u, 0u);
  ASSERT_GE(diff, -1u << 9);  // Simple encoding, -256 <= (diff >> 1) < 0 (checked as unsigned).
  auto expected_code = GenNopsAndBl(0u, kBlMinus256 | ((diff >> 1) & 0xffu));
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallTrampolineTooFar) {
  constexpr uint32_t missing_method_index = 1024u;
  auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0);
  constexpr uint32_t bl_offset_in_method3 = 3u * 2u;  // After NOPs.
  ArrayRef<const uint8_t> method3_code(method3_raw_code);
  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
  LinkerPatch method3_patches[] = {
      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, missing_method_index),
  };

  constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */;
  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
                                            ArrayRef<const LinkerPatch>(),
                                            method3_code,
                                            ArrayRef<const LinkerPatch>(method3_patches),
                                            just_over_max_negative_disp - bl_offset_in_method3);
  ASSERT_FALSE(thunk_in_gap);  // There should be a thunk but it should be after the method2.
  ASSERT_FALSE(method_offset_map_.FindMethodOffset(MethodRef(missing_method_index)).first);

  // Check linked code.
  uint32_t method3_offset = GetMethodOffset(3u);
  uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(), kThumb2);
  uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */);
  ASSERT_EQ(diff & 1u, 0u);
  ASSERT_LT(diff >> 1, 1u << 8);  // Simple encoding, (diff >> 1) fits into 8 bits.
  auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu));
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
  EXPECT_TRUE(CheckThunk(thunk_offset));
}

TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarAfter) {
  auto method1_raw_code = GenNopsAndBl(3u, kBlPlus0);
  constexpr uint32_t bl_offset_in_method1 = 3u * 2u;  // After NOPs.
  ArrayRef<const uint8_t> method1_code(method1_raw_code);
  ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
  LinkerPatch method1_patches[] = {
      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u),
  };

  constexpr uint32_t max_positive_disp = 16 * MB - 2u + 4u /* PC adjustment */;
  bool thunk_in_gap = Create2MethodsWithGap(method1_code,
                                            ArrayRef<const LinkerPatch>(method1_patches),
                                            kNopCode,
                                            ArrayRef<const LinkerPatch>(),
                                            bl_offset_in_method1 + max_positive_disp);
  ASSERT_FALSE(thunk_in_gap);  // There should be no thunk.

  // Check linked code.
  auto expected_code = GenNopsAndBl(3u, kBlPlusMax);
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarBefore) {
  auto method3_raw_code = GenNopsAndBl(2u, kBlPlus0);
  constexpr uint32_t bl_offset_in_method3 = 2u * 2u;  // After NOPs.
  ArrayRef<const uint8_t> method3_code(method3_raw_code);
  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
  LinkerPatch method3_patches[] = {
      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u),
  };

  constexpr uint32_t just_over_max_negative_disp = 16 * MB - 4u /* PC adjustment */;
  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
                                            ArrayRef<const LinkerPatch>(),
                                            method3_code,
                                            ArrayRef<const LinkerPatch>(method3_patches),
                                            just_over_max_negative_disp - bl_offset_in_method3);
  ASSERT_FALSE(thunk_in_gap);  // There should be no thunk.

  // Check linked code.
  auto expected_code = GenNopsAndBl(2u, kBlMinusMax);
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
}

TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarAfter) {
  auto method1_raw_code = GenNopsAndBl(2u, kBlPlus0);
  constexpr uint32_t bl_offset_in_method1 = 2u * 2u;  // After NOPs.
  ArrayRef<const uint8_t> method1_code(method1_raw_code);
  ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
  LinkerPatch method1_patches[] = {
      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u),
  };

  constexpr uint32_t just_over_max_positive_disp = 16 * MB + 4u /* PC adjustment */;
  bool thunk_in_gap = Create2MethodsWithGap(method1_code,
                                            ArrayRef<const LinkerPatch>(method1_patches),
                                            kNopCode,
                                            ArrayRef<const LinkerPatch>(),
                                            bl_offset_in_method1 + just_over_max_positive_disp);
  ASSERT_TRUE(thunk_in_gap);

  uint32_t method1_offset = GetMethodOffset(1u);
  uint32_t method3_offset = GetMethodOffset(3u);
  ASSERT_TRUE(IsAligned<kArmAlignment>(method3_offset));
  uint32_t method3_header_offset = method3_offset - sizeof(OatQuickMethodHeader);
  uint32_t thunk_size = MethodCallThunkSize();
  uint32_t thunk_offset =
      RoundDown(method3_header_offset - thunk_size, GetInstructionSetAlignment(kThumb2));
  DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size),
            method3_header_offset);
  ASSERT_TRUE(IsAligned<kArmAlignment>(thunk_offset));
  uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1 + 4u /* PC adjustment */);
  ASSERT_EQ(diff & 1u, 0u);
  ASSERT_GE(diff, 16 * MB - (1u << 9));  // Simple encoding, unknown bits fit into the low 8 bits.
  auto expected_code = GenNopsAndBl(2u, 0xf3ffd700 | ((diff >> 1) & 0xffu));
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
  CheckThunk(thunk_offset);
}

TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarBefore) {
  auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0);
  constexpr uint32_t bl_offset_in_method3 = 3u * 2u;  // After NOPs.
  ArrayRef<const uint8_t> method3_code(method3_raw_code);
  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
  LinkerPatch method3_patches[] = {
      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u),
  };

  constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */;
  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
                                            ArrayRef<const LinkerPatch>(),
                                            method3_code,
                                            ArrayRef<const LinkerPatch>(method3_patches),
                                            just_over_max_negative_disp - bl_offset_in_method3);
  ASSERT_FALSE(thunk_in_gap);  // There should be a thunk but it should be after the method2.

  // Check linked code.
  uint32_t method3_offset = GetMethodOffset(3u);
  uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(), kThumb2);
  uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */);
  ASSERT_EQ(diff & 1u, 0u);
  ASSERT_LT(diff >> 1, 1u << 8);  // Simple encoding, (diff >> 1) fits into 8 bits.
  auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu));
  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
  EXPECT_TRUE(CheckThunk(thunk_offset));
}

TEST_F(Thumb2RelativePatcherTest, DexCacheReference1) {
  TestDexCacheReference(0x00ff0000u, 0x00fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, DexCacheReference2) {
  TestDexCacheReference(0x02ff0000u, 0x05fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, DexCacheReference3) {
  TestDexCacheReference(0x08ff0000u, 0x08fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, DexCacheReference4) {
  TestDexCacheReference(0xd0ff0000u, 0x60fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, StringReference1) {
  TestStringReference(0x00ff00fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, StringReference2) {
  TestStringReference(0x02ff05fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, StringReference3) {
  TestStringReference(0x08ff08fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

TEST_F(Thumb2RelativePatcherTest, StringReference4) {
  TestStringReference(0xd0ff60fcu);
  ASSERT_LT(GetMethodOffset(1u), 0xfcu);
}

}  // namespace linker
}  // namespace art