[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>
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 47d3b8e..f3a9979 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -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 @@
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 @@
// 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;
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 25dc8dc..c07bf00 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -64,6 +64,7 @@
class RawFloat32x4;
class RawInt32x4;
class RawUserTag;
+class ReversePcLookupCache;
class SafepointHandler;
class SampleBuffer;
class SendPort;
@@ -704,6 +705,17 @@
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 @@
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_;
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 967a2fa..110bf5c 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -7947,6 +7947,8 @@
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.
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index cd760de..6b9695d 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -124,6 +124,7 @@
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) \
diff --git a/runtime/vm/reverse_pc_lookup_cache.cc b/runtime/vm/reverse_pc_lookup_cache.cc
new file mode 100644
index 0000000..325d98f
--- /dev/null
+++ b/runtime/vm/reverse_pc_lookup_cache.cc
@@ -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
diff --git a/runtime/vm/reverse_pc_lookup_cache.h b/runtime/vm/reverse_pc_lookup_cache.h
new file mode 100644
index 0000000..8300b62
--- /dev/null
+++ b/runtime/vm/reverse_pc_lookup_cache.h
@@ -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_
diff --git a/runtime/vm/stack_frame.cc b/runtime/vm/stack_frame.cc
index 09a452c..cc94c15 100644
--- a/runtime/vm/stack_frame.cc
+++ b/runtime/vm/stack_frame.cc
@@ -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 @@
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 @@
// 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 @@
// 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::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 {
diff --git a/runtime/vm/stack_frame.h b/runtime/vm/stack_frame.h
index 85ba367..9b6b67a 100644
--- a/runtime/vm/stack_frame.h
+++ b/runtime/vm/stack_frame.h
@@ -152,14 +152,24 @@
// 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 @@
// 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(); }
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index a9b02fc..593b76d 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -239,6 +239,8 @@
"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",