e4196ce8c6
Second sub-CL of https://dart-review.googlesource.com/c/sdk/+/100644 The first was here https://dart-review.googlesource.com/c/sdk/+/103487 Now that offsets_extractor is checked in, and we have a big header full of hard coded constants, the next step is to make sure everything is using those constants. This is essentially everything in the original CL, except the new simarm_x64 architecture. Bug: https://github.com/dart-lang/sdk/issues/36839 Change-Id: I236e4f30aa1df6d92209891c983b792d1835b608 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104286 Commit-Queue: Liam Appelbe <liama@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
512 lines
19 KiB
C++
512 lines
19 KiB
C++
// 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<RawCode*>* code_objects,
|
|
GrowableArray<ImageWriterCommand>* 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<Code::kSCallTableKindAndOffset>();
|
|
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<Code::kSCallTableFunctionTarget>();
|
|
if (target_.IsFunction()) {
|
|
auto& fun = Function::Cast(target_);
|
|
ASSERT(fun.HasCode());
|
|
destination_ = fun.CurrentCode();
|
|
ASSERT(!destination_.IsStubCode());
|
|
} else {
|
|
target_ = call.Get<Code::kSCallTableCodeTarget>();
|
|
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<Code::kSCallTableKindAndOffset>();
|
|
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<Code::kSCallTableFunctionTarget>();
|
|
if (target_.IsFunction()) {
|
|
auto& fun = Function::Cast(target_);
|
|
ASSERT(fun.HasCode());
|
|
destination_ = fun.CurrentCode();
|
|
ASSERT(!destination_.IsStubCode());
|
|
} else {
|
|
target_ = call.Get<Code::kSCallTableCodeTarget>();
|
|
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<int32_t>(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<uword>(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<uintptr_t*>(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
|