// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/compiler/relocation.h" #include "vm/code_patcher.h" #include "vm/heap/pages.h" #include "vm/instructions.h" #include "vm/object_store.h" #include "vm/stub_code.h" namespace dart { #if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC) && \ !defined(TARGET_ARCH_IA32) // Only for testing. DEFINE_FLAG(bool, always_generate_trampolines_for_testing, false, "Generate always trampolines (for testing purposes)."); // The trampolines will have a 1-word object header in front of them. const intptr_t kOffsetInTrampoline = kWordSize; const intptr_t kTrampolineSize = OS::kMaxPreferredCodeAlignment; CodeRelocator::CodeRelocator(Thread* thread, GrowableArray* code_objects, GrowableArray* commands) : StackResource(thread), code_objects_(code_objects), commands_(commands), kind_type_and_offset_(Smi::Handle(thread->zone())), target_(Object::Handle(thread->zone())), destination_(Code::Handle(thread->zone())) {} void CodeRelocator::Relocate(bool is_vm_isolate) { Zone* zone = Thread::Current()->zone(); auto& current_caller = Code::Handle(zone); auto& call_targets = Array::Handle(zone); // Do one linear pass over all code objects and determine: // // * the maximum instruction size // * the maximum number of calls // * the maximum offset into a target instruction // FindInstructionAndCallLimits(); // Emit all instructions and do relocations on the way. for (intptr_t i = 0; i < code_objects_->length(); ++i) { current_caller = (*code_objects_)[i]; const intptr_t code_text_offset = next_text_offset_; if (!AddInstructionsToText(current_caller.raw())) { continue; } call_targets = current_caller.static_calls_target_table(); ScanCallTargets(current_caller, call_targets, code_text_offset); // Any unresolved calls to this instruction can be fixed now. ResolveUnresolvedCallsTargeting(current_caller.instructions()); // If we have forward/backwards calls which are almost out-of-range, we'll // create trampolines now. BuildTrampolinesForAlmostOutOfRangeCalls(); } // We're guaranteed to have all calls resolved, since // * backwards calls are resolved eagerly // * forward calls are resolved once the target is written ASSERT(all_unresolved_calls_.IsEmpty()); ASSERT(unresolved_calls_by_destination_.IsEmpty()); // Any trampolines we created must be patched with the right offsets. auto it = trampolines_by_destination_.GetIterator(); while (true) { auto entry = it.Next(); if (entry == nullptr) break; UnresolvedTrampolineList* trampoline_list = entry->value; while (!trampoline_list->IsEmpty()) { auto unresolved_trampoline = trampoline_list->RemoveFirst(); ResolveTrampoline(unresolved_trampoline); delete unresolved_trampoline; } delete trampoline_list; } trampolines_by_destination_.Clear(); // We're done now, so we clear out the targets tables. auto& caller = Code::Handle(zone); if (!is_vm_isolate) { for (intptr_t i = 0; i < code_objects_->length(); ++i) { caller = (*code_objects_)[i]; caller.set_static_calls_target_table(Array::empty_array()); } } } void CodeRelocator::FindInstructionAndCallLimits() { Zone* zone = Thread::Current()->zone(); auto& current_caller = Code::Handle(zone); auto& call_targets = Array::Handle(zone); for (intptr_t i = 0; i < code_objects_->length(); ++i) { current_caller = (*code_objects_)[i]; const intptr_t size = ImageWriter::SizeInSnapshot(current_caller.instructions()); if (size > max_instructions_size_) { max_instructions_size_ = size; } call_targets = current_caller.static_calls_target_table(); if (!call_targets.IsNull()) { intptr_t num_calls = 0; StaticCallsTable calls(call_targets); for (auto call : calls) { kind_type_and_offset_ = call.Get(); auto kind = Code::KindField::decode(kind_type_and_offset_.Value()); auto offset = Code::OffsetField::decode(kind_type_and_offset_.Value()); auto call_entry_point = Code::EntryPointField::decode(kind_type_and_offset_.Value()); if (kind == Code::kCallViaCode) { continue; } num_calls++; target_ = call.Get(); if (target_.IsFunction()) { auto& fun = Function::Cast(target_); ASSERT(fun.HasCode()); destination_ = fun.CurrentCode(); ASSERT(!destination_.IsStubCode()); } else { target_ = call.Get(); ASSERT(target_.IsCode()); destination_ = Code::Cast(target_).raw(); } // A call site can decide to jump not to the beginning of a function but // rather jump into it at a certain (positive) offset. int32_t offset_into_target = 0; { PcRelativeCallPattern call( Instructions::PayloadStart(current_caller.instructions()) + offset); ASSERT(call.IsValid()); offset_into_target = call.distance(); } const uword destination_payload = Instructions::PayloadStart(destination_.instructions()); const uword entry_point = call_entry_point == Code::kUncheckedEntry ? Instructions::UncheckedEntryPoint(destination_.instructions()) : Instructions::EntryPoint(destination_.instructions()); offset_into_target += (entry_point - destination_payload); if (offset_into_target > max_offset_into_target_) { max_offset_into_target_ = offset_into_target; } } if (num_calls > max_calls_) { max_calls_ = num_calls; } } } } bool CodeRelocator::AddInstructionsToText(RawCode* code) { RawInstructions* instructions = Code::InstructionsOf(code); // If two [Code] objects point to the same [Instructions] object, we'll just // use the first one (they are equivalent for all practical purposes). if (text_offsets_.HasKey(instructions)) { return false; } text_offsets_.Insert({instructions, next_text_offset_}); commands_->Add(ImageWriterCommand(next_text_offset_, code)); next_text_offset_ += ImageWriter::SizeInSnapshot(instructions); return true; } UnresolvedTrampoline* CodeRelocator::FindTrampolineFor( UnresolvedCall* unresolved_call) { auto destination = Code::InstructionsOf(unresolved_call->callee); auto entry = trampolines_by_destination_.Lookup(destination); if (entry != nullptr) { UnresolvedTrampolineList* trampolines = entry->value; ASSERT(!trampolines->IsEmpty()); // For the destination of [unresolved_call] we might have multiple // trampolines. The trampolines are sorted according to insertion order, // which guarantees increasing text_offset's. So we go from the back of the // list as long as we have trampolines that are in-range and then check // whether the target offset matches. auto it = trampolines->End(); --it; do { UnresolvedTrampoline* trampoline = *it; if (!IsTargetInRangeFor(unresolved_call, trampoline->text_offset)) { break; } if (trampoline->offset_into_target == unresolved_call->offset_into_target) { return trampoline; } --it; } while (it != trampolines->Begin()); } return nullptr; } void CodeRelocator::AddTrampolineToText(RawInstructions* destination, uint8_t* trampoline_bytes, intptr_t trampoline_length) { commands_->Add(ImageWriterCommand(next_text_offset_, trampoline_bytes, trampoline_length)); next_text_offset_ += trampoline_length; } void CodeRelocator::ScanCallTargets(const Code& code, const Array& call_targets, intptr_t code_text_offset) { if (call_targets.IsNull()) { return; } StaticCallsTable calls(call_targets); for (auto call : calls) { kind_type_and_offset_ = call.Get(); auto kind = Code::KindField::decode(kind_type_and_offset_.Value()); auto offset = Code::OffsetField::decode(kind_type_and_offset_.Value()); auto call_entry_point = Code::EntryPointField::decode(kind_type_and_offset_.Value()); if (kind == Code::kCallViaCode) { continue; } target_ = call.Get(); if (target_.IsFunction()) { auto& fun = Function::Cast(target_); ASSERT(fun.HasCode()); destination_ = fun.CurrentCode(); ASSERT(!destination_.IsStubCode()); } else { target_ = call.Get(); ASSERT(target_.IsCode()); destination_ = Code::Cast(target_).raw(); } // A call site can decide to jump not to the beginning of a function but // rather jump into it at a certain offset. int32_t offset_into_target = 0; { PcRelativeCallPattern call( Instructions::PayloadStart(code.instructions()) + offset); ASSERT(call.IsValid()); offset_into_target = call.distance(); } const uword destination_payload = Instructions::PayloadStart(destination_.instructions()); const uword entry_point = call_entry_point == Code::kUncheckedEntry ? Instructions::UncheckedEntryPoint(destination_.instructions()) : Instructions::EntryPoint(destination_.instructions()); offset_into_target += (entry_point - destination_payload); const intptr_t text_offset = code_text_offset + Instructions::HeaderSize() + offset; UnresolvedCall unresolved_call(code.raw(), offset, text_offset, destination_.raw(), offset_into_target); if (!TryResolveBackwardsCall(&unresolved_call)) { EnqueueUnresolvedCall(new UnresolvedCall(unresolved_call)); } } } void CodeRelocator::EnqueueUnresolvedCall(UnresolvedCall* unresolved_call) { // Add it to the min-heap by .text offset. all_unresolved_calls_.Append(unresolved_call); // Add it to callers of destination. RawInstructions* destination = Code::InstructionsOf(unresolved_call->callee); if (!unresolved_calls_by_destination_.HasKey(destination)) { unresolved_calls_by_destination_.Insert( {destination, new SameDestinationUnresolvedCallsList()}); } unresolved_calls_by_destination_.LookupValue(destination) ->Append(unresolved_call); } void CodeRelocator::EnqueueUnresolvedTrampoline( UnresolvedTrampoline* unresolved_trampoline) { auto destination = Code::InstructionsOf(unresolved_trampoline->callee); auto entry = trampolines_by_destination_.Lookup(destination); UnresolvedTrampolineList* trampolines = nullptr; if (entry == nullptr) { trampolines = new UnresolvedTrampolineList(); trampolines_by_destination_.Insert({destination, trampolines}); } else { trampolines = entry->value; } trampolines->Append(unresolved_trampoline); } bool CodeRelocator::TryResolveBackwardsCall(UnresolvedCall* unresolved_call) { auto callee = Code::InstructionsOf(unresolved_call->callee); auto map_entry = text_offsets_.Lookup(callee); if (map_entry == nullptr) return false; ResolveCall(unresolved_call); return true; } void CodeRelocator::ResolveUnresolvedCallsTargeting( const RawInstructions* instructions) { if (unresolved_calls_by_destination_.HasKey(instructions)) { SameDestinationUnresolvedCallsList* calls = unresolved_calls_by_destination_.LookupValue(instructions); auto it = calls->Begin(); while (it != calls->End()) { UnresolvedCall* unresolved_call = *it; ++it; ASSERT(Code::InstructionsOf(unresolved_call->callee) == instructions); ResolveCall(unresolved_call); // Remove the call from both lists. calls->Remove(unresolved_call); all_unresolved_calls_.Remove(unresolved_call); delete unresolved_call; } ASSERT(calls->IsEmpty()); delete calls; bool ok = unresolved_calls_by_destination_.Remove(instructions); ASSERT(ok); } } void CodeRelocator::ResolveCall(UnresolvedCall* unresolved_call) { const intptr_t destination_text = FindDestinationInText(Code::InstructionsOf(unresolved_call->callee), unresolved_call->offset_into_target); ResolveCallToDestination(unresolved_call, destination_text); } void CodeRelocator::ResolveCallToDestination(UnresolvedCall* unresolved_call, intptr_t destination_text) { const intptr_t call_text_offset = unresolved_call->text_offset; const intptr_t call_offset = unresolved_call->call_offset; auto caller = Code::InstructionsOf(unresolved_call->caller); const int32_t distance = destination_text - call_text_offset; { uword addr = Instructions::PayloadStart(caller) + call_offset; if (FLAG_write_protect_code) { addr -= HeapPage::Of(caller)->AliasOffset(); } PcRelativeCallPattern call(addr); ASSERT(call.IsValid()); call.set_distance(static_cast(distance)); ASSERT(call.distance() == distance); } unresolved_call->caller = nullptr; unresolved_call->callee = nullptr; } void CodeRelocator::ResolveTrampoline( UnresolvedTrampoline* unresolved_trampoline) { const intptr_t trampoline_text_offset = unresolved_trampoline->text_offset; const uword trampoline_start = reinterpret_cast(unresolved_trampoline->trampoline_bytes); auto callee = Code::InstructionsOf(unresolved_trampoline->callee); auto destination_text = FindDestinationInText(callee, unresolved_trampoline->offset_into_target); const int32_t distance = destination_text - trampoline_text_offset; PcRelativeTrampolineJumpPattern pattern(trampoline_start + kOffsetInTrampoline); pattern.Initialize(); pattern.set_distance(distance); ASSERT(pattern.distance() == distance); } bool CodeRelocator::IsTargetInRangeFor(UnresolvedCall* unresolved_call, intptr_t target_text_offset) { const auto forward_distance = target_text_offset - unresolved_call->text_offset; return PcRelativeCallPattern::kLowerCallingRange < forward_distance && forward_distance < PcRelativeCallPattern::kUpperCallingRange; } static void MarkAsFreeListElement(uint8_t* trampoline_bytes, intptr_t trampoline_length) { uint32_t tags = 0; tags = RawObject::SizeTag::update(trampoline_length, tags); tags = RawObject::ClassIdTag::update(kFreeListElement, tags); tags = RawObject::OldBit::update(true, tags); tags = RawObject::OldAndNotMarkedBit::update(true, tags); tags = RawObject::OldAndNotRememberedBit::update(true, tags); tags = RawObject::NewBit::update(false, tags); auto header_word = reinterpret_cast(trampoline_bytes); *header_word = tags; } void CodeRelocator::BuildTrampolinesForAlmostOutOfRangeCalls() { while (!all_unresolved_calls_.IsEmpty()) { UnresolvedCall* unresolved_call = all_unresolved_calls_.First(); // If we can emit another instructions object without causing the unresolved // forward calls to become out-of-range, we'll not resolve it yet (maybe the // target function will come very soon and we don't need a trampoline at // all). const intptr_t future_boundary = next_text_offset_ + max_instructions_size_ + kTrampolineSize * (unresolved_calls_by_destination_.Length() + max_calls_) + kOffsetInTrampoline; if (IsTargetInRangeFor(unresolved_call, future_boundary) && !FLAG_always_generate_trampolines_for_testing) { break; } // We have a "critical" [unresolved_call] we have to resolve. If an // existing trampoline is in range, we use that otherwise we create a new // trampoline. // In the worst case we'll make a new trampoline here, in which case the // current text offset must be in range for the "critical" // [unresolved_call]. ASSERT(IsTargetInRangeFor(unresolved_call, next_text_offset_)); // See if there is already a trampoline we could use. intptr_t trampoline_text_offset = -1; auto callee = Code::InstructionsOf(unresolved_call->callee); if (!FLAG_always_generate_trampolines_for_testing) { auto old_trampoline_entry = FindTrampolineFor(unresolved_call); if (old_trampoline_entry != nullptr) { trampoline_text_offset = old_trampoline_entry->text_offset; } } // If there is no trampoline yet, we'll create a new one. if (trampoline_text_offset == -1) { // The ownership of the trampoline bytes will be transferred to the // [ImageWriter], which will eventually write out the bytes and delete the // buffer. auto trampoline_bytes = new uint8_t[kTrampolineSize]; memset(trampoline_bytes, 0x00, kTrampolineSize); ASSERT((kOffsetInTrampoline + PcRelativeTrampolineJumpPattern::kLengthInBytes) < kTrampolineSize); auto unresolved_trampoline = new UnresolvedTrampoline{ unresolved_call->callee, unresolved_call->offset_into_target, trampoline_bytes, next_text_offset_ + kOffsetInTrampoline, }; MarkAsFreeListElement(trampoline_bytes, kTrampolineSize); AddTrampolineToText(callee, trampoline_bytes, kTrampolineSize); EnqueueUnresolvedTrampoline(unresolved_trampoline); trampoline_text_offset = unresolved_trampoline->text_offset; } // Let the unresolved call to [destination] jump to the trampoline // instead. auto destination = Code::InstructionsOf(unresolved_call->callee); ResolveCallToDestination(unresolved_call, trampoline_text_offset); // Remove this unresolved call from the global list and the per-destination // list. auto calls = unresolved_calls_by_destination_.LookupValue(destination); calls->Remove(unresolved_call); all_unresolved_calls_.Remove(unresolved_call); delete unresolved_call; // If this destination has no longer any unresolved calls, remove it. if (calls->IsEmpty()) { unresolved_calls_by_destination_.Remove(destination); delete calls; } } } intptr_t CodeRelocator::FindDestinationInText( const RawInstructions* destination, intptr_t offset_into_target) { auto destination_offset = text_offsets_.LookupValue(destination); return destination_offset + Instructions::HeaderSize() + offset_into_target; } #endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC) && \ // !defined(TARGET_ARCH_IA32) } // namespace dart