| /* |
| * 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/arm64/relative_patcher_arm64.h" |
| |
| #include "arch/arm64/instruction_set_features_arm64.h" |
| #include "base/casts.h" |
| #include "driver/compiler_options.h" |
| #include "linker/relative_patcher_test.h" |
| #include "lock_word.h" |
| #include "mirror/array-inl.h" |
| #include "mirror/object.h" |
| #include "oat/oat_quick_method_header.h" |
| #include "optimizing/code_generator_arm64.h" |
| #include "optimizing/optimizing_unit_test.h" |
| |
| namespace art { |
| namespace linker { |
| |
| class Arm64RelativePatcherTest : public RelativePatcherTest { |
| public: |
| explicit Arm64RelativePatcherTest(const std::string& variant) |
| : RelativePatcherTest(InstructionSet::kArm64, variant) { } |
| |
| 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; |
| |
| // NOP instruction. |
| static constexpr uint32_t kNopInsn = 0xd503201f; |
| |
| // All branches can be created from kBlPlus0 or kBPlus0 by adding the low 26 bits. |
| static constexpr uint32_t kBlPlus0 = 0x94000000u; |
| static constexpr uint32_t kBPlus0 = 0x14000000u; |
| |
| // Special BL values. |
| static constexpr uint32_t kBlPlusMax = 0x95ffffffu; |
| static constexpr uint32_t kBlMinusMax = 0x96000000u; |
| |
| // LDR immediate, 32-bit, unsigned offset. |
| static constexpr uint32_t kLdrWInsn = 0xb9400000u; |
| |
| // LDR register, 32-bit, LSL #2. |
| static constexpr uint32_t kLdrWLsl2Insn = 0xb8607800u; |
| |
| // LDUR, 32-bit. |
| static constexpr uint32_t kLdurWInsn = 0xb8400000u; |
| |
| // ADD/ADDS/SUB/SUBS immediate, 64-bit. |
| static constexpr uint32_t kAddXInsn = 0x91000000u; |
| static constexpr uint32_t kAddsXInsn = 0xb1000000u; |
| static constexpr uint32_t kSubXInsn = 0xd1000000u; |
| static constexpr uint32_t kSubsXInsn = 0xf1000000u; |
| |
| // LDUR x2, [sp, #4], i.e. unaligned load crossing 64-bit boundary (assuming aligned sp). |
| static constexpr uint32_t kLdurInsn = 0xf840405fu; |
| |
| // LDR w12, <label> and LDR x12, <label>. Bits 5-23 contain label displacement in 4-byte units. |
| static constexpr uint32_t kLdrWPcRelInsn = 0x1800000cu; |
| static constexpr uint32_t kLdrXPcRelInsn = 0x5800000cu; |
| |
| // LDR w13, [SP, #<pimm>] and LDR x13, [SP, #<pimm>]. Bits 10-21 contain displacement from SP |
| // in units of 4-bytes (for 32-bit load) or 8-bytes (for 64-bit load). |
| static constexpr uint32_t kLdrWSpRelInsn = 0xb94003edu; |
| static constexpr uint32_t kLdrXSpRelInsn = 0xf94003edu; |
| |
| // CBNZ x17, +0. Bits 5-23 are a placeholder for target offset from PC in units of 4-bytes. |
| static constexpr uint32_t kCbnzIP1Plus0Insn = 0xb5000011u; |
| |
| static void InsertInsn(std::vector<uint8_t>* code, size_t pos, uint32_t insn) { |
| CHECK_LE(pos, code->size()); |
| const uint8_t insn_code[] = { |
| static_cast<uint8_t>(insn), |
| static_cast<uint8_t>(insn >> 8), |
| static_cast<uint8_t>(insn >> 16), |
| static_cast<uint8_t>(insn >> 24), |
| }; |
| static_assert(sizeof(insn_code) == 4u, "Invalid sizeof(insn_code)."); |
| code->insert(code->begin() + pos, insn_code, insn_code + sizeof(insn_code)); |
| } |
| |
| static void PushBackInsn(std::vector<uint8_t>* code, uint32_t insn) { |
| InsertInsn(code, code->size(), insn); |
| } |
| |
| static std::vector<uint8_t> RawCode(std::initializer_list<uint32_t> insns) { |
| std::vector<uint8_t> raw_code; |
| raw_code.reserve(insns.size() * 4u); |
| for (uint32_t insn : insns) { |
| PushBackInsn(&raw_code, insn); |
| } |
| return raw_code; |
| } |
| |
| uint32_t Create2MethodsWithGap(const ArrayRef<const uint8_t>& method1_code, |
| const ArrayRef<const LinkerPatch>& method1_patches, |
| const ArrayRef<const uint8_t>& last_method_code, |
| const ArrayRef<const LinkerPatch>& last_method_patches, |
| uint32_t distance_without_thunks) { |
| CHECK_EQ(distance_without_thunks % kArm64CodeAlignment, 0u); |
| uint32_t method1_offset = |
| kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader); |
| AddCompiledMethod(MethodRef(1u), method1_code, method1_patches); |
| const uint32_t gap_start = method1_offset + method1_code.size(); |
| |
| // We want to put the last method at a very precise offset. |
| const uint32_t last_method_offset = method1_offset + distance_without_thunks; |
| CHECK_ALIGNED(last_method_offset, kArm64CodeAlignment); |
| const uint32_t gap_end = last_method_offset - sizeof(OatQuickMethodHeader); |
| |
| // Fill the gap with intermediate methods in chunks of 2MiB and the first in [2MiB, 4MiB). |
| // (This allows deduplicating the small chunks to avoid using 256MiB of memory for +-128MiB |
| // offsets by this test. Making the first chunk bigger makes it easy to give all intermediate |
| // methods the same alignment of the end, so the thunk insertion adds a predictable size as |
| // long as it's after the first chunk.) |
| uint32_t method_idx = 2u; |
| constexpr uint32_t kSmallChunkSize = 2 * MB; |
| std::vector<uint8_t> gap_code; |
| uint32_t gap_size = gap_end - gap_start; |
| uint32_t num_small_chunks = std::max(gap_size / kSmallChunkSize, 1u) - 1u; |
| uint32_t chunk_start = gap_start; |
| uint32_t chunk_size = gap_size - num_small_chunks * kSmallChunkSize; |
| for (uint32_t i = 0; i <= num_small_chunks; ++i) { // num_small_chunks+1 iterations. |
| uint32_t chunk_code_size = |
| chunk_size - CodeAlignmentSize(chunk_start) - sizeof(OatQuickMethodHeader); |
| gap_code.resize(chunk_code_size, 0u); |
| AddCompiledMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(gap_code)); |
| method_idx += 1u; |
| chunk_start += chunk_size; |
| chunk_size = kSmallChunkSize; // For all but the first chunk. |
| DCHECK_EQ(CodeAlignmentSize(gap_end), CodeAlignmentSize(chunk_start)); |
| } |
| |
| // Add the last method and link |
| AddCompiledMethod(MethodRef(method_idx), last_method_code, last_method_patches); |
| Link(); |
| |
| // Check assumptions. |
| CHECK_EQ(GetMethodOffset(1), method1_offset); |
| auto last_result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); |
| CHECK(last_result.first); |
| // There may be a thunk before method2. |
| if (last_result.second != last_method_offset) { |
| // Thunk present. Check that there's only one. |
| uint32_t thunk_end = |
| CompiledCode::AlignCode(gap_end, InstructionSet::kArm64) + MethodCallThunkSize(); |
| uint32_t header_offset = thunk_end + CodeAlignmentSize(thunk_end); |
| CHECK_EQ(last_result.second, header_offset + sizeof(OatQuickMethodHeader)); |
| } |
| return method_idx; |
| } |
| |
| uint32_t GetMethodOffset(uint32_t method_idx) { |
| auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); |
| CHECK(result.first); |
| CHECK_ALIGNED(result.second, 4u); |
| return result.second; |
| } |
| |
| std::vector<uint8_t> CompileThunk(const LinkerPatch& patch, |
| /*out*/ std::string* debug_name = nullptr) { |
| OptimizingUnitTestHelper helper; |
| HGraph* graph = helper.CreateGraph(); |
| CompilerOptions compiler_options; |
| |
| // Set isa to arm64. |
| compiler_options.instruction_set_ = instruction_set_; |
| compiler_options.instruction_set_features_ = |
| InstructionSetFeatures::FromBitmap(instruction_set_, instruction_set_features_->AsBitmap()); |
| CHECK(compiler_options.instruction_set_features_->Equals(instruction_set_features_.get())); |
| |
| // If a test requests that implicit null checks are enabled or disabled, |
| // apply that option, otherwise use the default from `CompilerOptions`. |
| if (implicit_null_checks_.has_value()) { |
| compiler_options.implicit_null_checks_ = implicit_null_checks_.value(); |
| } |
| |
| arm64::CodeGeneratorARM64 codegen(graph, compiler_options); |
| ArenaVector<uint8_t> code(helper.GetAllocator()->Adapter()); |
| codegen.EmitThunkCode(patch, &code, debug_name); |
| return std::vector<uint8_t>(code.begin(), code.end()); |
| } |
| |
| void AddCompiledMethod( |
| MethodReference method_ref, |
| const ArrayRef<const uint8_t>& code, |
| const ArrayRef<const LinkerPatch>& patches = ArrayRef<const LinkerPatch>()) { |
| RelativePatcherTest::AddCompiledMethod(method_ref, code, patches); |
| |
| // Make sure the ThunkProvider has all the necessary thunks. |
| for (const LinkerPatch& patch : patches) { |
| if (patch.GetType() == LinkerPatch::Type::kCallEntrypoint || |
| patch.GetType() == LinkerPatch::Type::kBakerReadBarrierBranch || |
| patch.GetType() == LinkerPatch::Type::kCallRelative) { |
| std::string debug_name; |
| std::vector<uint8_t> thunk_code = CompileThunk(patch, &debug_name); |
| thunk_provider_.SetThunkCode(patch, ArrayRef<const uint8_t>(thunk_code), debug_name); |
| } |
| } |
| } |
| |
| std::vector<uint8_t> CompileMethodCallThunk() { |
| LinkerPatch patch = LinkerPatch::RelativeCodePatch(/* literal_offset */ 0u, |
| /* target_dex_file*/ nullptr, |
| /* target_method_idx */ 0u); |
| return CompileThunk(patch); |
| } |
| |
| 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; |
| } |
| |
| static std::vector<uint8_t> GenNops(size_t num_nops) { |
| std::vector<uint8_t> result; |
| result.reserve(num_nops * 4u); |
| for (size_t i = 0; i != num_nops; ++i) { |
| PushBackInsn(&result, kNopInsn); |
| } |
| return result; |
| } |
| |
| static std::vector<uint8_t> GenNopsAndBl(size_t num_nops, uint32_t bl) { |
| std::vector<uint8_t> result; |
| result.reserve(num_nops * 4u + 4u); |
| for (size_t i = 0; i != num_nops; ++i) { |
| PushBackInsn(&result, kNopInsn); |
| } |
| PushBackInsn(&result, bl); |
| return result; |
| } |
| |
| std::vector<uint8_t> GenNopsAndAdrpAndUse(size_t num_nops, |
| uint32_t method_offset, |
| uint32_t target_offset, |
| uint32_t use_insn) { |
| std::vector<uint8_t> result; |
| result.reserve(num_nops * 4u + 8u); |
| for (size_t i = 0; i != num_nops; ++i) { |
| PushBackInsn(&result, kNopInsn); |
| } |
| CHECK_ALIGNED(method_offset, 4u); |
| CHECK_ALIGNED(target_offset, 4u); |
| uint32_t adrp_offset = method_offset + num_nops * 4u; |
| uint32_t disp = target_offset - (adrp_offset & ~0xfffu); |
| if (use_insn == kLdrWInsn) { |
| DCHECK_ALIGNED(disp, 1u << 2); |
| use_insn |= 1 | // LDR x1, [x0, #(imm12 << 2)] |
| ((disp & 0xfffu) << (10 - 2)); // imm12 = ((disp & 0xfffu) >> 2) is at bit 10. |
| } else if (use_insn == kAddXInsn) { |
| use_insn |= 1 | // ADD x1, x0, #imm |
| (disp & 0xfffu) << 10; // imm12 = (disp & 0xfffu) is at bit 10. |
| } else { |
| LOG(FATAL) << "Unexpected instruction: 0x" << std::hex << use_insn; |
| } |
| uint32_t adrp = 0x90000000u | // ADRP x0, +SignExtend(immhi:immlo:Zeros(12), 64) |
| ((disp & 0x3000u) << (29 - 12)) | // immlo = ((disp & 0x3000u) >> 12) is at bit 29, |
| ((disp & 0xffffc000) >> (14 - 5)) | // immhi = (disp >> 14) is at bit 5, |
| // We take the sign bit from the disp, limiting disp to +- 2GiB. |
| ((disp & 0x80000000) >> (31 - 23)); // sign bit in immhi is at bit 23. |
| PushBackInsn(&result, adrp); |
| PushBackInsn(&result, use_insn); |
| return result; |
| } |
| |
| std::vector<uint8_t> GenNopsAndAdrpLdr(size_t num_nops, |
| uint32_t method_offset, |
| uint32_t target_offset) { |
| return GenNopsAndAdrpAndUse(num_nops, method_offset, target_offset, kLdrWInsn); |
| } |
| |
| void TestNopsAdrpLdr(size_t num_nops, uint32_t bss_begin, uint32_t string_entry_offset) { |
| constexpr uint32_t kStringIndex = 1u; |
| string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); |
| bss_begin_ = bss_begin; |
| auto code = GenNopsAndAdrpLdr(num_nops, 0u, 0u); // Unpatched. |
| const LinkerPatch patches[] = { |
| LinkerPatch::StringBssEntryPatch(num_nops * 4u , nullptr, num_nops * 4u, kStringIndex), |
| LinkerPatch::StringBssEntryPatch(num_nops * 4u + 4u, nullptr, num_nops * 4u, kStringIndex), |
| }; |
| AddCompiledMethod(MethodRef(1u), |
| ArrayRef<const uint8_t>(code), |
| ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t target_offset = bss_begin_ + string_entry_offset; |
| auto expected_code = GenNopsAndAdrpLdr(num_nops, method1_offset, target_offset); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| std::vector<uint8_t> GenNopsAndAdrpAdd(size_t num_nops, |
| uint32_t method_offset, |
| uint32_t target_offset) { |
| return GenNopsAndAdrpAndUse(num_nops, method_offset, target_offset, kAddXInsn); |
| } |
| |
| void TestNopsAdrpAdd(size_t num_nops, uint32_t string_offset) { |
| constexpr uint32_t kStringIndex = 1u; |
| string_index_to_offset_map_.Put(kStringIndex, string_offset); |
| auto code = GenNopsAndAdrpAdd(num_nops, 0u, 0u); // Unpatched. |
| const LinkerPatch patches[] = { |
| LinkerPatch::RelativeStringPatch(num_nops * 4u , nullptr, num_nops * 4u, kStringIndex), |
| LinkerPatch::RelativeStringPatch(num_nops * 4u + 4u, nullptr, num_nops * 4u, kStringIndex), |
| }; |
| AddCompiledMethod(MethodRef(1u), |
| ArrayRef<const uint8_t>(code), |
| ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| |
| uint32_t method1_offset = GetMethodOffset(1u); |
| auto expected_code = GenNopsAndAdrpAdd(num_nops, method1_offset, string_offset); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| void PrepareNopsAdrpInsn2Ldr(size_t num_nops, |
| uint32_t insn2, |
| uint32_t bss_begin, |
| uint32_t string_entry_offset) { |
| constexpr uint32_t kStringIndex = 1u; |
| string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); |
| bss_begin_ = bss_begin; |
| auto code = GenNopsAndAdrpLdr(num_nops, 0u, 0u); // Unpatched. |
| InsertInsn(&code, num_nops * 4u + 4u, insn2); |
| const LinkerPatch patches[] = { |
| LinkerPatch::StringBssEntryPatch(num_nops * 4u , nullptr, num_nops * 4u, kStringIndex), |
| LinkerPatch::StringBssEntryPatch(num_nops * 4u + 8u, nullptr, num_nops * 4u, kStringIndex), |
| }; |
| AddCompiledMethod(MethodRef(1u), |
| ArrayRef<const uint8_t>(code), |
| ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| } |
| |
| void PrepareNopsAdrpInsn2Add(size_t num_nops, uint32_t insn2, uint32_t string_offset) { |
| constexpr uint32_t kStringIndex = 1u; |
| string_index_to_offset_map_.Put(kStringIndex, string_offset); |
| auto code = GenNopsAndAdrpAdd(num_nops, 0u, 0u); // Unpatched. |
| InsertInsn(&code, num_nops * 4u + 4u, insn2); |
| const LinkerPatch patches[] = { |
| LinkerPatch::RelativeStringPatch(num_nops * 4u , nullptr, num_nops * 4u, kStringIndex), |
| LinkerPatch::RelativeStringPatch(num_nops * 4u + 8u, nullptr, num_nops * 4u, kStringIndex), |
| }; |
| AddCompiledMethod(MethodRef(1u), |
| ArrayRef<const uint8_t>(code), |
| ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| } |
| |
| void TestNopsAdrpInsn2AndUse(size_t num_nops, |
| uint32_t insn2, |
| uint32_t target_offset, |
| uint32_t use_insn) { |
| uint32_t method1_offset = GetMethodOffset(1u); |
| auto expected_code = GenNopsAndAdrpAndUse(num_nops, method1_offset, target_offset, use_insn); |
| InsertInsn(&expected_code, num_nops * 4u + 4u, insn2); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| void TestNopsAdrpInsn2AndUseHasThunk(size_t num_nops, |
| uint32_t insn2, |
| uint32_t target_offset, |
| uint32_t use_insn) { |
| uint32_t method1_offset = GetMethodOffset(1u); |
| CHECK(!compiled_method_refs_.empty()); |
| CHECK_EQ(compiled_method_refs_[0].index, 1u); |
| CHECK_EQ(compiled_method_refs_.size(), compiled_methods_.size()); |
| uint32_t method1_size = compiled_methods_[0]->GetQuickCode().size(); |
| uint32_t thunk_offset = |
| CompiledCode::AlignCode(method1_offset + method1_size, InstructionSet::kArm64); |
| uint32_t b_diff = thunk_offset - (method1_offset + num_nops * 4u); |
| CHECK_ALIGNED(b_diff, 4u); |
| ASSERT_LT(b_diff, 128 * MB); |
| uint32_t b_out = kBPlus0 + ((b_diff >> 2) & 0x03ffffffu); |
| uint32_t b_in = kBPlus0 + ((-b_diff >> 2) & 0x03ffffffu); |
| |
| auto expected_code = GenNopsAndAdrpAndUse(num_nops, method1_offset, target_offset, use_insn); |
| InsertInsn(&expected_code, num_nops * 4u + 4u, insn2); |
| // Replace adrp with bl. |
| expected_code.erase(expected_code.begin() + num_nops * 4u, |
| expected_code.begin() + num_nops * 4u + 4u); |
| InsertInsn(&expected_code, num_nops * 4u, b_out); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| |
| auto expected_thunk_code = GenNopsAndAdrpLdr(0u, thunk_offset, target_offset); |
| ASSERT_EQ(expected_thunk_code.size(), 8u); |
| expected_thunk_code.erase(expected_thunk_code.begin() + 4u, expected_thunk_code.begin() + 8u); |
| InsertInsn(&expected_thunk_code, 4u, b_in); |
| ASSERT_EQ(expected_thunk_code.size(), 8u); |
| |
| uint32_t thunk_size = MethodCallThunkSize(); |
| ASSERT_EQ(thunk_offset + thunk_size, output_.size()); |
| ASSERT_EQ(thunk_size, expected_thunk_code.size()); |
| ArrayRef<const uint8_t> thunk_code(&output_[thunk_offset], thunk_size); |
| if (ArrayRef<const uint8_t>(expected_thunk_code) != thunk_code) { |
| DumpDiff(ArrayRef<const uint8_t>(expected_thunk_code), thunk_code); |
| FAIL(); |
| } |
| } |
| |
| void TestAdrpInsn2Ldr(uint32_t insn2, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t bss_begin, |
| uint32_t string_entry_offset) { |
| uint32_t method1_offset = |
| kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader); |
| ASSERT_LT(method1_offset, adrp_offset); |
| CHECK_ALIGNED(adrp_offset, 4u); |
| uint32_t num_nops = (adrp_offset - method1_offset) / 4u; |
| PrepareNopsAdrpInsn2Ldr(num_nops, insn2, bss_begin, string_entry_offset); |
| uint32_t target_offset = bss_begin_ + string_entry_offset; |
| if (has_thunk) { |
| TestNopsAdrpInsn2AndUseHasThunk(num_nops, insn2, target_offset, kLdrWInsn); |
| } else { |
| TestNopsAdrpInsn2AndUse(num_nops, insn2, target_offset, kLdrWInsn); |
| } |
| ASSERT_EQ(method1_offset, GetMethodOffset(1u)); // If this fails, num_nops is wrong. |
| } |
| |
| void TestAdrpLdurLdr(uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t bss_begin, |
| uint32_t string_entry_offset) { |
| TestAdrpInsn2Ldr(kLdurInsn, adrp_offset, has_thunk, bss_begin, string_entry_offset); |
| } |
| |
| void TestAdrpLdrPcRelLdr(uint32_t pcrel_ldr_insn, |
| int32_t pcrel_disp, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t bss_begin, |
| uint32_t string_entry_offset) { |
| ASSERT_LT(pcrel_disp, 0x100000); |
| ASSERT_GE(pcrel_disp, -0x100000); |
| ASSERT_EQ(pcrel_disp & 0x3, 0); |
| uint32_t insn2 = pcrel_ldr_insn | (((static_cast<uint32_t>(pcrel_disp) >> 2) & 0x7ffffu) << 5); |
| TestAdrpInsn2Ldr(insn2, adrp_offset, has_thunk, bss_begin, string_entry_offset); |
| } |
| |
| void TestAdrpLdrSpRelLdr(uint32_t sprel_ldr_insn, |
| uint32_t sprel_disp_in_load_units, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t bss_begin, |
| uint32_t string_entry_offset) { |
| ASSERT_LT(sprel_disp_in_load_units, 0x1000u); |
| uint32_t insn2 = sprel_ldr_insn | ((sprel_disp_in_load_units & 0xfffu) << 10); |
| TestAdrpInsn2Ldr(insn2, adrp_offset, has_thunk, bss_begin, string_entry_offset); |
| } |
| |
| void TestAdrpInsn2Add(uint32_t insn2, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t string_offset) { |
| uint32_t method1_offset = |
| kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader); |
| ASSERT_LT(method1_offset, adrp_offset); |
| CHECK_ALIGNED(adrp_offset, 4u); |
| uint32_t num_nops = (adrp_offset - method1_offset) / 4u; |
| PrepareNopsAdrpInsn2Add(num_nops, insn2, string_offset); |
| if (has_thunk) { |
| TestNopsAdrpInsn2AndUseHasThunk(num_nops, insn2, string_offset, kAddXInsn); |
| } else { |
| TestNopsAdrpInsn2AndUse(num_nops, insn2, string_offset, kAddXInsn); |
| } |
| ASSERT_EQ(method1_offset, GetMethodOffset(1u)); // If this fails, num_nops is wrong. |
| } |
| |
| void TestAdrpLdurAdd(uint32_t adrp_offset, bool has_thunk, uint32_t string_offset) { |
| TestAdrpInsn2Add(kLdurInsn, adrp_offset, has_thunk, string_offset); |
| } |
| |
| void TestAdrpLdrPcRelAdd(uint32_t pcrel_ldr_insn, |
| int32_t pcrel_disp, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t string_offset) { |
| ASSERT_LT(pcrel_disp, 0x100000); |
| ASSERT_GE(pcrel_disp, -0x100000); |
| ASSERT_EQ(pcrel_disp & 0x3, 0); |
| uint32_t insn2 = pcrel_ldr_insn | (((static_cast<uint32_t>(pcrel_disp) >> 2) & 0x7ffffu) << 5); |
| TestAdrpInsn2Add(insn2, adrp_offset, has_thunk, string_offset); |
| } |
| |
| void TestAdrpLdrSpRelAdd(uint32_t sprel_ldr_insn, |
| uint32_t sprel_disp_in_load_units, |
| uint32_t adrp_offset, |
| bool has_thunk, |
| uint32_t string_offset) { |
| ASSERT_LT(sprel_disp_in_load_units, 0x1000u); |
| uint32_t insn2 = sprel_ldr_insn | ((sprel_disp_in_load_units & 0xfffu) << 10); |
| TestAdrpInsn2Add(insn2, adrp_offset, has_thunk, string_offset); |
| } |
| |
| static uint32_t EncodeBakerReadBarrierFieldData(uint32_t base_reg, uint32_t holder_reg) { |
| return arm64::CodeGeneratorARM64::EncodeBakerReadBarrierFieldData(base_reg, holder_reg); |
| } |
| |
| static uint32_t EncodeBakerReadBarrierArrayData(uint32_t base_reg) { |
| return arm64::CodeGeneratorARM64::EncodeBakerReadBarrierArrayData(base_reg); |
| } |
| |
| static uint32_t EncodeBakerReadBarrierGcRootData(uint32_t root_reg) { |
| return arm64::CodeGeneratorARM64::EncodeBakerReadBarrierGcRootData(root_reg); |
| } |
| |
| std::vector<uint8_t> CompileBakerOffsetThunk(uint32_t base_reg, uint32_t holder_reg) { |
| const LinkerPatch patch = LinkerPatch::BakerReadBarrierBranchPatch( |
| /* literal_offset */ 0u, EncodeBakerReadBarrierFieldData(base_reg, holder_reg)); |
| return CompileThunk(patch); |
| } |
| |
| std::vector<uint8_t> CompileBakerArrayThunk(uint32_t base_reg) { |
| LinkerPatch patch = LinkerPatch::BakerReadBarrierBranchPatch( |
| /* literal_offset */ 0u, EncodeBakerReadBarrierArrayData(base_reg)); |
| return CompileThunk(patch); |
| } |
| |
| std::vector<uint8_t> CompileBakerGcRootThunk(uint32_t root_reg) { |
| LinkerPatch patch = LinkerPatch::BakerReadBarrierBranchPatch( |
| /* literal_offset */ 0u, EncodeBakerReadBarrierGcRootData(root_reg)); |
| return CompileThunk(patch); |
| } |
| |
| uint32_t GetOutputInsn(uint32_t offset) { |
| CHECK_LE(offset, output_.size()); |
| CHECK_GE(output_.size() - offset, 4u); |
| return (static_cast<uint32_t>(output_[offset]) << 0) | |
| (static_cast<uint32_t>(output_[offset + 1]) << 8) | |
| (static_cast<uint32_t>(output_[offset + 2]) << 16) | |
| (static_cast<uint32_t>(output_[offset + 3]) << 24); |
| } |
| |
| void TestBakerField(uint32_t offset, uint32_t ref_reg, bool implicit_null_checks); |
| |
| void Reset() final { |
| RelativePatcherTest::Reset(); |
| implicit_null_checks_ = std::nullopt; |
| } |
| |
| private: |
| std::optional<bool> implicit_null_checks_ = std::nullopt; |
| }; |
| |
| const uint8_t Arm64RelativePatcherTest::kCallRawCode[] = { |
| 0x00, 0x00, 0x00, 0x94 |
| }; |
| |
| const ArrayRef<const uint8_t> Arm64RelativePatcherTest::kCallCode(kCallRawCode); |
| |
| const uint8_t Arm64RelativePatcherTest::kNopRawCode[] = { |
| 0x1f, 0x20, 0x03, 0xd5 |
| }; |
| |
| const ArrayRef<const uint8_t> Arm64RelativePatcherTest::kNopCode(kNopRawCode); |
| |
| class Arm64RelativePatcherTestDefault : public Arm64RelativePatcherTest { |
| public: |
| Arm64RelativePatcherTestDefault() : Arm64RelativePatcherTest("default") { } |
| }; |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallSelf) { |
| const LinkerPatch patches[] = { |
| LinkerPatch::RelativeCodePatch(0u, nullptr, 1u), |
| }; |
| AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| |
| const std::vector<uint8_t> expected_code = RawCode({kBlPlus0}); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallOther) { |
| const LinkerPatch method1_patches[] = { |
| LinkerPatch::RelativeCodePatch(0u, nullptr, 2u), |
| }; |
| AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(method1_patches)); |
| const 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; |
| CHECK_ALIGNED(diff_after, 4u); |
| ASSERT_LT(diff_after >> 2, 1u << 8); // Simple encoding, (diff_after >> 2) fits into 8 bits. |
| const std::vector<uint8_t> method1_expected_code = RawCode({kBlPlus0 + (diff_after >> 2)}); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(method1_expected_code))); |
| uint32_t diff_before = method1_offset - method2_offset; |
| CHECK_ALIGNED(diff_before, 4u); |
| ASSERT_GE(diff_before, -1u << 27); |
| auto method2_expected_code = GenNopsAndBl(0u, kBlPlus0 | ((diff_before >> 2) & 0x03ffffffu)); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(2u), ArrayRef<const uint8_t>(method2_expected_code))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallTrampoline) { |
| const 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; |
| ASSERT_EQ(diff & 1u, 0u); |
| ASSERT_GE(diff, -1u << 9); // Simple encoding, -256 <= (diff >> 1) < 0 (checked as unsigned). |
| auto expected_code = GenNopsAndBl(0u, kBlPlus0 | ((diff >> 2) & 0x03ffffffu)); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallTrampolineTooFar) { |
| constexpr uint32_t missing_method_index = 1024u; |
| auto last_method_raw_code = GenNopsAndBl(1u, kBlPlus0); |
| constexpr uint32_t bl_offset_in_last_method = 1u * 4u; // After NOPs. |
| ArrayRef<const uint8_t> last_method_code(last_method_raw_code); |
| ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size()); |
| const LinkerPatch last_method_patches[] = { |
| LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, missing_method_index), |
| }; |
| |
| constexpr uint32_t just_over_max_negative_disp = 128 * MB + 4; |
| uint32_t last_method_idx = Create2MethodsWithGap( |
| kNopCode, |
| ArrayRef<const LinkerPatch>(), |
| last_method_code, |
| ArrayRef<const LinkerPatch>(last_method_patches), |
| just_over_max_negative_disp - bl_offset_in_last_method); |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t last_method_offset = GetMethodOffset(last_method_idx); |
| ASSERT_EQ(method1_offset, |
| last_method_offset + bl_offset_in_last_method - just_over_max_negative_disp); |
| ASSERT_FALSE(method_offset_map_.FindMethodOffset(MethodRef(missing_method_index)).first); |
| |
| // Check linked code. |
| uint32_t thunk_offset = |
| CompiledCode::AlignCode(last_method_offset + last_method_code.size(), InstructionSet::kArm64); |
| uint32_t diff = thunk_offset - (last_method_offset + bl_offset_in_last_method); |
| ASSERT_TRUE(IsAligned<4u>(diff)); |
| ASSERT_LT(diff, 128 * MB); |
| auto expected_code = GenNopsAndBl(1u, kBlPlus0 | (diff >> 2)); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx), |
| ArrayRef<const uint8_t>(expected_code))); |
| EXPECT_TRUE(CheckThunk(thunk_offset)); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallOtherAlmostTooFarAfter) { |
| auto method1_raw_code = GenNopsAndBl(1u, kBlPlus0); |
| constexpr uint32_t bl_offset_in_method1 = 1u * 4u; // After NOPs. |
| ArrayRef<const uint8_t> method1_code(method1_raw_code); |
| ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size()); |
| const uint32_t kExpectedLastMethodIdx = 65u; // Based on 2MiB chunks in Create2MethodsWithGap(). |
| const LinkerPatch method1_patches[] = { |
| LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx), |
| }; |
| |
| constexpr uint32_t max_positive_disp = 128 * MB - 4u; |
| uint32_t last_method_idx = Create2MethodsWithGap(method1_code, |
| ArrayRef<const LinkerPatch>(method1_patches), |
| kNopCode, |
| ArrayRef<const LinkerPatch>(), |
| bl_offset_in_method1 + max_positive_disp); |
| ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx); |
| |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t last_method_offset = GetMethodOffset(last_method_idx); |
| ASSERT_EQ(method1_offset + bl_offset_in_method1 + max_positive_disp, last_method_offset); |
| |
| // Check linked code. |
| auto expected_code = GenNopsAndBl(1u, kBlPlusMax); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallOtherAlmostTooFarBefore) { |
| auto last_method_raw_code = GenNopsAndBl(0u, kBlPlus0); |
| constexpr uint32_t bl_offset_in_last_method = 0u * 4u; // After NOPs. |
| ArrayRef<const uint8_t> last_method_code(last_method_raw_code); |
| ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size()); |
| const LinkerPatch last_method_patches[] = { |
| LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, 1u), |
| }; |
| |
| constexpr uint32_t max_negative_disp = 128 * MB; |
| uint32_t last_method_idx = Create2MethodsWithGap(kNopCode, |
| ArrayRef<const LinkerPatch>(), |
| last_method_code, |
| ArrayRef<const LinkerPatch>(last_method_patches), |
| max_negative_disp - bl_offset_in_last_method); |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t last_method_offset = GetMethodOffset(last_method_idx); |
| ASSERT_EQ(method1_offset, last_method_offset + bl_offset_in_last_method - max_negative_disp); |
| |
| // Check linked code. |
| auto expected_code = GenNopsAndBl(0u, kBlMinusMax); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx), |
| ArrayRef<const uint8_t>(expected_code))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallOtherJustTooFarAfter) { |
| auto method1_raw_code = GenNopsAndBl(0u, kBlPlus0); |
| constexpr uint32_t bl_offset_in_method1 = 0u * 4u; // After NOPs. |
| ArrayRef<const uint8_t> method1_code(method1_raw_code); |
| ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size()); |
| const uint32_t kExpectedLastMethodIdx = 65u; // Based on 2MiB chunks in Create2MethodsWithGap(). |
| const LinkerPatch method1_patches[] = { |
| LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx), |
| }; |
| |
| constexpr uint32_t just_over_max_positive_disp = 128 * MB; |
| uint32_t last_method_idx = Create2MethodsWithGap( |
| method1_code, |
| ArrayRef<const LinkerPatch>(method1_patches), |
| kNopCode, |
| ArrayRef<const LinkerPatch>(), |
| bl_offset_in_method1 + just_over_max_positive_disp); |
| ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx); |
| uint32_t method_after_thunk_idx = last_method_idx; |
| if (sizeof(OatQuickMethodHeader) < kArm64CodeAlignment) { |
| // The thunk needs to start on a kArm64CodeAlignment-aligned address before the address where |
| // the last method would have been if there was no thunk. If the size of the |
| // OatQuickMethodHeader is at least kArm64CodeAlignment, the thunk start shall fit between the |
| // previous filler method and that address. Otherwise, it shall be inserted before that filler |
| // method. |
| method_after_thunk_idx -= 1u; |
| } |
| |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t method_after_thunk_offset = GetMethodOffset(method_after_thunk_idx); |
| ASSERT_TRUE(IsAligned<kArm64CodeAlignment>(method_after_thunk_offset)); |
| uint32_t method_after_thunk_header_offset = |
| method_after_thunk_offset - sizeof(OatQuickMethodHeader); |
| uint32_t thunk_size = MethodCallThunkSize(); |
| uint32_t thunk_offset = RoundDown( |
| method_after_thunk_header_offset - thunk_size, kArm64CodeAlignment); |
| DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size), |
| method_after_thunk_header_offset); |
| ASSERT_TRUE(IsAligned<kArm64CodeAlignment>(thunk_offset)); |
| uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1); |
| ASSERT_TRUE(IsAligned<4u>(diff)); |
| ASSERT_LT(diff, 128 * MB); |
| auto expected_code = GenNopsAndBl(0u, kBlPlus0 | (diff >> 2)); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| CheckThunk(thunk_offset); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, CallOtherJustTooFarBefore) { |
| auto last_method_raw_code = GenNopsAndBl(1u, kBlPlus0); |
| constexpr uint32_t bl_offset_in_last_method = 1u * 4u; // After NOPs. |
| ArrayRef<const uint8_t> last_method_code(last_method_raw_code); |
| ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size()); |
| const LinkerPatch last_method_patches[] = { |
| LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, 1u), |
| }; |
| |
| constexpr uint32_t just_over_max_negative_disp = 128 * MB + 4; |
| uint32_t last_method_idx = Create2MethodsWithGap( |
| kNopCode, ArrayRef<const LinkerPatch>(), last_method_code, |
| ArrayRef<const LinkerPatch>(last_method_patches), |
| just_over_max_negative_disp - bl_offset_in_last_method); |
| uint32_t method1_offset = GetMethodOffset(1u); |
| uint32_t last_method_offset = GetMethodOffset(last_method_idx); |
| ASSERT_EQ(method1_offset, |
| last_method_offset + bl_offset_in_last_method - just_over_max_negative_disp); |
| |
| // Check linked code. |
| uint32_t thunk_offset = |
| CompiledCode::AlignCode(last_method_offset + last_method_code.size(), InstructionSet::kArm64); |
| uint32_t diff = thunk_offset - (last_method_offset + bl_offset_in_last_method); |
| ASSERT_TRUE(IsAligned<4u>(diff)); |
| ASSERT_LT(diff, 128 * MB); |
| auto expected_code = GenNopsAndBl(1u, kBlPlus0 | (diff >> 2)); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx), |
| ArrayRef<const uint8_t>(expected_code))); |
| EXPECT_TRUE(CheckThunk(thunk_offset)); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntry) { |
| struct TestCase { |
| uint32_t bss_begin; |
| uint32_t string_entry_offset; |
| }; |
| static const TestCase test_cases[] = { |
| { 0x12345678u, 0x1234u }, |
| { -0x12345678u, 0x4444u }, |
| { 0x12345000u, 0x3ffcu }, |
| { 0x12345000u, 0x4000u } |
| }; |
| for (const TestCase& test_case : test_cases) { |
| Reset(); |
| TestNopsAdrpLdr(/*num_nops=*/ 0u, test_case.bss_begin, test_case.string_entry_offset); |
| } |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReference) { |
| for (uint32_t string_offset : { 0x12345678u, -0x12345678u, 0x12345000u, 0x12345ffcu}) { |
| Reset(); |
| TestNopsAdrpAdd(/*num_nops=*/ 0u, string_offset); |
| } |
| } |
| |
| template <typename Test> |
| void TestForAdrpOffsets(Test test, std::initializer_list<uint32_t> args) { |
| for (uint32_t adrp_offset : { 0xff4u, 0xff8u, 0xffcu, 0x1000u }) { |
| for (uint32_t arg : args) { |
| test(adrp_offset, arg); |
| } |
| } |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntryLdur) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_entry_offset) { |
| Reset(); |
| bool has_thunk = ((adrp_offset) == 0xff8u || (adrp_offset) == 0xffcu); |
| TestAdrpLdurLdr(adrp_offset, has_thunk, /*bss_begin=*/ 0x12345678u, string_entry_offset); |
| }, |
| { 0x1234u, 0x1238u }); |
| } |
| |
| // LDR <Wt>, <label> is always aligned. We should never have to use a fixup. |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntryWPcRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t pcrel_disp) { |
| Reset(); |
| TestAdrpLdrPcRelLdr(kLdrWPcRelInsn, |
| pcrel_disp, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*bss_begin=*/ 0x12345678u, |
| /*string_entry_offset=*/ 0x1234u); |
| }, |
| { 0x1234u, 0x1238u }); |
| } |
| |
| // LDR <Xt>, <label> is aligned when offset + displacement is a multiple of 8. |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntryXPcRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t pcrel_disp) { |
| Reset(); |
| bool unaligned = !IsAligned<8u>((adrp_offset) + 4u + static_cast<uint32_t>(pcrel_disp)); |
| bool has_thunk = ((adrp_offset) == 0xff8u || (adrp_offset) == 0xffcu) && unaligned; |
| TestAdrpLdrPcRelLdr(kLdrXPcRelInsn, |
| pcrel_disp, |
| adrp_offset, |
| has_thunk, |
| /*bss_begin=*/ 0x12345678u, |
| /*string_entry_offset=*/ 0x1234u); |
| }, |
| { 0x1234u, 0x1238u }); |
| } |
| |
| // LDR <Wt>, [SP, #<pimm>] and LDR <Xt>, [SP, #<pimm>] are always aligned. No fixup needed. |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntryWSpRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t disp) { |
| Reset(); |
| TestAdrpLdrSpRelLdr(kLdrWSpRelInsn, |
| /*sprel_disp_in_load_units=*/ disp >> 2, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*bss_begin=*/ 0x12345678u, |
| /*string_entry_offset=*/ 0x1234u); |
| }, |
| { 0u, 4u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringBssEntryXSpRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t disp) { |
| Reset(); |
| TestAdrpLdrSpRelLdr(kLdrXSpRelInsn, |
| /*sprel_disp_in_load_units=*/ (disp) >> 3, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*bss_begin=*/ 0x12345678u, |
| /*string_entry_offset=*/ 0x1234u); |
| }, |
| { 0u, 8u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceLdur) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_offset) { |
| Reset(); |
| bool has_thunk = ((adrp_offset) == 0xff8u || (adrp_offset) == 0xffcu); |
| TestAdrpLdurAdd(adrp_offset, has_thunk, string_offset); |
| }, |
| { 0x12345678u, 0xffffc840u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceSubX3X2) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_offset) { |
| Reset(); |
| /* SUB unrelated to "ADRP x0, addr". */ \ |
| uint32_t sub = kSubXInsn | (100 << 10) | (2u << 5) | 3u; /* SUB x3, x2, #100 */ |
| TestAdrpInsn2Add(sub, adrp_offset, /*has_thunk=*/ false, string_offset); |
| }, |
| { 0x12345678u, 0xffffc840u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceSubsX3X0) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_offset) { |
| Reset(); |
| /* SUBS that uses the result of "ADRP x0, addr". */ \ |
| uint32_t subs = kSubsXInsn | (100 << 10) | (0u << 5) | 3u; /* SUBS x3, x0, #100 */ |
| TestAdrpInsn2Add(subs, adrp_offset, /*has_thunk=*/ false, string_offset); |
| }, |
| { 0x12345678u, 0xffffc840u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceAddX0X0) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_offset) { |
| Reset(); |
| /* ADD that uses the result register of "ADRP x0, addr" as both source and destination. */ |
| uint32_t add = kAddXInsn | (100 << 10) | (0u << 5) | 0u; /* ADD x0, x0, #100 */ |
| TestAdrpInsn2Add(add, adrp_offset, /*has_thunk=*/ false, string_offset); |
| }, |
| { 0x12345678u, 0xffffc840 }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceAddsX0X2) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t string_offset) { |
| Reset(); |
| /* ADDS that does not use the result of "ADRP x0, addr" but overwrites that register. */ |
| uint32_t adds = kAddsXInsn | (100 << 10) | (2u << 5) | 0u; /* ADDS x0, x2, #100 */ |
| bool has_thunk = ((adrp_offset) == 0xff8u || (adrp_offset) == 0xffcu); |
| TestAdrpInsn2Add(adds, adrp_offset, has_thunk, string_offset); |
| }, |
| { 0x12345678u, 0xffffc840u }); |
| } |
| |
| // LDR <Wt>, <label> is always aligned. We should never have to use a fixup. |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceWPcRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t pcrel_disp) { |
| Reset(); |
| TestAdrpLdrPcRelAdd(kLdrWPcRelInsn, |
| pcrel_disp, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*string_offset=*/ 0x12345678u); |
| }, |
| { 0x1234u, 0x1238u }); |
| } |
| |
| // LDR <Xt>, <label> is aligned when offset + displacement is a multiple of 8. |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceXPcRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t pcrel_disp) { |
| Reset(); |
| bool unaligned = !IsAligned<8u>((adrp_offset) + 4u + static_cast<uint32_t>(pcrel_disp)); |
| bool has_thunk = ((adrp_offset) == 0xff8u || (adrp_offset) == 0xffcu) && unaligned; |
| TestAdrpLdrPcRelAdd(kLdrXPcRelInsn, |
| pcrel_disp, |
| adrp_offset, |
| has_thunk, |
| /*string_offset=*/ 0x12345678u); |
| }, |
| { 0x1234u, 0x1238u }); |
| } |
| |
| // LDR <Wt>, [SP, #<pimm>] and LDR <Xt>, [SP, #<pimm>] are always aligned. No fixup needed. |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceWSpRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t disp) { |
| Reset(); |
| TestAdrpLdrSpRelAdd(kLdrWSpRelInsn, |
| /*sprel_disp_in_load_units=*/ (disp) >> 2, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*string_offset=*/ 0x12345678u); |
| }, |
| { 0u, 4u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, StringReferenceXSpRel) { |
| TestForAdrpOffsets( |
| [&](uint32_t adrp_offset, uint32_t disp) { |
| Reset(); |
| TestAdrpLdrSpRelAdd(kLdrXSpRelInsn, |
| /*sprel_disp_in_load_units=*/ (disp) >> 3, |
| adrp_offset, |
| /*has_thunk=*/ false, |
| /*string_offset=*/ 0x12345678u); |
| }, |
| { 0u, 8u }); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, EntrypointCall) { |
| constexpr uint32_t kEntrypointOffset = 512; |
| const LinkerPatch patches[] = { |
| LinkerPatch::CallEntrypointPatch(0u, kEntrypointOffset), |
| }; |
| AddCompiledMethod(MethodRef(1u), kCallCode, ArrayRef<const LinkerPatch>(patches)); |
| Link(); |
| |
| uint32_t method_offset = GetMethodOffset(1u); |
| uint32_t thunk_offset = CompiledCode::AlignCode(method_offset + kCallCode.size(), |
| InstructionSet::kArm64); |
| uint32_t diff = thunk_offset - method_offset; |
| ASSERT_TRUE(IsAligned<4u>(diff)); |
| ASSERT_LT(diff, 128 * MB); |
| auto expected_code = RawCode({kBlPlus0 | (diff >> 2)}); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); |
| |
| // Verify the thunk. |
| uint32_t ldr_ip0_tr_offset = |
| 0xf9400000 | // LDR Xt, [Xn, #<simm>] |
| ((kEntrypointOffset >> 3) << 10) | // imm12 = (simm >> scale), scale = 3 |
| (/* tr */ 19 << 5) | // Xn = TR |
| /* ip0 */ 16; // Xt = ip0 |
| uint32_t br_ip0 = 0xd61f0000 | (/* ip0 */ 16 << 5); |
| auto expected_thunk = RawCode({ ldr_ip0_tr_offset, br_ip0 }); |
| ASSERT_LE(8u, output_.size() - thunk_offset); |
| EXPECT_EQ(ldr_ip0_tr_offset, GetOutputInsn(thunk_offset)); |
| EXPECT_EQ(br_ip0, GetOutputInsn(thunk_offset + 4u)); |
| } |
| |
| void Arm64RelativePatcherTest::TestBakerField(uint32_t offset, |
| uint32_t ref_reg, |
| bool implicit_null_checks) { |
| implicit_null_checks_ = implicit_null_checks; |
| uint32_t valid_regs[] = { |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
| 10, 11, 12, 13, 14, 15, 18, 19, // IP0 and IP1 are reserved. |
| 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, |
| // LR and SP/ZR are reserved. |
| }; |
| DCHECK_ALIGNED(offset, 4u); |
| DCHECK_LT(offset, 16 * KB); |
| constexpr size_t kMethodCodeSize = 8u; |
| constexpr size_t kLiteralOffset = 0u; |
| uint32_t method_idx = 0u; |
| for (uint32_t base_reg : valid_regs) { |
| for (uint32_t holder_reg : valid_regs) { |
| uint32_t ldr = kLdrWInsn | (offset << (10 - 2)) | (base_reg << 5) | ref_reg; |
| const std::vector<uint8_t> raw_code = RawCode({kCbnzIP1Plus0Insn, ldr}); |
| ASSERT_EQ(kMethodCodeSize, raw_code.size()); |
| ArrayRef<const uint8_t> code(raw_code); |
| uint32_t encoded_data = EncodeBakerReadBarrierFieldData(base_reg, holder_reg); |
| const LinkerPatch patches[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset, encoded_data), |
| }; |
| ++method_idx; |
| AddCompiledMethod(MethodRef(method_idx), code, ArrayRef<const LinkerPatch>(patches)); |
| } |
| } |
| Link(); |
| |
| // All thunks are at the end. |
| uint32_t thunk_offset = |
| GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment); |
| method_idx = 0u; |
| for (uint32_t base_reg : valid_regs) { |
| for (uint32_t holder_reg : valid_regs) { |
| ++method_idx; |
| uint32_t cbnz_offset = thunk_offset - (GetMethodOffset(method_idx) + kLiteralOffset); |
| uint32_t cbnz = kCbnzIP1Plus0Insn | (cbnz_offset << (5 - 2)); |
| uint32_t ldr = kLdrWInsn | (offset << (10 - 2)) | (base_reg << 5) | ref_reg; |
| const std::vector<uint8_t> expected_code = RawCode({cbnz, ldr}); |
| ASSERT_EQ(kMethodCodeSize, expected_code.size()); |
| ASSERT_TRUE( |
| CheckLinkedMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(expected_code))); |
| |
| std::vector<uint8_t> expected_thunk = CompileBakerOffsetThunk(base_reg, holder_reg); |
| ASSERT_GT(output_.size(), thunk_offset); |
| ASSERT_GE(output_.size() - thunk_offset, expected_thunk.size()); |
| ArrayRef<const uint8_t> compiled_thunk(output_.data() + thunk_offset, |
| expected_thunk.size()); |
| if (ArrayRef<const uint8_t>(expected_thunk) != compiled_thunk) { |
| DumpDiff(ArrayRef<const uint8_t>(expected_thunk), compiled_thunk); |
| ASSERT_TRUE(false); |
| } |
| |
| size_t gray_check_offset = thunk_offset; |
| if (implicit_null_checks && holder_reg == base_reg) { |
| // Verify that the null-check CBZ uses the correct register, i.e. holder_reg. |
| ASSERT_GE(output_.size() - gray_check_offset, 4u); |
| ASSERT_EQ(0x34000000u | holder_reg, GetOutputInsn(thunk_offset) & 0xff00001fu); |
| gray_check_offset +=4u; |
| } |
| // Verify that the lock word for gray bit check is loaded from the holder address. |
| static constexpr size_t kGrayCheckInsns = 5; |
| ASSERT_GE(output_.size() - gray_check_offset, 4u * kGrayCheckInsns); |
| const uint32_t load_lock_word = |
| kLdrWInsn | |
| (mirror::Object::MonitorOffset().Uint32Value() << (10 - 2)) | |
| (holder_reg << 5) | |
| /* ip0 */ 16; |
| EXPECT_EQ(load_lock_word, GetOutputInsn(gray_check_offset)); |
| // Verify the gray bit check. |
| const uint32_t check_gray_bit_without_offset = |
| 0x37000000u | (LockWord::kReadBarrierStateShift << 19) | /* ip0 */ 16; |
| EXPECT_EQ(check_gray_bit_without_offset, GetOutputInsn(gray_check_offset + 4u) & 0xfff8001fu); |
| // Verify the fake dependency. |
| const uint32_t fake_dependency = |
| 0x8b408000u | // ADD Xd, Xn, Xm, LSR 32 |
| (/* ip0 */ 16 << 16) | // Xm = ip0 |
| (base_reg << 5) | // Xn = base_reg |
| base_reg; // Xd = base_reg |
| EXPECT_EQ(fake_dependency, GetOutputInsn(gray_check_offset + 12u)); |
| // Do not check the rest of the implementation. |
| |
| // The next thunk follows on the next aligned offset. |
| thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment); |
| } |
| } |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerOffset) { |
| struct TestCase { |
| uint32_t offset; |
| uint32_t ref_reg; |
| }; |
| static const TestCase test_cases[] = { |
| { 0u, 0u }, |
| { 8u, 15u}, |
| { 0x3ffcu, 29u }, |
| }; |
| for (const TestCase& test_case : test_cases) { |
| Reset(); |
| TestBakerField(test_case.offset, test_case.ref_reg, /*implicit_null_checks=*/ true); |
| Reset(); |
| TestBakerField(test_case.offset, test_case.ref_reg, /*implicit_null_checks=*/ false); |
| } |
| } |
| |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerOffsetThunkInTheMiddle) { |
| // One thunk in the middle with maximum distance branches to it from both sides. |
| // Use offset = 0, base_reg = 0, ref_reg = 0, the LDR is simply `kLdrWInsn`. |
| constexpr uint32_t kLiteralOffset1 = 4; |
| const std::vector<uint8_t> raw_code1 = RawCode({kNopInsn, kCbnzIP1Plus0Insn, kLdrWInsn}); |
| ArrayRef<const uint8_t> code1(raw_code1); |
| uint32_t encoded_data = EncodeBakerReadBarrierFieldData(/* base_reg */ 0, /* holder_reg */ 0); |
| const LinkerPatch patches1[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset1, encoded_data), |
| }; |
| AddCompiledMethod(MethodRef(1u), code1, ArrayRef<const LinkerPatch>(patches1)); |
| |
| // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4 |
| // allows the branch to reach that thunk. |
| size_t filler1_size = |
| 1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment); |
| std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u); |
| ArrayRef<const uint8_t> filler1_code(raw_filler1_code); |
| AddCompiledMethod(MethodRef(2u), filler1_code); |
| |
| // Enforce thunk reservation with a tiny method. |
| AddCompiledMethod(MethodRef(3u), kNopCode); |
| |
| // Allow reaching the thunk from the very beginning of a method 1MiB away. Backward branch |
| // reaches the full 1MiB. Things to subtract: |
| // - thunk size and method 3 pre-header, rounded up (padding in between if needed) |
| // - method 3 code and method 4 pre-header, rounded up (padding in between if needed) |
| // - method 4 header (let there be no padding between method 4 code and method 5 pre-header). |
| size_t thunk_size = CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0).size(); |
| size_t filler2_size = |
| 1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64CodeAlignment) |
| - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment) |
| - sizeof(OatQuickMethodHeader); |
| std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 4u); |
| ArrayRef<const uint8_t> filler2_code(raw_filler2_code); |
| AddCompiledMethod(MethodRef(4u), filler2_code); |
| |
| constexpr uint32_t kLiteralOffset2 = 0; |
| const std::vector<uint8_t> raw_code2 = RawCode({kCbnzIP1Plus0Insn, kLdrWInsn}); |
| ArrayRef<const uint8_t> code2(raw_code2); |
| const LinkerPatch patches2[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset2, encoded_data), |
| }; |
| AddCompiledMethod(MethodRef(5u), code2, ArrayRef<const LinkerPatch>(patches2)); |
| |
| Link(); |
| |
| uint32_t first_method_offset = GetMethodOffset(1u); |
| uint32_t last_method_offset = GetMethodOffset(5u); |
| EXPECT_EQ(2 * MB, last_method_offset - first_method_offset); |
| |
| const uint32_t cbnz_max_forward = kCbnzIP1Plus0Insn | 0x007fffe0; |
| const uint32_t cbnz_max_backward = kCbnzIP1Plus0Insn | 0x00800000; |
| const std::vector<uint8_t> expected_code1 = RawCode({kNopInsn, cbnz_max_forward, kLdrWInsn}); |
| const std::vector<uint8_t> expected_code2 = RawCode({cbnz_max_backward, kLdrWInsn}); |
| ASSERT_TRUE(CheckLinkedMethod(MethodRef(1), ArrayRef<const uint8_t>(expected_code1))); |
| ASSERT_TRUE(CheckLinkedMethod(MethodRef(5), ArrayRef<const uint8_t>(expected_code2))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerOffsetThunkBeforeFiller) { |
| // Based on the first part of BakerOffsetThunkInTheMiddle but the CBNZ is one instruction |
| // earlier, so the thunk is emitted before the filler. |
| // Use offset = 0, base_reg = 0, ref_reg = 0, the LDR is simply `kLdrWInsn`. |
| constexpr uint32_t kLiteralOffset1 = 0; |
| const std::vector<uint8_t> raw_code1 = RawCode({kCbnzIP1Plus0Insn, kLdrWInsn, kNopInsn}); |
| ArrayRef<const uint8_t> code1(raw_code1); |
| uint32_t encoded_data = EncodeBakerReadBarrierFieldData(/* base_reg */ 0, /* holder_reg */ 0); |
| const LinkerPatch patches1[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset1, encoded_data), |
| }; |
| AddCompiledMethod(MethodRef(1u), code1, ArrayRef<const LinkerPatch>(patches1)); |
| |
| // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4 |
| // allows the branch to reach that thunk. |
| size_t filler1_size = |
| 1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment); |
| std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u); |
| ArrayRef<const uint8_t> filler1_code(raw_filler1_code); |
| AddCompiledMethod(MethodRef(2u), filler1_code); |
| |
| Link(); |
| |
| const uint32_t cbnz_offset = RoundUp(raw_code1.size(), kArm64CodeAlignment) - kLiteralOffset1; |
| const uint32_t cbnz = kCbnzIP1Plus0Insn | (cbnz_offset << (5 - 2)); |
| const std::vector<uint8_t> expected_code1 = RawCode({cbnz, kLdrWInsn, kNopInsn}); |
| ASSERT_TRUE(CheckLinkedMethod(MethodRef(1), ArrayRef<const uint8_t>(expected_code1))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerOffsetThunkInTheMiddleUnreachableFromLast) { |
| // Based on the BakerOffsetThunkInTheMiddle but the CBNZ in the last method is preceded |
| // by NOP and cannot reach the thunk in the middle, so we emit an extra thunk at the end. |
| // Use offset = 0, base_reg = 0, ref_reg = 0, the LDR is simply `kLdrWInsn`. |
| constexpr uint32_t kLiteralOffset1 = 4; |
| const std::vector<uint8_t> raw_code1 = RawCode({kNopInsn, kCbnzIP1Plus0Insn, kLdrWInsn}); |
| ArrayRef<const uint8_t> code1(raw_code1); |
| uint32_t encoded_data = EncodeBakerReadBarrierFieldData(/* base_reg */ 0, /* holder_reg */ 0); |
| const LinkerPatch patches1[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset1, encoded_data), |
| }; |
| AddCompiledMethod(MethodRef(1u), code1, ArrayRef<const LinkerPatch>(patches1)); |
| |
| // Allow thunk at 1MiB offset from the start of the method above. Literal offset being 4 |
| // allows the branch to reach that thunk. |
| size_t filler1_size = |
| 1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment); |
| std::vector<uint8_t> raw_filler1_code = GenNops(filler1_size / 4u); |
| ArrayRef<const uint8_t> filler1_code(raw_filler1_code); |
| AddCompiledMethod(MethodRef(2u), filler1_code); |
| |
| // Enforce thunk reservation with a tiny method. |
| AddCompiledMethod(MethodRef(3u), kNopCode); |
| |
| // If not for the extra NOP, this would allow reaching the thunk from the very beginning |
| // of a method 1MiB away. Backward branch reaches the full 1MiB. Things to subtract: |
| // - thunk size and method 3 pre-header, rounded up (padding in between if needed) |
| // - method 3 code and method 4 pre-header, rounded up (padding in between if needed) |
| // - method 4 header (let there be no padding between method 4 code and method 5 pre-header). |
| size_t thunk_size = CompileBakerOffsetThunk(/* base_reg */ 0, /* holder_reg */ 0).size(); |
| size_t filler2_size = |
| 1 * MB - RoundUp(thunk_size + sizeof(OatQuickMethodHeader), kArm64CodeAlignment) |
| - RoundUp(kNopCode.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment) |
| - sizeof(OatQuickMethodHeader); |
| std::vector<uint8_t> raw_filler2_code = GenNops(filler2_size / 4u); |
| ArrayRef<const uint8_t> filler2_code(raw_filler2_code); |
| AddCompiledMethod(MethodRef(4u), filler2_code); |
| |
| // Extra NOP compared to BakerOffsetThunkInTheMiddle. |
| constexpr uint32_t kLiteralOffset2 = 4; |
| const std::vector<uint8_t> raw_code2 = RawCode({kNopInsn, kCbnzIP1Plus0Insn, kLdrWInsn}); |
| ArrayRef<const uint8_t> code2(raw_code2); |
| const LinkerPatch patches2[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kLiteralOffset2, encoded_data), |
| }; |
| AddCompiledMethod(MethodRef(5u), code2, ArrayRef<const LinkerPatch>(patches2)); |
| |
| Link(); |
| |
| const uint32_t cbnz_max_forward = kCbnzIP1Plus0Insn | 0x007fffe0; |
| const uint32_t cbnz_last_offset = |
| RoundUp(raw_code2.size(), kArm64CodeAlignment) - kLiteralOffset2; |
| const uint32_t cbnz_last = kCbnzIP1Plus0Insn | (cbnz_last_offset << (5 - 2)); |
| const std::vector<uint8_t> expected_code1 = RawCode({kNopInsn, cbnz_max_forward, kLdrWInsn}); |
| const std::vector<uint8_t> expected_code2 = RawCode({kNopInsn, cbnz_last, kLdrWInsn}); |
| ASSERT_TRUE(CheckLinkedMethod(MethodRef(1), ArrayRef<const uint8_t>(expected_code1))); |
| ASSERT_TRUE(CheckLinkedMethod(MethodRef(5), ArrayRef<const uint8_t>(expected_code2))); |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerArray) { |
| uint32_t valid_regs[] = { |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
| 10, 11, 12, 13, 14, 15, 18, 19, // IP0 and IP1 are reserved. |
| 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, |
| // LR and SP/ZR are reserved. |
| }; |
| auto ldr = [](uint32_t base_reg) { |
| uint32_t index_reg = (base_reg == 0u) ? 1u : 0u; |
| uint32_t ref_reg = (base_reg == 2) ? 3u : 2u; |
| return kLdrWLsl2Insn | (index_reg << 16) | (base_reg << 5) | ref_reg; |
| }; |
| constexpr size_t kMethodCodeSize = 8u; |
| constexpr size_t kLiteralOffset = 0u; |
| uint32_t method_idx = 0u; |
| for (uint32_t base_reg : valid_regs) { |
| ++method_idx; |
| const std::vector<uint8_t> raw_code = RawCode({kCbnzIP1Plus0Insn, ldr(base_reg)}); |
| ASSERT_EQ(kMethodCodeSize, raw_code.size()); |
| ArrayRef<const uint8_t> code(raw_code); |
| const LinkerPatch patches[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch( |
| kLiteralOffset, EncodeBakerReadBarrierArrayData(base_reg)), |
| }; |
| AddCompiledMethod(MethodRef(method_idx), code, ArrayRef<const LinkerPatch>(patches)); |
| } |
| Link(); |
| |
| // All thunks are at the end. |
| uint32_t thunk_offset = |
| GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment); |
| method_idx = 0u; |
| for (uint32_t base_reg : valid_regs) { |
| ++method_idx; |
| uint32_t cbnz_offset = thunk_offset - (GetMethodOffset(method_idx) + kLiteralOffset); |
| uint32_t cbnz = kCbnzIP1Plus0Insn | (cbnz_offset << (5 - 2)); |
| const std::vector<uint8_t> expected_code = RawCode({cbnz, ldr(base_reg)}); |
| ASSERT_EQ(kMethodCodeSize, expected_code.size()); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(expected_code))); |
| |
| std::vector<uint8_t> expected_thunk = CompileBakerArrayThunk(base_reg); |
| ASSERT_GT(output_.size(), thunk_offset); |
| ASSERT_GE(output_.size() - thunk_offset, expected_thunk.size()); |
| ArrayRef<const uint8_t> compiled_thunk(output_.data() + thunk_offset, |
| expected_thunk.size()); |
| if (ArrayRef<const uint8_t>(expected_thunk) != compiled_thunk) { |
| DumpDiff(ArrayRef<const uint8_t>(expected_thunk), compiled_thunk); |
| ASSERT_TRUE(false); |
| } |
| |
| // Verify that the lock word for gray bit check is loaded from the correct address |
| // before the base_reg which points to the array data. |
| static constexpr size_t kGrayCheckInsns = 5; |
| ASSERT_GE(output_.size() - thunk_offset, 4u * kGrayCheckInsns); |
| int32_t data_offset = |
| mirror::Array::DataOffset(Primitive::ComponentSize(Primitive::kPrimNot)).Int32Value(); |
| int32_t offset = mirror::Object::MonitorOffset().Int32Value() - data_offset; |
| ASSERT_LT(offset, 0); |
| const uint32_t load_lock_word = |
| kLdurWInsn | |
| ((offset & 0x1ffu) << 12) | |
| (base_reg << 5) | |
| /* ip0 */ 16; |
| EXPECT_EQ(load_lock_word, GetOutputInsn(thunk_offset)); |
| // Verify the gray bit check. |
| const uint32_t check_gray_bit_without_offset = |
| 0x37000000u | (LockWord::kReadBarrierStateShift << 19) | /* ip0 */ 16; |
| EXPECT_EQ(check_gray_bit_without_offset, GetOutputInsn(thunk_offset + 4u) & 0xfff8001fu); |
| // Verify the fake dependency. |
| const uint32_t fake_dependency = |
| 0x8b408000u | // ADD Xd, Xn, Xm, LSR 32 |
| (/* ip0 */ 16 << 16) | // Xm = ip0 |
| (base_reg << 5) | // Xn = base_reg |
| base_reg; // Xd = base_reg |
| EXPECT_EQ(fake_dependency, GetOutputInsn(thunk_offset + 12u)); |
| // Do not check the rest of the implementation. |
| |
| // The next thunk follows on the next aligned offset. |
| thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment); |
| } |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerGcRoot) { |
| uint32_t valid_regs[] = { |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
| 10, 11, 12, 13, 14, 15, 18, 19, // IP0 and IP1 are reserved. |
| 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, |
| // LR and SP/ZR are reserved. |
| }; |
| constexpr size_t kMethodCodeSize = 8u; |
| constexpr size_t kLiteralOffset = 4u; |
| uint32_t method_idx = 0u; |
| for (uint32_t root_reg : valid_regs) { |
| ++method_idx; |
| uint32_t ldr = kLdrWInsn | (/* offset */ 8 << (10 - 2)) | (/* base_reg */ 0 << 5) | root_reg; |
| const std::vector<uint8_t> raw_code = RawCode({ldr, kCbnzIP1Plus0Insn}); |
| ASSERT_EQ(kMethodCodeSize, raw_code.size()); |
| ArrayRef<const uint8_t> code(raw_code); |
| const LinkerPatch patches[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch( |
| kLiteralOffset, EncodeBakerReadBarrierGcRootData(root_reg)), |
| }; |
| AddCompiledMethod(MethodRef(method_idx), code, ArrayRef<const LinkerPatch>(patches)); |
| } |
| Link(); |
| |
| // All thunks are at the end. |
| uint32_t thunk_offset = |
| GetMethodOffset(method_idx) + RoundUp(kMethodCodeSize, kArm64CodeAlignment); |
| method_idx = 0u; |
| for (uint32_t root_reg : valid_regs) { |
| ++method_idx; |
| uint32_t cbnz_offset = thunk_offset - (GetMethodOffset(method_idx) + kLiteralOffset); |
| uint32_t cbnz = kCbnzIP1Plus0Insn | (cbnz_offset << (5 - 2)); |
| uint32_t ldr = kLdrWInsn | (/* offset */ 8 << (10 - 2)) | (/* base_reg */ 0 << 5) | root_reg; |
| const std::vector<uint8_t> expected_code = RawCode({ldr, cbnz}); |
| ASSERT_EQ(kMethodCodeSize, expected_code.size()); |
| EXPECT_TRUE(CheckLinkedMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(expected_code))); |
| |
| std::vector<uint8_t> expected_thunk = CompileBakerGcRootThunk(root_reg); |
| ASSERT_GT(output_.size(), thunk_offset); |
| ASSERT_GE(output_.size() - thunk_offset, expected_thunk.size()); |
| ArrayRef<const uint8_t> compiled_thunk(output_.data() + thunk_offset, |
| expected_thunk.size()); |
| if (ArrayRef<const uint8_t>(expected_thunk) != compiled_thunk) { |
| DumpDiff(ArrayRef<const uint8_t>(expected_thunk), compiled_thunk); |
| ASSERT_TRUE(false); |
| } |
| |
| // Verify that the fast-path null-check CBZ uses the correct register, i.e. root_reg. |
| ASSERT_GE(output_.size() - thunk_offset, 4u); |
| ASSERT_EQ(0x34000000u | root_reg, GetOutputInsn(thunk_offset) & 0xff00001fu); |
| // Do not check the rest of the implementation. |
| |
| // The next thunk follows on the next aligned offset. |
| thunk_offset += RoundUp(expected_thunk.size(), kArm64CodeAlignment); |
| } |
| } |
| |
| TEST_F(Arm64RelativePatcherTestDefault, BakerAndMethodCallInteraction) { |
| // During development, there was a `DCHECK_LE(MaxNextOffset(), next_thunk.MaxNextOffset());` |
| // in `ArmBaseRelativePatcher::ThunkData::MakeSpaceBefore()` which does not necessarily |
| // hold when we're reserving thunks of different sizes. This test exposes the situation |
| // by using Baker thunks and a method call thunk. |
| |
| // Add a method call patch that can reach to method 1 offset + 128MiB. |
| uint32_t method_idx = 0u; |
| constexpr size_t kMethodCallLiteralOffset = 4u; |
| constexpr uint32_t kMissingMethodIdx = 2u; |
| const std::vector<uint8_t> raw_code1 = RawCode({kNopInsn, kBlPlus0}); |
| const LinkerPatch method1_patches[] = { |
| LinkerPatch::RelativeCodePatch(kMethodCallLiteralOffset, nullptr, 2u), |
| }; |
| ArrayRef<const uint8_t> code1(raw_code1); |
| ++method_idx; |
| AddCompiledMethod(MethodRef(1u), code1, ArrayRef<const LinkerPatch>(method1_patches)); |
| |
| // Skip kMissingMethodIdx. |
| ++method_idx; |
| ASSERT_EQ(kMissingMethodIdx, method_idx); |
| // Add a method with the right size that the method code for the next one starts 1MiB |
| // after code for method 1. |
| size_t filler_size = |
| 1 * MB - RoundUp(raw_code1.size() + sizeof(OatQuickMethodHeader), kArm64CodeAlignment) |
| - sizeof(OatQuickMethodHeader); |
| std::vector<uint8_t> filler_code = GenNops(filler_size / 4u); |
| ++method_idx; |
| AddCompiledMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(filler_code)); |
| // Add 126 methods with 1MiB code+header, making the code for the next method start 1MiB |
| // before the currently scheduled MaxNextOffset() for the method call thunk. |
| for (uint32_t i = 0; i != 126; ++i) { |
| filler_size = 1 * MB - sizeof(OatQuickMethodHeader); |
| filler_code = GenNops(filler_size / 4u); |
| ++method_idx; |
| AddCompiledMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(filler_code)); |
| } |
| |
| // Add 2 Baker GC root patches to the last method, one that would allow the thunk at |
| // 1MiB + kArm64CodeAlignment, i.e. kArm64CodeAlignment after the method call thunk, and the |
| // second that needs it kArm64CodeAlignment after that. Given the size of the GC root thunk |
| // is more than the space required by the method call thunk plus kArm64CodeAlignment, |
| // this pushes the first GC root thunk's pending MaxNextOffset() before the method call |
| // thunk's pending MaxNextOffset() which needs to be adjusted. |
| ASSERT_LT(RoundUp(CompileMethodCallThunk().size(), kArm64CodeAlignment) + kArm64CodeAlignment, |
| CompileBakerGcRootThunk(/* root_reg */ 0).size()); |
| static_assert(kArm64CodeAlignment == 16, "Code below assumes kArm64CodeAlignment == 16"); |
| constexpr size_t kBakerLiteralOffset1 = 4u + kArm64CodeAlignment; |
| constexpr size_t kBakerLiteralOffset2 = 4u + 2 * kArm64CodeAlignment; |
| // Use offset = 0, base_reg = 0, the LDR is simply `kLdrWInsn | root_reg`. |
| const uint32_t ldr1 = kLdrWInsn | /* root_reg */ 1; |
| const uint32_t ldr2 = kLdrWInsn | /* root_reg */ 2; |
| const std::vector<uint8_t> last_method_raw_code = RawCode({ |
| kNopInsn, kNopInsn, kNopInsn, kNopInsn, // Padding before first GC root read barrier. |
| ldr1, kCbnzIP1Plus0Insn, // First GC root LDR with read barrier. |
| kNopInsn, kNopInsn, // Padding before second GC root read barrier. |
| ldr2, kCbnzIP1Plus0Insn, // Second GC root LDR with read barrier. |
| }); |
| uint32_t encoded_data1 = EncodeBakerReadBarrierGcRootData(/* root_reg */ 1); |
| uint32_t encoded_data2 = EncodeBakerReadBarrierGcRootData(/* root_reg */ 2); |
| const LinkerPatch last_method_patches[] = { |
| LinkerPatch::BakerReadBarrierBranchPatch(kBakerLiteralOffset1, encoded_data1), |
| LinkerPatch::BakerReadBarrierBranchPatch(kBakerLiteralOffset2, encoded_data2), |
| }; |
| ++method_idx; |
| AddCompiledMethod(MethodRef(method_idx), |
| ArrayRef<const uint8_t>(last_method_raw_code), |
| ArrayRef<const LinkerPatch>(last_method_patches)); |
| |
| // The main purpose of the test is to check that Link() does not cause a crash. |
| Link(); |
| |
| ASSERT_EQ(127 * MB, GetMethodOffset(method_idx) - GetMethodOffset(1u)); |
| } |
| |
| } // namespace linker |
| } // namespace art |