Files
sdk/runtime/vm/analyze_snapshot_api_impl.cc
T
Ryan Macnak d36adbacaf [vm] Remove the VM isolate.
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL).

The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls.

The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap.

Class serialization no longer distinguishes predefined classes.

The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.)

Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code.

The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime.

The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration.

Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot.

TEST=ci
Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2026-05-18 11:35:03 -07:00

682 lines
24 KiB
C++

// Copyright (c) 2021, 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 <cstddef>
#include <cstdint>
#include <set>
#include <sstream>
#include <unordered_map>
#include <vector>
#include "include/analyze_snapshot_api.h"
#include "vm/compiler/runtime_api.h"
#include "vm/dart.h"
#include "vm/dart_api_impl.h"
#include "vm/globals.h"
#include "vm/json_writer.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/thread.h"
namespace dart {
namespace snapshot_analyzer {
constexpr intptr_t kSnapshotAnalyzerVersion = 2;
constexpr intptr_t kStartIndex = 1;
class FieldVisitor : public ObjectPointerVisitor {
public:
explicit FieldVisitor(IsolateGroup* isolate_group)
: ObjectPointerVisitor(isolate_group) {}
void init(std::function<void(ObjectPtr)>* fun) { callback_ = fun; }
void VisitPointers(ObjectPtr* first, ObjectPtr* last) override {
for (ObjectPtr* current = first; current <= last; current++) {
(*callback_)(*current);
}
}
#if defined(DART_COMPRESSED_POINTERS)
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* first,
CompressedObjectPtr* last) override {
for (CompressedObjectPtr* current = first; current <= last; current++) {
(*callback_)(current->Decompress(heap_base));
}
}
#endif
private:
std::function<void(ObjectPtr object)>* callback_ = nullptr;
};
class SnapshotAnalyzer {
public:
explicit SnapshotAnalyzer(const Dart_SnapshotAnalyzerInformation& info)
: info_(info),
class_fields_(IsolateGroup::Current()->class_table()->NumCids()),
top_level_class_fields_(
IsolateGroup::Current()->class_table()->NumTopLevelCids()) {}
// Saves JSON format snapshot information in the output character buffer.
void DumpSnapshotInformation(char** buffer, intptr_t* buffer_length);
private:
void DumpLibrary(const Library& library);
void DumpArray(const Array& array, const char* name);
void DumpClass(const Class& klass);
void DumpClassInstanceSlots(const Class& klass,
const std::vector<const Field*>& fields);
void DumpFunction(const Function& function);
void DumpCode(const Code& code);
void DumpCode(uword start_pc, uword end_pc, const char* name);
void DumpField(const Field& field);
void DumpString(const String& string);
void DumpInstance(const Object& object);
void DumpType(const Type& type);
void DumpObjectPool(const ObjectPool& pool);
void DumpInterestingObjects();
void DumpMetadata();
intptr_t GetObjectId(ObjectPtr obj) { return heap_->GetObjectId(obj); }
const Dart_SnapshotAnalyzerInformation& info_;
std::vector<std::vector<const Field*>> class_fields_;
std::vector<std::vector<const Field*>> top_level_class_fields_;
std::unordered_map<uword, const char*> stub_names_;
JSONWriter js_;
Thread* thread_;
Heap* heap_;
};
void SnapshotAnalyzer::DumpLibrary(const Library& library) {
js_.PrintProperty("type", "Library");
js_.PrintProperty("url", String::Handle(library.url()).ToCString());
js_.PrintProperty("toplevel_class",
GetObjectId(Object::RawCast(library.toplevel_class())));
}
void SnapshotAnalyzer::DumpArray(const Array& array, const char* name) {
js_.OpenArray(name);
for (intptr_t i = 0; i < array.Length(); ++i) {
js_.PrintValue64(GetObjectId(array.At(i)));
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpClass(const Class& klass) {
js_.PrintProperty("type", "Class");
auto& class_fields =
klass.IsTopLevel() ? top_level_class_fields_ : class_fields_;
const auto& fields =
class_fields[klass.IsTopLevel()
? ClassTable::IndexFromTopLevelCid(klass.id())
: klass.id()];
js_.PrintProperty("class_id", klass.id());
js_.PrintProperty("name", String::Handle(klass.Name()).ToCString());
js_.PrintProperty("super_class", GetObjectId(klass.SuperClass()));
Zone* zone = thread_->zone();
Array& array = Array::Handle(zone);
// We use [fields] instead of [Class.fields()] as a [Field] object may not
// appear in [Class.fields()] but may still be available (e.g. for
// `LateInitizializationError`) so we can include all fields of the class we
// found.
js_.OpenArray("fields");
for (uintptr_t i = 0; i < fields.size(); ++i) {
js_.PrintValue64(GetObjectId(fields[i]->ptr()));
}
js_.CloseArray();
// Here we write information about every slot in an instance. Even if there's
// no corresponding [Field] object, we will write out an entry describing the
// slot (e.g. whether it's boxed or not, ...)
//
// So this information is always available, whereas the "fields" above only
// writes non-tree shaken [Field] obejcts.
if (!klass.IsTopLevel()) {
DumpClassInstanceSlots(klass, fields);
}
array = klass.functions();
if (!array.IsNull()) {
DumpArray(array, "functions");
}
array = klass.interfaces();
if (!array.IsNull()) {
DumpArray(array, "interfaces");
}
Library& library = Library::Handle(klass.library());
if (!library.IsNull()) {
js_.PrintProperty("library", GetObjectId(klass.library()));
}
}
void SnapshotAnalyzer::DumpClassInstanceSlots(
const Class& klass,
const std::vector<const Field*>& fields) {
const auto& super_class = Class::Handle(klass.SuperClass());
auto& field = Field::Handle();
auto& type = AbstractType::Handle();
auto class_table = thread_->isolate_group()->class_table();
const auto bitmap = class_table->GetUnboxedFieldsMapAt(klass.id());
const intptr_t start_offset = super_class.IsNull()
? Instance::NextFieldOffset()
: super_class.host_next_field_offset();
const intptr_t end_offset = klass.host_next_field_offset();
js_.OpenArray("instance_slots");
intptr_t offset = start_offset;
while (offset < end_offset) {
const bool is_reference = !bitmap.Get(offset / kCompressedWordSize);
js_.OpenObject();
js_.PrintProperty("offset", offset);
js_.PrintPropertyBool("is_reference", is_reference);
if (offset == klass.host_type_arguments_field_offset()) {
RELEASE_ASSERT(is_reference);
js_.PrintProperty("slot_type", "type_arguments_field");
js_.CloseObject();
offset += kCompressedWordSize;
continue;
}
// Try to see if the corresponding [Field] object was not tree shaken.
bool found = false;
for (uintptr_t i = 0; i < fields.size(); ++i) {
field = fields[i]->ptr();
if (field.is_static()) continue;
if (field.HostOffset() == offset) {
found = true;
break;
}
}
if (found) {
type = field.type();
intptr_t slots = 0;
if (field.is_unboxed()) {
if (type.IsDoubleType()) {
slots = sizeof(double) / kCompressedWordSize;
} else if (type.IsIntType()) {
slots = sizeof(int64_t) / kCompressedWordSize;
} else if (type.IsFloat32x4Type()) {
slots = sizeof(simd128_value_t) / kCompressedWordSize;
} else if (type.IsFloat64x2Type()) {
slots = sizeof(simd128_value_t) / kCompressedWordSize;
} else {
// Rare: Could be that the field type isn't telling us the unboxed
// type but field is still unboxed (e.g. `dynamic` field which TFA
// inferred to be of certain type).
//
// In this case we treat it as unknown field below (as we don't know
// it's size).
slots = -1;
}
} else {
slots = 1;
}
if (!field.is_unboxed() || slots > 0) {
js_.PrintProperty("slot_type", "instance_field");
js_.PrintProperty64("field", GetObjectId(field.ptr()));
js_.CloseObject();
offset += slots * kCompressedWordSize;
continue;
}
}
// This slot is either an unknown reference field or part of an unknown
// unboxed field (64-bit integer/double or 128-bit float32x4/float64x2).
// We cannot know the size or type of the unboxed field as the [Field]
// object was tree shaken.
js_.PrintProperty("slot_type", "unknown_slot");
js_.CloseObject();
offset += kCompressedWordSize;
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpFunction(const Function& function) {
js_.PrintProperty("type", "Function");
js_.PrintProperty("name", function.QualifiedScrubbedNameCString());
js_.PrintProperty("signature",
String::Handle(function.InternalSignature()).ToCString());
js_.PrintProperty("code", GetObjectId(function.CurrentCode()));
js_.PrintProperty("owner_class", GetObjectId(function.Owner()));
if (function.IsClosureFunction()) {
js_.PrintProperty("parent_function",
GetObjectId(function.parent_function()));
}
}
namespace {
// Try to identify stubs which were effectively copied into the isolate
// instructions section by comparing payloads.
const char* TryIdentifyIsolateSpecificStubCopy(ObjectStore* object_store,
const Code& code) {
return nullptr;
}
} // namespace
void SnapshotAnalyzer::DumpCode(const Code& code) {
js_.PrintProperty("type", "Code");
const auto instruction_base = reinterpret_cast<uint64_t>(info_.snapshot_text);
if (code.IsUnknownDartCode()) {
js_.PrintProperty64("offset", 0);
js_.PrintProperty64("size", 0);
js_.PrintProperty("name", "UnknownDartCode");
js_.PrintProperty("section", "_kDartVmSnapshotInstructions");
return;
}
// On different architectures the type of the underlying
// dart::uword can result in an unsigned long long vs unsigned long
// mismatch.
const auto code_addr = static_cast<uint64_t>(code.PayloadStart());
js_.PrintProperty64("offset", code_addr - instruction_base);
js_.PrintProperty64("size", static_cast<uint64_t>(code.Size()));
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
if (code.owner() != Object::null()) {
const auto& owner = Object::Handle(code.owner());
js_.PrintProperty("owner", GetObjectId(owner.ptr()));
if (owner.IsClass()) {
js_.PrintfProperty("name", "new %s",
Class::Cast(owner).ScrubbedNameCString());
js_.PrintPropertyBool("is_stub", true);
} else if (owner.IsAbstractType()) {
js_.PrintfProperty("name", "as %s",
AbstractType::Cast(owner).ScrubbedNameCString());
js_.PrintPropertyBool("is_stub", true);
} else if (owner.IsFunction()) {
js_.PrintProperty("name", Function::Cast(owner).UserVisibleNameCString());
} else if (owner.IsSmi()) {
// This is a class id of the class which owned the function.
// See Precompiler::DropFunctions.
const auto cid = Smi::Cast(owner).Value();
auto class_table = thread_->isolate_group()->class_table();
if (class_table->IsValidIndex(cid) &&
class_table->At(cid) != Class::null()) {
const auto& cls = Class::Handle(class_table->At(cid));
js_.PrintProperty("owner", GetObjectId(cls.ptr()));
js_.PrintfProperty("name", "unknown function of %s",
Class::Cast(cls).ScrubbedNameCString());
} else {
js_.PrintfProperty("name", "unknown function of class #%" Pd "", cid);
}
} else {
// Expected to handle all possibilities.
UNREACHABLE();
}
} else {
js_.PrintPropertyBool("is_stub", true);
const auto it = stub_names_.find(code.EntryPoint());
if (it != stub_names_.end()) {
js_.PrintProperty("name", it->second);
} else if (auto stub_name = TryIdentifyIsolateSpecificStubCopy(
thread_->isolate_group()->object_store(), code)) {
js_.PrintProperty("name", stub_name);
} else {
UNREACHABLE();
}
}
}
void SnapshotAnalyzer::DumpCode(uword start_pc,
uword end_pc,
const char* name) {
js_.PrintProperty("type", "Code");
const auto instruction_base = reinterpret_cast<uint64_t>(info_.snapshot_text);
js_.PrintProperty64("offset",
static_cast<uint64_t>(start_pc) - instruction_base);
js_.PrintProperty64("size", static_cast<uint64_t>(end_pc - start_pc));
js_.PrintProperty("name", name);
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
}
void SnapshotAnalyzer::DumpField(const Field& field) {
const auto& name = String::Handle(field.name());
const auto& type = AbstractType::Handle(field.type());
js_.PrintProperty("type", "Field");
js_.PrintProperty("name", name.ToCString());
js_.PrintProperty64("type_class", GetObjectId(field.type()));
js_.PrintProperty("owner_class", GetObjectId(field.Owner()));
if (field.is_static()) {
js_.PrintProperty("instance", GetObjectId(field.StaticValue()));
js_.PrintProperty64("static_field_offset",
FieldTable::FieldOffsetFor(field.field_id()));
}
if (field.HasInitializerFunction()) {
js_.PrintProperty("initializer_function",
GetObjectId(field.InitializerFunction()));
}
js_.PrintPropertyBool("is_reference", !field.is_unboxed());
if (field.is_unboxed()) {
const char* unboxed_type = nullptr;
if (type.IsDoubleType()) {
unboxed_type = "double";
} else if (type.IsIntType()) {
unboxed_type = "int";
} else if (type.IsFloat32x4Type()) {
unboxed_type = "Float32x4";
} else if (type.IsFloat64x2Type()) {
unboxed_type = "Float64x2";
} else {
unboxed_type = "unknown";
}
js_.PrintProperty("unboxed_type", unboxed_type);
}
js_.OpenArray("flags");
if (field.is_final()) js_.PrintValue("final");
if (field.is_static()) {
js_.PrintValue("static");
if (field.is_shared()) js_.PrintValue("shared");
}
if (field.is_instance()) {
if (field.is_late()) js_.PrintValue("late");
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpString(const String& string) {
js_.PrintProperty("type", "String");
js_.PrintProperty("value", string.ToCString());
}
void SnapshotAnalyzer::DumpInstance(const Object& object) {
js_.PrintProperty("type", "Instance");
js_.PrintProperty("class", GetObjectId(object.clazz()));
FieldVisitor visitor(thread_->isolate_group());
// Two phase algorithm, first discover all relevant objects
// and assign ids, then write them out.
std::function<void(ObjectPtr)> print_reference = [&](ObjectPtr value) {
if (!value.IsHeapObject()) return;
intptr_t index = GetObjectId(value);
js_.PrintValue64(index);
};
visitor.init(&print_reference);
js_.OpenArray("references");
object.ptr().untag()->VisitPointers(&visitor);
js_.CloseArray();
}
void SnapshotAnalyzer::DumpType(const Type& type) {
js_.PrintProperty("type", "Type");
js_.PrintProperty("type_class", GetObjectId(type.type_class()));
const TypeArguments& arguments = TypeArguments::Handle(type.arguments());
js_.OpenArray("type_arguments");
for (intptr_t i = 0; i < arguments.Length(); ++i) {
js_.PrintValue64(GetObjectId(arguments.TypeAt(i)));
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpObjectPool(const ObjectPool& pool) {
js_.PrintProperty("type", "ObjectPool");
js_.OpenArray("references");
for (intptr_t i = 0; i < pool.Length(); ++i) {
if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) {
// We write (index, offset, value) triplets.
js_.PrintValue64(i);
js_.PrintValue64(pool.OffsetFromIndex(i));
js_.PrintValue64(GetObjectId(pool.ObjectAt(i)));
}
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpInterestingObjects() {
// Collect stubs into stub_names to enable quick name lookup
StubCode::ForEachStub([&](const char* name, uword entry_point) {
stub_names_[entry_point] = name;
return true;
});
Zone* zone = thread_->zone();
auto class_table = thread_->isolate_group()->class_table();
class_table->NumCids();
heap_->ResetObjectIdTable();
std::vector<const Object*> discovered_objects;
Object& object = Object::Handle(zone);
{
NoSafepointScope ns(thread_);
FieldVisitor visitor(thread_->isolate_group());
std::function<void(ObjectPtr)> handle_object = [&](ObjectPtr value) {
if (!value.IsHeapObject()) return;
// Ensure we never handle an object more than once.
if (heap_->GetObjectId(value) != 0) return;
heap_->SetObjectId(value, kStartIndex + discovered_objects.size());
discovered_objects.push_back(&Object::Handle(zone, value));
// Ensure all references of this object are visited first.
value->untag()->VisitPointers(&visitor);
};
visitor.init(&handle_object);
// BEGIN Visit all things we are interested in.
// - All constants reachable via object pool
object = thread_->isolate_group()->object_store()->global_object_pool();
handle_object(object.ptr());
// - All libraries
object = thread_->isolate_group()->object_store()->libraries();
object = GrowableObjectArray::Cast(object).data();
object.ptr().untag()->VisitPointers(&visitor);
// - All classes
auto class_table = thread_->isolate_group()->class_table();
for (intptr_t cid = 0; cid < class_table->NumCids(); ++cid) {
if (!class_table->HasValidClassAt(cid)) continue;
object = class_table->At(cid);
handle_object(object.ptr());
}
// - All instructions tables
const auto& instruction_tables = GrowableObjectArray::Handle(
thread_->isolate_group()->object_store()->instructions_tables());
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
object = instruction_tables.At(i);
object = InstructionsTable::Cast(object).code_objects();
handle_object(object.ptr());
}
// - All VM stubs
for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
if (!StubCode::EntryAt(i).IsNull()) {
handle_object(StubCode::EntryAt(i).ptr());
}
}
// - Object store.
//
// This will include a bunch of stuff we don't care about
// but it will also capture things like isolate specific stubs and
// canonicalized types which themselves include references to stubs.
thread_->isolate_group()->object_store()->VisitObjectPointers(&visitor);
}
// Sometimes we have [Field] objects for fields but they are not available
// from [Class.fields] (e.g. late final fields where the slow path uses
// [Field] from object pool to throw a nice error).
//
// So we manually look for all [Field]s and associate them with classes
// instead of relying on the [Class.fields] array.
auto& owner = Class::Handle();
for (uintptr_t i = 0; i < discovered_objects.size(); ++i) {
const Object& object = *discovered_objects[i];
if (object.IsField()) {
const auto& field = Field::Cast(object);
owner = field.Owner();
auto& array =
owner.IsTopLevel() ? top_level_class_fields_ : class_fields_;
const intptr_t index = owner.IsTopLevel()
? ClassTable::IndexFromTopLevelCid(owner.id())
: owner.id();
array[index].push_back(&field);
}
}
// Print information about objects
js_.OpenArray("objects");
// The 0 object id is used in the VM's weak hashmap implementation
// to indicate no value.
js_.OpenObject();
js_.PrintProperty("type", "NoValue");
js_.CloseObject();
for (size_t id = 0; id < discovered_objects.size(); ++id) {
const auto* object = discovered_objects[id];
js_.OpenObject();
// TODO(balid): Remove this as it can be inferred from the array position.
// Used for manual debugging at the moment.
js_.PrintProperty64("id", id + kStartIndex);
// Order matters here, Strings are a subtype of Instance, for example.
if (object->IsNull()) {
js_.PrintProperty("type", "Null");
} else if (object->IsLibrary()) {
DumpLibrary(Library::Cast(*object));
} else if (object->IsObjectPool()) {
DumpObjectPool(ObjectPool::Cast(*object));
} else if (object->IsClass()) {
DumpClass(Class::Cast(*object));
} else if (object->IsFunction()) {
DumpFunction(Function::Cast(*object));
} else if (object->IsCode()) {
DumpCode(Code::Cast(*object));
} else if (object->IsField()) {
DumpField(Field::Cast(*object));
} else if (object->IsString()) {
DumpString(String::Cast(*object));
} else if (object->IsArray()) {
js_.PrintProperty("type", "Array");
const Array& array = Array::Handle(Array::RawCast(object->ptr()));
DumpArray(array, "elements");
} else if (object->IsType()) {
DumpType(Type::Cast(*object));
} else if (object->IsInstance()) {
DumpInstance(*object);
}
js_.CloseObject();
}
// Finally dump pseudo-Code objects for all entries in the instructions
// tables without code objects.
uint64_t pseudo_code_id = kStartIndex + discovered_objects.size();
const auto& instruction_tables = GrowableObjectArray::Handle(
thread_->isolate_group()->object_store()->instructions_tables());
auto& instructions_table = InstructionsTable::Handle();
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
instructions_table ^= instruction_tables.At(i);
for (intptr_t index = 0; index < instructions_table.FirstEntryWithCode();
index++) {
js_.OpenObject();
js_.PrintProperty64("id", pseudo_code_id);
DumpCode(instructions_table.EntryPointAt(index),
instructions_table.EntryPointAt(index + 1), "Unknown Code");
js_.CloseObject();
pseudo_code_id++;
}
}
js_.CloseArray();
}
void SnapshotAnalyzer::DumpMetadata() {
js_.OpenObject("metadata");
js_.OpenObject("offsets");
js_.OpenObject("thread");
// TODO(balid): Use `dart::compiler::target::` versions.
js_.PrintProperty("isolate", Thread::isolate_offset());
js_.PrintProperty("isolate_group", Thread::isolate_group_offset());
js_.PrintProperty("dispatch_table_array",
Thread::dispatch_table_array_offset());
js_.CloseObject();
js_.OpenObject("isolate_group");
js_.PrintProperty("class_table", IsolateGroup::class_table_offset());
js_.PrintProperty("cached_class_table",
IsolateGroup::cached_class_table_table_offset());
js_.PrintProperty("object_store_offset", IsolateGroup::object_store_offset());
js_.CloseObject();
js_.CloseObject();
js_.PrintProperty64("word_size", dart::compiler::target::kWordSize);
js_.PrintProperty64("compressed_word_size",
dart::compiler::target::kCompressedWordSize);
js_.PrintProperty64("analyzer_version", kSnapshotAnalyzerVersion);
js_.CloseObject();
}
void SnapshotAnalyzer::DumpSnapshotInformation(char** buffer,
intptr_t* buffer_length) {
thread_ = Thread::Current();
heap_ = thread_->isolate_group()->heap();
DARTSCOPE(thread_);
// Open empty object so output is valid/parsable JSON.
js_.OpenObject();
js_.OpenObject("snapshot_data");
// Base addresses of the snapshot data, useful to calculate relative offsets.
js_.PrintfProperty("vm_data", "%p", nullptr);
js_.PrintfProperty("vm_instructions", "%p", nullptr);
js_.PrintfProperty("isolate_data", "%p", info_.snapshot_data);
js_.PrintfProperty("isolate_instructions", "%p", info_.snapshot_text);
js_.CloseObject();
{
// Debug builds assert that our thread has a lock before accessing
// vm internal fields.
SafepointReadRwLocker ml(thread_, thread_->isolate_group()->program_lock());
DumpInterestingObjects();
DumpMetadata();
}
// Close our empty object.
js_.CloseObject();
// Give ownership to caller.
js_.Steal(buffer, buffer_length);
}
void Dart_DumpSnapshotInformationAsJson(
const Dart_SnapshotAnalyzerInformation& info,
char** out,
intptr_t* out_len) {
SnapshotAnalyzer analyzer(info);
analyzer.DumpSnapshotInformation(out, out_len);
}
} // namespace snapshot_analyzer
} // namespace dart