[VM] Bare instructions - Part 3: Add support for building a PC -> Code mapping table

This CL adds a [ReversePcLookupCache] which, based on a list of code
objects, builds up a binary searchable table for mapping PCs to Code
objects (where all the metadata is available, like stackmaps, ...).

This CL also adds stack walking support for "bare instruction" frames.

In a later part we will start emitting the sorted list of code objects
(via the new field in the object store) under a flag.

Issue https://github.com/dart-lang/sdk/issues/33274

Change-Id: I3c06c12bc0fb266dc1bd843a4a11b5208773151d
Reviewed-on: https://dart-review.googlesource.com/c/85746
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
Martin Kustermann 2018-12-13 17:45:02 +00:00 committed by commit-bot@chromium.org
parent 7a968ae283
commit 2440a32461
9 changed files with 340 additions and 23 deletions

View file

@ -36,6 +36,7 @@
#include "vm/port.h"
#include "vm/profiler.h"
#include "vm/reusable_handles.h"
#include "vm/reverse_pc_lookup_cache.h"
#include "vm/service.h"
#include "vm/service_event.h"
#include "vm/service_isolate.h"
@ -946,7 +947,8 @@ Isolate::Isolate(const Dart_IsolateFlags& api_flags)
handler_info_cache_(),
catch_entry_moves_cache_(),
embedder_entry_points_(NULL),
obfuscation_map_(NULL) {
obfuscation_map_(NULL),
reverse_pc_lookup_cache_(nullptr) {
FlagsCopyFrom(api_flags);
SetErrorsFatal(true);
set_compilation_allowed(true);
@ -974,6 +976,9 @@ Isolate::~Isolate() {
// RELEASE_ASSERT(reload_context_ == NULL);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
delete reverse_pc_lookup_cache_;
reverse_pc_lookup_cache_ = nullptr;
delete background_compiler_;
background_compiler_ = NULL;

View file

@ -64,6 +64,7 @@ class RawInteger;
class RawFloat32x4;
class RawInt32x4;
class RawUserTag;
class ReversePcLookupCache;
class SafepointHandler;
class SampleBuffer;
class SendPort;
@ -704,6 +705,17 @@ class Isolate : public BaseIsolate {
void set_obfuscation_map(const char** map) { obfuscation_map_ = map; }
const char** obfuscation_map() const { return obfuscation_map_; }
// Returns the pc -> code lookup cache object for this isolate.
ReversePcLookupCache* reverse_pc_lookup_cache() const {
return reverse_pc_lookup_cache_;
}
// Sets the pc -> code lookup cache object for this isolate.
void set_reverse_pc_lookup_cache(ReversePcLookupCache* table) {
ASSERT(reverse_pc_lookup_cache_ == nullptr);
reverse_pc_lookup_cache_ = table;
}
// Isolate-specific flag handling.
static void FlagsInitialize(Dart_IsolateFlags* api_flags);
void FlagsCopyTo(Dart_IsolateFlags* api_flags) const;
@ -1006,6 +1018,8 @@ class Isolate : public BaseIsolate {
Dart_QualifiedFunctionName* embedder_entry_points_;
const char** obfuscation_map_;
ReversePcLookupCache* reverse_pc_lookup_cache_;
static Dart_IsolateCreateCallback create_callback_;
static Dart_IsolateShutdownCallback shutdown_callback_;
static Dart_IsolateCleanupCallback cleanup_callback_;

View file

@ -7947,6 +7947,8 @@ class Array : public Instance {
return memcmp(a->ptr()->data(), b->ptr()->data(), kWordSize * length) == 0;
}
static RawObject** DataOf(RawArray* array) { return array->ptr()->data(); }
RawObject* At(intptr_t index) const { return *ObjectAddr(index); }
void SetAt(intptr_t index, const Object& value) const {
// TODO(iposva): Add storing NoSafepointScope.

View file

@ -124,6 +124,7 @@ class ObjectPointerVisitor;
RW(Array, unique_dynamic_targets) \
RW(GrowableObjectArray, megamorphic_cache_table) \
RW(Code, build_method_extractor_code) \
RW(Array, code_order_table) \
R_(Code, megamorphic_miss_code) \
R_(Function, megamorphic_miss_function) \
RW(Array, obfuscation_map) \

View file

@ -0,0 +1,56 @@
// Copyright (c) 2018, 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/reverse_pc_lookup_cache.h"
#include "vm/isolate.h"
namespace dart {
#if defined(DART_PRECOMPILED_RUNTIME)
static uword BeginPcFromCode(const RawCode* code) {
auto instr = Code::InstructionsOf(code);
return Instructions::PayloadStart(instr);
}
static uword EndPcFromCode(const RawCode* code) {
auto instr = Code::InstructionsOf(code);
return Instructions::PayloadStart(instr) + Instructions::Size(instr);
}
void ReversePcLookupCache::BuildAndAttachToIsolate(Isolate* isolate) {
auto object_store = isolate->object_store();
auto& array = Array::Handle(object_store->code_order_table());
if (!array.IsNull()) {
const intptr_t length = array.Length();
{
NoSafepointScope no_safepoint_scope;
const uword begin =
BeginPcFromCode(reinterpret_cast<RawCode*>(array.At(0)));
const uword end =
EndPcFromCode(reinterpret_cast<RawCode*>(array.At(length - 1)));
auto pc_array = new uint32_t[length];
for (intptr_t i = 0; i < length; i++) {
const auto end_pc =
EndPcFromCode(reinterpret_cast<RawCode*>(array.At(i)));
pc_array[i] = end_pc - begin;
}
#if defined(DEBUG)
for (intptr_t i = 1; i < length; i++) {
ASSERT(pc_array[i - 1] <= pc_array[i]);
}
#endif // defined(DEBUG)
auto cache =
new ReversePcLookupCache(isolate, pc_array, length, begin, end);
isolate->set_reverse_pc_lookup_cache(cache);
}
}
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
} // namespace dart

View file

@ -0,0 +1,126 @@
// Copyright (c) 2018, 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.
#ifndef RUNTIME_VM_REVERSE_PC_LOOKUP_CACHE_H_
#define RUNTIME_VM_REVERSE_PC_LOOKUP_CACHE_H_
#include "vm/allocation.h"
#include "vm/growable_array.h"
#include "vm/object.h"
#include "vm/object_store.h"
namespace dart {
class Isolate;
#if defined(DART_PRECOMPILED_RUNTIME)
// A cache for looking up a Code object based on pc (currently the cache is
// implemented as a binary-searchable uint32 array)
//
// If an AOT snapshot was created with --use_bare_instructions the isolate's
// object store will contain a `code_order_table` - which is a sorted array
// of [Code] objects. The order is based on addresses of the code's
// instructions in memory.
//
// For a binary search we would need to touch O(log(array-size)) array entries,
// code objects and instruction objects.
//
// To avoid this we make another uint32 array which is initialized from the end
// PCs of the instructions (relative to the start pc of the first instruction
// object).
//
// We have the following invariants:
//
// BeginPcFromCode(code_array[0]) <= pc_array[0]
// pc_array[i] == EndPcFromCode(code_array[i])
// pc_array[i] <= pc_array[i+1]
//
// The lookup will then do a binary search in pc_array. The index can then be
// used in the `code_order_table` of the object store.
//
// WARNING: This class cannot do memory allocation or handle allocation!
class ReversePcLookupCache {
public:
ReversePcLookupCache(Isolate* isolate,
uint32_t* pc_array,
intptr_t length,
uword first_absolute_pc,
uword last_absolute_pc)
: isolate_(isolate),
pc_array_(pc_array),
length_(length),
first_absolute_pc_(first_absolute_pc),
last_absolute_pc_(last_absolute_pc) {}
~ReversePcLookupCache() { delete[] pc_array_; }
// Builds a [ReversePcLookupCache] and attaches it to the isolate (if
// `code_order_table` is non-`null`).
static void BuildAndAttachToIsolate(Isolate* isolate);
// Returns `true` if the given [pc] contains can be mapped to a [Code] object
// using this cache.
inline bool Contains(uword pc) {
return first_absolute_pc_ <= pc && pc <= last_absolute_pc_;
}
// Looks up the [Code] object from a given [pc].
inline RawCode* Lookup(uword pc) {
NoSafepointScope no_safepoint_scope;
intptr_t left = 0;
intptr_t right = length_ - 1;
ASSERT(first_absolute_pc_ <= pc && pc < last_absolute_pc_);
uint32_t pc_offset = static_cast<uint32_t>(pc - first_absolute_pc_);
while (left < right) {
intptr_t middle = left + (right - left) / 2;
uword middle_pc = pc_array_[middle];
if (middle_pc < pc_offset) {
left = middle + 1;
} else {
right = middle;
}
}
auto code_array = isolate_->object_store()->code_order_table();
auto raw_code = reinterpret_cast<RawCode*>(Array::DataOf(code_array)[left]);
#if defined(DEBUG)
ASSERT(raw_code->GetClassIdMayBeSmi() == kCodeCid);
ASSERT(Code::ContainsInstructionAt(raw_code, pc));
#endif
return raw_code;
}
private:
Isolate* isolate_;
uint32_t* pc_array_;
intptr_t length_;
uword first_absolute_pc_;
uword last_absolute_pc_;
};
#else // defined(DART_PRECOMPILED_RUNTIME
class ReversePcLookupCache {
public:
ReversePcLookupCache() {}
~ReversePcLookupCache() {}
static void BuildAndAttachToIsolate(Isolate* isolate) {}
inline bool Contains(uword pc) { return false; }
inline RawCode* Lookup(uword pc) { UNREACHABLE(); }
};
#endif // defined(DART_PRECOMPILED_RUNTIME
} // namespace dart
#endif // RUNTIME_VM_REVERSE_PC_LOOKUP_CACHE_H_

View file

@ -15,6 +15,7 @@
#include "vm/parser.h"
#include "vm/raw_object.h"
#include "vm/reusable_handles.h"
#include "vm/reverse_pc_lookup_cache.h"
#include "vm/scopes.h"
#include "vm/stub_code.h"
#include "vm/visitor.h"
@ -65,16 +66,100 @@ void FrameLayout::Init() {
runtime_frame_layout = default_frame_layout;
}
Isolate* StackFrame::IsolateOfBareInstructionsFrame() const {
auto isolate = this->isolate();
if (isolate->object_store()->code_order_table() != Object::null()) {
auto rct = isolate->reverse_pc_lookup_cache();
if (rct->Contains(pc())) return isolate;
}
isolate = Dart::vm_isolate();
if (isolate->object_store()->code_order_table() != Object::null()) {
auto rct = isolate->reverse_pc_lookup_cache();
if (rct->Contains(pc())) return isolate;
}
return nullptr;
}
bool StackFrame::IsBareInstructionsDartFrame() const {
NoSafepointScope no_safepoint;
if (auto isolate = IsolateOfBareInstructionsFrame()) {
Code code;
auto rct = isolate->reverse_pc_lookup_cache();
code = rct->Lookup(pc());
// All stub codes have a `null` owner except for the megamorphic miss
// stub. So if it's neither of those, we are know it must be a
// precompiled dart frame.
RawObject* owner = code.owner();
if (owner != Object::null()) {
if (code.raw() ==
Isolate::Current()->object_store()->megamorphic_miss_code()) {
return true;
}
}
}
return false;
}
bool StackFrame::IsBareInstructionsStubFrame() const {
NoSafepointScope no_safepoint;
if (auto isolate = IsolateOfBareInstructionsFrame()) {
Code code;
auto rct = isolate->reverse_pc_lookup_cache();
code = rct->Lookup(pc());
// All stub codes have a `null` owner except for the megamorphic miss stub.
// So if it's either of those, we are know it must be a precompiled stub
// frame.
RawObject* owner = code.owner();
if (owner == Object::null()) {
return true;
}
if (code.raw() ==
Isolate::Current()->object_store()->megamorphic_miss_code()) {
return true;
}
}
return false;
}
bool StackFrame::IsDartFrame(bool validate) const {
ASSERT(!validate || IsValid());
if (IsEntryFrame() || IsExitFrame()) return false;
// Even though the megamorphic miss stub is a stub, we consider it as a
// dart frame for all practical purposes.
const bool is_megamorphic_miss_stub = Code::ContainsInstructionAt(
thread_->isolate()->object_store()->megamorphic_miss_code(), pc_);
if (is_megamorphic_miss_stub) return true;
return !IsStubFrame();
}
bool StackFrame::IsStubFrame() const {
if (is_interpreted()) {
return false;
}
if (IsBareInstructionsStubFrame()) {
return true;
}
ASSERT(!(IsEntryFrame() || IsExitFrame()));
#if !defined(HOST_OS_WINDOWS) && !defined(HOST_OS_FUCHSIA)
// On Windows and Fuchsia, the profiler calls this from a separate thread
// where Thread::Current() is NULL, so we cannot create a NoSafepointScope.
NoSafepointScope no_safepoint;
#endif
RawCode* code = GetCodeObject();
ASSERT(code != Object::null());
const intptr_t cid = code->ptr()->owner_->GetClassId();
@ -172,21 +257,27 @@ void StackFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) {
// be able to reuse the handle based code and avoid having to add
// helper functions to the raw object interface.
NoSafepointScope no_safepoint;
RawObject* pc_marker = *(reinterpret_cast<RawObject**>(
fp() + ((is_interpreted() ? kKBCPcMarkerSlotFromFp
: runtime_frame_layout.code_from_fp) *
kWordSize)));
// May forward raw code. Note we don't just visit the pc marker slot first
// because the visitor's forwarding might not be idempotent.
visitor->VisitPointer(&pc_marker);
Code code;
if (pc_marker->IsHeapObject() && (pc_marker->GetClassId() == kCodeCid)) {
code ^= pc_marker;
if (auto isolate = IsolateOfBareInstructionsFrame()) {
code = isolate->reverse_pc_lookup_cache()->Lookup(pc());
} else {
ASSERT(pc_marker == Object::null() ||
(is_interpreted() && (!pc_marker->IsHeapObject() ||
(pc_marker->GetClassId() == kBytecodeCid))));
RawObject* pc_marker = *(reinterpret_cast<RawObject**>(
fp() + ((is_interpreted() ? kKBCPcMarkerSlotFromFp
: runtime_frame_layout.code_from_fp) *
kWordSize)));
// May forward raw code. Note we don't just visit the pc marker slot first
// because the visitor's forwarding might not be idempotent.
visitor->VisitPointer(&pc_marker);
if (pc_marker->IsHeapObject() && (pc_marker->GetClassId() == kCodeCid)) {
code ^= pc_marker;
} else {
ASSERT(pc_marker == Object::null() ||
(is_interpreted() && (!pc_marker->IsHeapObject() ||
(pc_marker->GetClassId() == kBytecodeCid))));
}
}
if (!code.IsNull()) {
// Optimized frames have a stack map. We need to visit the frame based
// on the stack map.
@ -328,6 +419,10 @@ RawCode* StackFrame::LookupDartCode() const {
// where Thread::Current() is NULL, so we cannot create a NoSafepointScope.
NoSafepointScope no_safepoint;
#endif
if (auto isolate = IsolateOfBareInstructionsFrame()) {
return isolate->reverse_pc_lookup_cache()->Lookup(pc());
}
RawCode* code = GetCodeObject();
if ((code != Code::null()) &&
(code->ptr()->owner_->GetClassId() == kFunctionCid)) {
@ -338,11 +433,15 @@ RawCode* StackFrame::LookupDartCode() const {
RawCode* StackFrame::GetCodeObject() const {
ASSERT(!is_interpreted());
RawObject* pc_marker = *(reinterpret_cast<RawObject**>(
fp() + runtime_frame_layout.code_from_fp * kWordSize));
ASSERT((pc_marker == Object::null()) ||
(pc_marker->GetClassId() == kCodeCid));
return reinterpret_cast<RawCode*>(pc_marker);
if (auto isolate = IsolateOfBareInstructionsFrame()) {
return isolate->reverse_pc_lookup_cache()->Lookup(pc());
} else {
RawObject* pc_marker = *(reinterpret_cast<RawObject**>(
fp() + runtime_frame_layout.code_from_fp * kWordSize));
ASSERT((pc_marker == Object::null()) ||
(pc_marker->GetClassId() == kCodeCid));
return reinterpret_cast<RawCode*>(pc_marker);
}
}
RawBytecode* StackFrame::LookupDartBytecode() const {

View file

@ -152,14 +152,24 @@ class StackFrame : public ValueObject {
// Check validity of a frame, used for assertion purposes.
virtual bool IsValid() const;
// Returns the isolate containing the bare instructions of the current frame.
//
// If the frame does not belong to a bare instructions snapshot, it will
// return nullptr.
Isolate* IsolateOfBareInstructionsFrame() const;
// Returns true iff the current frame is a bare instructions dart frame.
bool IsBareInstructionsDartFrame() const;
// Returns true iff the current frame is a bare instructions stub frame.
bool IsBareInstructionsStubFrame() const;
// Frame type.
virtual bool IsDartFrame(bool validate = true) const {
ASSERT(!validate || IsValid());
return !(IsEntryFrame() || IsExitFrame() || IsStubFrame());
}
virtual bool IsDartFrame(bool validate = true) const;
virtual bool IsStubFrame() const;
virtual bool IsEntryFrame() const { return false; }
virtual bool IsExitFrame() const { return false; }
virtual bool is_interpreted() const { return is_interpreted_; }
RawFunction* LookupDartFunction() const;
@ -180,7 +190,9 @@ class StackFrame : public ValueObject {
// Name of the frame, used for generic frame printing functionality.
virtual const char* GetName() const {
return IsStubFrame() ? "stub" : "dart";
if (IsBareInstructionsStubFrame()) return "bare-stub";
if (IsStubFrame()) return "stub";
return IsBareInstructionsDartFrame() ? "bare-dart" : "dart";
}
Isolate* isolate() const { return thread_->isolate(); }

View file

@ -239,6 +239,8 @@ vm_sources = [
"resolver.cc",
"resolver.h",
"reusable_handles.h",
"reverse_pc_lookup_cache.cc",
"reverse_pc_lookup_cache.h",
"ring_buffer.h",
"runtime_entry.cc",
"runtime_entry.h",