Home | History | Annotate | Download | only in arm
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "linker/arm/relative_patcher_arm_base.h"
     18 
     19 #include "compiled_method.h"
     20 #include "linker/output_stream.h"
     21 #include "oat.h"
     22 #include "oat_quick_method_header.h"
     23 
     24 namespace art {
     25 namespace linker {
     26 
     27 class ArmBaseRelativePatcher::ThunkData {
     28  public:
     29   ThunkData(std::vector<uint8_t> code, uint32_t max_next_offset)
     30       : code_(code),
     31         offsets_(),
     32         max_next_offset_(max_next_offset),
     33         pending_offset_(0u) {
     34     DCHECK(NeedsNextThunk());  // The data is constructed only when we expect to need the thunk.
     35   }
     36 
     37   ThunkData(ThunkData&& src) = default;
     38 
     39   size_t CodeSize() const {
     40     return code_.size();
     41   }
     42 
     43   ArrayRef<const uint8_t> GetCode() const {
     44     return ArrayRef<const uint8_t>(code_);
     45   }
     46 
     47   bool NeedsNextThunk() const {
     48     return max_next_offset_ != 0u;
     49   }
     50 
     51   uint32_t MaxNextOffset() const {
     52     DCHECK(NeedsNextThunk());
     53     return max_next_offset_;
     54   }
     55 
     56   void ClearMaxNextOffset() {
     57     DCHECK(NeedsNextThunk());
     58     max_next_offset_ = 0u;
     59   }
     60 
     61   void SetMaxNextOffset(uint32_t max_next_offset) {
     62     DCHECK(!NeedsNextThunk());
     63     max_next_offset_ = max_next_offset;
     64   }
     65 
     66   // Adjust the MaxNextOffset() down if needed to fit the code before the next thunk.
     67   // Returns true if it was adjusted, false if the old value was kept.
     68   bool MakeSpaceBefore(const ThunkData& next_thunk, size_t alignment) {
     69     DCHECK(NeedsNextThunk());
     70     DCHECK(next_thunk.NeedsNextThunk());
     71     DCHECK_ALIGNED_PARAM(MaxNextOffset(), alignment);
     72     DCHECK_ALIGNED_PARAM(next_thunk.MaxNextOffset(), alignment);
     73     if (next_thunk.MaxNextOffset() - CodeSize() < MaxNextOffset()) {
     74       max_next_offset_ = RoundDown(next_thunk.MaxNextOffset() - CodeSize(), alignment);
     75       return true;
     76     } else {
     77       return false;
     78     }
     79   }
     80 
     81   uint32_t ReserveOffset(size_t offset) {
     82     DCHECK(NeedsNextThunk());
     83     DCHECK_LE(offset, max_next_offset_);
     84     max_next_offset_ = 0u;  // The reserved offset should satisfy all pending references.
     85     offsets_.push_back(offset);
     86     return offset + CodeSize();
     87   }
     88 
     89   bool HasReservedOffset() const {
     90     return !offsets_.empty();
     91   }
     92 
     93   uint32_t LastReservedOffset() const {
     94     DCHECK(HasReservedOffset());
     95     return offsets_.back();
     96   }
     97 
     98   bool HasPendingOffset() const {
     99     return pending_offset_ != offsets_.size();
    100   }
    101 
    102   uint32_t GetPendingOffset() const {
    103     DCHECK(HasPendingOffset());
    104     return offsets_[pending_offset_];
    105   }
    106 
    107   void MarkPendingOffsetAsWritten() {
    108     DCHECK(HasPendingOffset());
    109     ++pending_offset_;
    110   }
    111 
    112   bool HasWrittenOffset() const {
    113     return pending_offset_ != 0u;
    114   }
    115 
    116   uint32_t LastWrittenOffset() const {
    117     DCHECK(HasWrittenOffset());
    118     return offsets_[pending_offset_ - 1u];
    119   }
    120 
    121  private:
    122   std::vector<uint8_t> code_;       // The code of the thunk.
    123   std::vector<uint32_t> offsets_;   // Offsets at which the thunk needs to be written.
    124   uint32_t max_next_offset_;        // The maximum offset at which the next thunk can be placed.
    125   uint32_t pending_offset_;         // The index of the next offset to write.
    126 };
    127 
    128 class ArmBaseRelativePatcher::PendingThunkComparator {
    129  public:
    130   bool operator()(const ThunkData* lhs, const ThunkData* rhs) const {
    131     DCHECK(lhs->HasPendingOffset());
    132     DCHECK(rhs->HasPendingOffset());
    133     // The top of the heap is defined to contain the highest element and we want to pick
    134     // the thunk with the smallest pending offset, so use the reverse ordering, i.e. ">".
    135     return lhs->GetPendingOffset() > rhs->GetPendingOffset();
    136   }
    137 };
    138 
    139 uint32_t ArmBaseRelativePatcher::ReserveSpace(uint32_t offset,
    140                                               const CompiledMethod* compiled_method,
    141                                               MethodReference method_ref) {
    142   return ReserveSpaceInternal(offset, compiled_method, method_ref, 0u);
    143 }
    144 
    145 uint32_t ArmBaseRelativePatcher::ReserveSpaceEnd(uint32_t offset) {
    146   // For multi-oat compilations (boot image), ReserveSpaceEnd() is called for each oat file.
    147   // Since we do not know here whether this is the last file or whether the next opportunity
    148   // to place thunk will be soon enough, we need to reserve all needed thunks now. Code for
    149   // subsequent oat files can still call back to them.
    150   if (!unprocessed_method_call_patches_.empty()) {
    151     ResolveMethodCalls(offset, MethodReference(nullptr, DexFile::kDexNoIndex));
    152   }
    153   for (ThunkData* data : unreserved_thunks_) {
    154     uint32_t thunk_offset = CompiledCode::AlignCode(offset, instruction_set_);
    155     offset = data->ReserveOffset(thunk_offset);
    156   }
    157   unreserved_thunks_.clear();
    158   // We also need to delay initiating the pending_thunks_ until the call to WriteThunks().
    159   // Check that the `pending_thunks_.capacity()` indicates that no WriteThunks() has taken place.
    160   DCHECK_EQ(pending_thunks_.capacity(), 0u);
    161   return offset;
    162 }
    163 
    164 uint32_t ArmBaseRelativePatcher::WriteThunks(OutputStream* out, uint32_t offset) {
    165   if (pending_thunks_.capacity() == 0u) {
    166     if (thunks_.empty()) {
    167       return offset;
    168     }
    169     // First call to WriteThunks(), prepare the thunks for writing.
    170     pending_thunks_.reserve(thunks_.size());
    171     for (auto& entry : thunks_) {
    172       ThunkData* data = &entry.second;
    173       if (data->HasPendingOffset()) {
    174         pending_thunks_.push_back(data);
    175       }
    176     }
    177     std::make_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator());
    178   }
    179   uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_);
    180   while (!pending_thunks_.empty() &&
    181          pending_thunks_.front()->GetPendingOffset() == aligned_offset) {
    182     // Write alignment bytes and code.
    183     uint32_t aligned_code_delta = aligned_offset - offset;
    184     if (aligned_code_delta != 0u && UNLIKELY(!WriteCodeAlignment(out, aligned_code_delta))) {
    185       return 0u;
    186     }
    187     if (UNLIKELY(!WriteThunk(out, pending_thunks_.front()->GetCode()))) {
    188       return 0u;
    189     }
    190     offset = aligned_offset + pending_thunks_.front()->CodeSize();
    191     // Mark the thunk as written at the pending offset and update the `pending_thunks_` heap.
    192     std::pop_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator());
    193     pending_thunks_.back()->MarkPendingOffsetAsWritten();
    194     if (pending_thunks_.back()->HasPendingOffset()) {
    195       std::push_heap(pending_thunks_.begin(), pending_thunks_.end(), PendingThunkComparator());
    196     } else {
    197       pending_thunks_.pop_back();
    198     }
    199     aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_);
    200   }
    201   DCHECK(pending_thunks_.empty() || pending_thunks_.front()->GetPendingOffset() > aligned_offset);
    202   return offset;
    203 }
    204 
    205 ArmBaseRelativePatcher::ArmBaseRelativePatcher(RelativePatcherTargetProvider* provider,
    206                                                InstructionSet instruction_set)
    207     : provider_(provider),
    208       instruction_set_(instruction_set),
    209       thunks_(),
    210       unprocessed_method_call_patches_(),
    211       method_call_thunk_(nullptr),
    212       pending_thunks_() {
    213 }
    214 
    215 ArmBaseRelativePatcher::~ArmBaseRelativePatcher() {
    216   // All work done by member destructors.
    217 }
    218 
    219 uint32_t ArmBaseRelativePatcher::ReserveSpaceInternal(uint32_t offset,
    220                                                       const CompiledMethod* compiled_method,
    221                                                       MethodReference method_ref,
    222                                                       uint32_t max_extra_space) {
    223   // Adjust code size for extra space required by the subclass.
    224   uint32_t max_code_size = compiled_method->GetQuickCode().size() + max_extra_space;
    225   uint32_t code_offset;
    226   uint32_t next_aligned_offset;
    227   while (true) {
    228     code_offset = compiled_method->AlignCode(offset + sizeof(OatQuickMethodHeader));
    229     next_aligned_offset = compiled_method->AlignCode(code_offset + max_code_size);
    230     if (unreserved_thunks_.empty() ||
    231         unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset) {
    232       break;
    233     }
    234     ThunkData* thunk = unreserved_thunks_.front();
    235     if (thunk == method_call_thunk_) {
    236       ResolveMethodCalls(code_offset, method_ref);
    237       // This may have changed `method_call_thunk_` data, so re-check if we need to reserve.
    238       if (unreserved_thunks_.empty() ||
    239           unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset) {
    240         break;
    241       }
    242       // We need to process the new `front()` whether it's still the `method_call_thunk_` or not.
    243       thunk = unreserved_thunks_.front();
    244     }
    245     unreserved_thunks_.pop_front();
    246     uint32_t thunk_offset = CompiledCode::AlignCode(offset, instruction_set_);
    247     offset = thunk->ReserveOffset(thunk_offset);
    248     if (thunk == method_call_thunk_) {
    249       // All remaining method call patches will be handled by this thunk.
    250       DCHECK(!unprocessed_method_call_patches_.empty());
    251       DCHECK_LE(thunk_offset - unprocessed_method_call_patches_.front().GetPatchOffset(),
    252                 MaxPositiveDisplacement(ThunkType::kMethodCall));
    253       unprocessed_method_call_patches_.clear();
    254     }
    255   }
    256 
    257   // Process patches and check that adding thunks for the current method did not push any
    258   // thunks (previously existing or newly added) before `next_aligned_offset`. This is
    259   // essentially a check that we never compile a method that's too big. The calls or branches
    260   // from the method should be able to reach beyond the end of the method and over any pending
    261   // thunks. (The number of different thunks should be relatively low and their code short.)
    262   ProcessPatches(compiled_method, code_offset);
    263   CHECK(unreserved_thunks_.empty() ||
    264         unreserved_thunks_.front()->MaxNextOffset() >= next_aligned_offset);
    265 
    266   return offset;
    267 }
    268 
    269 uint32_t ArmBaseRelativePatcher::CalculateMethodCallDisplacement(uint32_t patch_offset,
    270                                                                  uint32_t target_offset) {
    271   DCHECK(method_call_thunk_ != nullptr);
    272   // Unsigned arithmetic with its well-defined overflow behavior is just fine here.
    273   uint32_t displacement = target_offset - patch_offset;
    274   uint32_t max_positive_displacement = MaxPositiveDisplacement(ThunkType::kMethodCall);
    275   uint32_t max_negative_displacement = MaxNegativeDisplacement(ThunkType::kMethodCall);
    276   // NOTE: With unsigned arithmetic we do mean to use && rather than || below.
    277   if (displacement > max_positive_displacement && displacement < -max_negative_displacement) {
    278     // Unwritten thunks have higher offsets, check if it's within range.
    279     DCHECK(!method_call_thunk_->HasPendingOffset() ||
    280            method_call_thunk_->GetPendingOffset() > patch_offset);
    281     if (method_call_thunk_->HasPendingOffset() &&
    282         method_call_thunk_->GetPendingOffset() - patch_offset <= max_positive_displacement) {
    283       displacement = method_call_thunk_->GetPendingOffset() - patch_offset;
    284     } else {
    285       // We must have a previous thunk then.
    286       DCHECK(method_call_thunk_->HasWrittenOffset());
    287       DCHECK_LT(method_call_thunk_->LastWrittenOffset(), patch_offset);
    288       displacement = method_call_thunk_->LastWrittenOffset() - patch_offset;
    289       DCHECK_GE(displacement, -max_negative_displacement);
    290     }
    291   }
    292   return displacement;
    293 }
    294 
    295 uint32_t ArmBaseRelativePatcher::GetThunkTargetOffset(const ThunkKey& key, uint32_t patch_offset) {
    296   auto it = thunks_.find(key);
    297   CHECK(it != thunks_.end());
    298   const ThunkData& data = it->second;
    299   if (data.HasWrittenOffset()) {
    300     uint32_t offset = data.LastWrittenOffset();
    301     DCHECK_LT(offset, patch_offset);
    302     if (patch_offset - offset <= MaxNegativeDisplacement(key.GetType())) {
    303       return offset;
    304     }
    305   }
    306   DCHECK(data.HasPendingOffset());
    307   uint32_t offset = data.GetPendingOffset();
    308   DCHECK_GT(offset, patch_offset);
    309   DCHECK_LE(offset - patch_offset, MaxPositiveDisplacement(key.GetType()));
    310   return offset;
    311 }
    312 
    313 void ArmBaseRelativePatcher::ProcessPatches(const CompiledMethod* compiled_method,
    314                                             uint32_t code_offset) {
    315   for (const LinkerPatch& patch : compiled_method->GetPatches()) {
    316     uint32_t patch_offset = code_offset + patch.LiteralOffset();
    317     ThunkType key_type = static_cast<ThunkType>(-1);
    318     ThunkData* old_data = nullptr;
    319     if (patch.GetType() == LinkerPatch::Type::kCallRelative) {
    320       key_type = ThunkType::kMethodCall;
    321       unprocessed_method_call_patches_.emplace_back(patch_offset, patch.TargetMethod());
    322       if (method_call_thunk_ == nullptr) {
    323         ThunkKey key(key_type, ThunkParams{{ 0u, 0u }});  // NOLINT(whitespace/braces)
    324         uint32_t max_next_offset = CalculateMaxNextOffset(patch_offset, key_type);
    325         auto it = thunks_.Put(key, ThunkData(CompileThunk(key), max_next_offset));
    326         method_call_thunk_ = &it->second;
    327         AddUnreservedThunk(method_call_thunk_);
    328       } else {
    329         old_data = method_call_thunk_;
    330       }
    331     } else if (patch.GetType() == LinkerPatch::Type::kBakerReadBarrierBranch) {
    332       ThunkKey key = GetBakerReadBarrierKey(patch);
    333       key_type = key.GetType();
    334       auto lb = thunks_.lower_bound(key);
    335       if (lb == thunks_.end() || thunks_.key_comp()(key, lb->first)) {
    336         uint32_t max_next_offset = CalculateMaxNextOffset(patch_offset, key_type);
    337         auto it = thunks_.PutBefore(lb, key, ThunkData(CompileThunk(key), max_next_offset));
    338         AddUnreservedThunk(&it->second);
    339       } else {
    340         old_data = &lb->second;
    341       }
    342     }
    343     if (old_data != nullptr) {
    344       // Shared path where an old thunk may need an update.
    345       DCHECK(key_type != static_cast<ThunkType>(-1));
    346       DCHECK(!old_data->HasReservedOffset() || old_data->LastReservedOffset() < patch_offset);
    347       if (old_data->NeedsNextThunk()) {
    348         // Patches for a method are ordered by literal offset, so if we still need to place
    349         // this thunk for a previous patch, that thunk shall be in range for this patch.
    350         DCHECK_LE(old_data->MaxNextOffset(), CalculateMaxNextOffset(patch_offset, key_type));
    351       } else {
    352         if (!old_data->HasReservedOffset() ||
    353             patch_offset - old_data->LastReservedOffset() > MaxNegativeDisplacement(key_type)) {
    354           old_data->SetMaxNextOffset(CalculateMaxNextOffset(patch_offset, key_type));
    355           AddUnreservedThunk(old_data);
    356         }
    357       }
    358     }
    359   }
    360 }
    361 
    362 void ArmBaseRelativePatcher::AddUnreservedThunk(ThunkData* data) {
    363   DCHECK(data->NeedsNextThunk());
    364   size_t index = unreserved_thunks_.size();
    365   while (index != 0u && data->MaxNextOffset() < unreserved_thunks_[index - 1u]->MaxNextOffset()) {
    366     --index;
    367   }
    368   unreserved_thunks_.insert(unreserved_thunks_.begin() + index, data);
    369   // We may need to update the max next offset(s) if the thunk code would not fit.
    370   size_t alignment = GetInstructionSetAlignment(instruction_set_);
    371   if (index + 1u != unreserved_thunks_.size()) {
    372     // Note: Ignore the return value as we need to process previous thunks regardless.
    373     data->MakeSpaceBefore(*unreserved_thunks_[index + 1u], alignment);
    374   }
    375   // Make space for previous thunks. Once we find a pending thunk that does
    376   // not need an adjustment, we can stop.
    377   while (index != 0u && unreserved_thunks_[index - 1u]->MakeSpaceBefore(*data, alignment)) {
    378     --index;
    379     data = unreserved_thunks_[index];
    380   }
    381 }
    382 
    383 void ArmBaseRelativePatcher::ResolveMethodCalls(uint32_t quick_code_offset,
    384                                                 MethodReference method_ref) {
    385   DCHECK(!unreserved_thunks_.empty());
    386   DCHECK(!unprocessed_method_call_patches_.empty());
    387   DCHECK(method_call_thunk_ != nullptr);
    388   uint32_t max_positive_displacement = MaxPositiveDisplacement(ThunkType::kMethodCall);
    389   uint32_t max_negative_displacement = MaxNegativeDisplacement(ThunkType::kMethodCall);
    390   // Process as many patches as possible, stop only on unresolved targets or calls too far back.
    391   while (!unprocessed_method_call_patches_.empty()) {
    392     MethodReference target_method = unprocessed_method_call_patches_.front().GetTargetMethod();
    393     uint32_t patch_offset = unprocessed_method_call_patches_.front().GetPatchOffset();
    394     DCHECK(!method_call_thunk_->HasReservedOffset() ||
    395            method_call_thunk_->LastReservedOffset() <= patch_offset);
    396     if (!method_call_thunk_->HasReservedOffset() ||
    397         patch_offset - method_call_thunk_->LastReservedOffset() > max_negative_displacement) {
    398       // No previous thunk in range, check if we can reach the target directly.
    399       if (target_method.dex_file == method_ref.dex_file &&
    400           target_method.dex_method_index == method_ref.dex_method_index) {
    401         DCHECK_GT(quick_code_offset, patch_offset);
    402         if (quick_code_offset - patch_offset > max_positive_displacement) {
    403           break;
    404         }
    405       } else {
    406         auto result = provider_->FindMethodOffset(target_method);
    407         if (!result.first) {
    408           break;
    409         }
    410         uint32_t target_offset = result.second - CompiledCode::CodeDelta(instruction_set_);
    411         if (target_offset >= patch_offset) {
    412           DCHECK_LE(target_offset - patch_offset, max_positive_displacement);
    413         } else if (patch_offset - target_offset > max_negative_displacement) {
    414           break;
    415         }
    416       }
    417     }
    418     unprocessed_method_call_patches_.pop_front();
    419   }
    420   if (!unprocessed_method_call_patches_.empty()) {
    421     // Try to adjust the max next offset in `method_call_thunk_`. Do this conservatively only if
    422     // the thunk shall be at the end of the `unreserved_thunks_` to avoid dealing with overlaps.
    423     uint32_t new_max_next_offset =
    424         unprocessed_method_call_patches_.front().GetPatchOffset() + max_positive_displacement;
    425     if (new_max_next_offset >
    426         unreserved_thunks_.back()->MaxNextOffset() + unreserved_thunks_.back()->CodeSize()) {
    427       method_call_thunk_->ClearMaxNextOffset();
    428       method_call_thunk_->SetMaxNextOffset(new_max_next_offset);
    429       if (method_call_thunk_ != unreserved_thunks_.back()) {
    430         RemoveElement(unreserved_thunks_, method_call_thunk_);
    431         unreserved_thunks_.push_back(method_call_thunk_);
    432       }
    433     }
    434   } else {
    435     // We have resolved all method calls, we do not need a new thunk anymore.
    436     method_call_thunk_->ClearMaxNextOffset();
    437     RemoveElement(unreserved_thunks_, method_call_thunk_);
    438   }
    439 }
    440 
    441 inline uint32_t ArmBaseRelativePatcher::CalculateMaxNextOffset(uint32_t patch_offset,
    442                                                                ThunkType type) {
    443   return RoundDown(patch_offset + MaxPositiveDisplacement(type),
    444                    GetInstructionSetAlignment(instruction_set_));
    445 }
    446 
    447 }  // namespace linker
    448 }  // namespace art
    449