// 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. #ifndef RUNTIME_VM_CLUSTERED_SNAPSHOT_H_ #define RUNTIME_VM_CLUSTERED_SNAPSHOT_H_ #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) #define SNAPSHOT_BACKTRACE #endif 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 { public: 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) { deferred_objects_.Add(&Code::ZoneHandle(obj)); } GrowableArray* deferred_objects() { return &deferred_objects_; } ZoneGrowableArray* objects() { ASSERT(objects_ != nullptr); return objects_; } void set_objects(ZoneGrowableArray* objects) { ASSERT(objects_ == nullptr); objects_ = objects; } private: intptr_t id_; LoadingUnitSerializationData* parent_; GrowableArray deferred_objects_; ZoneGrowableArray* objects_; }; class SerializationCluster : public ZoneAllocated { public: 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), cid_(cid), target_instance_size_(target_instance_size), 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_; } protected: 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 { public: explicit DeserializationCluster(const char* name, bool is_canonical = false) : name_(name), is_canonical_(is_canonical), start_index_(-1), 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_; } protected: 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 { public: virtual ~SerializationRoots() {} virtual void AddBaseObjects(Serializer* serializer) = 0; virtual void PushRoots(Serializer* serializer) = 0; virtual void WriteRoots(Serializer* serializer) = 0; }; class DeserializationRoots { public: 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 canoncalization 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 { public: Serializer(Thread* thread, Snapshot::Kind kind, NonStreamingWriteStream* stream, ImageWriter* image_writer_, bool vm_, V8SnapshotProfileWriter* profile_writer = nullptr); ~Serializer(); 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); #if defined(SNAPSHOT_BACKTRACE) ObjectPtr ParentOf(const Object& object); #endif SerializationCluster* NewClusterForClass(intptr_t cid, bool is_canonical); void ReserveHeader() { // Make room for recording snapshot buffer size. stream_->SetPosition(Snapshot::kHeaderSize); } void FillHeader(Snapshot::Kind kind) { Snapshot* header = reinterpret_cast(stream_->buffer()); header->set_magic(); header->set_length(stream_->bytes_written()); header->set_kind(kind); } void WriteVersionAndFeatures(bool is_vm_snapshot); ZoneGrowableArray* 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 { public: WritingObjectScope(Serializer* serializer, const char* type, ObjectPtr object, StringPtr name) : WritingObjectScope( serializer, ReserveId(serializer, type, object, 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, serializer->GetProfileId(object), object) {} ~WritingObjectScope(); private: static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer, const char* type, ObjectPtr object, const char* name); private: 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 void Write(T value) { BaseWriteStream::Raw::Write(stream_, value); } void WriteUnsigned(intptr_t value) { stream_->WriteUnsigned(value); } void WriteUnsigned64(uint64_t value) { stream_->WriteUnsigned(value); } void WriteWordWith32BitWrites(uword value) { stream_->WriteWordWith32BitWrites(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); WriteUnsigned(id); 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) { AttributeReference(object, V8SnapshotProfileWriter::Reference::Element(index)); } void WriteElementRef(ObjectPtr object, intptr_t index) { AttributeElementRef(object, index); WriteUnsigned(RefId(object)); } void AttributePropertyRef(ObjectPtr object, const char* property) { AttributeReference(object, V8SnapshotProfileWriter::Reference::Property(property)); } void WritePropertyRef(ObjectPtr object, const char* property) { AttributePropertyRef(object, property); WriteUnsigned(RefId(object)); } void WriteOffsetRef(ObjectPtr object, intptr_t offset) { intptr_t id = RefId(object); WriteUnsigned(id); if (profile_writer_ != nullptr) { if (auto const property = offsets_table_->FieldNameForOffset( object_currently_writing_.cid_, offset)) { AttributePropertyRef(object, property); } else { AttributeElementRef(object, offset); } } } template void WriteFromTo(T obj, P&&... args) { auto* from = obj->untag()->from(); auto* to = obj->untag()->to_snapshot(kind(), args...); for (auto* p = from; p <= to; p++) { WriteOffsetRef( p->Decompress(obj->heap_base()), reinterpret_cast(p) - reinterpret_cast(obj->untag())); } } template void PushFromTo(T obj, P&&... args) { auto* from = obj->untag()->from(); auto* to = obj->untag()->to_snapshot(kind(), args...); for (auto* p = from; p <= to; p++) { Push(p->Decompress(obj->heap_base())); } } void WriteTokenPosition(TokenPosition pos) { Write(pos.Serialize()); } void WriteCid(intptr_t cid) { COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32); Write(cid); } // 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 snaposhot 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 InCurrentLoadingUnit(ObjectPtr obj, bool record = false); GrowableArray* loading_units() const { return loading_units_; } void set_loading_units(GrowableArray* 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_; } private: 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 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_ = V8SnapshotProfileWriter::kArtificialRootId; intptr_t last_stream_position_ = 0; intptr_t cid_ = -1; } object_currently_writing_; OffsetsTable* offsets_table_ = nullptr; #if defined(SNAPSHOT_BACKTRACE) ObjectPtr current_parent_; GrowableArray parent_pairs_; #endif #if defined(DART_PRECOMPILER) IntMap deduped_instructions_sources_; #endif intptr_t current_loading_unit_id_ = 0; GrowableArray* loading_units_ = nullptr; ZoneGrowableArray* objects_ = new ZoneGrowableArray(); DISALLOW_IMPLICIT_CONSTRUCTORS(Serializer); }; #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 { public: static char* InitializeGlobalVMFlagsFromSnapshot(const Snapshot* snapshot); static bool NullSafetyFromSnapshot(const Snapshot* snapshot); explicit SnapshotHeaderReader(const Snapshot* snapshot) : SnapshotHeaderReader(snapshot->kind(), snapshot->Addr(), snapshot->length()) {} SnapshotHeaderReader(Snapshot::Kind kind, const uint8_t* buffer, intptr_t size) : kind_(kind), stream_(buffer, size) { stream_.SetPosition(Snapshot::kHeaderSize); } // 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); private: 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 { public: 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); ~Deserializer(); // 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 T Read() { return ReadStream::Raw::Read(&stream_); } intptr_t ReadUnsigned() { return stream_.ReadUnsigned(); } uint64_t ReadUnsigned64() { return stream_.ReadUnsigned(); } 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; next_ref_index_++; } ObjectPtr Ref(intptr_t index) const { ASSERT(index > 0); ASSERT(index <= num_objects_); return refs_->untag()->data()[index]; } ObjectPtr ReadRef() { return Ref(ReadUnsigned()); } template 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()); } intptr_t ReadCid() { COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32); return Read(); } 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_; } private: 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 { public: 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); ~FullSnapshotWriter(); 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* data = nullptr); void WriteUnitSnapshot(GrowableArray* units, LoadingUnitSerializationData* unit, uint32_t program_hash); intptr_t VmIsolateSnapshotSize() const { return vm_isolate_snapshot_size_; } intptr_t IsolateSnapshotSize() const { return isolate_snapshot_size_; } private: // Writes a snapshot of the VM Isolate. ZoneGrowableArray* WriteVMSnapshot(); // Writes a full snapshot of regular Dart isolate group. void WriteProgramSnapshot(ZoneGrowableArray* objects, GrowableArray* 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; DISALLOW_COPY_AND_ASSIGN(FullSnapshotWriter); }; class FullSnapshotReader { public: FullSnapshotReader(const Snapshot* snapshot, const uint8_t* instructions_buffer, Thread* thread); ~FullSnapshotReader() {} ApiErrorPtr ReadVMSnapshot(); ApiErrorPtr ReadProgramSnapshot(); ApiErrorPtr ReadUnitSnapshot(const LoadingUnit& unit); private: 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_; DISALLOW_COPY_AND_ASSIGN(FullSnapshotReader); }; } // namespace dart #endif // RUNTIME_VM_CLUSTERED_SNAPSHOT_H_