| /* |
| * 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/arm/relative_patcher_arm_base.h" |
| |
| #include "base/stl_util.h" |
| #include "compiled_method-inl.h" |
| #include "debug/method_debug_info.h" |
| #include "dex/dex_file_types.h" |
| #include "linker/linker_patch.h" |
| #include "oat.h" |
| #include "oat_quick_method_header.h" |
| #include "stream/output_stream.h" |
| |
| namespace art { |
| namespace linker { |
| |
| class ArmBaseRelativePatcher::ThunkData { |
| public: |
| ThunkData(ArrayRef<const uint8_t> code, const std::string& debug_name, uint32_t max_next_offset) |
| : code_(code), |
| debug_name_(debug_name), |
| offsets_(), |
| max_next_offset_(max_next_offset), |
| pending_offset_(0u) { |
| DCHECK(NeedsNextThunk()); // The data is constructed only when we expect to need the thunk. |
| } |
| |
| ThunkData(ThunkData&& src) = default; |
| |
| size_t CodeSize() const { |
| return code_.size(); |
| } |
| |
| ArrayRef<const uint8_t> GetCode() const { |
| return code_; |
| } |
| |
| const std::string& GetDebugName() const { |
| return debug_name_; |
| } |
| |
| bool NeedsNextThunk() const { |
| return max_next_offset_ != 0u; |
| } |
| |
| uint32_t MaxNextOffset() const { |
| DCHECK(NeedsNextThunk()); |
| return max_next_offset_; |
| } |
| |
| void ClearMaxNextOffset() { |
| DCHECK(NeedsNextThunk()); |
| max_next_offset_ = 0u; |
| } |
| |
| void SetMaxNextOffset(uint32_t max_next_offset) { |
| DCHECK(!NeedsNextThunk()); |
| max_next_offset_ = max_next_offset; |
| } |
| |
| // Adjust the MaxNextOffset() down if needed to fit the code before the next thunk. |
| // Returns true if it was adjusted, false if the old value was kept. |
| bool MakeSpaceBefore(const ThunkData& next_thunk, size_t alignment) { |
| DCHECK(NeedsNextThunk()); |
| DCHECK(next_thunk.NeedsNextThunk()); |
| DCHECK_ALIGNED_PARAM(MaxNextOffset(), alignment); |
| DCHECK_ALIGNED_PARAM(next_thunk.MaxNextOffset(), alignment); |
| if (next_thunk.MaxNextOffset() - CodeSize() < MaxNextOffset()) { |
| max_next_offset_ = RoundDown(next_thunk.MaxNextOffset() - CodeSize(), alignment); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| uint32_t ReserveOffset(size_t offset) { |
| DCHECK(NeedsNextThunk()); |
| DCHECK_LE(offset, max_next_offset_); |
| max_next_offset_ = 0u; // The reserved offset should satisfy all pending references. |
| offsets_.push_back(offset); |
| return offset + CodeSize(); |
| } |
| |
| bool HasReservedOffset() const { |
| return !offsets_.empty(); |
| } |
| |
| uint32_t LastReservedOffset() const { |
| DCHECK(HasReservedOffset()); |
| return offsets_.back(); |
| } |
| |
| bool HasPendingOffset() const { |
| return pending_offset_ != offsets_.size(); |
| } |
| |
| uint32_t GetPendingOffset() const { |
| DCHECK(HasPendingOffset()); |
| return offsets_[pending_offset_]; |
| } |
| |
| void MarkPendingOffsetAsWritten() { |
| DCHECK(HasPendingOffset()); |
| ++pending_offset_; |
| } |
| |
| bool HasWrittenOffset() const { |
| return pending_offset_ != 0u; |
| } |
| |
| uint32_t LastWrittenOffset() const { |
| DCHECK(HasWrittenOffset()); |
| return offsets_[pending_offset_ - 1u]; |
| } |
| |
| size_t IndexOfFirstThunkAtOrAfter(uint32_t offset) const { |
| size_t number_of_thunks = NumberOfThunks(); |
| for (size_t i = 0; i != number_of_thunks; ++i) { |
| if (GetThunkOffset(i) >= offset) { |
| return i; |
| } |
| } |
| return number_of_thunks; |
| } |
| |
| size_t NumberOfThunks() const { |
| return offsets_.size(); |
| } |
| |
| uint32_t GetThunkOffset(size_t index) const { |
| DCHECK_LT(index, NumberOfThunks()); |
| return offsets_[index]; |
| } |
| |
| private: |
| const ArrayRef<const uint8_t> code_; // The code of the thunk. |
| const std::string debug_name_; // The debug name of the thunk. |
| std::vector<uint32_t> offsets_; // Offsets at which the thunk needs to be written. |
| uint32_t max_next_offset_; // The maximum offset at which the next thunk can be placed. |
| uint32_t pending_offset_; // The index of the next offset to write. |
| }; |
| |
| class ArmBaseRelativePatcher::PendingThunkComparator { |
| public: |
| bool operator()(const ThunkData* lhs, const ThunkData* rhs) const { |
| DCHECK(lhs->HasPendingOffset()); |
| DCHECK(rhs->HasPendingOffset()); |
| // The top of the heap is defined to contain the highest element and we want to pick |
| // the thunk with the smallest pending offset, so use the reverse ordering, i.e. ">". |
| return lhs->GetPendingOffset() > rhs->GetPendingOffset(); |
| } |
| }; |
| |
| uint32_t ArmBaseRelativePatcher::ReserveSpace(uint32_t offset, |
| const CompiledMethod* compiled_method, |
| MethodReference method_ref) { |
| return ReserveSpaceInternal(offset, compiled_method, method_ref, 0u); |
| } |
| |
| uint32_t ArmBaseRelativePatcher::ReserveSpaceEnd(uint32_t offset) { |
| // For multi-oat compilations (boot image), ReserveSpaceEnd() is called for each oat file. |
| // Since we do not know here whether this is the last file or whether the next opportunity |
| // to place thunk will be soon enough, we need to reserve all needed thunks now. Code for |
| // subsequent oat files can still call back to them. |
| if (!unprocessed_method_call_patches_.empty()) { |
| ResolveMethodCalls(offset, MethodReference(nullptr, dex::kDexNoIndex)); |
| } |
| for (ThunkData* data : unreserved_thunks_) { |
| uint32_t thunk_offset = CompiledCode::AlignCode(offset, instruction_set_); |
| offset = data->ReserveOffset(thunk_offset); |
| } |
| unreserved_thunks_.clear(); |
| // We also need to delay initiating the pending_thunks_ until the call to WriteThunks(). |
| // Check that the `pending_thunks_.capacity()` indicates that no WriteThunks() has taken place. |
| DCHECK_EQ(pending_thunks_.capacity(), 0u); |
| return offset; |
| } |
| |
| uint32_t ArmBaseRelativePatcher::WriteThunks(OutputStream* out, uint32_t offset) { |
| if (pending_thunks_.capacity() == 0u) { |
| if (thunks_.empty()) { |
| return offset; |
| } |
| // First call to WriteThunks(), prepare the thunks for writing. |
| pending_thunks_.reserve(thunks_.size()); |
| for (auto& entry : thunks_) { |
| ThunkData* data = &entry.second; |
| if (data->HasPendingOffset()) { |
| pending_thunks_.push_back(data); |
| } |
| } |
| std::make_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator()); |
| } |
| uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); |
| while (!pending_thunks_.empty() && |
| pending_thunks_.front()->GetPendingOffset() == aligned_offset) { |
| // Write alignment bytes and code. |
| uint32_t aligned_code_delta = aligned_offset - offset; |
| if (aligned_code_delta != 0u && UNLIKELY(!WriteCodeAlignment(out, aligned_code_delta))) { |
| return 0u; |
| } |
| if (UNLIKELY(!WriteThunk(out, pending_thunks_.front()->GetCode()))) { |
| return 0u; |
| } |
| offset = aligned_offset + pending_thunks_.front()->CodeSize(); |
| // Mark the thunk as written at the pending offset and update the `pending_thunks_` heap. |
| std::pop_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator()); |
| pending_thunks_.back()->MarkPendingOffsetAsWritten(); |
| if (pending_thunks_.back()->HasPendingOffset()) { |
| std::push_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator()); |
| } else { |
| pending_thunks_.pop_back(); |
| } |
| aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); |
| } |
| DCHECK(pending_thunks_.empty() || pending_thunks_.front()->GetPendingOffset() > aligned_offset); |
| return offset; |
| } |
| |
| std::vector<debug::MethodDebugInfo> ArmBaseRelativePatcher::GenerateThunkDebugInfo( |
| uint32_t executable_offset) { |
| // For multi-oat compilation (boot image), `thunks_` records thunks for all oat files. |
| // To return debug info for the current oat file, we must ignore thunks before the |
| // `executable_offset` as they are in the previous oat files and this function must be |
| // called before reserving thunk positions for subsequent oat files. |
| size_t number_of_thunks = 0u; |
| for (auto&& entry : thunks_) { |
| const ThunkData& data = entry.second; |
| number_of_thunks += data.NumberOfThunks() - data.IndexOfFirstThunkAtOrAfter(executable_offset); |
| } |
| std::vector<debug::MethodDebugInfo> result; |
| result.reserve(number_of_thunks); |
| for (auto&& entry : thunks_) { |
| const ThunkData& data = entry.second; |
| size_t start = data.IndexOfFirstThunkAtOrAfter(executable_offset); |
| if (start == data.NumberOfThunks()) { |
| continue; |
| } |
| // Get the base name to use for the first occurrence of the thunk. |
| const std::string& base_name = data.GetDebugName(); |
| for (size_t i = start, num = data.NumberOfThunks(); i != num; ++i) { |
| debug::MethodDebugInfo info = {}; |
| if (i == 0u) { |
| info.custom_name = base_name; |
| } else { |
| // Add a disambiguating tag for subsequent identical thunks. Since the `thunks_` |
| // keeps records also for thunks in previous oat files, names based on the thunk |
| // index shall be unique across the whole multi-oat output. |
| info.custom_name = base_name + "_" + std::to_string(i); |
| } |
| info.isa = instruction_set_; |
| info.is_code_address_text_relative = true; |
| info.code_address = data.GetThunkOffset(i) - executable_offset; |
| info.code_size = data.CodeSize(); |
| result.push_back(std::move(info)); |
| } |
| } |
| return result; |
| } |
| |
| ArmBaseRelativePatcher::ArmBaseRelativePatcher(RelativePatcherThunkProvider* thunk_provider, |
| RelativePatcherTargetProvider* target_provider, |
| InstructionSet instruction_set) |
| : thunk_provider_(thunk_provider), |
| target_provider_(target_provider), |
| instruction_set_(instruction_set), |
| thunks_(), |
| unprocessed_method_call_patches_(), |
| method_call_thunk_(nullptr), |
| pending_thunks_() { |
| } |
| |
| ArmBaseRelativePatcher::~ArmBaseRelativePatcher() { |
| // All work done by member destructors. |
| } |
| |
| uint32_t ArmBaseRelativePatcher::ReserveSpaceInternal(uint32_t offset, |
| const CompiledMethod* compiled_method, |
| MethodReference method_ref, |
| uint32_t max_extra_space) { |
| // Adjust code size for extra space required by the subclass. |
| uint32_t max_code_size = compiled_method->GetQuickCode().size() + max_extra_space; |
| uint32_t code_offset; |
| uint32_t next_aligned_offset; |
| while (true) { |
| code_offset = compiled_method->AlignCode(offset + sizeof(OatQuickMethodHeader)); |
| next_aligned_offset = compiled_method->AlignCode(code_offset + max_code_size); |
| if (unreserved_thunks_.empty() || |
| unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset) { |
| break; |
| } |
| ThunkData* thunk = unreserved_thunks_.front(); |
| if (thunk == method_call_thunk_) { |
| ResolveMethodCalls(code_offset, method_ref); |
| // This may have changed `method_call_thunk_` data, so re-check if we need to reserve. |
| if (unreserved_thunks_.empty() || |
| unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset) { |
| break; |
| } |
| // We need to process the new `front()` whether it's still the `method_call_thunk_` or not. |
| thunk = unreserved_thunks_.front(); |
| } |
| unreserved_thunks_.pop_front(); |
| uint32_t thunk_offset = CompiledCode::AlignCode(offset, instruction_set_); |
| offset = thunk->ReserveOffset(thunk_offset); |
| if (thunk == method_call_thunk_) { |
| // All remaining method call patches will be handled by this thunk. |
| DCHECK(!unprocessed_method_call_patches_.empty()); |
| DCHECK_LE(thunk_offset - unprocessed_method_call_patches_.front().GetPatchOffset(), |
| MaxPositiveDisplacement(GetMethodCallKey())); |
| unprocessed_method_call_patches_.clear(); |
| } |
| } |
| |
| // Process patches and check that adding thunks for the current method did not push any |
| // thunks (previously existing or newly added) before `next_aligned_offset`. This is |
| // essentially a check that we never compile a method that's too big. The calls or branches |
| // from the method should be able to reach beyond the end of the method and over any pending |
| // thunks. (The number of different thunks should be relatively low and their code short.) |
| ProcessPatches(compiled_method, code_offset); |
| CHECK(unreserved_thunks_.empty() || |
| unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset); |
| |
| return offset; |
| } |
| |
| uint32_t ArmBaseRelativePatcher::CalculateMethodCallDisplacement(uint32_t patch_offset, |
| uint32_t target_offset) { |
| DCHECK(method_call_thunk_ != nullptr); |
| // Unsigned arithmetic with its well-defined overflow behavior is just fine here. |
| uint32_t displacement = target_offset - patch_offset; |
| uint32_t max_positive_displacement = MaxPositiveDisplacement(GetMethodCallKey()); |
| uint32_t max_negative_displacement = MaxNegativeDisplacement(GetMethodCallKey()); |
| // NOTE: With unsigned arithmetic we do mean to use && rather than || below. |
| if (displacement > max_positive_displacement && displacement < -max_negative_displacement) { |
| // Unwritten thunks have higher offsets, check if it's within range. |
| DCHECK(!method_call_thunk_->HasPendingOffset() || |
| method_call_thunk_->GetPendingOffset() > patch_offset); |
| if (method_call_thunk_->HasPendingOffset() && |
| method_call_thunk_->GetPendingOffset() - patch_offset <= max_positive_displacement) { |
| displacement = method_call_thunk_->GetPendingOffset() - patch_offset; |
| } else { |
| // We must have a previous thunk then. |
| DCHECK(method_call_thunk_->HasWrittenOffset()); |
| DCHECK_LT(method_call_thunk_->LastWrittenOffset(), patch_offset); |
| displacement = method_call_thunk_->LastWrittenOffset() - patch_offset; |
| DCHECK_GE(displacement, -max_negative_displacement); |
| } |
| } |
| return displacement; |
| } |
| |
| uint32_t ArmBaseRelativePatcher::GetThunkTargetOffset(const ThunkKey& key, uint32_t patch_offset) { |
| auto it = thunks_.find(key); |
| CHECK(it != thunks_.end()); |
| const ThunkData& data = it->second; |
| if (data.HasWrittenOffset()) { |
| uint32_t offset = data.LastWrittenOffset(); |
| DCHECK_LT(offset, patch_offset); |
| if (patch_offset - offset <= MaxNegativeDisplacement(key)) { |
| return offset; |
| } |
| } |
| DCHECK(data.HasPendingOffset()); |
| uint32_t offset = data.GetPendingOffset(); |
| DCHECK_GT(offset, patch_offset); |
| DCHECK_LE(offset - patch_offset, MaxPositiveDisplacement(key)); |
| return offset; |
| } |
| |
| ArmBaseRelativePatcher::ThunkKey ArmBaseRelativePatcher::GetMethodCallKey() { |
| return ThunkKey(ThunkType::kMethodCall); |
| } |
| |
| ArmBaseRelativePatcher::ThunkKey ArmBaseRelativePatcher::GetEntrypointCallKey( |
| const LinkerPatch& patch) { |
| DCHECK_EQ(patch.GetType(), LinkerPatch::Type::kCallEntrypoint); |
| return ThunkKey(ThunkType::kEntrypointCall, patch.EntrypointOffset()); |
| } |
| |
| ArmBaseRelativePatcher::ThunkKey ArmBaseRelativePatcher::GetBakerThunkKey( |
| const LinkerPatch& patch) { |
| DCHECK_EQ(patch.GetType(), LinkerPatch::Type::kBakerReadBarrierBranch); |
| return ThunkKey(ThunkType::kBakerReadBarrier, |
| patch.GetBakerCustomValue1(), |
| patch.GetBakerCustomValue2()); |
| } |
| |
| void ArmBaseRelativePatcher::ProcessPatches(const CompiledMethod* compiled_method, |
| uint32_t code_offset) { |
| for (const LinkerPatch& patch : compiled_method->GetPatches()) { |
| uint32_t patch_offset = code_offset + patch.LiteralOffset(); |
| ThunkKey key(static_cast<ThunkType>(-1)); |
| bool simple_thunk_patch = false; |
| ThunkData* old_data = nullptr; |
| if (patch.GetType() == LinkerPatch::Type::kCallRelative) { |
| key = GetMethodCallKey(); |
| unprocessed_method_call_patches_.emplace_back(patch_offset, patch.TargetMethod()); |
| if (method_call_thunk_ == nullptr) { |
| uint32_t max_next_offset = CalculateMaxNextOffset(patch_offset, key); |
| auto it = thunks_.Put(key, ThunkDataForPatch(patch, max_next_offset)); |
| method_call_thunk_ = &it->second; |
| AddUnreservedThunk(method_call_thunk_); |
| } else { |
| old_data = method_call_thunk_; |
| } |
| } else if (patch.GetType() == LinkerPatch::Type::kCallEntrypoint) { |
| key = GetEntrypointCallKey(patch); |
| simple_thunk_patch = true; |
| } else if (patch.GetType() == LinkerPatch::Type::kBakerReadBarrierBranch) { |
| key = GetBakerThunkKey(patch); |
| simple_thunk_patch = true; |
| } |
| if (simple_thunk_patch) { |
| auto lb = thunks_.lower_bound(key); |
| if (lb == thunks_.end() || thunks_.key_comp()(key, lb->first)) { |
| uint32_t max_next_offset = CalculateMaxNextOffset(patch_offset, key); |
| auto it = thunks_.PutBefore(lb, key, ThunkDataForPatch(patch, max_next_offset)); |
| AddUnreservedThunk(&it->second); |
| } else { |
| old_data = &lb->second; |
| } |
| } |
| if (old_data != nullptr) { |
| // Shared path where an old thunk may need an update. |
| DCHECK(key.GetType() != static_cast<ThunkType>(-1)); |
| DCHECK(!old_data->HasReservedOffset() || old_data->LastReservedOffset() < patch_offset); |
| if (old_data->NeedsNextThunk()) { |
| // Patches for a method are ordered by literal offset, so if we still need to place |
| // this thunk for a previous patch, that thunk shall be in range for this patch. |
| DCHECK_LE(old_data->MaxNextOffset(), CalculateMaxNextOffset(patch_offset, key)); |
| } else { |
| if (!old_data->HasReservedOffset() || |
| patch_offset - old_data->LastReservedOffset() > MaxNegativeDisplacement(key)) { |
| old_data->SetMaxNextOffset(CalculateMaxNextOffset(patch_offset, key)); |
| AddUnreservedThunk(old_data); |
| } |
| } |
| } |
| } |
| } |
| |
| void ArmBaseRelativePatcher::AddUnreservedThunk(ThunkData* data) { |
| DCHECK(data->NeedsNextThunk()); |
| size_t index = unreserved_thunks_.size(); |
| while (index != 0u && data->MaxNextOffset() < unreserved_thunks_[index - 1u]->MaxNextOffset()) { |
| --index; |
| } |
| unreserved_thunks_.insert(unreserved_thunks_.begin() + index, data); |
| // We may need to update the max next offset(s) if the thunk code would not fit. |
| size_t alignment = GetInstructionSetAlignment(instruction_set_); |
| if (index + 1u != unreserved_thunks_.size()) { |
| // Note: Ignore the return value as we need to process previous thunks regardless. |
| data->MakeSpaceBefore(*unreserved_thunks_[index + 1u], alignment); |
| } |
| // Make space for previous thunks. Once we find a pending thunk that does |
| // not need an adjustment, we can stop. |
| while (index != 0u && unreserved_thunks_[index - 1u]->MakeSpaceBefore(*data, alignment)) { |
| --index; |
| data = unreserved_thunks_[index]; |
| } |
| } |
| |
| void ArmBaseRelativePatcher::ResolveMethodCalls(uint32_t quick_code_offset, |
| MethodReference method_ref) { |
| DCHECK(!unreserved_thunks_.empty()); |
| DCHECK(!unprocessed_method_call_patches_.empty()); |
| DCHECK(method_call_thunk_ != nullptr); |
| uint32_t max_positive_displacement = MaxPositiveDisplacement(GetMethodCallKey()); |
| uint32_t max_negative_displacement = MaxNegativeDisplacement(GetMethodCallKey()); |
| // Process as many patches as possible, stop only on unresolved targets or calls too far back. |
| while (!unprocessed_method_call_patches_.empty()) { |
| MethodReference target_method = unprocessed_method_call_patches_.front().GetTargetMethod(); |
| uint32_t patch_offset = unprocessed_method_call_patches_.front().GetPatchOffset(); |
| DCHECK(!method_call_thunk_->HasReservedOffset() || |
| method_call_thunk_->LastReservedOffset() <= patch_offset); |
| if (!method_call_thunk_->HasReservedOffset() || |
| patch_offset - method_call_thunk_->LastReservedOffset() > max_negative_displacement) { |
| // No previous thunk in range, check if we can reach the target directly. |
| if (target_method == method_ref) { |
| DCHECK_GT(quick_code_offset, patch_offset); |
| if (quick_code_offset - patch_offset > max_positive_displacement) { |
| break; |
| } |
| } else { |
| auto result = target_provider_->FindMethodOffset(target_method); |
| if (!result.first) { |
| break; |
| } |
| uint32_t target_offset = result.second - CompiledCode::CodeDelta(instruction_set_); |
| if (target_offset >= patch_offset) { |
| DCHECK_LE(target_offset - patch_offset, max_positive_displacement); |
| } else if (patch_offset - target_offset > max_negative_displacement) { |
| break; |
| } |
| } |
| } |
| unprocessed_method_call_patches_.pop_front(); |
| } |
| if (!unprocessed_method_call_patches_.empty()) { |
| // Try to adjust the max next offset in `method_call_thunk_`. Do this conservatively only if |
| // the thunk shall be at the end of the `unreserved_thunks_` to avoid dealing with overlaps. |
| uint32_t new_max_next_offset = |
| unprocessed_method_call_patches_.front().GetPatchOffset() + max_positive_displacement; |
| if (new_max_next_offset > |
| unreserved_thunks_.back()->MaxNextOffset() + unreserved_thunks_.back()->CodeSize()) { |
| method_call_thunk_->ClearMaxNextOffset(); |
| method_call_thunk_->SetMaxNextOffset(new_max_next_offset); |
| if (method_call_thunk_ != unreserved_thunks_.back()) { |
| RemoveElement(unreserved_thunks_, method_call_thunk_); |
| unreserved_thunks_.push_back(method_call_thunk_); |
| } |
| } |
| } else { |
| // We have resolved all method calls, we do not need a new thunk anymore. |
| method_call_thunk_->ClearMaxNextOffset(); |
| RemoveElement(unreserved_thunks_, method_call_thunk_); |
| } |
| } |
| |
| inline uint32_t ArmBaseRelativePatcher::CalculateMaxNextOffset(uint32_t patch_offset, |
| const ThunkKey& key) { |
| return RoundDown(patch_offset + MaxPositiveDisplacement(key), |
| GetInstructionSetAlignment(instruction_set_)); |
| } |
| |
| inline ArmBaseRelativePatcher::ThunkData ArmBaseRelativePatcher::ThunkDataForPatch( |
| const LinkerPatch& patch, uint32_t max_next_offset) { |
| ArrayRef<const uint8_t> code; |
| std::string debug_name; |
| thunk_provider_->GetThunkCode(patch, &code, &debug_name); |
| DCHECK(!code.empty()); |
| return ThunkData(code, debug_name, max_next_offset); |
| } |
| |
| } // namespace linker |
| } // namespace art |