blob: d80cb7453617550419b557fc45bb6afbc70356b8 [file] [log] [blame]
// Copyright (c) 2016, 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 "platform/assert.h"
#include "vm/allocation.h"
#include "vm/bitfield.h"
#include "vm/datastream.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/hash_map.h"
#include "vm/heap/heap.h"
#include "vm/image_snapshot.h"
#include "vm/object.h"
#include "vm/raw_object_fields.h"
#include "vm/snapshot.h"
#include "vm/version.h"
#if defined(DEBUG)
namespace dart {
// For full snapshots, we use a clustered snapshot format that trades longer
// serialization time for faster deserialization time and smaller snapshots.
// Objects are clustered by class to allow writing type information once per
// class instead once per object, and to allow filling the objects in a tight
// loop. The snapshot has two major sections: the first describes how to
// allocate the objects and the second describes how to initialize them.
// Deserialization starts by allocating a reference array large enough to hold
// the base objects (objects already available to both the serializer and
// deserializer) and the objects written in the snapshot. The allocation section
// is then read for each cluster, filling the reference array. Then the
// initialization/fill secton is read for each cluster, using the indices into
// the reference array to fill pointers. At this point, every object has been
// touched exactly once and in order, making this approach very cache friendly.
// Finally, each cluster is given an opportunity to perform some fix-ups that
// require the graph has been fully loaded, such as rehashing, though most
// clusters do not require fixups.
// Forward declarations.
class Serializer;
class Deserializer;
class ObjectStore;
class ImageWriter;
class ImageReader;
class LoadingUnitSerializationData : public ZoneAllocated {
LoadingUnitSerializationData(intptr_t id,
LoadingUnitSerializationData* parent)
: id_(id), parent_(parent), deferred_objects_(), objects_(nullptr) {}
intptr_t id() const { return id_; }
LoadingUnitSerializationData* parent() const { return parent_; }
void AddDeferredObject(CodePtr obj) {
GrowableArray<Code*>* deferred_objects() { return &deferred_objects_; }
ZoneGrowableArray<Object*>* objects() {
ASSERT(objects_ != nullptr);
return objects_;
void set_objects(ZoneGrowableArray<Object*>* objects) {
ASSERT(objects_ == nullptr);
objects_ = objects;
intptr_t id_;
LoadingUnitSerializationData* parent_;
GrowableArray<Code*> deferred_objects_;
ZoneGrowableArray<Object*>* objects_;
class SerializationCluster : public ZoneAllocated {
static constexpr intptr_t kSizeVaries = -1;
explicit SerializationCluster(const char* name,
intptr_t cid,
intptr_t target_instance_size = kSizeVaries,
bool is_canonical = false)
: name_(name),
is_canonical_(is_canonical) {
ASSERT(target_instance_size == kSizeVaries || target_instance_size >= 0);
virtual ~SerializationCluster() {}
// Add [object] to the cluster and push its outgoing references.
virtual void Trace(Serializer* serializer, ObjectPtr object) = 0;
// Write the cluster type and information needed to allocate the cluster's
// objects. For fixed sized objects, this is just the object count. For
// variable sized objects, this is the object count and length of each object.
virtual void WriteAlloc(Serializer* serializer) = 0;
// Write the byte and reference data of the cluster's objects.
virtual void WriteFill(Serializer* serializer) = 0;
void WriteAndMeasureAlloc(Serializer* serializer);
void WriteAndMeasureFill(Serializer* serializer);
const char* name() const { return name_; }
intptr_t cid() const { return cid_; }
bool is_canonical() const { return is_canonical_; }
intptr_t size() const { return size_; }
intptr_t num_objects() const { return num_objects_; }
// Returns number of bytes needed for deserialized objects in
// this cluster. Printed in --print_snapshot_sizes_verbose statistics.
// In order to calculate this size, clusters of fixed-size objects
// can pass instance size as [target_instance_size] constructor parameter.
// Otherwise clusters should count [target_memory_size] in
// their [WriteAlloc] methods.
intptr_t target_memory_size() const { return target_memory_size_; }
const char* const name_;
const intptr_t cid_;
const intptr_t target_instance_size_;
const bool is_canonical_;
intptr_t size_ = 0;
intptr_t num_objects_ = 0;
intptr_t target_memory_size_ = 0;
class DeserializationCluster : public ZoneAllocated {
explicit DeserializationCluster(const char* name, bool is_canonical = false)
: name_(name),
stop_index_(-1) {}
virtual ~DeserializationCluster() {}
// Allocate memory for all objects in the cluster and write their addresses
// into the ref array. Do not touch this memory.
virtual void ReadAlloc(Deserializer* deserializer) = 0;
// Initialize the cluster's objects. Do not touch the memory of other objects.
virtual void ReadFill(Deserializer* deserializer, bool primary) = 0;
// Complete any action that requires the full graph to be deserialized, such
// as rehashing.
virtual void PostLoad(Deserializer* deserializer,
const Array& refs,
bool primary) {
if (!primary && is_canonical()) {
FATAL1("%s needs canonicalization but doesn't define PostLoad", name());
const char* name() const { return name_; }
bool is_canonical() const { return is_canonical_; }
void ReadAllocFixedSize(Deserializer* deserializer, intptr_t instance_size);
const char* const name_;
const bool is_canonical_;
// The range of the ref array that belongs to this cluster.
intptr_t start_index_;
intptr_t stop_index_;
class SerializationRoots {
virtual ~SerializationRoots() {}
virtual void AddBaseObjects(Serializer* serializer) = 0;
virtual void PushRoots(Serializer* serializer) = 0;
virtual void WriteRoots(Serializer* serializer) = 0;
class DeserializationRoots {
virtual ~DeserializationRoots() {}
// Returns true if these roots are the first snapshot loaded into a heap, and
// so can assume any canonical objects don't already exist. Returns false if
// some other snapshot may be loaded before these roots, and so written
// canonical objects need to run canonicalization during load.
virtual bool AddBaseObjects(Deserializer* deserializer) = 0;
virtual void ReadRoots(Deserializer* deserializer) = 0;
virtual void PostLoad(Deserializer* deserializer, const Array& refs) = 0;
// Reference value for objects that either are not reachable from the roots or
// should never have a reference in the snapshot (because they are dropped,
// for example). Should be the default value for Heap::GetObjectId.
static constexpr intptr_t kUnreachableReference = 0;
COMPILE_ASSERT(kUnreachableReference == WeakTable::kNoValue);
static constexpr intptr_t kFirstReference = 1;
// Reference value for traced objects that have not been allocated their final
// reference ID.
static const intptr_t kUnallocatedReference = -1;
static constexpr bool IsAllocatedReference(intptr_t ref) {
return ref > kUnreachableReference;
static constexpr bool IsArtificialReference(intptr_t ref) {
return ref < kUnallocatedReference;
static constexpr bool IsReachableReference(intptr_t ref) {
return ref == kUnallocatedReference || IsAllocatedReference(ref);
class CodeSerializationCluster;
class Serializer : public ThreadStackResource {
Serializer(Thread* thread,
Snapshot::Kind kind,
NonStreamingWriteStream* stream,
ImageWriter* image_writer_,
bool vm_,
V8SnapshotProfileWriter* profile_writer = nullptr);
void AddBaseObject(ObjectPtr base_object,
const char* type = nullptr,
const char* name = nullptr);
intptr_t AssignRef(ObjectPtr object);
intptr_t AssignArtificialRef(ObjectPtr object = nullptr);
void Push(ObjectPtr object);
void AddUntracedRef() { num_written_objects_++; }
void Trace(ObjectPtr object);
void UnexpectedObject(ObjectPtr object, const char* message);
ObjectPtr ParentOf(const Object& object);
SerializationCluster* NewClusterForClass(intptr_t cid, bool is_canonical);
void ReserveHeader() {
// Make room for recording snapshot buffer size.
void FillHeader(Snapshot::Kind kind) {
Snapshot* header = reinterpret_cast<Snapshot*>(stream_->buffer());
void WriteVersionAndFeatures(bool is_vm_snapshot);
ZoneGrowableArray<Object*>* Serialize(SerializationRoots* roots);
void PrintSnapshotSizes();
FieldTable* initial_field_table() const { return initial_field_table_; }
NonStreamingWriteStream* stream() { return stream_; }
intptr_t bytes_written() { return stream_->bytes_written(); }
intptr_t bytes_heap_allocated() { return bytes_heap_allocated_; }
class WritingObjectScope : ValueObject {
WritingObjectScope(Serializer* serializer,
const char* type,
ObjectPtr object,
StringPtr name)
: WritingObjectScope(
String::ToCString(serializer->thread(), name)),
object) {}
WritingObjectScope(Serializer* serializer,
const char* type,
ObjectPtr object,
const char* name)
: WritingObjectScope(serializer,
ReserveId(serializer, type, object, name),
object) {}
WritingObjectScope(Serializer* serializer,
const V8SnapshotProfileWriter::ObjectId& id,
ObjectPtr object = nullptr);
WritingObjectScope(Serializer* serializer, ObjectPtr object)
: WritingObjectScope(serializer,
object) {}
static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer,
const char* type,
ObjectPtr object,
const char* name);
Serializer* const serializer_;
const ObjectPtr old_object_;
const V8SnapshotProfileWriter::ObjectId old_id_;
const classid_t old_cid_;
// Writes raw data to the stream (basic type).
// sizeof(T) must be in {1,2,4,8}.
template <typename T>
void Write(T value) {
BaseWriteStream::Raw<sizeof(T), T>::Write(stream_, value);
void WriteUnsigned(intptr_t value) { stream_->WriteUnsigned(value); }
void WriteUnsigned64(uint64_t value) { stream_->WriteUnsigned(value); }
void WriteWordWith32BitWrites(uword value) {
void WriteBytes(const uint8_t* addr, intptr_t len) {
stream_->WriteBytes(addr, len);
void Align(intptr_t alignment) { stream_->Align(alignment); }
V8SnapshotProfileWriter::ObjectId GetProfileId(ObjectPtr object) const;
V8SnapshotProfileWriter::ObjectId GetProfileId(intptr_t ref) const;
void WriteRootRef(ObjectPtr object, const char* name = nullptr) {
intptr_t id = RefId(object);
if (profile_writer_ != nullptr) {
profile_writer_->AddRoot(GetProfileId(object), name);
// Record a reference from the currently written object to the given object
// and return reference id for the given object.
void AttributeReference(ObjectPtr object,
const V8SnapshotProfileWriter::Reference& reference);
void AttributeElementRef(ObjectPtr object, intptr_t index) {
void WriteElementRef(ObjectPtr object, intptr_t index) {
AttributeElementRef(object, index);
void AttributePropertyRef(ObjectPtr object, const char* property) {
void WritePropertyRef(ObjectPtr object, const char* property) {
AttributePropertyRef(object, property);
void WriteOffsetRef(ObjectPtr object, intptr_t offset) {
intptr_t id = RefId(object);
if (profile_writer_ != nullptr) {
if (auto const property = offsets_table_->FieldNameForOffset(
object_currently_writing_.cid_, offset)) {
AttributePropertyRef(object, property);
} else {
AttributeElementRef(object, offset);
template <typename T, typename... P>
void WriteFromTo(T obj, P&&... args) {
auto* from = obj->untag()->from();
auto* to = obj->untag()->to_snapshot(kind(), args...);
WriteRange(obj, from, to);
template <typename T>
DART_NOINLINE void WriteRange(ObjectPtr obj, T from, T to) {
for (auto* p = from; p <= to; p++) {
reinterpret_cast<uword>(p) - reinterpret_cast<uword>(obj->untag()));
template <typename T, typename... P>
void PushFromTo(T obj, P&&... args) {
auto* from = obj->untag()->from();
auto* to = obj->untag()->to_snapshot(kind(), args...);
PushRange(obj, from, to);
template <typename T>
DART_NOINLINE void PushRange(ObjectPtr obj, T from, T to) {
for (auto* p = from; p <= to; p++) {
void WriteTokenPosition(TokenPosition pos) { Write(pos.Serialize()); }
void WriteCid(intptr_t cid) {
COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32);
// Sorts Code objects and reorders instructions before writing snapshot.
// Returns length of instructions table (in bare instructions mode).
intptr_t PrepareInstructions();
void WriteInstructions(InstructionsPtr instr,
uint32_t unchecked_offset,
CodePtr code,
bool deferred);
uint32_t GetDataOffset(ObjectPtr object) const;
void TraceDataOffset(uint32_t offset);
intptr_t GetDataSize() const;
void WriteDispatchTable(const Array& entries);
Heap* heap() const { return heap_; }
Zone* zone() const { return zone_; }
Snapshot::Kind kind() const { return kind_; }
intptr_t next_ref_index() const { return next_ref_index_; }
void DumpCombinedCodeStatistics();
V8SnapshotProfileWriter* profile_writer() const { return profile_writer_; }
// If the given [obj] was not included into the snapshot and have not
// yet gotten an artificial node created for it create an artificial node
// in the profile representing this object.
// Returns true if [obj] has an artificial profile node associated with it.
bool CreateArtificialNodeIfNeeded(ObjectPtr obj);
bool InCurrentLoadingUnitOrRoot(ObjectPtr obj);
void RecordDeferredCode(CodePtr ptr);
GrowableArray<LoadingUnitSerializationData*>* loading_units() const {
return loading_units_;
void set_loading_units(GrowableArray<LoadingUnitSerializationData*>* units) {
loading_units_ = units;
intptr_t current_loading_unit_id() const { return current_loading_unit_id_; }
void set_current_loading_unit_id(intptr_t id) {
current_loading_unit_id_ = id;
// Returns the reference ID for the object. Fails for objects that have not
// been allocated a reference ID yet, so should be used only after all
// WriteAlloc calls.
intptr_t RefId(ObjectPtr object) const;
// Same as RefId, but allows artificial and unreachable references. Still
// fails for unallocated references.
intptr_t UnsafeRefId(ObjectPtr object) const;
// Whether the object is reachable.
bool IsReachable(ObjectPtr object) const {
return IsReachableReference(heap_->GetObjectId(object));
// Whether the object has an allocated reference.
bool HasRef(ObjectPtr object) const {
return IsAllocatedReference(heap_->GetObjectId(object));
// Whether the object only appears in the V8 snapshot profile.
bool HasArtificialRef(ObjectPtr object) const {
return IsArtificialReference(heap_->GetObjectId(object));
// Whether a node for the object already has been added to the V8 snapshot
// profile.
bool HasProfileNode(ObjectPtr object) const {
ASSERT(profile_writer_ != nullptr);
return profile_writer_->HasId(GetProfileId(object));
bool IsWritten(ObjectPtr object) const {
return heap_->GetObjectId(object) > num_base_objects_;
const char* ReadOnlyObjectType(intptr_t cid);
void FlushProfile();
Heap* heap_;
Zone* zone_;
Snapshot::Kind kind_;
NonStreamingWriteStream* stream_;
ImageWriter* image_writer_;
SerializationCluster** canonical_clusters_by_cid_;
SerializationCluster** clusters_by_cid_;
CodeSerializationCluster* code_cluster_ = nullptr;
GrowableArray<ObjectPtr> stack_;
intptr_t num_cids_;
intptr_t num_tlc_cids_;
intptr_t num_base_objects_;
intptr_t num_written_objects_;
intptr_t next_ref_index_;
intptr_t previous_text_offset_;
FieldTable* initial_field_table_;
intptr_t dispatch_table_size_ = 0;
intptr_t bytes_heap_allocated_ = 0;
intptr_t instructions_table_len_ = 0;
// True if writing VM snapshot, false for Isolate snapshot.
bool vm_;
V8SnapshotProfileWriter* profile_writer_ = nullptr;
struct ProfilingObject {
ObjectPtr object_ = nullptr;
// Unless within a WritingObjectScope, any bytes written are attributed to
// the artificial root.
V8SnapshotProfileWriter::ObjectId id_ =
intptr_t last_stream_position_ = 0;
intptr_t cid_ = -1;
} object_currently_writing_;
OffsetsTable* offsets_table_ = nullptr;
ObjectPtr current_parent_;
GrowableArray<Object*> parent_pairs_;
IntMap<intptr_t> deduped_instructions_sources_;
intptr_t current_loading_unit_id_ = 0;
GrowableArray<LoadingUnitSerializationData*>* loading_units_ = nullptr;
ZoneGrowableArray<Object*>* objects_ = new ZoneGrowableArray<Object*>();
#define AutoTraceObject(obj) \
Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr)
#define AutoTraceObjectName(obj, str) \
Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, str)
#define WriteFieldValue(field, value) s->WritePropertyRef(value, #field);
#define WriteFromTo(obj, ...) s->WriteFromTo(obj, ##__VA_ARGS__);
#define PushFromTo(obj, ...) s->PushFromTo(obj, ##__VA_ARGS__);
#define WriteField(obj, field) s->WritePropertyRef(obj->untag()->field, #field)
#define WriteCompressedField(obj, name) \
s->WritePropertyRef(obj->untag()->name(), #name "_")
// This class can be used to read version and features from a snapshot before
// the VM has been initialized.
class SnapshotHeaderReader {
static char* InitializeGlobalVMFlagsFromSnapshot(const Snapshot* snapshot);
static bool NullSafetyFromSnapshot(const Snapshot* snapshot);
explicit SnapshotHeaderReader(const Snapshot* snapshot)
: SnapshotHeaderReader(snapshot->kind(),
snapshot->length()) {}
SnapshotHeaderReader(Snapshot::Kind kind,
const uint8_t* buffer,
intptr_t size)
: kind_(kind), stream_(buffer, size) {
// Verifies the version and features in the snapshot are compatible with the
// current VM. If isolate is non-null it validates isolate-specific features.
// Returns null on success and a malloc()ed error on failure.
// The [offset] will be the next position in the snapshot stream after the
// features.
char* VerifyVersionAndFeatures(IsolateGroup* isolate_group, intptr_t* offset);
char* VerifyVersion();
char* ReadFeatures(const char** features, intptr_t* features_length);
char* VerifyFeatures(IsolateGroup* isolate_group);
char* BuildError(const char* message);
Snapshot::Kind kind_;
ReadStream stream_;
class Deserializer : public ThreadStackResource {
Deserializer(Thread* thread,
Snapshot::Kind kind,
const uint8_t* buffer,
intptr_t size,
const uint8_t* data_buffer,
const uint8_t* instructions_buffer,
bool is_non_root_unit,
intptr_t offset = 0);
// Verifies the image alignment.
// Returns ApiError::null() on success and an ApiError with an an appropriate
// message otherwise.
ApiErrorPtr VerifyImageAlignment();
static void InitializeHeader(ObjectPtr raw,
intptr_t cid,
intptr_t size,
bool is_canonical = false);
// Reads raw data (for basic types).
// sizeof(T) must be in {1,2,4,8}.
template <typename T>
T Read() {
return ReadStream::Raw<sizeof(T), T>::Read(&stream_);
intptr_t ReadUnsigned() { return stream_.ReadUnsigned(); }
uint64_t ReadUnsigned64() { return stream_.ReadUnsigned<uint64_t>(); }
void ReadBytes(uint8_t* addr, intptr_t len) { stream_.ReadBytes(addr, len); }
uword ReadWordWith32BitReads() { return stream_.ReadWordWith32BitReads(); }
intptr_t position() const { return stream_.Position(); }
void set_position(intptr_t p) { stream_.SetPosition(p); }
const uint8_t* CurrentBufferAddress() const {
return stream_.AddressOfCurrentPosition();
void Advance(intptr_t value) { stream_.Advance(value); }
void Align(intptr_t alignment) { stream_.Align(alignment); }
void AddBaseObject(ObjectPtr base_object) { AssignRef(base_object); }
void AssignRef(ObjectPtr object) {
ASSERT(next_ref_index_ <= num_objects_);
refs_->untag()->data()[next_ref_index_] = object;
ObjectPtr Ref(intptr_t index) const {
ASSERT(index > 0);
ASSERT(index <= num_objects_);
return refs_->untag()->element(index);
ObjectPtr ReadRef() { return Ref(ReadUnsigned()); }
template <typename T, typename... P>
void ReadFromTo(T obj, P&&... params) {
auto* from = obj->untag()->from();
auto* to_snapshot = obj->untag()->to_snapshot(kind(), params...);
auto* to = obj->untag()->to(params...);
for (auto* p = from; p <= to_snapshot; p++) {
*p = ReadRef();
// This is necessary because, unlike Object::Allocate, the clustered
// deserializer allocates object without null-initializing them. Instead,
// each deserialization cluster is responsible for initializing every field,
// ensuring that every field is written to exactly once.
for (auto* p = to_snapshot + 1; p <= to; p++) {
*p = Object::null();
TokenPosition ReadTokenPosition() {
return TokenPosition::Deserialize(Read<int32_t>());
intptr_t ReadCid() {
COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32);
return Read<int32_t>();
void ReadInstructions(CodePtr code, bool deferred, bool discarded);
void EndInstructions();
ObjectPtr GetObjectAt(uint32_t offset) const;
void Deserialize(DeserializationRoots* roots);
DeserializationCluster* ReadCluster();
void ReadDispatchTable() {
ReadDispatchTable(&stream_, /*deferred=*/false, -1, -1);
void ReadDispatchTable(ReadStream* stream,
bool deferred,
intptr_t deferred_code_start_index,
intptr_t deferred_code_end_index);
intptr_t next_index() const { return next_ref_index_; }
Heap* heap() const { return heap_; }
Zone* zone() const { return zone_; }
Snapshot::Kind kind() const { return kind_; }
FieldTable* initial_field_table() const { return initial_field_table_; }
bool is_non_root_unit() const { return is_non_root_unit_; }
void set_code_start_index(intptr_t value) { code_start_index_ = value; }
intptr_t code_start_index() { return code_start_index_; }
const InstructionsTable& instructions_table() const {
return instructions_table_;
Heap* heap_;
Zone* zone_;
Snapshot::Kind kind_;
ReadStream stream_;
ImageReader* image_reader_;
intptr_t num_base_objects_;
intptr_t num_objects_;
intptr_t num_clusters_;
ArrayPtr refs_;
intptr_t next_ref_index_;
intptr_t previous_text_offset_;
intptr_t code_start_index_ = 0;
intptr_t instructions_index_ = 0;
DeserializationCluster** clusters_;
FieldTable* initial_field_table_;
const bool is_non_root_unit_;
InstructionsTable& instructions_table_;
#define ReadFromTo(obj, ...) d->ReadFromTo(obj, ##__VA_ARGS__);
class FullSnapshotWriter {
static const intptr_t kInitialSize = 64 * KB;
FullSnapshotWriter(Snapshot::Kind kind,
NonStreamingWriteStream* vm_snapshot_data,
NonStreamingWriteStream* isolate_snapshot_data,
ImageWriter* vm_image_writer,
ImageWriter* iso_image_writer);
Thread* thread() const { return thread_; }
Zone* zone() const { return thread_->zone(); }
IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
Heap* heap() const { return isolate_group()->heap(); }
// Writes a full snapshot of the program(VM isolate, regular isolate group).
void WriteFullSnapshot(
GrowableArray<LoadingUnitSerializationData*>* data = nullptr);
void WriteUnitSnapshot(GrowableArray<LoadingUnitSerializationData*>* units,
LoadingUnitSerializationData* unit,
uint32_t program_hash);
intptr_t VmIsolateSnapshotSize() const { return vm_isolate_snapshot_size_; }
intptr_t IsolateSnapshotSize() const { return isolate_snapshot_size_; }
// Writes a snapshot of the VM Isolate.
ZoneGrowableArray<Object*>* WriteVMSnapshot();
// Writes a full snapshot of regular Dart isolate group.
void WriteProgramSnapshot(ZoneGrowableArray<Object*>* objects,
GrowableArray<LoadingUnitSerializationData*>* data);
Thread* thread_;
Snapshot::Kind kind_;
NonStreamingWriteStream* const vm_snapshot_data_;
NonStreamingWriteStream* const isolate_snapshot_data_;
intptr_t vm_isolate_snapshot_size_;
intptr_t isolate_snapshot_size_;
ImageWriter* vm_image_writer_;
ImageWriter* isolate_image_writer_;
// Stats for benchmarking.
intptr_t clustered_vm_size_ = 0;
intptr_t clustered_isolate_size_ = 0;
intptr_t mapped_data_size_ = 0;
intptr_t mapped_text_size_ = 0;
intptr_t heap_vm_size_ = 0;
intptr_t heap_isolate_size_ = 0;
V8SnapshotProfileWriter* profile_writer_ = nullptr;
class FullSnapshotReader {
FullSnapshotReader(const Snapshot* snapshot,
const uint8_t* instructions_buffer,
Thread* thread);
~FullSnapshotReader() {}
ApiErrorPtr ReadVMSnapshot();
ApiErrorPtr ReadProgramSnapshot();
ApiErrorPtr ReadUnitSnapshot(const LoadingUnit& unit);
IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
ApiErrorPtr ConvertToApiError(char* message);
void PatchGlobalObjectPool();
void InitializeBSS();
Snapshot::Kind kind_;
Thread* thread_;
const uint8_t* buffer_;
intptr_t size_;
const uint8_t* data_image_;
const uint8_t* instructions_image_;
} // namespace dart