// 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 <memory>
#include <utility>

#include "vm/app_snapshot.h"

#include "platform/assert.h"
#include "vm/bootstrap.h"
#include "vm/bss_relocs.h"
#include "vm/canonical_tables.h"
#include "vm/class_id.h"
#include "vm/code_observers.h"
#include "vm/compiler/api/print_filter.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/dart.h"
#include "vm/dart_entry.h"
#include "vm/dispatch_table.h"
#include "vm/flag_list.h"
#include "vm/growable_array.h"
#include "vm/heap/heap.h"
#include "vm/image_snapshot.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/program_visitor.h"
#include "vm/raw_object_fields.h"
#include "vm/stub_code.h"
#include "vm/symbols.h"
#include "vm/timeline.h"
#include "vm/v8_snapshot_writer.h"
#include "vm/version.h"
#include "vm/zone_text_buffer.h"

#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/backend/code_statistics.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/relocation.h"
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

namespace dart {

#if !defined(DART_PRECOMPILED_RUNTIME)
DEFINE_FLAG(bool,
            print_cluster_information,
            false,
            "Print information about clusters written to snapshot");
#endif

#if defined(DART_PRECOMPILER)
DEFINE_FLAG(charp,
            write_v8_snapshot_profile_to,
            nullptr,
            "Write a snapshot profile in V8 format to a file.");
DEFINE_FLAG(bool,
            print_array_optimization_candidates,
            false,
            "Print information about how many array are candidates for Smi and "
            "ROData optimizations.");
#endif  // defined(DART_PRECOMPILER)

// Forward declarations.
class Serializer;
class Deserializer;

namespace {

// Serialized clusters are identified by their CID. So to insert custom clusters
// we need to assign them a CID that is otherwise never serialized.
static constexpr intptr_t kDeltaEncodedTypedDataCid = kNativePointer;

// StorageTrait for HashTable which allows to create hash tables backed by
// zone memory. Used to compute cluster order for canonical clusters.
struct GrowableArrayStorageTraits {
  class Array : public ZoneObject {
   public:
    Array(Zone* zone, intptr_t length)
        : length_(length), array_(zone->Alloc<ObjectPtr>(length)) {}

    intptr_t Length() const { return length_; }
    void SetAt(intptr_t index, const Object& value) const {
      array_[index] = value.ptr();
    }
    ObjectPtr At(intptr_t index) const { return array_[index]; }

   private:
    intptr_t length_ = 0;
    ObjectPtr* array_ = nullptr;
    DISALLOW_COPY_AND_ASSIGN(Array);
  };

  using ArrayPtr = Array*;
  class ArrayHandle : public ZoneObject {
   public:
    explicit ArrayHandle(ArrayPtr ptr) : ptr_(ptr) {}
    ArrayHandle() {}

    void SetFrom(const ArrayHandle& other) { ptr_ = other.ptr_; }
    void Clear() { ptr_ = nullptr; }
    bool IsNull() const { return ptr_ == nullptr; }
    ArrayPtr ptr() { return ptr_; }

    intptr_t Length() const { return ptr_->Length(); }
    void SetAt(intptr_t index, const Object& value) const {
      ptr_->SetAt(index, value);
    }
    ObjectPtr At(intptr_t index) const { return ptr_->At(index); }

   private:
    ArrayPtr ptr_ = nullptr;
    DISALLOW_COPY_AND_ASSIGN(ArrayHandle);
  };

  static ArrayHandle& PtrToHandle(ArrayPtr ptr) {
    return *new ArrayHandle(ptr);
  }

  static void SetHandle(ArrayHandle& dst, const ArrayHandle& src) {  // NOLINT
    dst.SetFrom(src);
  }

  static void ClearHandle(ArrayHandle& dst) {  // NOLINT
    dst.Clear();
  }

  static ArrayPtr New(Zone* zone, intptr_t length, Heap::Space space) {
    return new (zone) Array(zone, length);
  }

  static bool IsImmutable(const ArrayHandle& handle) { return false; }

  static ObjectPtr At(ArrayHandle* array, intptr_t index) {
    return array->At(index);
  }

  static void SetAt(ArrayHandle* array, intptr_t index, const Object& value) {
    array->SetAt(index, value);
  }
};
}  // namespace

#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)

static void RelocateCodeObjects(
    GrowableArray<CodePtr>* code_objects,
    GrowableArray<ImageWriterCommand>* image_writer_commands) {
  auto thread = Thread::Current();
  auto isolate_group = thread->isolate_group();

  WritableCodePages writable_code_pages(thread, isolate_group);
  CodeRelocator::Relocate(thread, code_objects, image_writer_commands);
}

#endif  // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)

class SerializationCluster : public ZoneObject {
 public:
  static constexpr intptr_t kSizeVaries = -1;
  SerializationCluster(const char* name,
                       intptr_t cid,
                       intptr_t target_instance_size = kSizeVaries,
                       bool is_canonical = false,
                       bool is_deeply_immutable = false)
      : name_(name),
        cid_(cid),
        target_instance_size_(target_instance_size),
        is_canonical_(is_canonical),
        is_deeply_immutable_(is_canonical_ ||
                             Object::ShouldHaveDeeplyImmutabilityBitSet(cid)) {
    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_; }
  bool is_deeply_immutable() const { return is_deeply_immutable_; }
  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_;
  const bool is_deeply_immutable_;
  intptr_t size_ = 0;
  intptr_t num_objects_ = 0;
  intptr_t target_memory_size_ = 0;
};

class DeserializationCluster : public ZoneObject {
 public:
  explicit DeserializationCluster(const char* name,
                                  bool is_canonical = false,
                                  bool is_deeply_immutable = false)
      : name_(name),
        is_canonical_(is_canonical),
        is_deeply_immutable_(is_deeply_immutable),
        start_index_(-1),
        stop_index_(-1) {
    // All canonical should be deeply-immutable
    ASSERT(is_deeply_immutable_ || !is_canonical_);
  }
  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) = 0;

  // Complete any action that requires the full graph to be deserialized, such
  // as rehashing.
  virtual void PostLoad(Deserializer* deserializer, const Array& refs) {
    // We only need to worry about how canonical values are handled during
    // deserialization if there may be multiple loading units, which only
    // happens in the precompiled runtime.
#if defined(DART_PRECOMPILED_RUNTIME)
    if (is_canonical()) {
      FATAL("%s needs canonicalization but doesn't define PostLoad", name());
    }
#endif
  }

  const char* name() const { return name_; }
  bool is_canonical() const { return is_canonical_; }
  bool is_deeply_immutable() const { return is_deeply_immutable_; }

 protected:
  void ReadAllocFixedSize(Deserializer* deserializer, intptr_t instance_size);

  const char* const name_;
  const bool is_canonical_;
  const bool is_deeply_immutable_;
  // 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;

  virtual const CompressedStackMaps& canonicalized_stack_map_entries() const;
};

class DeserializationRoots {
 public:
  virtual ~DeserializationRoots() {}
  virtual void 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 constexpr 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_,
             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);

  intptr_t GetCodeIndex(CodePtr code);

  void Push(ObjectPtr object, intptr_t cid_override = kIllegalCid);
  void PushWeak(ObjectPtr object);

  void AddUntracedRef() { num_written_objects_++; }

  void Trace(ObjectPtr object, intptr_t cid_override);

  void UnexpectedObject(ObjectPtr object, const char* message);
#if defined(SNAPSHOT_BACKTRACE)
  ObjectPtr ParentOf(ObjectPtr object) const;
  ObjectPtr ParentOf(const Object& object) const;
#endif

  SerializationCluster* NewClusterForClass(intptr_t cid,
                                           bool is_canonical,
                                           bool is_deeply_immutable);

  void ReserveHeader() {
    // Make room for recording snapshot buffer size.
    stream_->SetPosition(Snapshot::kHeaderSize);
  }

  void FillHeader(Snapshot::Kind kind) {
    Snapshot* header = reinterpret_cast<Snapshot*>(stream_->buffer());
    header->set_magic();
    header->set_length(stream_->bytes_written());
    header->set_kind(kind);
  }

  void WriteVersionAndFeatures();

  void CreateEarlyClusters();
  ZoneGrowableArray<Object*>* Serialize(SerializationRoots* roots);
  void PrintSnapshotSizes();

  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 <typename T>
  void Write(T value) {
    BaseWriteStream::Raw<sizeof(T), T>::Write(stream_, value);
  }
  void WriteRefId(intptr_t value) { stream_->WriteRefId(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 void* addr, intptr_t len) {
    stream_->WriteBytes(addr, len);
  }
  void Align(intptr_t alignment, intptr_t offset = 0) {
    stream_->Align(alignment, offset);
  }

  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);
    WriteRefId(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);
    WriteRefId(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);
    WriteRefId(RefId(object));
  }

  void WriteOffsetRef(ObjectPtr object, intptr_t offset) {
    intptr_t id = RefId(object);
    WriteRefId(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 <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++) {
      WriteOffsetRef(
          p->Decompress(obj->heap_base()),
          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++) {
      Push(p->Decompress(obj->heap_base()));
    }
  }

  void WriteTokenPosition(TokenPosition pos) { Write(pos.Serialize()); }

  void WriteCid(intptr_t cid) {
    COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32);
    Write<int32_t>(cid);
  }

  // Sorts Code objects and reorders instructions before writing snapshot.
  // Builds binary search table for stack maps.
  void PrepareInstructions(const CompressedStackMaps& canonical_smap);

  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_;
  }

 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** immutable_clusters_by_cid_;
  SerializationCluster** clusters_by_cid_;
  CodeSerializationCluster* code_cluster_ = nullptr;
  GrowableArray<SerializationCluster*> clusters_;

  struct StackEntry {
    ObjectPtr obj;
    intptr_t cid_override;
  };
  GrowableArray<StackEntry> 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 dispatch_table_size_ = 0;
  intptr_t bytes_heap_allocated_ = 0;
  intptr_t instructions_table_len_ = 0;
  intptr_t instructions_table_rodata_offset_ = 0;

  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_;
#endif

#if defined(DART_PRECOMPILER)
  IntMap<intptr_t> deduped_instructions_sources_;
  IntMap<intptr_t> code_index_;
#endif

  intptr_t current_loading_unit_id_ = 0;
  GrowableArray<LoadingUnitSerializationData*>* loading_units_ = nullptr;
  ZoneGrowableArray<Object*>* objects_ = new ZoneGrowableArray<Object*>();

  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 "_")

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.
  //
  // On success, returns nullptr. On failure, returns an error message that the
  // caller must free.
  char* VerifyImageAlignment();

  ObjectPtr Allocate(intptr_t size);
  static void InitializeHeader(ObjectPtr raw, intptr_t cid, intptr_t size) {
    InitializeHeader(raw, cid, size, /*is_canonical=*/false,
                     /*is_deeply_immutable=*/false);
  }
  static void InitializeHeader(ObjectPtr raw,
                               intptr_t cid,
                               intptr_t size,
                               bool is_canonical,
                               bool is_deeply_immutable);

  // 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 ReadRefId() { return stream_.ReadRefId(); }
  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* AddressOfCurrentPosition() const {
    return stream_.AddressOfCurrentPosition();
  }

  void Advance(intptr_t value) { stream_.Advance(value); }
  void Align(intptr_t alignment, intptr_t offset = 0) {
    stream_.Align(alignment, offset);
  }

  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()->element(index);
  }

  CodePtr GetCodeByIndex(intptr_t code_index, uword* entry_point) const;
  uword GetEntryPointByCodeIndex(intptr_t code_index) const;

  // If |code_index| corresponds to a non-discarded Code object returns
  // index within the code cluster that corresponds to this Code object.
  // Otherwise, if |code_index| corresponds to the discarded Code then
  // returns -1.
  static intptr_t CodeIndexToClusterIndex(const InstructionsTable& table,
                                          intptr_t code_index);

  ObjectPtr ReadRef() { return Ref(ReadRefId()); }

  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);
  void EndInstructions();
  ObjectPtr GetObjectAt(uint32_t offset) const;

  void Deserialize(DeserializationRoots* roots);

  DeserializationCluster* ReadCluster();

  void ReadDispatchTable() {
    ReadDispatchTable(&stream_, /*deferred=*/false, InstructionsTable::Handle(),
                      -1, -1);
  }
  void ReadDispatchTable(ReadStream* stream,
                         bool deferred,
                         const InstructionsTable& root_instruction_table,
                         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 {
#if defined(DART_PRECOMPILED_RUNTIME)
    return Snapshot::kFullAOT;
#else
    return kind_;
#endif
  }
  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() const { return code_start_index_; }
  void set_code_stop_index(intptr_t value) { code_stop_index_ = value; }
  intptr_t code_stop_index() const { return code_stop_index_; }
  void set_unknown_dart_code_index(intptr_t value) {
    unknown_dart_code_index_ = value;
  }
  intptr_t unknown_dart_code_index() const { return unknown_dart_code_index_; }
  void set_lazy_compile_index(intptr_t value) { lazy_compile_index_ = value; }
  intptr_t lazy_compile_index() const { return lazy_compile_index_; }
  const InstructionsTable& instructions_table() const {
    return instructions_table_;
  }
  intptr_t num_base_objects() const { return num_base_objects_; }

  // This serves to make the snapshot cursor, ref table and null be locals
  // during ReadFill, which allows the C compiler to see they are not aliased
  // and can be kept in registers.
  class Local : public ReadStream {
   public:
    explicit Local(Deserializer* d)
        : ReadStream(d->stream_.buffer_, d->stream_.current_, d->stream_.end_),
          d_(d),
          refs_(d->refs_),
          null_(Object::null()) {
#if defined(DEBUG)
      // Can't mix use of Deserializer::Read*.
      d->stream_.current_ = nullptr;
#endif
    }
    ~Local() { d_->stream_.current_ = current_; }

    ObjectPtr Ref(intptr_t index) const {
      ASSERT(index > 0);
      ASSERT(index <= d_->num_objects_);
      return refs_->untag()->element(index);
    }

    template <typename T>
    T Read() {
      return ReadStream::Raw<sizeof(T), T>::Read(this);
    }
    uint64_t ReadUnsigned64() { return ReadUnsigned<uint64_t>(); }

    ObjectPtr ReadRef() { return Ref(ReadRefId()); }
    TokenPosition ReadTokenPosition() {
      return TokenPosition::Deserialize(Read<int32_t>());
    }

    intptr_t ReadCid() {
      COMPILE_ASSERT(UntaggedObject::kClassIdTagSize <= 32);
      return Read<int32_t>();
    }

    template <typename T, typename... P>
    DART_FORCE_INLINE void ReadFromTo(T obj, P&&... params) {
      auto* from = obj->untag()->from();
      auto* to_snapshot = obj->untag()->to_snapshot(d_->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 = null_;
      }
    }

    ObjectPtr null() const { return null_; }

   private:
    Deserializer* const d_;
    const ArrayPtr refs_;
    const ObjectPtr null_;
  };

 private:
  Heap* heap_;
  PageSpace* old_space_;
  FreeList* freelist_;
  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 code_start_index_ = 0;
  intptr_t code_stop_index_ = 0;
  intptr_t lazy_compile_index_ = 0;
  intptr_t unknown_dart_code_index_ = 0;
  intptr_t instructions_index_ = 0;
  DeserializationCluster** clusters_;
  const bool is_non_root_unit_;
  InstructionsTable& instructions_table_;
};

DART_FORCE_INLINE
ObjectPtr Deserializer::Allocate(intptr_t size) {
  return UntaggedObject::FromAddr(
      old_space_->AllocateSnapshotLocked(freelist_, size));
}

void Deserializer::InitializeHeader(ObjectPtr raw,
                                    intptr_t class_id,
                                    intptr_t size,
                                    bool is_canonical,
                                    bool is_deeply_immutable) {
  ASSERT(Utils::IsAligned(size, kObjectAlignment));
  uword tags = 0;
  tags = UntaggedObject::ClassIdTag::update(class_id, tags);
  tags = UntaggedObject::SizeTag::update(size, tags);
  tags = UntaggedObject::CanonicalBit::update(is_canonical, tags);
  tags = UntaggedObject::AlwaysSetBit::update(true, tags);
  tags = UntaggedObject::NotMarkedBit::update(true, tags);
  tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
  tags = UntaggedObject::NewOrEvacuationCandidateBit::update(false, tags);
  tags = UntaggedObject::ShallowImmutableBit::update(
      Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
  tags = UntaggedObject::DeeplyImmutableBit::update(is_deeply_immutable, tags);
  raw->untag()->tags_ = tags;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void SerializationCluster::WriteAndMeasureAlloc(Serializer* serializer) {
  intptr_t start_size = serializer->bytes_written();
  intptr_t start_data = serializer->GetDataSize();
  intptr_t start_objects = serializer->next_ref_index();
  uint32_t tags =
      UntaggedObject::ClassIdTag::encode(cid_) |
      UntaggedObject::CanonicalBit::encode(is_canonical()) |
      UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable());
  serializer->Write<uint32_t>(tags);
  WriteAlloc(serializer);
  intptr_t stop_size = serializer->bytes_written();
  intptr_t stop_data = serializer->GetDataSize();
  intptr_t stop_objects = serializer->next_ref_index();
  if (FLAG_print_cluster_information) {
    OS::PrintErr("Snapshot 0x%" Pp " (%" Pd "), ", start_size,
                 stop_size - start_size);
    OS::PrintErr("Data 0x%" Pp " (%" Pd "): ", start_data,
                 stop_data - start_data);
    OS::PrintErr("Alloc %s (%" Pd ")\n", name(), stop_objects - start_objects);
  }
  size_ += (stop_size - start_size) + (stop_data - start_data);
  num_objects_ += (stop_objects - start_objects);
  if (target_instance_size_ != kSizeVaries) {
    target_memory_size_ += num_objects_ * target_instance_size_;
  }
}

void SerializationCluster::WriteAndMeasureFill(Serializer* serializer) {
  intptr_t start = serializer->bytes_written();
  WriteFill(serializer);
  intptr_t stop = serializer->bytes_written();
  if (FLAG_print_cluster_information) {
    OS::PrintErr("Snapshot 0x%" Pp " (%" Pd "): Fill %s\n", start, stop - start,
                 name());
  }
  size_ += (stop - start);
}
#endif  // !DART_PRECOMPILED_RUNTIME

DART_NOINLINE
void DeserializationCluster::ReadAllocFixedSize(Deserializer* d,
                                                intptr_t instance_size) {
  start_index_ = d->next_index();
  intptr_t count = d->ReadUnsigned();
  for (intptr_t i = 0; i < count; i++) {
    d->AssignRef(d->Allocate(instance_size));
  }
  stop_index_ = d->next_index();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
static UnboxedFieldBitmap CalculateTargetUnboxedFieldsBitmap(
    Serializer* s,
    intptr_t class_id) {
  const auto unboxed_fields_bitmap_host =
      s->isolate_group()->class_table()->GetUnboxedFieldsMapAt(class_id);

  UnboxedFieldBitmap unboxed_fields_bitmap;
  if (unboxed_fields_bitmap_host.IsEmpty() ||
      kWordSize == compiler::target::kWordSize) {
    unboxed_fields_bitmap = unboxed_fields_bitmap_host;
  } else {
    ASSERT(kWordSize == 8 && compiler::target::kWordSize == 4);
    // A new bitmap is built if the word sizes in the target and
    // host are different
    unboxed_fields_bitmap.Reset();
    intptr_t target_i = 0, host_i = 0;

    while (host_i < UnboxedFieldBitmap::Length()) {
      // Each unboxed field has constant length, therefore the number of
      // words used by it should double when compiling from 64-bit to 32-bit.
      if (unboxed_fields_bitmap_host.Get(host_i++)) {
        unboxed_fields_bitmap.Set(target_i++);
        unboxed_fields_bitmap.Set(target_i++);
      } else {
        // For object pointers, the field is always one word length
        target_i++;
      }
    }
  }

  return unboxed_fields_bitmap;
}

class ClassSerializationCluster : public SerializationCluster {
 public:
  explicit ClassSerializationCluster(intptr_t num_cids)
      : SerializationCluster("Class",
                             kClassCid,
                             compiler::target::Class::InstanceSize()),
        objects_(num_cids) {}
  ~ClassSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ClassPtr cls = Class::RawCast(object);
    intptr_t class_id = cls->untag()->id_;

    if (class_id == kIllegalCid) {
      // Classes expected to be dropped by the precompiler should not be traced.
      s->UnexpectedObject(cls, "Class with illegal cid");
    }

    objects_.Add(cls);
    PushFromTo(cls);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ClassPtr cls = objects_[i];
      s->AssignRef(cls);
    }
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WriteClass(s, objects_[i]);
    }
  }

 private:
  void WriteClass(Serializer* s, ClassPtr cls) {
    AutoTraceObjectName(cls, cls->untag()->name());
    WriteFromTo(cls);
    intptr_t class_id = cls->untag()->id_;
    if (class_id == kIllegalCid) {
      s->UnexpectedObject(cls, "Class with illegal cid");
    }
    s->WriteCid(class_id);
    if (s->kind() != Snapshot::kFullAOT) {
      s->Write<uint32_t>(cls->untag()->kernel_offset_);
    }
    if (s->kind() == Snapshot::kFullAOT) {
      s->Write<int32_t>(Class::target_instance_size_in_words(cls));
      s->Write<int32_t>(Class::target_next_field_offset_in_words(cls));
      s->Write<int32_t>(
          Class::target_type_arguments_field_offset_in_words(cls));
    } else {
      s->Write<int32_t>(Class::host_instance_size_in_words(cls));
      s->Write<int32_t>(Class::host_next_field_offset_in_words(cls));
      s->Write<int32_t>(Class::host_type_arguments_field_offset_in_words(cls));
    }
    s->Write<int16_t>(cls->untag()->num_type_arguments_);
    s->Write<uint16_t>(cls->untag()->num_native_fields_);
    if (s->kind() != Snapshot::kFullAOT) {
      s->WriteTokenPosition(cls->untag()->token_pos_);
      s->WriteTokenPosition(cls->untag()->end_token_pos_);
      s->WriteCid(cls->untag()->implementor_cid_);
    }
    s->Write<uint32_t>(cls->untag()->state_bits_);

    if (!ClassTable::IsTopLevelCid(class_id)) {
      const auto unboxed_fields_map =
          CalculateTargetUnboxedFieldsBitmap(s, class_id);
      s->WriteUnsigned64(unboxed_fields_map.Value());
    }
  }

  GrowableArray<ClassPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ClassDeserializationCluster : public DeserializationCluster {
 public:
  ClassDeserializationCluster() : DeserializationCluster("Class") {}
  ~ClassDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Class::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ClassTable* table = d_->isolate_group()->class_table();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ClassPtr cls = static_cast<ClassPtr>(d.Ref(id));
      Deserializer::InitializeHeader(cls, kClassCid, Class::InstanceSize());
      d.ReadFromTo(cls);

      intptr_t class_id = d.ReadCid();
      cls->untag()->id_ = class_id;

#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d_->kind() != Snapshot::kFullAOT);
      cls->untag()->kernel_offset_ = d.Read<uint32_t>();
#endif
      cls->untag()->host_instance_size_in_words_ = d.Read<int32_t>();
      cls->untag()->host_next_field_offset_in_words_ = d.Read<int32_t>();
      cls->untag()->host_type_arguments_field_offset_in_words_ =
          d.Read<int32_t>();
#if defined(DART_PRECOMPILER)
      cls->untag()->target_instance_size_in_words_ =
          cls->untag()->host_instance_size_in_words_;
      cls->untag()->target_next_field_offset_in_words_ =
          cls->untag()->host_next_field_offset_in_words_;
      cls->untag()->target_type_arguments_field_offset_in_words_ =
          cls->untag()->host_type_arguments_field_offset_in_words_;
#endif  // defined(DART_PRECOMPILER)
      cls->untag()->num_type_arguments_ = d.Read<int16_t>();
      cls->untag()->num_native_fields_ = d.Read<uint16_t>();
#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d_->kind() != Snapshot::kFullAOT);
      cls->untag()->token_pos_ = d.ReadTokenPosition();
      cls->untag()->end_token_pos_ = d.ReadTokenPosition();
      cls->untag()->implementor_cid_ = d.ReadCid();
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
      cls->untag()->state_bits_ = d.Read<uint32_t>();

      table->AllocateIndex(class_id);
      table->SetAt(class_id, cls);

      if (!ClassTable::IsTopLevelCid(class_id)) {
        const UnboxedFieldBitmap unboxed_fields_map(d.ReadUnsigned64());
        table->SetUnboxedFieldsMapAt(class_id, unboxed_fields_map);
      }
    }
  }
};

// Super classes for writing out clusters which contain objects grouped into
// a canonical set (e.g. String, Type, TypeArguments, etc).
// To save space in the snapshot we avoid writing such canonical sets
// explicitly as Array objects into the snapshot and instead utilize a different
// encoding: objects in a cluster representing a canonical set are sorted
// to appear in the same order they appear in the Array representing the set,
// and we additionally write out array of values describing gaps between
// objects.
//
// In some situations not all canonical objects of the some type need to
// be added to the resulting canonical set because they are cached in some
// special way (see Type::Canonicalize as an example, which caches declaration
// types in a special way). In this case subclass can set
// kAllCanonicalObjectsAreIncludedIntoSet to |false| and override
// IsInCanonicalSet filter.
#if !defined(DART_PRECOMPILED_RUNTIME)
template <typename SetType,
          typename HandleType,
          typename PointerType,
          bool kAllCanonicalObjectsAreIncludedIntoSet = true>
class CanonicalSetSerializationCluster : public SerializationCluster {
 protected:
  CanonicalSetSerializationCluster(intptr_t cid,
                                   bool is_canonical,
                                   bool is_deeply_immutable,
                                   bool represents_canonical_set,
                                   const char* name,
                                   intptr_t target_instance_size = 0)
      : SerializationCluster(name,
                             cid,
                             target_instance_size,
                             is_canonical,
                             is_deeply_immutable),
        represents_canonical_set_(represents_canonical_set) {}

  virtual bool IsInCanonicalSet(Serializer* s, PointerType ptr) {
    // Must override this function if kAllCanonicalObjectsAreIncludedIntoSet
    // is set to |false|.
    ASSERT(kAllCanonicalObjectsAreIncludedIntoSet);
    return true;
  }

  void ReorderObjects(Serializer* s) {
    if (!represents_canonical_set_) {
      return;
    }

    // Sort objects before writing them out so that they appear in the same
    // order as they would appear in a CanonicalStringSet.
    using ZoneCanonicalSet =
        HashTable<typename SetType::Traits, 0, 0, GrowableArrayStorageTraits>;

    // Compute required capacity for the hashtable (to avoid overallocating).
    intptr_t required_capacity = 0;
    for (auto ptr : objects_) {
      if (kAllCanonicalObjectsAreIncludedIntoSet || IsInCanonicalSet(s, ptr)) {
        required_capacity++;
      }
    }
    // Over-allocate capacity so a few inserts can happen at startup without
    // causing a rehash.
    const intptr_t kSpareCapacity = 32;
    required_capacity = static_cast<intptr_t>(
        static_cast<double>(required_capacity + kSpareCapacity) /
        HashTables::kMaxLoadFactor);

    intptr_t num_occupied = 0;

    // Build canonical set out of objects that should belong to it.
    // Objects that don't belong to it are copied to the prefix of objects_.
    ZoneCanonicalSet table(
        s->zone(), HashTables::New<ZoneCanonicalSet>(required_capacity));
    HandleType& element = HandleType::Handle(s->zone());
    for (auto ptr : objects_) {
      if (kAllCanonicalObjectsAreIncludedIntoSet || IsInCanonicalSet(s, ptr)) {
        element ^= ptr;
        intptr_t entry = -1;
        const bool present = table.FindKeyOrDeletedOrUnused(element, &entry);
        ASSERT(!present);
        table.InsertKey(entry, element);
      } else {
        objects_[num_occupied++] = ptr;
      }
    }

    const auto prefix_length = num_occupied;

    // Compute objects_ order and gaps based on canonical set layout.
    auto& arr = table.Release();
    intptr_t last_occupied = ZoneCanonicalSet::kFirstKeyIndex - 1;
    for (intptr_t i = ZoneCanonicalSet::kFirstKeyIndex, length = arr.Length();
         i < length; i++) {
      ObjectPtr v = arr.At(i);
      ASSERT(v != ZoneCanonicalSet::DeletedMarker().ptr());
      if (v != ZoneCanonicalSet::UnusedMarker().ptr()) {
        const intptr_t unused_run_length = (i - 1) - last_occupied;
        gaps_.Add(unused_run_length);
        objects_[num_occupied++] = static_cast<PointerType>(v);
        last_occupied = i;
      }
    }
    ASSERT(num_occupied == objects_.length());
    ASSERT(prefix_length == (objects_.length() - gaps_.length()));
    table_length_ = arr.Length();
  }

  void WriteCanonicalSetLayout(Serializer* s) {
    if (represents_canonical_set_) {
      s->WriteUnsigned(table_length_);
      s->WriteUnsigned(objects_.length() - gaps_.length());
      for (auto gap : gaps_) {
        s->WriteUnsigned(gap);
      }
      target_memory_size_ +=
          compiler::target::Array::InstanceSize(table_length_);
    }
  }

  GrowableArray<PointerType> objects_;

 private:
  const bool represents_canonical_set_;
  GrowableArray<intptr_t> gaps_;
  intptr_t table_length_ = 0;
};
#endif

template <typename SetType, bool kAllCanonicalObjectsAreIncludedIntoSet = true>
class CanonicalSetDeserializationCluster : public DeserializationCluster {
 public:
  CanonicalSetDeserializationCluster(bool is_canonical,
                                     bool is_deeply_immutable,
                                     bool is_root_unit,
                                     const char* name)
      : DeserializationCluster(name, is_canonical, is_deeply_immutable),
        is_root_unit_(is_root_unit),
        table_(SetType::ArrayHandle::Handle()) {}

  void BuildCanonicalSetFromLayout(Deserializer* d) {
    if (!is_root_unit_ || !is_canonical()) {
      return;
    }

    const auto table_length = d->ReadUnsigned();
    first_element_ = d->ReadUnsigned();
    const intptr_t count = stop_index_ - (start_index_ + first_element_);
    auto table = StartDeserialization(d, table_length, count);
    for (intptr_t i = start_index_ + first_element_; i < stop_index_; i++) {
      table.FillGap(d->ReadUnsigned());
      table.WriteElement(d, d->Ref(i));
    }
    table_ = table.Finish();
  }

 protected:
  const bool is_root_unit_;
  intptr_t first_element_;
  typename SetType::ArrayHandle& table_;

  void VerifyCanonicalSet(Deserializer* d,
                          const Array& refs,
                          const typename SetType::ArrayHandle& current_table) {
#if defined(DEBUG)
    // First check that we are not overwriting a table and loosing information.
    // TODO(bootstrap-only-in-gen-snapshot): Also assert for kFull.
    if (!current_table.IsNull() && d->kind() != Snapshot::kFull) {
      SetType current_set(d->zone(), current_table.ptr());
      ASSERT(current_set.NumOccupied() == 0);
      current_set.Release();
    }

    // Now check that manually created table behaves correctly as a canonical
    // set.
    SetType canonical_set(d->zone(), table_.ptr());
    Object& key = Object::Handle();
    for (intptr_t i = start_index_ + first_element_; i < stop_index_; i++) {
      key = refs.At(i);
      ASSERT(canonical_set.GetOrNull(key) != Object::null());
    }
    canonical_set.Release();
#endif  // defined(DEBUG)
  }

 private:
  struct DeserializationFinger {
    typename SetType::ArrayPtr table;
    intptr_t current_index;
    ObjectPtr gap_element;

    void FillGap(int length) {
      for (intptr_t j = 0; j < length; j++) {
        table->untag()->data()[current_index + j] = gap_element;
      }
      current_index += length;
    }

    void WriteElement(Deserializer* d, ObjectPtr object) {
      table->untag()->data()[current_index++] = object;
    }

    typename SetType::ArrayPtr Finish() {
      if (table != SetType::ArrayHandle::null()) {
        FillGap(Smi::Value(table->untag()->length()) - current_index);
      }
      auto result = table;
      table = SetType::ArrayHandle::null();
      return result;
    }
  };

  static DeserializationFinger StartDeserialization(Deserializer* d,
                                                    intptr_t length,
                                                    intptr_t count) {
    const intptr_t instance_size = SetType::ArrayHandle::InstanceSize(length);
    typename SetType::ArrayPtr table =
        static_cast<typename SetType::ArrayPtr>(d->Allocate(instance_size));
    Deserializer::InitializeHeader(table, SetType::Storage::ArrayCid,
                                   instance_size);
    if ((SetType::Storage::ArrayCid == kArrayCid) &&
        Array::UseCardMarkingForAllocation(length)) {
      table->untag()->SetCardRememberedBitUnsynchronized();
      Page::Of(table)->AllocateCardTable();
    }
    InitTypeArgsOrNext(table);
    table->untag()->length_ = Smi::New(length);
    for (intptr_t i = 0; i < SetType::kFirstKeyIndex; i++) {
      table->untag()->data()[i] = Smi::New(0);
    }
    table->untag()->data()[SetType::kOccupiedEntriesIndex] = Smi::New(count);
    return {table, SetType::kFirstKeyIndex, SetType::UnusedMarker().ptr()};
  }

  static void InitTypeArgsOrNext(ArrayPtr table) {
    table->untag()->type_arguments_ = TypeArguments::null();
  }
  static void InitTypeArgsOrNext(WeakArrayPtr table) {
    table->untag()->next_seen_by_gc_ = WeakArray::null();
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypeParametersSerializationCluster : public SerializationCluster {
 public:
  TypeParametersSerializationCluster(bool is_canonical,
                                     bool is_deeply_immutable)
      : SerializationCluster("TypeParameters",
                             kTypeParametersCid,
                             compiler::target::TypeParameters::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~TypeParametersSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypeParametersPtr type_params = TypeParameters::RawCast(object);
    objects_.Add(type_params);
    PushFromTo(type_params);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      TypeParametersPtr type_params = objects_[i];
      s->AssignRef(type_params);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      TypeParametersPtr type_params = objects_[i];
      AutoTraceObject(type_params);
      WriteFromTo(type_params);
    }
  }

 private:
  GrowableArray<TypeParametersPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypeParametersDeserializationCluster : public DeserializationCluster {
 public:
  TypeParametersDeserializationCluster(bool is_canonical,
                                       bool is_deeply_immutable)
      : DeserializationCluster("TypeParameters",
                               is_canonical,
                               is_deeply_immutable) {}
  ~TypeParametersDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, TypeParameters::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypeParametersPtr type_params = static_cast<TypeParametersPtr>(d.Ref(id));
      Deserializer::InitializeHeader(type_params, kTypeParametersCid,
                                     TypeParameters::InstanceSize());
      d.ReadFromTo(type_params);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypeArgumentsSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalTypeArgumentsSet,
                                              TypeArguments,
                                              TypeArgumentsPtr> {
 public:
  TypeArgumentsSerializationCluster(bool is_canonical,
                                    bool is_deeply_immutable,
                                    bool represents_canonical_set)
      : CanonicalSetSerializationCluster(kTypeArgumentsCid,
                                         is_canonical,
                                         is_deeply_immutable,
                                         represents_canonical_set,
                                         "TypeArguments") {}
  ~TypeArgumentsSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypeArgumentsPtr type_args = TypeArguments::RawCast(object);
    objects_.Add(type_args);

    s->Push(type_args->untag()->instantiations());
    const intptr_t length = Smi::Value(type_args->untag()->length());
    for (intptr_t i = 0; i < length; i++) {
      s->Push(type_args->untag()->element(i));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);
    for (intptr_t i = 0; i < count; i++) {
      TypeArgumentsPtr type_args = objects_[i];
      s->AssignRef(type_args);
      AutoTraceObject(type_args);
      const intptr_t length = Smi::Value(type_args->untag()->length());
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::TypeArguments::InstanceSize(length);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      TypeArgumentsPtr type_args = objects_[i];
      AutoTraceObject(type_args);
      const intptr_t length = Smi::Value(type_args->untag()->length());
      s->WriteUnsigned(length);
      intptr_t hash = Smi::Value(type_args->untag()->hash());
      s->Write<int32_t>(hash);
      const intptr_t nullability =
          Smi::Value(type_args->untag()->nullability());
      s->WriteUnsigned(nullability);
      WriteField(type_args, instantiations());
      for (intptr_t j = 0; j < length; j++) {
        s->WriteElementRef(type_args->untag()->element(j), j);
      }
    }
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypeArgumentsDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalTypeArgumentsSet> {
 public:
  TypeArgumentsDeserializationCluster(bool is_canonical,
                                      bool is_deeply_immutable,
                                      bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           is_deeply_immutable,
                                           is_root_unit,
                                           "TypeArguments") {}
  ~TypeArgumentsDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(TypeArguments::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypeArgumentsPtr type_args = static_cast<TypeArgumentsPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(type_args, kTypeArgumentsCid,
                                     TypeArguments::InstanceSize(length),
                                     mark_canonical, is_deeply_immutable());
      type_args->untag()->length_ = Smi::New(length);
      type_args->untag()->hash_ = Smi::New(d.Read<int32_t>());
      type_args->untag()->nullability_ = Smi::New(d.ReadUnsigned());
      type_args->untag()->instantiations_ = static_cast<ArrayPtr>(d.ReadRef());
      for (intptr_t j = 0; j < length; j++) {
        type_args->untag()->types()[j] =
            static_cast<AbstractTypePtr>(d.ReadRef());
      }
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(
          d, refs, Array::Handle(object_store->canonical_type_arguments()));
      object_store->set_canonical_type_arguments(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      TypeArguments& type_arg = TypeArguments::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        type_arg ^= refs.At(i);
        type_arg = type_arg.Canonicalize(d->thread());
        refs.SetAt(i, type_arg);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class PatchClassSerializationCluster : public SerializationCluster {
 public:
  PatchClassSerializationCluster()
      : SerializationCluster("PatchClass",
                             kPatchClassCid,
                             compiler::target::PatchClass::InstanceSize()) {}
  ~PatchClassSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    PatchClassPtr cls = PatchClass::RawCast(object);
    objects_.Add(cls);
    PushFromTo(cls);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      PatchClassPtr cls = objects_[i];
      s->AssignRef(cls);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      PatchClassPtr cls = objects_[i];
      AutoTraceObject(cls);
      WriteFromTo(cls);
      if (s->kind() != Snapshot::kFullAOT) {
        s->Write<int32_t>(cls->untag()->kernel_library_index_);
      }
    }
  }

 private:
  GrowableArray<PatchClassPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class PatchClassDeserializationCluster : public DeserializationCluster {
 public:
  PatchClassDeserializationCluster() : DeserializationCluster("PatchClass") {}
  ~PatchClassDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, PatchClass::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      PatchClassPtr cls = static_cast<PatchClassPtr>(d.Ref(id));
      Deserializer::InitializeHeader(cls, kPatchClassCid,
                                     PatchClass::InstanceSize());
      d.ReadFromTo(cls);
#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d_->kind() != Snapshot::kFullAOT);
      cls->untag()->kernel_library_index_ = d.Read<int32_t>();
#endif
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class FunctionSerializationCluster : public SerializationCluster {
 public:
  FunctionSerializationCluster()
      : SerializationCluster("Function",
                             kFunctionCid,
                             compiler::target::Function::InstanceSize()) {}
  ~FunctionSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    Snapshot::Kind kind = s->kind();
    FunctionPtr func = Function::RawCast(object);
    objects_.Add(func);

    PushFromTo(func);
    if (kind == Snapshot::kFullAOT) {
      s->Push(func->untag()->code());
    } else if (kind == Snapshot::kFullJIT) {
      NOT_IN_PRECOMPILED(s->Push(func->untag()->unoptimized_code()));
      s->Push(func->untag()->code());
      s->Push(func->untag()->ic_data_array_or_bytecode());
    }
    if (kind != Snapshot::kFullAOT) {
      NOT_IN_PRECOMPILED(s->Push(func->untag()->positional_parameter_names()));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      FunctionPtr func = objects_[i];
      s->AssignRef(func);
    }
  }

  void WriteFill(Serializer* s) {
    Snapshot::Kind kind = s->kind();
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      FunctionPtr func = objects_[i];
      AutoTraceObjectName(func, MakeDisambiguatedFunctionName(s, func));
      WriteFromTo(func);
      if (kind == Snapshot::kFullAOT) {
#if defined(DART_PRECOMPILER)
        CodePtr code = func->untag()->code();
        const auto code_index = s->GetCodeIndex(code);
        s->WriteUnsigned(code_index);
        s->AttributePropertyRef(code, "code_");
#else
        UNREACHABLE();
#endif
      } else if (s->kind() == Snapshot::kFullJIT) {
        NOT_IN_PRECOMPILED(WriteCompressedField(func, unoptimized_code));
        WriteCompressedField(func, code);
        WriteCompressedField(func, ic_data_array_or_bytecode);
      }

      if (kind != Snapshot::kFullAOT) {
        NOT_IN_PRECOMPILED(
            WriteCompressedField(func, positional_parameter_names));
      }

#if defined(DART_PRECOMPILER) && !defined(PRODUCT)
      TokenPosition token_pos = func->untag()->token_pos_;
      if (kind == Snapshot::kFullAOT) {
        // We use then token_pos property to store the line number
        // in AOT snapshots.
        intptr_t line = -1;
        const Function& function = Function::Handle(func);
        const Script& script = Script::Handle(function.script());
        if (!script.IsNull()) {
          script.GetTokenLocation(token_pos, &line, nullptr);
        }
        token_pos = line == -1 ? TokenPosition::kNoSource
                               : TokenPosition::Deserialize(line);
      }
      s->WriteTokenPosition(token_pos);
#else
      if (kind != Snapshot::kFullAOT) {
        s->WriteTokenPosition(func->untag()->token_pos_);
      }
#endif
      if (kind != Snapshot::kFullAOT) {
        s->WriteTokenPosition(func->untag()->end_token_pos_);
        s->Write<uint32_t>(func->untag()->kernel_offset_);
        s->Write<bool>(func->untag()->is_optimizable_);
      }
      s->Write<uint32_t>(func->untag()->kind_tag_);
    }
  }

  static const char* MakeDisambiguatedFunctionName(Serializer* s,
                                                   FunctionPtr f) {
    if (s->profile_writer() == nullptr) {
      return nullptr;
    }

    REUSABLE_FUNCTION_HANDLESCOPE(s->thread());
    Function& fun = reused_function_handle.Handle();
    fun = f;
    ZoneTextBuffer printer(s->thread()->zone());
    fun.PrintName(NameFormattingParams::DisambiguatedUnqualified(
                      Object::NameVisibility::kInternalName),
                  &printer);
    return printer.buffer();
  }

 private:
  GrowableArray<FunctionPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

template <bool need_entry_point_for_non_discarded>
DART_FORCE_INLINE static CodePtr GetCodeAndEntryPointByIndex(
    const Deserializer* d,
    intptr_t code_index,
    uword* entry_point) {
  code_index -= 1;  // 0 is reserved for LazyCompile stub.

  // In root unit and VM isolate snapshot code_indices are self-contained
  // they point into instruction table and/or into the code cluster.
  // In non-root units we might also refer to code objects from the
  // parent unit which means code_index is biased by num_base_objects_
  const intptr_t base = d->is_non_root_unit() ? d->num_base_objects() : 0;
  if (code_index < base) {
    CodePtr code = static_cast<CodePtr>(d->Ref(code_index));
    if (need_entry_point_for_non_discarded) {
      *entry_point = Code::EntryPointOf(code);
    }
    return code;
  }
  code_index -= base;

  // At this point code_index is referring to a code object which is either
  // discarded or exists in the Code cluster. Non-discarded Code objects
  // are associated with the tail of the instruction table and have the
  // same order there and in the Code cluster. This means that
  // subtracting first_entry_with_code yields index into the Code cluster.
  // This also works for deferred code objects in root unit's snapshot
  // due to the choice of encoding (see Serializer::GetCodeIndex).
  const intptr_t first_entry_with_code =
      d->instructions_table().rodata()->first_entry_with_code;
  if (code_index < first_entry_with_code) {
    *entry_point = d->instructions_table().EntryPointAt(code_index);
    const intptr_t ref = d->unknown_dart_code_index();
    ASSERT(ref != 0);
    return static_cast<CodePtr>(d->Ref(ref));
  } else {
    const intptr_t cluster_index = code_index - first_entry_with_code;
    CodePtr code =
        static_cast<CodePtr>(d->Ref(d->code_start_index() + cluster_index));
    if (need_entry_point_for_non_discarded) {
      *entry_point = Code::EntryPointOf(code);
    }
    return code;
  }
}

CodePtr Deserializer::GetCodeByIndex(intptr_t code_index,
                                     uword* entry_point) const {
  // See Serializer::GetCodeIndex for how code_index is encoded.
  if (code_index == 0) {
    const intptr_t ref = lazy_compile_index();
    ASSERT(ref != 0);
    return static_cast<CodePtr>(Ref(ref));
  } else if (FLAG_precompiled_mode) {
    return GetCodeAndEntryPointByIndex<
        /*need_entry_point_for_non_discarded=*/false>(this, code_index,
                                                      entry_point);
  } else {
    // -1 below because 0 is reserved for LazyCompile stub.
    const intptr_t ref = code_start_index_ + code_index - 1;
    ASSERT(code_start_index_ <= ref && ref < code_stop_index_);
    return static_cast<CodePtr>(Ref(ref));
  }
}

intptr_t Deserializer::CodeIndexToClusterIndex(const InstructionsTable& table,
                                               intptr_t code_index) {
  // Note: code indices we are interpreting here originate from the root
  // loading unit which means base is equal to 0.
  // See comments which clarify the connection between code_index and
  // index into the Code cluster.
  ASSERT(FLAG_precompiled_mode);
  const intptr_t first_entry_with_code = table.rodata()->first_entry_with_code;
  return code_index - 1 - first_entry_with_code;
}

uword Deserializer::GetEntryPointByCodeIndex(intptr_t code_index) const {
  // See Deserializer::GetCodeByIndex which this code repeats.
  ASSERT(FLAG_precompiled_mode);
  uword entry_point = 0;
  GetCodeAndEntryPointByIndex</*need_entry_point_for_non_discarded=*/true>(
      this, code_index, &entry_point);
  return entry_point;
}

class FunctionDeserializationCluster : public DeserializationCluster {
 public:
  FunctionDeserializationCluster() : DeserializationCluster("Function") {}
  ~FunctionDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Function::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    Snapshot::Kind kind = d_->kind();

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      FunctionPtr func = static_cast<FunctionPtr>(d.Ref(id));
      Deserializer::InitializeHeader(func, kFunctionCid,
                                     Function::InstanceSize());
      d.ReadFromTo(func);

#if defined(DEBUG)
      func->untag()->entry_point_ = 0;
      func->untag()->unchecked_entry_point_ = 0;
#endif

#if defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(kind == Snapshot::kFullAOT);
      const intptr_t code_index = d.ReadUnsigned();
      uword entry_point = 0;
      CodePtr code = d_->GetCodeByIndex(code_index, &entry_point);
      func->untag()->code_ = code;
      if (entry_point != 0) {
        func->untag()->entry_point_ = entry_point;
        func->untag()->unchecked_entry_point_ = entry_point;
      }
#else
      ASSERT(kind != Snapshot::kFullAOT);
      if (kind == Snapshot::kFullJIT) {
        func->untag()->unoptimized_code_ = static_cast<CodePtr>(d.ReadRef());
        func->untag()->code_ = static_cast<CodePtr>(d.ReadRef());
        func->untag()->ic_data_array_or_bytecode_ = d.ReadRef();
      }
#endif

#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(kind != Snapshot::kFullAOT);
      func->untag()->positional_parameter_names_ =
          static_cast<ArrayPtr>(d.ReadRef());
#endif
#if !defined(DART_PRECOMPILED_RUNTIME) ||                                      \
    (defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT))
      func->untag()->token_pos_ = d.ReadTokenPosition();
#endif
#if !defined(DART_PRECOMPILED_RUNTIME)
      func->untag()->end_token_pos_ = d.ReadTokenPosition();
      func->untag()->kernel_offset_ = d.Read<uint32_t>();
      func->untag()->unboxed_parameters_info_.Reset();
      func->untag()->is_optimizable_ = d.Read<bool>();
#endif

      func->untag()->kind_tag_ = d.Read<uint32_t>();
#if !defined(DART_PRECOMPILED_RUNTIME)
      func->untag()->usage_counter_ = 0;
      func->untag()->optimized_instruction_count_ = 0;
      func->untag()->optimized_call_site_count_ = 0;
      func->untag()->deoptimization_counter_ = 0;
      func->untag()->state_bits_ = 0;
      func->untag()->inlining_depth_ = 0;
#endif
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (d->kind() == Snapshot::kFullAOT) {
      Function& func = Function::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        func ^= refs.At(i);
        auto const code = func.ptr()->untag()->code();
        ASSERT(code->IsCode());
        if (!Code::IsUnknownDartCode(code)) {
          uword entry_point = code->untag()->entry_point_;
          ASSERT(entry_point != 0);
          func.ptr()->untag()->entry_point_ = entry_point;
          uword unchecked_entry_point = code->untag()->unchecked_entry_point_;
          ASSERT(unchecked_entry_point != 0);
          func.ptr()->untag()->unchecked_entry_point_ = unchecked_entry_point;
        }
      }
    } else if (d->kind() == Snapshot::kFullJIT) {
      Function& func = Function::Handle(d->zone());
      Code& code = Code::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        func ^= refs.At(i);
        code = func.CurrentCode();
        if (func.HasCode() && !code.IsDisabled()) {
          func.SetInstructionsSafe(code);  // Set entrypoint.
          func.SetWasCompiled(true);
        } else {
          func.ClearCodeSafe();  // Set code and entrypoint to lazy compile stub
        }
      }
    } else {
      Function& func = Function::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        func ^= refs.At(i);
        func.ClearCodeSafe();  // Set code and entrypoint to lazy compile stub.
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ClosureDataSerializationCluster : public SerializationCluster {
 public:
  ClosureDataSerializationCluster()
      : SerializationCluster("ClosureData",
                             kClosureDataCid,
                             compiler::target::ClosureData::InstanceSize()) {}
  ~ClosureDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ClosureDataPtr data = ClosureData::RawCast(object);
    objects_.Add(data);

    if (s->kind() != Snapshot::kFullAOT) {
      s->Push(data->untag()->context_scope());
    }
    s->Push(data->untag()->parent_function());
    s->Push(data->untag()->closure());
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ClosureDataPtr data = objects_[i];
      s->AssignRef(data);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ClosureDataPtr data = objects_[i];
      AutoTraceObject(data);
      if (s->kind() != Snapshot::kFullAOT) {
        WriteCompressedField(data, context_scope);
      }
      WriteCompressedField(data, parent_function);
      WriteCompressedField(data, closure);
      s->WriteUnsigned(static_cast<uint32_t>(data->untag()->packed_fields_));
    }
  }

 private:
  GrowableArray<ClosureDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ClosureDataDeserializationCluster : public DeserializationCluster {
 public:
  ClosureDataDeserializationCluster() : DeserializationCluster("ClosureData") {}
  ~ClosureDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, ClosureData::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ClosureDataPtr data = static_cast<ClosureDataPtr>(d.Ref(id));
      Deserializer::InitializeHeader(data, kClosureDataCid,
                                     ClosureData::InstanceSize());
      if (d_->kind() == Snapshot::kFullAOT) {
        data->untag()->context_scope_ = ContextScope::null();
      } else {
        data->untag()->context_scope_ =
            static_cast<ContextScopePtr>(d.ReadRef());
      }
      data->untag()->parent_function_ = static_cast<FunctionPtr>(d.ReadRef());
      data->untag()->closure_ = static_cast<ClosurePtr>(d.ReadRef());
      data->untag()->packed_fields_ = d.ReadUnsigned<uint32_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class FfiTrampolineDataSerializationCluster : public SerializationCluster {
 public:
  FfiTrampolineDataSerializationCluster()
      : SerializationCluster(
            "FfiTrampolineData",
            kFfiTrampolineDataCid,
            compiler::target::FfiTrampolineData::InstanceSize()) {}
  ~FfiTrampolineDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    FfiTrampolineDataPtr data = FfiTrampolineData::RawCast(object);
    objects_.Add(data);
    PushFromTo(data);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      s->AssignRef(objects_[i]);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      FfiTrampolineDataPtr const data = objects_[i];
      AutoTraceObject(data);
      WriteFromTo(data);
      s->Write<int32_t>(data->untag()->callback_id_);
      s->Write<uint8_t>(data->untag()->ffi_function_kind_);
    }
  }

 private:
  GrowableArray<FfiTrampolineDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class FfiTrampolineDataDeserializationCluster : public DeserializationCluster {
 public:
  FfiTrampolineDataDeserializationCluster()
      : DeserializationCluster("FfiTrampolineData") {}
  ~FfiTrampolineDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, FfiTrampolineData::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      FfiTrampolineDataPtr data = static_cast<FfiTrampolineDataPtr>(d.Ref(id));
      Deserializer::InitializeHeader(data, kFfiTrampolineDataCid,
                                     FfiTrampolineData::InstanceSize());
      d.ReadFromTo(data);
      data->untag()->callback_id_ = d.Read<int32_t>();
      data->untag()->ffi_function_kind_ = d.Read<uint8_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class FieldSerializationCluster : public SerializationCluster {
 public:
  FieldSerializationCluster()
      : SerializationCluster("Field",
                             kFieldCid,
                             compiler::target::Field::InstanceSize()) {}
  ~FieldSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    FieldPtr field = Field::RawCast(object);
    objects_.Add(field);

    Snapshot::Kind kind = s->kind();

    s->Push(field->untag()->name());
    s->Push(field->untag()->owner());
    s->Push(field->untag()->type());
    // Write out the initializer function
    s->Push(field->untag()->initializer_function());

    if (kind != Snapshot::kFullAOT) {
      s->Push(field->untag()->guarded_list_length());
    }
    if (kind == Snapshot::kFullJIT) {
      s->Push(field->untag()->dependent_code());
    }
    // Write out either the initial static value or field offset.
    if (Field::StaticBit::decode(field->untag()->kind_bits_)) {
      s->Push(field->untag()->host_offset_or_field_id());
    } else {
      s->Push(Smi::New(Field::TargetOffsetOf(field)));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      FieldPtr field = objects_[i];
      s->AssignRef(field);
    }
  }

  void WriteFill(Serializer* s) {
    Snapshot::Kind kind = s->kind();
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      FieldPtr field = objects_[i];
      AutoTraceObjectName(field, field->untag()->name());

      WriteCompressedField(field, name);
      WriteCompressedField(field, owner);
      WriteCompressedField(field, type);
      // Write out the initializer function and initial value if not in AOT.
      WriteCompressedField(field, initializer_function);
      if (kind != Snapshot::kFullAOT) {
        WriteCompressedField(field, guarded_list_length);
      }
      if (kind == Snapshot::kFullJIT) {
        WriteCompressedField(field, dependent_code);
      }

      if (kind != Snapshot::kFullAOT) {
        s->WriteTokenPosition(field->untag()->token_pos_);
        s->WriteTokenPosition(field->untag()->end_token_pos_);
        s->WriteCid(field->untag()->guarded_cid_);
        s->WriteCid(field->untag()->is_nullable_);
        s->Write<int8_t>(field->untag()->static_type_exactness_state_);
        s->Write<uint32_t>(field->untag()->kernel_offset_);
      }
      s->Write<uint32_t>(field->untag()->kind_bits_);

      // Write out either the initial static value or field offset.
      if (Field::StaticBit::decode(field->untag()->kind_bits_)) {
        WriteFieldValue("id", field->untag()->host_offset_or_field_id());
      } else {
        WriteFieldValue("offset", Smi::New(Field::TargetOffsetOf(field)));
      }
    }
  }

 private:
  GrowableArray<FieldPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class FieldDeserializationCluster : public DeserializationCluster {
 public:
  FieldDeserializationCluster() : DeserializationCluster("Field") {}
  ~FieldDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Field::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
#if !defined(DART_PRECOMPILED_RUNTIME)
    Snapshot::Kind kind = d_->kind();
#endif
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      FieldPtr field = static_cast<FieldPtr>(d.Ref(id));
      Deserializer::InitializeHeader(field, kFieldCid, Field::InstanceSize());
      d.ReadFromTo(field);
#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d_->kind() != Snapshot::kFullAOT);
      field->untag()->guarded_list_length_ = static_cast<SmiPtr>(d.ReadRef());
      if (kind == Snapshot::kFullJIT) {
        field->untag()->dependent_code_ =
            static_cast<WeakArrayPtr>(d.ReadRef());
      }
      field->untag()->token_pos_ = d.ReadTokenPosition();
      field->untag()->end_token_pos_ = d.ReadTokenPosition();
      field->untag()->guarded_cid_ = d.ReadCid();
      field->untag()->is_nullable_ = d.ReadCid();
      const int8_t static_type_exactness_state = d.Read<int8_t>();
#if defined(TARGET_ARCH_X64)
      field->untag()->static_type_exactness_state_ =
          static_type_exactness_state;
#else
      // We might produce core snapshots using X64 VM and then consume
      // them in IA32 or ARM VM. In which case we need to simply ignore
      // static type exactness state written into snapshot because non-X64
      // builds don't have this feature enabled.
      // TODO(dartbug.com/34170) Support other architectures.
      USE(static_type_exactness_state);
      field->untag()->static_type_exactness_state_ =
          StaticTypeExactnessState::NotTracking().Encode();
#endif  // defined(TARGET_ARCH_X64)
      field->untag()->kernel_offset_ = d.Read<uint32_t>();
#endif
      field->untag()->kind_bits_ = d.Read<uint32_t>();

      field->untag()->host_offset_or_field_id_ =
          static_cast<SmiPtr>(d.ReadRef());
#if !defined(DART_PRECOMPILED_RUNTIME)
      field->untag()->target_offset_ =
          Smi::Value(field->untag()->host_offset_or_field_id());
#endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    Field& field = Field::Handle(d->zone());
    if (!d->isolate_group()->use_field_guards()) {
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        field ^= refs.At(i);
        field.set_guarded_cid_unsafe(kDynamicCid);
        field.set_is_nullable_unsafe(true);
        field.set_guarded_list_length_unsafe(Field::kNoFixedLength);
        field.set_guarded_list_length_in_object_offset_unsafe(
            Field::kUnknownLengthOffset);
        field.set_static_type_exactness_state_unsafe(
            StaticTypeExactnessState::NotTracking());
      }
    } else {
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        field ^= refs.At(i);
        field.InitializeGuardedListLengthInObjectOffset(/*unsafe=*/true);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ScriptSerializationCluster : public SerializationCluster {
 public:
  ScriptSerializationCluster()
      : SerializationCluster("Script",
                             kScriptCid,
                             compiler::target::Script::InstanceSize()) {}
  ~ScriptSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ScriptPtr script = Script::RawCast(object);
    objects_.Add(script);
    auto* from = script->untag()->from();
    auto* to = script->untag()->to_snapshot(s->kind());
    for (auto* p = from; p <= to; p++) {
      const intptr_t offset =
          reinterpret_cast<uword>(p) - reinterpret_cast<uword>(script->untag());
      const ObjectPtr obj = p->Decompress(script->heap_base());
      if (offset == Script::line_starts_offset()) {
        // Line starts are delta encoded.
        s->Push(obj, kDeltaEncodedTypedDataCid);
      } else {
        s->Push(obj);
      }
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ScriptPtr script = objects_[i];
      s->AssignRef(script);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ScriptPtr script = objects_[i];
      AutoTraceObjectName(script, script->untag()->url());
      WriteFromTo(script);
      if (s->kind() != Snapshot::kFullAOT) {
        // Clear out the max position cache in snapshots to ensure no
        // differences in the snapshot due to triggering caching vs. not.
        int32_t written_flags =
            UntaggedScript::CachedMaxPositionBitField::update(
                0, script->untag()->flags_and_max_position_);
        written_flags = UntaggedScript::HasCachedMaxPositionBit::update(
            false, written_flags);
        s->Write<int32_t>(written_flags);
      }
      s->Write<int32_t>(script->untag()->kernel_script_index_);
    }
  }

 private:
  GrowableArray<ScriptPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ScriptDeserializationCluster : public DeserializationCluster {
 public:
  ScriptDeserializationCluster() : DeserializationCluster("Script") {}
  ~ScriptDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Script::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ScriptPtr script = static_cast<ScriptPtr>(d.Ref(id));
      Deserializer::InitializeHeader(script, kScriptCid,
                                     Script::InstanceSize());
      d.ReadFromTo(script);
#if !defined(DART_PRECOMPILED_RUNTIME)
      script->untag()->flags_and_max_position_ = d.Read<int32_t>();
#endif
      script->untag()->kernel_script_index_ = d.Read<int32_t>();
      script->untag()->load_timestamp_ = 0;
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class LibrarySerializationCluster : public SerializationCluster {
 public:
  LibrarySerializationCluster()
      : SerializationCluster("Library",
                             kLibraryCid,
                             compiler::target::Library::InstanceSize()) {}
  ~LibrarySerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    LibraryPtr lib = Library::RawCast(object);
    objects_.Add(lib);
    PushFromTo(lib);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      LibraryPtr lib = objects_[i];
      s->AssignRef(lib);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      LibraryPtr lib = objects_[i];
      AutoTraceObjectName(lib, lib->untag()->url());
      WriteFromTo(lib);
      s->Write<int32_t>(lib->untag()->index_);
      s->Write<uint16_t>(lib->untag()->num_imports_);
      s->Write<int8_t>(lib->untag()->load_state_);
      s->Write<uint8_t>(lib->untag()->flags_);
      if (s->kind() != Snapshot::kFullAOT) {
        s->Write<uint32_t>(lib->untag()->kernel_library_index_);
      }
    }
  }

 private:
  GrowableArray<LibraryPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class LibraryDeserializationCluster : public DeserializationCluster {
 public:
  LibraryDeserializationCluster() : DeserializationCluster("Library") {}
  ~LibraryDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Library::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      LibraryPtr lib = static_cast<LibraryPtr>(d.Ref(id));
      Deserializer::InitializeHeader(lib, kLibraryCid, Library::InstanceSize());
      d.ReadFromTo(lib);
      lib->untag()->native_entry_resolver_ = nullptr;
      lib->untag()->native_entry_symbol_resolver_ = nullptr;
      lib->untag()->ffi_native_resolver_ = nullptr;
      lib->untag()->index_ = d.Read<int32_t>();
      lib->untag()->num_imports_ = d.Read<uint16_t>();
      lib->untag()->load_state_ = d.Read<int8_t>();
      lib->untag()->flags_ = d.Read<uint8_t>();
#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d_->kind() != Snapshot::kFullAOT);
      lib->untag()->kernel_library_index_ = d.Read<uint32_t>();
#endif
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class NamespaceSerializationCluster : public SerializationCluster {
 public:
  NamespaceSerializationCluster()
      : SerializationCluster("Namespace",
                             kNamespaceCid,
                             compiler::target::Namespace::InstanceSize()) {}
  ~NamespaceSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    NamespacePtr ns = Namespace::RawCast(object);
    objects_.Add(ns);
    PushFromTo(ns);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      NamespacePtr ns = objects_[i];
      s->AssignRef(ns);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      NamespacePtr ns = objects_[i];
      AutoTraceObject(ns);
      WriteFromTo(ns);
    }
  }

 private:
  GrowableArray<NamespacePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class NamespaceDeserializationCluster : public DeserializationCluster {
 public:
  NamespaceDeserializationCluster() : DeserializationCluster("Namespace") {}
  ~NamespaceDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Namespace::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      NamespacePtr ns = static_cast<NamespacePtr>(d.Ref(id));
      Deserializer::InitializeHeader(ns, kNamespaceCid,
                                     Namespace::InstanceSize());
      d.ReadFromTo(ns);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
// KernelProgramInfo objects are not written into a full AOT snapshot.
class KernelProgramInfoSerializationCluster : public SerializationCluster {
 public:
  KernelProgramInfoSerializationCluster()
      : SerializationCluster(
            "KernelProgramInfo",
            kKernelProgramInfoCid,
            compiler::target::KernelProgramInfo::InstanceSize()) {}
  ~KernelProgramInfoSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    KernelProgramInfoPtr info = KernelProgramInfo::RawCast(object);
    objects_.Add(info);
    PushFromTo(info);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      KernelProgramInfoPtr info = objects_[i];
      s->AssignRef(info);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      KernelProgramInfoPtr info = objects_[i];
      AutoTraceObject(info);
      WriteFromTo(info);
    }
  }

 private:
  GrowableArray<KernelProgramInfoPtr> objects_;
};

// Since KernelProgramInfo objects are not written into full AOT snapshots,
// one will never need to read them from a full AOT snapshot.
class KernelProgramInfoDeserializationCluster : public DeserializationCluster {
 public:
  KernelProgramInfoDeserializationCluster()
      : DeserializationCluster("KernelProgramInfo") {}
  ~KernelProgramInfoDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, KernelProgramInfo::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      KernelProgramInfoPtr info = static_cast<KernelProgramInfoPtr>(d.Ref(id));
      Deserializer::InitializeHeader(info, kKernelProgramInfoCid,
                                     KernelProgramInfo::InstanceSize());
      d.ReadFromTo(info);
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    Array& array = Array::Handle(d->zone());
    KernelProgramInfo& info = KernelProgramInfo::Handle(d->zone());
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      info ^= refs.At(id);
      array = HashTables::New<UnorderedHashMap<SmiTraits>>(16, Heap::kOld);
      info.set_libraries_cache(array);
      array = HashTables::New<UnorderedHashMap<SmiTraits>>(16, Heap::kOld);
      info.set_classes_cache(array);
    }
  }
};

class CodeSerializationCluster : public SerializationCluster {
 public:
  explicit CodeSerializationCluster(Heap* heap)
      : SerializationCluster("Code", kCodeCid), array_(Array::Handle()) {}
  ~CodeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    CodePtr code = Code::RawCast(object);

    const bool is_deferred = !s->InCurrentLoadingUnitOrRoot(code);
    if (is_deferred) {
      s->RecordDeferredCode(code);
    } else {
      objects_.Add(code);
    }

    // Even if this code object is itself deferred we still need to scan
    // the pool for references to other code objects (which might reside
    // in the current loading unit).
    ObjectPoolPtr pool = code->untag()->object_pool_;
    if (s->kind() == Snapshot::kFullAOT) {
      TracePool(s, pool, /*only_call_targets=*/is_deferred);
    } else {
      if (s->InCurrentLoadingUnitOrRoot(pool)) {
        s->Push(pool);
      } else {
        TracePool(s, pool, /*only_call_targets=*/true);
      }
    }

    if (s->kind() == Snapshot::kFullJIT) {
      s->Push(code->untag()->deopt_info_array_);
      s->Push(code->untag()->static_calls_target_table_);
      s->Push(code->untag()->compressed_stackmaps_);
    } else if (s->kind() == Snapshot::kFullAOT) {
      // Note: we don't trace compressed_stackmaps_ because we are going to emit
      // a separate mapping table into RO data which is not going to be a real
      // heap object.
#if defined(DART_PRECOMPILER)
      auto const calls_array = code->untag()->static_calls_target_table_;
      if (calls_array != Array::null()) {
        // Some Code entries in the static calls target table may only be
        // accessible via here, so push the Code objects.
        array_ = calls_array;
        for (auto entry : StaticCallsTable(array_)) {
          auto kind = Code::KindField::decode(
              Smi::Value(entry.Get<Code::kSCallTableKindAndOffset>()));
          switch (kind) {
            case Code::kCallViaCode:
              // Code object in the pool.
              continue;
            case Code::kPcRelativeTTSCall:
              // TTS will be reachable through type object which itself is
              // in the pool.
              continue;
            case Code::kPcRelativeCall:
            case Code::kPcRelativeTailCall:
              auto destination = entry.Get<Code::kSCallTableCodeOrTypeTarget>();
              ASSERT(destination->IsHeapObject() && destination->IsCode());
              s->Push(destination);
          }
        }
      }
#else
      UNREACHABLE();
#endif
    }

    if (Code::IsDiscarded(code)) {
      ASSERT(s->kind() == Snapshot::kFullAOT && FLAG_dwarf_stack_traces_mode &&
             !FLAG_retain_code_objects);
      // Only object pool and static call table entries and the compressed
      // stack maps should be pushed.
      return;
    }

    s->Push(code->untag()->owner_);
    s->Push(code->untag()->exception_handlers_);
    s->Push(code->untag()->pc_descriptors_);
    s->Push(code->untag()->catch_entry_);
    if (!FLAG_precompiled_mode || !FLAG_dwarf_stack_traces_mode) {
      s->Push(code->untag()->inlined_id_to_function_);
      if (s->InCurrentLoadingUnitOrRoot(code->untag()->code_source_map_)) {
        s->Push(code->untag()->code_source_map_);
      }
    }
#if !defined(PRODUCT)
    s->Push(code->untag()->return_address_metadata_);
    if (s->isolate_group()->code_comments()) {
      s->Push(code->untag()->comments_);
    }
#endif
  }

  void TracePool(Serializer* s, ObjectPoolPtr pool, bool only_call_targets) {
    if (pool == ObjectPool::null()) {
      return;
    }

    const intptr_t length = pool->untag()->length_;
    uint8_t* entry_bits = pool->untag()->entry_bits();
    for (intptr_t i = 0; i < length; i++) {
      auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
      if (entry_type == ObjectPool::EntryType::kTaggedObject) {
        const ObjectPtr target = pool->untag()->data()[i].raw_obj_;
        // A field is a call target because its initializer may be called
        // indirectly by passing the field to the runtime. A const closure
        // is a call target because its function may be called indirectly
        // via a closure call.
        intptr_t cid = target->GetClassId();
        if (!only_call_targets || (cid == kCodeCid) || (cid == kFunctionCid) ||
            (cid == kFieldCid) || (cid == kClosureCid)) {
          s->Push(target);
        } else if (cid >= kNumPredefinedCids) {
          s->Push(s->isolate_group()->class_table()->At(cid));
        }
      }
    }
  }

  struct CodeOrderInfo {
    CodePtr code;
    intptr_t not_discarded;  // 1 if this code was not discarded and
                             // 0 otherwise.
    intptr_t instructions_id;
  };

  // We sort code objects in such a way that code objects with the same
  // instructions are grouped together and ensure that all instructions
  // without associated code objects are grouped together at the beginning of
  // the code section. InstructionsTable encoding assumes that all
  // instructions with non-discarded Code objects are grouped at the end.
  //
  // Note that in AOT mode we expect that all Code objects pointing to
  // the same instructions are deduplicated, as in bare instructions mode
  // there is no way to identify which specific Code object (out of those
  // which point to the specific instructions range) actually corresponds
  // to a particular frame.
  static int CompareCodeOrderInfo(CodeOrderInfo const* a,
                                  CodeOrderInfo const* b) {
    if (a->not_discarded < b->not_discarded) return -1;
    if (a->not_discarded > b->not_discarded) return 1;
    if (a->instructions_id < b->instructions_id) return -1;
    if (a->instructions_id > b->instructions_id) return 1;
    return 0;
  }

  static void Insert(Serializer* s,
                     GrowableArray<CodeOrderInfo>* order_list,
                     IntMap<intptr_t>* order_map,
                     CodePtr code) {
    InstructionsPtr instr = code->untag()->instructions_;
    intptr_t key = static_cast<intptr_t>(instr);
    intptr_t instructions_id = 0;

    if (order_map->HasKey(key)) {
      // We are expected to merge code objects which point to the same
      // instructions in the precompiled mode.
      RELEASE_ASSERT(!FLAG_precompiled_mode);
      instructions_id = order_map->Lookup(key);
    } else {
      instructions_id = order_map->Length() + 1;
      order_map->Insert(key, instructions_id);
    }
    CodeOrderInfo info;
    info.code = code;
    info.instructions_id = instructions_id;
    info.not_discarded = Code::IsDiscarded(code) ? 0 : 1;
    order_list->Add(info);
  }

  static void Sort(Serializer* s, GrowableArray<CodePtr>* codes) {
    GrowableArray<CodeOrderInfo> order_list;
    IntMap<intptr_t> order_map;
    for (intptr_t i = 0; i < codes->length(); i++) {
      Insert(s, &order_list, &order_map, (*codes)[i]);
    }
    order_list.Sort(CompareCodeOrderInfo);
    ASSERT(order_list.length() == codes->length());
    for (intptr_t i = 0; i < order_list.length(); i++) {
      (*codes)[i] = order_list[i].code;
    }
  }

  static void Sort(Serializer* s, GrowableArray<Code*>* codes) {
    GrowableArray<CodeOrderInfo> order_list;
    IntMap<intptr_t> order_map;
    for (intptr_t i = 0; i < codes->length(); i++) {
      Insert(s, &order_list, &order_map, (*codes)[i]->ptr());
    }
    order_list.Sort(CompareCodeOrderInfo);
    ASSERT(order_list.length() == codes->length());
    for (intptr_t i = 0; i < order_list.length(); i++) {
      *(*codes)[i] = order_list[i].code;
    }
  }

  intptr_t NonDiscardedCodeCount() {
    intptr_t count = 0;
    for (auto code : objects_) {
      if (!Code::IsDiscarded(code)) {
        count++;
      }
    }
    return count;
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t non_discarded_count = NonDiscardedCodeCount();
    const intptr_t count = objects_.length();
    ASSERT(count == non_discarded_count || (s->kind() == Snapshot::kFullAOT));

    first_ref_ = s->next_ref_index();
    s->WriteUnsigned(non_discarded_count);
    for (auto code : objects_) {
      if (!Code::IsDiscarded(code)) {
        WriteAlloc(s, code);
      } else {
        // Mark discarded code unreachable, so that we could later
        // assign artificial references to it.
        s->heap()->SetObjectId(code, kUnreachableReference);
      }
    }

    s->WriteUnsigned(deferred_objects_.length());
    first_deferred_ref_ = s->next_ref_index();
    for (auto code : deferred_objects_) {
      ASSERT(!Code::IsDiscarded(code));
      WriteAlloc(s, code);
    }
    last_ref_ = s->next_ref_index() - 1;
  }

  void WriteAlloc(Serializer* s, CodePtr code) {
    ASSERT(!Code::IsDiscarded(code));
    s->AssignRef(code);
    AutoTraceObjectName(code, MakeDisambiguatedCodeName(s, code));
    const int32_t state_bits = code->untag()->state_bits_;
    s->Write<int32_t>(state_bits);
    target_memory_size_ += compiler::target::Code::InstanceSize(0);
  }

  void WriteFill(Serializer* s) {
    s->WriteRefId(s->RefId(StubCode::LazyCompile().ptr()));
    s->WriteRefId(s->RefId(StubCode::UnknownDartCode().ptr()));

    Snapshot::Kind kind = s->kind();
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      CodePtr code = objects_[i];
#if defined(DART_PRECOMPILER)
      if (FLAG_write_v8_snapshot_profile_to != nullptr &&
          Code::IsDiscarded(code)) {
        s->CreateArtificialNodeIfNeeded(code);
      }
#endif
      // Note: for discarded code this function will not write anything out
      // it is only called to produce information into snapshot profile.
      WriteFill(s, kind, code, /*deferred=*/false);
    }
    const intptr_t deferred_count = deferred_objects_.length();
    for (intptr_t i = 0; i < deferred_count; i++) {
      CodePtr code = deferred_objects_[i];
      WriteFill(s, kind, code, /*deferred=*/true);
    }
  }

  void WriteFill(Serializer* s,
                 Snapshot::Kind kind,
                 CodePtr code,
                 bool deferred) {
    const intptr_t bytes_written = s->bytes_written();
    AutoTraceObjectName(code, MakeDisambiguatedCodeName(s, code));

    intptr_t pointer_offsets_length =
        Code::PtrOffBits::decode(code->untag()->state_bits_);
    if (pointer_offsets_length != 0) {
      FATAL("Cannot serialize code with embedded pointers");
    }
    if (kind == Snapshot::kFullAOT && Code::IsDisabled(code)) {
      // Disabled code is fatal in AOT since we cannot recompile.
      s->UnexpectedObject(code, "Disabled code");
    }

    s->WriteInstructions(code->untag()->instructions_,
                         code->untag()->unchecked_offset_, code, deferred);
    if (kind == Snapshot::kFullJIT) {
      // TODO(rmacnak): Fix references to disabled code before serializing.
      // For now, we may write the FixCallersTarget or equivalent stub. This
      // will cause a fixup if this code is called.
      const uint32_t active_unchecked_offset =
          code->untag()->unchecked_entry_point_ - code->untag()->entry_point_;
      s->WriteInstructions(code->untag()->active_instructions_,
                           active_unchecked_offset, code, deferred);
    }

#if defined(DART_PRECOMPILER)
    if (FLAG_write_v8_snapshot_profile_to != nullptr) {
      // If we are writing V8 snapshot profile then attribute references going
      // through the object pool and static calls to the code object itself.
      if (kind == Snapshot::kFullAOT &&
          code->untag()->object_pool_ != ObjectPool::null()) {
        ObjectPoolPtr pool = code->untag()->object_pool_;
        // Non-empty per-code object pools should not be reachable in this mode.
        ASSERT(!s->HasRef(pool) || pool == Object::empty_object_pool().ptr());
        s->CreateArtificialNodeIfNeeded(pool);
        s->AttributePropertyRef(pool, "object_pool_");
      }
      if (kind != Snapshot::kFullJIT &&
          code->untag()->static_calls_target_table_ != Array::null()) {
        auto const table = code->untag()->static_calls_target_table_;
        // Non-empty static call target tables shouldn't be reachable in this
        // mode.
        ASSERT(!s->HasRef(table) || table == Object::empty_array().ptr());
        s->CreateArtificialNodeIfNeeded(table);
        s->AttributePropertyRef(table, "static_calls_target_table_");
      }
    }
#endif  // defined(DART_PRECOMPILER)

    if (Code::IsDiscarded(code)) {
      // No bytes should be written to represent this code.
      ASSERT(s->bytes_written() == bytes_written);
      // Only write instructions, compressed stackmaps and state bits
      // for the discarded Code objects.
      ASSERT(kind == Snapshot::kFullAOT && FLAG_dwarf_stack_traces_mode &&
             !FLAG_retain_code_objects);
#if defined(DART_PRECOMPILER)
      if (FLAG_write_v8_snapshot_profile_to != nullptr) {
        // Keep the owner as a (possibly artificial) node for snapshot analysis.
        const auto& owner = code->untag()->owner_;
        s->CreateArtificialNodeIfNeeded(owner);
        s->AttributePropertyRef(owner, "owner_");
      }
#endif
      return;
    }

    // No need to write object pool out if we are producing full AOT
    // snapshot with bare instructions.
    if (kind != Snapshot::kFullAOT) {
      if (s->InCurrentLoadingUnitOrRoot(code->untag()->object_pool_)) {
        WriteField(code, object_pool_);
      } else {
        WriteFieldValue(object_pool_, ObjectPool::null());
      }
    }
    WriteField(code, owner_);
    WriteField(code, exception_handlers_);
    WriteField(code, pc_descriptors_);
    WriteField(code, catch_entry_);
    if (s->kind() == Snapshot::kFullJIT) {
      WriteField(code, compressed_stackmaps_);
    }
    if (FLAG_precompiled_mode && FLAG_dwarf_stack_traces_mode) {
      WriteFieldValue(inlined_id_to_function_, Array::null());
      WriteFieldValue(code_source_map_, CodeSourceMap::null());
    } else {
      WriteField(code, inlined_id_to_function_);
      if (s->InCurrentLoadingUnitOrRoot(code->untag()->code_source_map_)) {
        WriteField(code, code_source_map_);
      } else {
        WriteFieldValue(code_source_map_, CodeSourceMap::null());
      }
    }
    if (kind == Snapshot::kFullJIT) {
      WriteField(code, deopt_info_array_);
      WriteField(code, static_calls_target_table_);
    }

#if !defined(PRODUCT)
    WriteField(code, return_address_metadata_);
    if (s->isolate_group()->code_comments()) {
      WriteField(code, comments_);
    }
#endif
  }

  GrowableArray<CodePtr>* objects() { return &objects_; }
  GrowableArray<CodePtr>* deferred_objects() { return &deferred_objects_; }

  static const char* MakeDisambiguatedCodeName(Serializer* s, CodePtr c) {
    if (s->profile_writer() == nullptr) {
      return nullptr;
    }

    REUSABLE_CODE_HANDLESCOPE(s->thread());
    Code& code = reused_code_handle.Handle();
    code = c;
    return code.QualifiedName(
        NameFormattingParams::DisambiguatedWithoutClassName(
            Object::NameVisibility::kInternalName));
  }

  intptr_t first_ref() const { return first_ref_; }
  intptr_t first_deferred_ref() const { return first_deferred_ref_; }
  intptr_t last_ref() const { return last_ref_; }

 private:
  intptr_t first_ref_;
  intptr_t first_deferred_ref_;
  intptr_t last_ref_;
  GrowableArray<CodePtr> objects_;
  GrowableArray<CodePtr> deferred_objects_;
  Array& array_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class CodeDeserializationCluster : public DeserializationCluster {
 public:
  CodeDeserializationCluster() : DeserializationCluster("Code") {}
  ~CodeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    d->set_code_start_index(start_index_);
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      ReadAllocOneCode(d);
    }
    stop_index_ = d->next_index();
    d->set_code_stop_index(stop_index_);
    deferred_start_index_ = d->next_index();
    const intptr_t deferred_count = d->ReadUnsigned();
    for (intptr_t i = 0; i < deferred_count; i++) {
      ReadAllocOneCode(d);
    }
    deferred_stop_index_ = d->next_index();
  }

  void ReadAllocOneCode(Deserializer* d) {
    const int32_t state_bits = d->Read<int32_t>();
    ASSERT(!Code::DiscardedBit::decode(state_bits));
    auto code = static_cast<CodePtr>(d->Allocate(Code::InstanceSize(0)));
    d->AssignRef(code);
    code->untag()->state_bits_ = state_bits;
  }

  void ReadFill(Deserializer* d) override {
    ASSERT(!is_canonical());  // Never canonical.
    d->set_lazy_compile_index(d->ReadRefId());
    d->set_unknown_dart_code_index(d->ReadRefId());
    ReadFill(d, start_index_, stop_index_, false);
#if defined(DART_PRECOMPILED_RUNTIME)
    ReadFill(d, deferred_start_index_, deferred_stop_index_, true);
#else
    ASSERT(deferred_start_index_ == deferred_stop_index_);
#endif
  }

  void ReadFill(Deserializer* d,
                intptr_t start_index,
                intptr_t stop_index,
                bool deferred) {
    for (intptr_t id = start_index, n = stop_index; id < n; id++) {
      auto const code = static_cast<CodePtr>(d->Ref(id));

      Deserializer::InitializeHeader(code, kCodeCid, Code::InstanceSize(0));
      ASSERT(!Code::IsDiscarded(code));

      d->ReadInstructions(code, deferred);

#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d->kind() == Snapshot::kFullJIT);
      code->untag()->object_pool_ = static_cast<ObjectPoolPtr>(d->ReadRef());
#else
      ASSERT(d->kind() == Snapshot::kFullAOT);
      // There is a single global pool.
      code->untag()->object_pool_ = ObjectPool::null();
#endif
      code->untag()->owner_ = d->ReadRef();
      code->untag()->exception_handlers_ =
          static_cast<ExceptionHandlersPtr>(d->ReadRef());
      code->untag()->pc_descriptors_ =
          static_cast<PcDescriptorsPtr>(d->ReadRef());
      code->untag()->catch_entry_ = d->ReadRef();
#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d->kind() == Snapshot::kFullJIT);
      code->untag()->compressed_stackmaps_ =
          static_cast<CompressedStackMapsPtr>(d->ReadRef());
#else
      ASSERT(d->kind() == Snapshot::kFullAOT);
      code->untag()->compressed_stackmaps_ = CompressedStackMaps::null();
#endif
      code->untag()->inlined_id_to_function_ =
          static_cast<ArrayPtr>(d->ReadRef());
      code->untag()->code_source_map_ =
          static_cast<CodeSourceMapPtr>(d->ReadRef());

#if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(d->kind() == Snapshot::kFullJIT);
      code->untag()->deopt_info_array_ = static_cast<ArrayPtr>(d->ReadRef());
      code->untag()->static_calls_target_table_ =
          static_cast<ArrayPtr>(d->ReadRef());
#endif  // !DART_PRECOMPILED_RUNTIME

#if !defined(PRODUCT)
      code->untag()->return_address_metadata_ = d->ReadRef();
      code->untag()->var_descriptors_ = LocalVarDescriptors::null();
      code->untag()->comments_ = d->isolate_group()->code_comments()
                                     ? static_cast<ArrayPtr>(d->ReadRef())
                                     : Array::null();
      code->untag()->compile_timestamp_ = 0;
#endif
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    d->EndInstructions();

#if !defined(PRODUCT)
    if (!CodeObservers::AreActive() && !FLAG_support_disassembler) return;
#endif
    Code& code = Code::Handle(d->zone());
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
    Object& owner = Object::Handle(d->zone());
#endif
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      code ^= refs.At(id);
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT)
      if (CodeObservers::AreActive()) {
        Code::NotifyCodeObservers(code, code.is_optimized());
      }
#endif
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
      owner = code.owner();
      if (owner.IsFunction()) {
        if ((FLAG_disassemble ||
             (code.is_optimized() && FLAG_disassemble_optimized)) &&
            compiler::PrintFilter::ShouldPrint(Function::Cast(owner))) {
          Disassembler::DisassembleCode(Function::Cast(owner), code,
                                        code.is_optimized());
        }
      } else if (FLAG_disassemble_stubs) {
        Disassembler::DisassembleStub(code.Name(), code);
      }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
    }
  }

 private:
  intptr_t deferred_start_index_;
  intptr_t deferred_stop_index_;
};

#if defined(DART_DYNAMIC_MODULES)
#if !defined(DART_PRECOMPILED_RUNTIME)
class BytecodeSerializationCluster : public SerializationCluster {
 public:
  BytecodeSerializationCluster()
      : SerializationCluster("Bytecode",
                             kBytecodeCid,
                             compiler::target::Bytecode::InstanceSize()) {}
  ~BytecodeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    BytecodePtr code = Bytecode::RawCast(object);
    objects_.Add(code);
    PushFromTo(code);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      BytecodePtr code = objects_[i];
      s->AssignRef(code);
    }
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      BytecodePtr code = objects_[i];
      AutoTraceObject(code);
      WriteFromTo(code);

      uint8_t* data = reinterpret_cast<uint8_t*>(code->untag()->instructions_);
      intptr_t length = code->untag()->instructions_size_;
      s->WriteUnsigned(length);
      s->WriteBytes(data, length);
    }
  }

  GrowableArray<BytecodePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class BytecodeDeserializationCluster : public DeserializationCluster {
 public:
  BytecodeDeserializationCluster() : DeserializationCluster("Bytecode") {}
  ~BytecodeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Bytecode::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      BytecodePtr code = static_cast<BytecodePtr>(d.Ref(id));
      Deserializer::InitializeHeader(code, kBytecodeCid,
                                     Bytecode::InstanceSize());
      d.ReadFromTo(code);

      intptr_t length = d.ReadUnsigned();
      code->untag()->instructions_ =
          reinterpret_cast<uword>(d.AddressOfCurrentPosition());
      code->untag()->instructions_size_ = length;
      code->untag()->instructions_binary_offset_ = -1;
      code->untag()->code_offset_ = 0;
      code->untag()->source_positions_binary_offset_ = 0;
      d.Advance(length);
    }
  }
};
#endif  // defined(DART_DYNAMIC_MODULES)

#if !defined(DART_PRECOMPILED_RUNTIME)
class ObjectPoolSerializationCluster : public SerializationCluster {
 public:
  ObjectPoolSerializationCluster()
      : SerializationCluster("ObjectPool", kObjectPoolCid) {}
  ~ObjectPoolSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ObjectPoolPtr pool = ObjectPool::RawCast(object);
    objects_.Add(pool);

    if (s->kind() != Snapshot::kFullAOT) {
      const intptr_t length = pool->untag()->length_;
      uint8_t* entry_bits = pool->untag()->entry_bits();
      for (intptr_t i = 0; i < length; i++) {
        auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
        if (entry_type == ObjectPool::EntryType::kTaggedObject) {
          s->Push(pool->untag()->data()[i].raw_obj_);
        }
      }
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ObjectPoolPtr pool = objects_[i];
      s->AssignRef(pool);
      AutoTraceObject(pool);
      const intptr_t length = pool->untag()->length_;
      s->WriteUnsigned(length);
      target_memory_size_ += compiler::target::ObjectPool::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    bool weak = s->kind() == Snapshot::kFullAOT;

    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ObjectPoolPtr pool = objects_[i];
      AutoTraceObject(pool);
      const intptr_t length = pool->untag()->length_;
      s->WriteUnsigned(length);
      uint8_t* entry_bits = pool->untag()->entry_bits();
      for (intptr_t j = 0; j < length; j++) {
        UntaggedObjectPool::Entry& entry = pool->untag()->data()[j];
        uint8_t bits = entry_bits[j];
        ObjectPool::EntryType type = ObjectPool::TypeBits::decode(bits);
        auto snapshot_behavior = ObjectPool::SnapshotBehaviorBits::decode(bits);
        ASSERT(snapshot_behavior !=
               ObjectPool::SnapshotBehavior::kNotSnapshotable);
        s->Write<uint8_t>(bits);
        if (snapshot_behavior != ObjectPool::SnapshotBehavior::kSnapshotable) {
          // The deserializer will reset this to a specific value, no need to
          // write anything.
          continue;
        }
        switch (type) {
          case ObjectPool::EntryType::kTaggedObject: {
            if (weak && !s->HasRef(entry.raw_obj_)) {
              // Any value will do, but null has the shortest id.
              s->WriteElementRef(Object::null(), j);
            } else {
              s->WriteElementRef(entry.raw_obj_, j);
            }
            break;
          }
          case ObjectPool::EntryType::kImmediate: {
            s->Write<intptr_t>(entry.raw_value_);
            break;
          }
          case ObjectPool::EntryType::kNativeFunction: {
            // Write nothing. Will initialize with the lazy link entry.
            break;
          }
          default:
            UNREACHABLE();
        }
      }
    }
  }

 private:
  GrowableArray<ObjectPoolPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ObjectPoolDeserializationCluster : public DeserializationCluster {
 public:
  ObjectPoolDeserializationCluster() : DeserializationCluster("ObjectPool") {}
  ~ObjectPoolDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(ObjectPool::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    fill_position_ = d.Position();

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      const intptr_t length = d.ReadUnsigned();
      ObjectPoolPtr pool = static_cast<ObjectPoolPtr>(d.Ref(id));
      Deserializer::InitializeHeader(pool, kObjectPoolCid,
                                     ObjectPool::InstanceSize(length));
      pool->untag()->length_ = length;
      for (intptr_t j = 0; j < length; j++) {
        const uint8_t entry_bits = d.Read<uint8_t>();
        pool->untag()->entry_bits()[j] = entry_bits;
        UntaggedObjectPool::Entry& entry = pool->untag()->data()[j];
#if defined(DEBUG)
        entry.raw_value_ = 0xabababab;
#endif
        const auto snapshot_behavior =
            ObjectPool::SnapshotBehaviorBits::decode(entry_bits);
        ASSERT(snapshot_behavior !=
               ObjectPool::SnapshotBehavior::kNotSnapshotable);
        switch (snapshot_behavior) {
          case ObjectPool::SnapshotBehavior::kSnapshotable:
            // Handled below.
            break;
          case ObjectPool::SnapshotBehavior::kResetToBootstrapNative:
            entry.raw_obj_ = 0;  // Stub not yet available.
            continue;
#if defined(DART_PRECOMPILED_RUNTIME)
          case ObjectPool::SnapshotBehavior::
              kResetToSwitchableCallMissEntryPoint:
            entry.raw_value_ = 0;  // Stub not yet available.
            continue;
#endif  // defined(DART_PRECOMPILED_RUNTIME)
          case ObjectPool::SnapshotBehavior::kSetToZero:
            entry.raw_value_ = 0;
            continue;
          default:
            FATAL("Unexpected snapshot behavior: %d\n", snapshot_behavior);
        }
        switch (ObjectPool::TypeBits::decode(entry_bits)) {
          case ObjectPool::EntryType::kTaggedObject:
            entry.raw_obj_ = d.ReadRef();
            break;
          case ObjectPool::EntryType::kImmediate:
            entry.raw_value_ = d.Read<intptr_t>();
            break;
          case ObjectPool::EntryType::kNativeFunction: {
            // Read nothing. Initialize with the lazy link entry.
            uword new_entry = NativeEntry::LinkNativeCallEntry();
            entry.raw_value_ = static_cast<intptr_t>(new_entry);
            break;
          }
          default:
            UNREACHABLE();
        }
      }
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    const Code& call_bootstrap_native = StubCode::CallBootstrapNative();
#if defined(DART_PRECOMPILED_RUNTIME)
    const uword switchable_call_miss_entry_point =
        StubCode::SwitchableCallMiss().MonomorphicEntryPoint();
#endif
    ObjectPool& pool = ObjectPool::Handle(d->zone());
    for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
      pool ^= refs.At(i);
      intptr_t length = pool.Length();
      for (intptr_t j = 0; j < length; j++) {
        switch (pool.SnapshotBehaviorAt(j)) {
          case ObjectPool::SnapshotBehavior::kResetToBootstrapNative:
            pool.SetObjectAt(j, call_bootstrap_native);
            break;
#if defined(DART_PRECOMPILED_RUNTIME)
          case ObjectPool::SnapshotBehavior::
              kResetToSwitchableCallMissEntryPoint:
            pool.SetTypeAt(j, ObjectPool::EntryType::kImmediate,
                           ObjectPool::Patchability::kPatchable,
                           ObjectPool::SnapshotBehavior::kSnapshotable);
            pool.SetRawValueAt(j, switchable_call_miss_entry_point);
            break;
#endif
          default:
            break;
        }
      }
    }

#if defined(DART_PRECOMPILED_RUNTIME) &&                                       \
    (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
    if (FLAG_disassemble) {
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        pool ^= refs.At(i);
        pool.DebugPrint();
      }
    }
#endif
  }

 private:
  intptr_t fill_position_ = 0;
};

#if defined(DART_PRECOMPILER)
class WeakSerializationReferenceSerializationCluster
    : public SerializationCluster {
 public:
  WeakSerializationReferenceSerializationCluster()
      : SerializationCluster(
            "WeakSerializationReference",
            compiler::target::WeakSerializationReference::InstanceSize()) {}
  ~WeakSerializationReferenceSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ASSERT(s->kind() == Snapshot::kFullAOT);
    objects_.Add(WeakSerializationReference::RawCast(object));
  }

  void RetraceEphemerons(Serializer* s) {
    for (intptr_t i = 0; i < objects_.length(); i++) {
      WeakSerializationReferencePtr weak = objects_[i];
      if (!s->IsReachable(weak->untag()->target())) {
        s->Push(weak->untag()->replacement());
      }
    }
  }

  intptr_t Count(Serializer* s) { return objects_.length(); }

  void CreateArtificialTargetNodesIfNeeded(Serializer* s) {
    for (intptr_t i = 0; i < objects_.length(); i++) {
      WeakSerializationReferencePtr weak = objects_[i];
      s->CreateArtificialNodeIfNeeded(weak->untag()->target());
    }
  }

  void WriteAlloc(Serializer* s) {
    UNREACHABLE();  // No WSRs are serialized, and so this cluster is not added.
  }

  void WriteFill(Serializer* s) {
    UNREACHABLE();  // No WSRs are serialized, and so this cluster is not added.
  }

 private:
  GrowableArray<WeakSerializationReferencePtr> objects_;
};
#endif

#if !defined(DART_PRECOMPILED_RUNTIME)
class PcDescriptorsSerializationCluster : public SerializationCluster {
 public:
  PcDescriptorsSerializationCluster()
      : SerializationCluster("PcDescriptors", kPcDescriptorsCid) {}
  ~PcDescriptorsSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    PcDescriptorsPtr desc = PcDescriptors::RawCast(object);
    objects_.Add(desc);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      PcDescriptorsPtr desc = objects_[i];
      s->AssignRef(desc);
      AutoTraceObject(desc);
      const intptr_t length = desc->untag()->length_;
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::PcDescriptors::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      PcDescriptorsPtr desc = objects_[i];
      AutoTraceObject(desc);
      const intptr_t length = desc->untag()->length_;
      s->WriteUnsigned(length);
      uint8_t* cdata = reinterpret_cast<uint8_t*>(desc->untag()->data());
      s->WriteBytes(cdata, length);
    }
  }

 private:
  GrowableArray<PcDescriptorsPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class PcDescriptorsDeserializationCluster : public DeserializationCluster {
 public:
  PcDescriptorsDeserializationCluster()
      : DeserializationCluster("PcDescriptors") {}
  ~PcDescriptorsDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(PcDescriptors::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      const intptr_t length = d.ReadUnsigned();
      PcDescriptorsPtr desc = static_cast<PcDescriptorsPtr>(d.Ref(id));
      Deserializer::InitializeHeader(desc, kPcDescriptorsCid,
                                     PcDescriptors::InstanceSize(length));
      desc->untag()->length_ = length;
      uint8_t* cdata = reinterpret_cast<uint8_t*>(desc->untag()->data());
      d.ReadBytes(cdata, length);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class CodeSourceMapSerializationCluster : public SerializationCluster {
 public:
  CodeSourceMapSerializationCluster()
      : SerializationCluster("CodeSourceMap", kCodeSourceMapCid) {}
  ~CodeSourceMapSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    CodeSourceMapPtr map = CodeSourceMap::RawCast(object);
    objects_.Add(map);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      CodeSourceMapPtr map = objects_[i];
      s->AssignRef(map);
      AutoTraceObject(map);
      const intptr_t length = map->untag()->length_;
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::PcDescriptors::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      CodeSourceMapPtr map = objects_[i];
      AutoTraceObject(map);
      const intptr_t length = map->untag()->length_;
      s->WriteUnsigned(length);
      uint8_t* cdata = reinterpret_cast<uint8_t*>(map->untag()->data());
      s->WriteBytes(cdata, length);
    }
  }

 private:
  GrowableArray<CodeSourceMapPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class CodeSourceMapDeserializationCluster : public DeserializationCluster {
 public:
  CodeSourceMapDeserializationCluster()
      : DeserializationCluster("CodeSourceMap") {}
  ~CodeSourceMapDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(CodeSourceMap::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      const intptr_t length = d.ReadUnsigned();
      CodeSourceMapPtr map = static_cast<CodeSourceMapPtr>(d.Ref(id));
      Deserializer::InitializeHeader(map, kPcDescriptorsCid,
                                     CodeSourceMap::InstanceSize(length));
      map->untag()->length_ = length;
      uint8_t* cdata = reinterpret_cast<uint8_t*>(map->untag()->data());
      d.ReadBytes(cdata, length);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class CompressedStackMapsSerializationCluster : public SerializationCluster {
 public:
  CompressedStackMapsSerializationCluster()
      : SerializationCluster("CompressedStackMaps", kCompressedStackMapsCid) {}
  ~CompressedStackMapsSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    CompressedStackMapsPtr desc = CompressedStackMaps::RawCast(object);
    objects_.Add(desc);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      CompressedStackMapsPtr map = objects_[i];
      s->AssignRef(map);
      AutoTraceObject(map);
      const intptr_t length = UntaggedCompressedStackMaps::SizeField::decode(
          map->untag()->payload()->flags_and_size());
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::CompressedStackMaps::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      CompressedStackMapsPtr map = objects_[i];
      AutoTraceObject(map);
      s->WriteUnsigned(map->untag()->payload()->flags_and_size());
      const intptr_t length = UntaggedCompressedStackMaps::SizeField::decode(
          map->untag()->payload()->flags_and_size());
      uint8_t* cdata =
          reinterpret_cast<uint8_t*>(map->untag()->payload()->data());
      s->WriteBytes(cdata, length);
    }
  }

 private:
  GrowableArray<CompressedStackMapsPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class CompressedStackMapsDeserializationCluster
    : public DeserializationCluster {
 public:
  CompressedStackMapsDeserializationCluster()
      : DeserializationCluster("CompressedStackMaps") {}
  ~CompressedStackMapsDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(CompressedStackMaps::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      const intptr_t flags_and_size = d.ReadUnsigned();
      const intptr_t length =
          UntaggedCompressedStackMaps::SizeField::decode(flags_and_size);
      CompressedStackMapsPtr map =
          static_cast<CompressedStackMapsPtr>(d.Ref(id));
      Deserializer::InitializeHeader(map, kCompressedStackMapsCid,
                                     CompressedStackMaps::InstanceSize(length));
      map->untag()->payload()->set_flags_and_size(flags_and_size);
      uint8_t* cdata =
          reinterpret_cast<uint8_t*>(map->untag()->payload()->data());
      d.ReadBytes(cdata, length);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_COMPRESSED_POINTERS)
// PcDescriptor, CompressedStackMaps, OneByteString, TwoByteString
class RODataSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalStringSet,
                                              String,
                                              ObjectPtr> {
 public:
  RODataSerializationCluster(Zone* zone,
                             const char* type,
                             intptr_t cid,
                             bool is_canonical,
                             bool is_deeply_immutable)
      : CanonicalSetSerializationCluster(
            cid,
            is_canonical,
            is_deeply_immutable,
            is_canonical && IsStringClassId(cid),
            ImageWriter::TagObjectTypeAsReadOnly(zone, type)),
        type_(type) {}
  ~RODataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    // A string's hash must already be computed when we write it because it
    // will be loaded into read-only memory. Extra bytes due to allocation
    // rounding need to be deterministically set for reliable deduplication in
    // shared images.
    if (s->heap()->old_space()->IsObjectFromImagePages(object)) {
      // This object is already read-only.
    } else {
      Object::FinalizeReadOnlyObject(object);
    }

    objects_.Add(object);
  }

  void WriteAlloc(Serializer* s) {
    const bool is_string_cluster = IsStringClassId(cid_);

    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);

    uint32_t running_offset = 0;
    for (intptr_t i = 0; i < count; i++) {
      ObjectPtr object = objects_[i];
      s->AssignRef(object);
      const StringPtr name =
          is_string_cluster ? String::RawCast(object) : nullptr;
      Serializer::WritingObjectScope scope(s, type_, object, name);
      uint32_t offset = s->GetDataOffset(object);
      s->TraceDataOffset(offset);
      ASSERT(Utils::IsAligned(
          offset, compiler::target::ObjectAlignment::kObjectAlignment));
      ASSERT(offset > running_offset);
      s->WriteUnsigned((offset - running_offset) >>
                       compiler::target::ObjectAlignment::kObjectAlignmentLog2);
      running_offset = offset;
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    // No-op.
  }

 private:
  const char* const type_;
};
#endif  // !DART_PRECOMPILED_RUNTIME && !DART_COMPRESSED_POINTERS

#if !defined(DART_COMPRESSED_POINTERS)
class RODataDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalStringSet> {
 public:
  RODataDeserializationCluster(intptr_t cid,
                               bool is_canonical,
                               bool is_deeply_immutable,
                               bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           is_deeply_immutable,
                                           is_root_unit,
                                           "ROData"),
        cid_(cid) {}
  ~RODataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    intptr_t count = d->ReadUnsigned();
    uint32_t running_offset = 0;
    for (intptr_t i = 0; i < count; i++) {
      running_offset += d->ReadUnsigned() << kObjectAlignmentLog2;
      ObjectPtr object = d->GetObjectAt(running_offset);
      d->AssignRef(object);
    }
    stop_index_ = d->next_index();
    if (cid_ == kStringCid) {
      BuildCanonicalSetFromLayout(d);
    }
  }

  void ReadFill(Deserializer* d_) override {}

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      ASSERT(cid_ == kStringCid);
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(d, refs,
                         WeakArray::Handle(object_store->symbol_table()));
      object_store->set_symbol_table(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      FATAL("Cannot recanonicalize RO objects.");
    }
  }

 private:
  const intptr_t cid_;
};
#endif  // !DART_COMPRESSED_POINTERS

#if !defined(DART_PRECOMPILED_RUNTIME)
class LocalVarDescriptorsSerializationCluster : public SerializationCluster {
 public:
  LocalVarDescriptorsSerializationCluster()
      : SerializationCluster("LocalVarDescriptors", kLocalVarDescriptorsCid) {}
  ~LocalVarDescriptorsSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    LocalVarDescriptorsPtr desc = LocalVarDescriptors::RawCast(object);
    objects_.Add(desc);

    const intptr_t length = desc->untag()->num_entries_;
    for (intptr_t i = 0; i < length; i++) {
      s->Push(desc->untag()->name(i));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      LocalVarDescriptorsPtr desc = objects_[i];
      s->AssignRef(desc);
      AutoTraceObject(desc);
      const intptr_t length = desc->untag()->num_entries_;
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::LocalVarDescriptors::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      LocalVarDescriptorsPtr desc = objects_[i];
      AutoTraceObject(desc);
      const intptr_t length = desc->untag()->num_entries_;
      s->WriteUnsigned(length);
      WriteFromTo(desc, length);
      for (intptr_t j = 0; j < length; j++) {
        UntaggedLocalVarDescriptors::VarInfo& entry = desc->untag()->data()[j];
        s->Write<int32_t>(entry.index_kind);
        s->WriteTokenPosition(entry.declaration_pos);
        s->WriteTokenPosition(entry.begin_pos);
        s->WriteTokenPosition(entry.end_pos);
        s->Write<int64_t>(entry.scope_id);
      }
    }
  }

 private:
  GrowableArray<LocalVarDescriptorsPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class LocalVarDescriptorsDeserializationCluster
    : public DeserializationCluster {
 public:
  LocalVarDescriptorsDeserializationCluster()
      : DeserializationCluster("LocalVarDescriptors") {}
  ~LocalVarDescriptorsDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(LocalVarDescriptors::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      LocalVarDescriptorsPtr desc =
          static_cast<LocalVarDescriptorsPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(desc, kLocalVarDescriptorsCid,
                                     LocalVarDescriptors::InstanceSize(length));
      desc->untag()->num_entries_ = length;
      d.ReadFromTo(desc, length);
      for (intptr_t j = 0; j < length; j++) {
        UntaggedLocalVarDescriptors::VarInfo& entry = desc->untag()->data()[j];
        entry.index_kind = d.Read<int32_t>();
        entry.declaration_pos = d.ReadTokenPosition();
        entry.begin_pos = d.ReadTokenPosition();
        entry.end_pos = d.ReadTokenPosition();
        entry.scope_id = d.Read<int64_t>();
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ExceptionHandlersSerializationCluster : public SerializationCluster {
 public:
  ExceptionHandlersSerializationCluster()
      : SerializationCluster("ExceptionHandlers", kExceptionHandlersCid) {}
  ~ExceptionHandlersSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ExceptionHandlersPtr handlers = ExceptionHandlers::RawCast(object);
    objects_.Add(handlers);

    s->Push(handlers->untag()->handled_types_data());
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ExceptionHandlersPtr handlers = objects_[i];
      s->AssignRef(handlers);
      AutoTraceObject(handlers);
      const intptr_t length = handlers->untag()->num_entries();
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::ExceptionHandlers::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ExceptionHandlersPtr handlers = objects_[i];
      AutoTraceObject(handlers);
      const intptr_t packed_fields = handlers->untag()->packed_fields_;
      const intptr_t length =
          UntaggedExceptionHandlers::NumEntriesBits::decode(packed_fields);
      s->WriteUnsigned(packed_fields);
      WriteCompressedField(handlers, handled_types_data);
      for (intptr_t j = 0; j < length; j++) {
        const ExceptionHandlerInfo& info = handlers->untag()->data()[j];
        s->Write<uint32_t>(info.handler_pc_offset);
        s->Write<int16_t>(info.outer_try_index);
        s->Write<int8_t>(info.needs_stacktrace);
        s->Write<int8_t>(info.has_catch_all);
        s->Write<int8_t>(info.is_generated);
      }
    }
  }

 private:
  GrowableArray<ExceptionHandlersPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ExceptionHandlersDeserializationCluster : public DeserializationCluster {
 public:
  ExceptionHandlersDeserializationCluster()
      : DeserializationCluster("ExceptionHandlers") {}
  ~ExceptionHandlersDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(ExceptionHandlers::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ExceptionHandlersPtr handlers =
          static_cast<ExceptionHandlersPtr>(d.Ref(id));
      const intptr_t packed_fields = d.ReadUnsigned();
      const intptr_t length =
          UntaggedExceptionHandlers::NumEntriesBits::decode(packed_fields);
      Deserializer::InitializeHeader(handlers, kExceptionHandlersCid,
                                     ExceptionHandlers::InstanceSize(length));
      handlers->untag()->packed_fields_ = packed_fields;
      handlers->untag()->handled_types_data_ =
          static_cast<ArrayPtr>(d.ReadRef());
      for (intptr_t j = 0; j < length; j++) {
        ExceptionHandlerInfo& info = handlers->untag()->data()[j];
        info.handler_pc_offset = d.Read<uint32_t>();
        info.outer_try_index = d.Read<int16_t>();
        info.needs_stacktrace = d.Read<int8_t>();
        info.has_catch_all = d.Read<int8_t>();
        info.is_generated = d.Read<int8_t>();
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ContextSerializationCluster : public SerializationCluster {
 public:
  ContextSerializationCluster()
      : SerializationCluster("Context", kContextCid) {}
  ~ContextSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ContextPtr context = Context::RawCast(object);
    objects_.Add(context);

    s->Push(context->untag()->parent());
    const intptr_t length = context->untag()->num_variables_;
    for (intptr_t i = 0; i < length; i++) {
      s->Push(context->untag()->element(i));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ContextPtr context = objects_[i];
      s->AssignRef(context);
      AutoTraceObject(context);
      const intptr_t length = context->untag()->num_variables_;
      s->WriteUnsigned(length);
      target_memory_size_ += compiler::target::Context::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ContextPtr context = objects_[i];
      AutoTraceObject(context);
      const intptr_t length = context->untag()->num_variables_;
      s->WriteUnsigned(length);
      WriteField(context, parent());
      for (intptr_t j = 0; j < length; j++) {
        s->WriteElementRef(context->untag()->element(j), j);
      }
    }
  }

 private:
  GrowableArray<ContextPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ContextDeserializationCluster : public DeserializationCluster {
 public:
  ContextDeserializationCluster() : DeserializationCluster("Context") {}
  ~ContextDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(Context::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ContextPtr context = static_cast<ContextPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(context, kContextCid,
                                     Context::InstanceSize(length));
      context->untag()->num_variables_ = length;
      context->untag()->parent_ = static_cast<ContextPtr>(d.ReadRef());
      for (intptr_t j = 0; j < length; j++) {
        context->untag()->data()[j] = d.ReadRef();
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ContextScopeSerializationCluster : public SerializationCluster {
 public:
  ContextScopeSerializationCluster()
      : SerializationCluster("ContextScope", kContextScopeCid) {}
  ~ContextScopeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ContextScopePtr scope = ContextScope::RawCast(object);
    objects_.Add(scope);

    const intptr_t length = scope->untag()->num_variables_;
    PushFromTo(scope, length);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ContextScopePtr scope = objects_[i];
      s->AssignRef(scope);
      AutoTraceObject(scope);
      const intptr_t length = scope->untag()->num_variables_;
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::ContextScope::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ContextScopePtr scope = objects_[i];
      AutoTraceObject(scope);
      const intptr_t length = scope->untag()->num_variables_;
      s->WriteUnsigned(length);
      s->Write<bool>(scope->untag()->is_implicit_);
      WriteFromTo(scope, length);
    }
  }

 private:
  GrowableArray<ContextScopePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ContextScopeDeserializationCluster : public DeserializationCluster {
 public:
  ContextScopeDeserializationCluster()
      : DeserializationCluster("ContextScope") {}
  ~ContextScopeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(ContextScope::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ContextScopePtr scope = static_cast<ContextScopePtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(scope, kContextScopeCid,
                                     ContextScope::InstanceSize(length));
      scope->untag()->num_variables_ = length;
      scope->untag()->is_implicit_ = d.Read<bool>();
      d.ReadFromTo(scope, length);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class UnlinkedCallSerializationCluster : public SerializationCluster {
 public:
  UnlinkedCallSerializationCluster()
      : SerializationCluster("UnlinkedCall",
                             kUnlinkedCallCid,
                             compiler::target::UnlinkedCall::InstanceSize()) {}
  ~UnlinkedCallSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    UnlinkedCallPtr unlinked = UnlinkedCall::RawCast(object);
    objects_.Add(unlinked);
    PushFromTo(unlinked);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      UnlinkedCallPtr unlinked = objects_[i];
      s->AssignRef(unlinked);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      UnlinkedCallPtr unlinked = objects_[i];
      AutoTraceObjectName(unlinked, unlinked->untag()->target_name_);
      WriteFromTo(unlinked);
      s->Write<bool>(unlinked->untag()->can_patch_to_monomorphic_);
    }
  }

 private:
  GrowableArray<UnlinkedCallPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class UnlinkedCallDeserializationCluster : public DeserializationCluster {
 public:
  UnlinkedCallDeserializationCluster()
      : DeserializationCluster("UnlinkedCall") {}
  ~UnlinkedCallDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, UnlinkedCall::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      UnlinkedCallPtr unlinked = static_cast<UnlinkedCallPtr>(d.Ref(id));
      Deserializer::InitializeHeader(unlinked, kUnlinkedCallCid,
                                     UnlinkedCall::InstanceSize());
      d.ReadFromTo(unlinked);
      unlinked->untag()->can_patch_to_monomorphic_ = d.Read<bool>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ICDataSerializationCluster : public SerializationCluster {
 public:
  ICDataSerializationCluster()
      : SerializationCluster("ICData",
                             kICDataCid,
                             compiler::target::ICData::InstanceSize()) {}
  ~ICDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ICDataPtr ic = ICData::RawCast(object);
    objects_.Add(ic);
    PushFromTo(ic);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ICDataPtr ic = objects_[i];
      s->AssignRef(ic);
    }
  }

  void WriteFill(Serializer* s) {
    Snapshot::Kind kind = s->kind();
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ICDataPtr ic = objects_[i];
      AutoTraceObjectName(ic, ic->untag()->target_name_);
      WriteFromTo(ic);
      if (kind != Snapshot::kFullAOT) {
        NOT_IN_PRECOMPILED(s->Write<int32_t>(ic->untag()->deopt_id_));
      }
      s->Write<uint32_t>(ic->untag()->state_bits_);
    }
  }

 private:
  GrowableArray<ICDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ICDataDeserializationCluster : public DeserializationCluster {
 public:
  ICDataDeserializationCluster() : DeserializationCluster("ICData") {}
  ~ICDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, ICData::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ICDataPtr ic = static_cast<ICDataPtr>(d.Ref(id));
      Deserializer::InitializeHeader(ic, kICDataCid, ICData::InstanceSize());
      d.ReadFromTo(ic);
      NOT_IN_PRECOMPILED(ic->untag()->deopt_id_ = d.Read<int32_t>());
      ic->untag()->state_bits_ = d.Read<int32_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class MegamorphicCacheSerializationCluster : public SerializationCluster {
 public:
  MegamorphicCacheSerializationCluster()
      : SerializationCluster(
            "MegamorphicCache",
            kMegamorphicCacheCid,
            compiler::target::MegamorphicCache::InstanceSize()) {}
  ~MegamorphicCacheSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    MegamorphicCachePtr cache = MegamorphicCache::RawCast(object);
    objects_.Add(cache);
    PushFromTo(cache);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      MegamorphicCachePtr cache = objects_[i];
      s->AssignRef(cache);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      MegamorphicCachePtr cache = objects_[i];
      AutoTraceObjectName(cache, cache->untag()->target_name_);
      WriteFromTo(cache);
      s->Write<int32_t>(cache->untag()->filled_entry_count_);
    }
  }

 private:
  GrowableArray<MegamorphicCachePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class MegamorphicCacheDeserializationCluster : public DeserializationCluster {
 public:
  MegamorphicCacheDeserializationCluster()
      : DeserializationCluster("MegamorphicCache") {}
  ~MegamorphicCacheDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, MegamorphicCache::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      MegamorphicCachePtr cache = static_cast<MegamorphicCachePtr>(d.Ref(id));
      Deserializer::InitializeHeader(cache, kMegamorphicCacheCid,
                                     MegamorphicCache::InstanceSize());
      d.ReadFromTo(cache);
      cache->untag()->filled_entry_count_ = d.Read<int32_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class SubtypeTestCacheSerializationCluster : public SerializationCluster {
 public:
  SubtypeTestCacheSerializationCluster()
      : SerializationCluster(
            "SubtypeTestCache",
            kSubtypeTestCacheCid,
            compiler::target::SubtypeTestCache::InstanceSize()) {}
  ~SubtypeTestCacheSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    SubtypeTestCachePtr cache = SubtypeTestCache::RawCast(object);
    objects_.Add(cache);
    s->Push(cache->untag()->cache_);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      SubtypeTestCachePtr cache = objects_[i];
      s->AssignRef(cache);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      SubtypeTestCachePtr cache = objects_[i];
      AutoTraceObject(cache);
      WriteField(cache, cache_);
      s->Write<uint32_t>(cache->untag()->num_inputs_);
      s->Write<uint32_t>(cache->untag()->num_occupied_);
    }
  }

 private:
  GrowableArray<SubtypeTestCachePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class SubtypeTestCacheDeserializationCluster : public DeserializationCluster {
 public:
  SubtypeTestCacheDeserializationCluster()
      : DeserializationCluster("SubtypeTestCache") {}
  ~SubtypeTestCacheDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, SubtypeTestCache::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      SubtypeTestCachePtr cache = static_cast<SubtypeTestCachePtr>(d.Ref(id));
      Deserializer::InitializeHeader(cache, kSubtypeTestCacheCid,
                                     SubtypeTestCache::InstanceSize());
      cache->untag()->cache_ = static_cast<ArrayPtr>(d.ReadRef());
      cache->untag()->num_inputs_ = d.Read<uint32_t>();
      cache->untag()->num_occupied_ = d.Read<uint32_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class LoadingUnitSerializationCluster : public SerializationCluster {
 public:
  LoadingUnitSerializationCluster()
      : SerializationCluster("LoadingUnit",
                             kLoadingUnitCid,
                             compiler::target::LoadingUnit::InstanceSize()) {}
  ~LoadingUnitSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    LoadingUnitPtr unit = LoadingUnit::RawCast(object);
    objects_.Add(unit);
    s->Push(unit->untag()->parent());
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      LoadingUnitPtr unit = objects_[i];
      s->AssignRef(unit);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      LoadingUnitPtr unit = objects_[i];
      AutoTraceObject(unit);
      WriteCompressedField(unit, parent);
      s->Write<intptr_t>(
          unit->untag()->packed_fields_.Read<UntaggedLoadingUnit::IdBits>());
    }
  }

 private:
  GrowableArray<LoadingUnitPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class LoadingUnitDeserializationCluster : public DeserializationCluster {
 public:
  LoadingUnitDeserializationCluster() : DeserializationCluster("LoadingUnit") {}
  ~LoadingUnitDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, LoadingUnit::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      LoadingUnitPtr unit = static_cast<LoadingUnitPtr>(d.Ref(id));
      Deserializer::InitializeHeader(unit, kLoadingUnitCid,
                                     LoadingUnit::InstanceSize());
      unit->untag()->parent_ = static_cast<LoadingUnitPtr>(d.ReadRef());
      unit->untag()->base_objects_ = Array::null();
      unit->untag()->instructions_image_ = nullptr;
      unit->untag()->packed_fields_ =
          UntaggedLoadingUnit::LoadStateBits::encode(
              UntaggedLoadingUnit::kNotLoaded) |
          UntaggedLoadingUnit::IdBits::encode(d.Read<intptr_t>());
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ApiErrorSerializationCluster : public SerializationCluster {
 public:
  ApiErrorSerializationCluster()
      : SerializationCluster("ApiError",
                             kApiErrorCid,
                             compiler::target::ApiError::InstanceSize()) {}
  ~ApiErrorSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ApiErrorPtr error = ApiError::RawCast(object);
    objects_.Add(error);
    PushFromTo(error);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ApiErrorPtr error = objects_[i];
      s->AssignRef(error);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ApiErrorPtr error = objects_[i];
      AutoTraceObject(error);
      WriteFromTo(error);
    }
  }

 private:
  GrowableArray<ApiErrorPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ApiErrorDeserializationCluster : public DeserializationCluster {
 public:
  ApiErrorDeserializationCluster() : DeserializationCluster("ApiError") {}
  ~ApiErrorDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, ApiError::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ApiErrorPtr error = static_cast<ApiErrorPtr>(d.Ref(id));
      Deserializer::InitializeHeader(error, kApiErrorCid,
                                     ApiError::InstanceSize());
      d.ReadFromTo(error);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class LanguageErrorSerializationCluster : public SerializationCluster {
 public:
  LanguageErrorSerializationCluster()
      : SerializationCluster("LanguageError",
                             kLanguageErrorCid,
                             compiler::target::LanguageError::InstanceSize()) {}
  ~LanguageErrorSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    LanguageErrorPtr error = LanguageError::RawCast(object);
    objects_.Add(error);
    PushFromTo(error);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      LanguageErrorPtr error = objects_[i];
      s->AssignRef(error);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      LanguageErrorPtr error = objects_[i];
      AutoTraceObject(error);
      WriteFromTo(error);
      s->WriteTokenPosition(error->untag()->token_pos_);
      s->Write<bool>(error->untag()->report_after_token_);
      s->Write<int8_t>(error->untag()->kind_);
    }
  }

 private:
  GrowableArray<LanguageErrorPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class LanguageErrorDeserializationCluster : public DeserializationCluster {
 public:
  LanguageErrorDeserializationCluster()
      : DeserializationCluster("LanguageError") {}
  ~LanguageErrorDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, LanguageError::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      LanguageErrorPtr error = static_cast<LanguageErrorPtr>(d.Ref(id));
      Deserializer::InitializeHeader(error, kLanguageErrorCid,
                                     LanguageError::InstanceSize());
      d.ReadFromTo(error);
      error->untag()->token_pos_ = d.ReadTokenPosition();
      error->untag()->report_after_token_ = d.Read<bool>();
      error->untag()->kind_ = d.Read<int8_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class UnhandledExceptionSerializationCluster : public SerializationCluster {
 public:
  UnhandledExceptionSerializationCluster()
      : SerializationCluster(
            "UnhandledException",
            kUnhandledExceptionCid,
            compiler::target::UnhandledException::InstanceSize()) {}
  ~UnhandledExceptionSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    UnhandledExceptionPtr exception = UnhandledException::RawCast(object);
    objects_.Add(exception);
    PushFromTo(exception);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      UnhandledExceptionPtr exception = objects_[i];
      s->AssignRef(exception);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      UnhandledExceptionPtr exception = objects_[i];
      AutoTraceObject(exception);
      WriteFromTo(exception);
    }
  }

 private:
  GrowableArray<UnhandledExceptionPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class UnhandledExceptionDeserializationCluster : public DeserializationCluster {
 public:
  UnhandledExceptionDeserializationCluster()
      : DeserializationCluster("UnhandledException") {}
  ~UnhandledExceptionDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, UnhandledException::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      UnhandledExceptionPtr exception =
          static_cast<UnhandledExceptionPtr>(d.Ref(id));
      Deserializer::InitializeHeader(exception, kUnhandledExceptionCid,
                                     UnhandledException::InstanceSize());
      d.ReadFromTo(exception);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class UnwindErrorSerializationCluster : public SerializationCluster {
 public:
  UnwindErrorSerializationCluster()
      : SerializationCluster("UnwindError",
                             kUnwindErrorCid,
                             compiler::target::UnwindError::InstanceSize()) {}
  ~UnwindErrorSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    UnwindErrorPtr error = UnwindError::RawCast(object);
    objects_.Add(error);
    PushFromTo(error);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      UnwindErrorPtr error = objects_[i];
      s->AssignRef(error);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      UnwindErrorPtr error = objects_[i];
      AutoTraceObject(error);
      WriteFromTo(error);
      s->Write<bool>(error->untag()->is_user_initiated_);
    }
  }

 private:
  GrowableArray<UnwindErrorPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class UnwindErrorDeserializationCluster : public DeserializationCluster {
 public:
  UnwindErrorDeserializationCluster() : DeserializationCluster("UnwindError") {}
  ~UnwindErrorDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, UnwindError::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      UnwindErrorPtr error = static_cast<UnwindErrorPtr>(d.Ref(id));
      Deserializer::InitializeHeader(error, kUnwindErrorCid,
                                     UnwindError::InstanceSize());
      d.ReadFromTo(error);
      error->untag()->is_user_initiated_ = d.Read<bool>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class InstanceSerializationCluster : public SerializationCluster {
 public:
  InstanceSerializationCluster(bool is_canonical,
                               bool is_deeply_immutable,
                               intptr_t cid)
      : SerializationCluster("Instance",
                             cid,
                             kSizeVaries,
                             is_canonical,
                             is_deeply_immutable) {
    ClassPtr cls = IsolateGroup::Current()->class_table()->At(cid);
    host_next_field_offset_in_words_ =
        cls->untag()->host_next_field_offset_in_words_;
    ASSERT(host_next_field_offset_in_words_ > 0);
#if defined(DART_PRECOMPILER)
    target_next_field_offset_in_words_ =
        cls->untag()->target_next_field_offset_in_words_;
    target_instance_size_in_words_ =
        cls->untag()->target_instance_size_in_words_;
#else
    target_next_field_offset_in_words_ =
        cls->untag()->host_next_field_offset_in_words_;
    target_instance_size_in_words_ = cls->untag()->host_instance_size_in_words_;
#endif  // defined(DART_PRECOMPILER)
    ASSERT(target_next_field_offset_in_words_ > 0);
    ASSERT(target_instance_size_in_words_ > 0);
  }
  ~InstanceSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    InstancePtr instance = Instance::RawCast(object);
    objects_.Add(instance);
    const intptr_t next_field_offset = host_next_field_offset_in_words_
                                       << kCompressedWordSizeLog2;
    const auto unboxed_fields_bitmap =
        s->isolate_group()->class_table()->GetUnboxedFieldsMapAt(cid_);
    intptr_t offset = Instance::NextFieldOffset();
    while (offset < next_field_offset) {
      // Skips unboxed fields
      if (!unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
        ObjectPtr raw_obj =
            reinterpret_cast<CompressedObjectPtr*>(
                reinterpret_cast<uword>(instance->untag()) + offset)
                ->Decompress(instance->untag()->heap_base());
        s->Push(raw_obj);
      }
      offset += kCompressedWordSize;
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);

    s->Write<int32_t>(target_next_field_offset_in_words_);
    s->Write<int32_t>(target_instance_size_in_words_);

    for (intptr_t i = 0; i < count; i++) {
      InstancePtr instance = objects_[i];
      s->AssignRef(instance);
    }

    const intptr_t instance_size = compiler::target::RoundedAllocationSize(
        target_instance_size_in_words_ * compiler::target::kCompressedWordSize);
    target_memory_size_ += instance_size * count;
  }

  void WriteFill(Serializer* s) {
    intptr_t next_field_offset = host_next_field_offset_in_words_
                                 << kCompressedWordSizeLog2;
    const intptr_t count = objects_.length();
    s->WriteUnsigned64(CalculateTargetUnboxedFieldsBitmap(s, cid_).Value());
    const auto unboxed_fields_bitmap =
        s->isolate_group()->class_table()->GetUnboxedFieldsMapAt(cid_);

    for (intptr_t i = 0; i < count; i++) {
      InstancePtr instance = objects_[i];
      AutoTraceObject(instance);
#if defined(DART_PRECOMPILER)
      if (FLAG_write_v8_snapshot_profile_to != nullptr) {
        ClassPtr cls = s->isolate_group()->class_table()->At(cid_);
        s->AttributePropertyRef(cls, "<class>");
      }
#endif
      intptr_t offset = Instance::NextFieldOffset();
      while (offset < next_field_offset) {
        if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
          // Writes 32 bits of the unboxed value at a time.
          const compressed_uword value = *reinterpret_cast<compressed_uword*>(
              reinterpret_cast<uword>(instance->untag()) + offset);
          s->WriteWordWith32BitWrites(value);
        } else {
          ObjectPtr raw_obj =
              reinterpret_cast<CompressedObjectPtr*>(
                  reinterpret_cast<uword>(instance->untag()) + offset)
                  ->Decompress(instance->untag()->heap_base());
          s->WriteElementRef(raw_obj, offset);
        }
        offset += kCompressedWordSize;
      }
    }
  }

 private:
  intptr_t host_next_field_offset_in_words_;
  intptr_t target_next_field_offset_in_words_;
  intptr_t target_instance_size_in_words_;
  GrowableArray<InstancePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class AbstractInstanceDeserializationCluster : public DeserializationCluster {
 protected:
  AbstractInstanceDeserializationCluster(const char* name,
                                         bool is_canonical,
                                         bool is_deeply_immutable,
                                         bool is_root_unit)
      : DeserializationCluster(name, is_canonical, is_deeply_immutable),
        is_root_unit_(is_root_unit) {}

  const bool is_root_unit_;

 public:
#if defined(DART_PRECOMPILED_RUNTIME)
  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!is_root_unit_ && is_canonical()) {
      SafepointMutexLocker ml(
          d->isolate_group()->constant_canonicalization_mutex());
      Instance& instance = Instance::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        instance ^= refs.At(i);
        instance = instance.CanonicalizeLocked(d->thread());
        refs.SetAt(i, instance);
      }
    }
  }
#endif
};

class InstanceDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  InstanceDeserializationCluster(intptr_t cid,
                                 bool is_canonical,
                                 bool is_deeply_immutable,
                                 bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Instance",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit),
        cid_(cid) {}
  ~InstanceDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    next_field_offset_in_words_ = d->Read<int32_t>();
    instance_size_in_words_ = d->Read<int32_t>();
    intptr_t instance_size = Object::RoundedAllocationSize(
        instance_size_in_words_ * kCompressedWordSize);
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(d->Allocate(instance_size));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const intptr_t cid = cid_;
    const bool mark_canonical = is_root_unit_ && is_canonical();
    const bool is_deeply_immutable = is_deeply_immutable_;
    intptr_t next_field_offset = next_field_offset_in_words_
                                 << kCompressedWordSizeLog2;
    intptr_t instance_size = Object::RoundedAllocationSize(
        instance_size_in_words_ * kCompressedWordSize);
    const UnboxedFieldBitmap unboxed_fields_bitmap(d.ReadUnsigned64());

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      InstancePtr instance = static_cast<InstancePtr>(d.Ref(id));
      Deserializer::InitializeHeader(instance, cid, instance_size,
                                     mark_canonical, is_deeply_immutable);
      intptr_t offset = Instance::NextFieldOffset();
      while (offset < next_field_offset) {
        if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
          compressed_uword* p = reinterpret_cast<compressed_uword*>(
              reinterpret_cast<uword>(instance->untag()) + offset);
          // Reads 32 bits of the unboxed value at a time
          *p = d.ReadWordWith32BitReads();
        } else {
          CompressedObjectPtr* p = reinterpret_cast<CompressedObjectPtr*>(
              reinterpret_cast<uword>(instance->untag()) + offset);
          *p = d.ReadRef();
        }
        offset += kCompressedWordSize;
      }
      while (offset < instance_size) {
        CompressedObjectPtr* p = reinterpret_cast<CompressedObjectPtr*>(
            reinterpret_cast<uword>(instance->untag()) + offset);
        *p = d.null();
        offset += kCompressedWordSize;
      }
      ASSERT(offset == instance_size);
    }
  }

 private:
  const intptr_t cid_;
  intptr_t next_field_offset_in_words_;
  intptr_t instance_size_in_words_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class LibraryPrefixSerializationCluster : public SerializationCluster {
 public:
  LibraryPrefixSerializationCluster()
      : SerializationCluster("LibraryPrefix",
                             kLibraryPrefixCid,
                             compiler::target::LibraryPrefix::InstanceSize()) {}
  ~LibraryPrefixSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    LibraryPrefixPtr prefix = LibraryPrefix::RawCast(object);
    objects_.Add(prefix);
    PushFromTo(prefix);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      LibraryPrefixPtr prefix = objects_[i];
      s->AssignRef(prefix);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      LibraryPrefixPtr prefix = objects_[i];
      AutoTraceObject(prefix);
      WriteFromTo(prefix);
      s->Write<uint16_t>(prefix->untag()->num_imports_);
      s->Write<bool>(prefix->untag()->is_deferred_load_);
    }
  }

 private:
  GrowableArray<LibraryPrefixPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class LibraryPrefixDeserializationCluster : public DeserializationCluster {
 public:
  LibraryPrefixDeserializationCluster()
      : DeserializationCluster("LibraryPrefix") {}
  ~LibraryPrefixDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, LibraryPrefix::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      LibraryPrefixPtr prefix = static_cast<LibraryPrefixPtr>(d.Ref(id));
      Deserializer::InitializeHeader(prefix, kLibraryPrefixCid,
                                     LibraryPrefix::InstanceSize());
      d.ReadFromTo(prefix);
      prefix->untag()->num_imports_ = d.Read<uint16_t>();
      prefix->untag()->is_deferred_load_ = d.Read<bool>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypeSerializationCluster
    : public CanonicalSetSerializationCluster<
          CanonicalTypeSet,
          Type,
          TypePtr,
          /*kAllCanonicalObjectsAreIncludedIntoSet=*/false> {
 public:
  TypeSerializationCluster(bool is_canonical,
                           bool is_deeply_immutable,
                           bool represents_canonical_set)
      : CanonicalSetSerializationCluster(
            kTypeCid,
            is_canonical,
            is_deeply_immutable,
            represents_canonical_set,
            "Type",
            compiler::target::Type::InstanceSize()) {}
  ~TypeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypePtr type = Type::RawCast(object);
    objects_.Add(type);

    PushFromTo(type);

    ASSERT(type->untag()->type_class_id() != kIllegalCid);
    ClassPtr type_class =
        s->isolate_group()->class_table()->At(type->untag()->type_class_id());
    s->Push(type_class);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);
    for (intptr_t i = 0; i < count; i++) {
      TypePtr type = objects_[i];
      s->AssignRef(type);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WriteType(s, objects_[i]);
    }
  }

 private:
  Type& type_ = Type::Handle();
  Class& cls_ = Class::Handle();

  // Type::Canonicalize does not actually put all canonical Type objects into
  // canonical_types set. Some of the canonical declaration types (but not all
  // of them) are simply cached in UntaggedClass::declaration_type_ and are not
  // inserted into the canonical_types set.
  // Keep in sync with Type::Canonicalize.
  virtual bool IsInCanonicalSet(Serializer* s, TypePtr type) {
    ClassPtr type_class =
        s->isolate_group()->class_table()->At(type->untag()->type_class_id());
    if (type_class->untag()->declaration_type() != type) {
      return true;
    }

    type_ = type;
    cls_ = type_class;
    return !type_.IsDeclarationTypeOf(cls_);
  }

  void WriteType(Serializer* s, TypePtr type) {
    AutoTraceObject(type);
#if defined(DART_PRECOMPILER)
    if (FLAG_write_v8_snapshot_profile_to != nullptr) {
      ClassPtr type_class =
          s->isolate_group()->class_table()->At(type->untag()->type_class_id());
      s->AttributePropertyRef(type_class, "<type_class>");
    }
#endif
    WriteFromTo(type);
    s->WriteUnsigned(type->untag()->flags());
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypeDeserializationCluster
    : public CanonicalSetDeserializationCluster<
          CanonicalTypeSet,
          /*kAllCanonicalObjectsAreIncludedIntoSet=*/false> {
 public:
  TypeDeserializationCluster(bool is_canonical, bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           /*is_deeply_immutable=*/true,
                                           is_root_unit,
                                           "Type") {}
  ~TypeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Type::InstanceSize());
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypePtr type = static_cast<TypePtr>(d.Ref(id));
      Deserializer::InitializeHeader(type, kTypeCid, Type::InstanceSize(),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(type);
      type->untag()->set_flags(d.ReadUnsigned());
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(d, refs,
                         Array::Handle(object_store->canonical_types()));
      object_store->set_canonical_types(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      AbstractType& type = AbstractType::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        type ^= refs.At(i);
        type = type.Canonicalize(d->thread());
        refs.SetAt(i, type);
      }
    }

    Type& type = Type::Handle(d->zone());
    Code& stub = Code::Handle(d->zone());

    if (Snapshot::IncludesCode(d->kind())) {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        type.UpdateTypeTestingStubEntryPoint();
      }
    } else {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        stub = TypeTestingStubGenerator::DefaultCodeForType(type);
        type.InitializeTypeTestingStubNonAtomic(stub);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class FunctionTypeSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalFunctionTypeSet,
                                              FunctionType,
                                              FunctionTypePtr> {
 public:
  FunctionTypeSerializationCluster(bool is_canonical,
                                   bool is_deeply_immutable,
                                   bool represents_canonical_set)
      : CanonicalSetSerializationCluster(
            kFunctionTypeCid,
            is_canonical,
            is_deeply_immutable,
            represents_canonical_set,
            "FunctionType",
            compiler::target::FunctionType::InstanceSize()) {}
  ~FunctionTypeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    FunctionTypePtr type = FunctionType::RawCast(object);
    objects_.Add(type);
    PushFromTo(type);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);

    for (intptr_t i = 0; i < count; i++) {
      FunctionTypePtr type = objects_[i];
      s->AssignRef(type);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WriteFunctionType(s, objects_[i]);
    }
  }

 private:
  void WriteFunctionType(Serializer* s, FunctionTypePtr type) {
    AutoTraceObject(type);
    WriteFromTo(type);
    ASSERT(Utils::IsUint(8, type->untag()->flags()));
    s->Write<uint8_t>(type->untag()->flags());
    s->Write<uint32_t>(type->untag()->packed_parameter_counts_);
    s->Write<uint16_t>(type->untag()->packed_type_parameter_counts_);
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class FunctionTypeDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalFunctionTypeSet> {
 public:
  FunctionTypeDeserializationCluster(bool is_canonical, bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           /*is_deeply_immutable=*/true,
                                           is_root_unit,
                                           "FunctionType") {}
  ~FunctionTypeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, FunctionType::InstanceSize());
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      FunctionTypePtr type = static_cast<FunctionTypePtr>(d.Ref(id));
      Deserializer::InitializeHeader(type, kFunctionTypeCid,
                                     FunctionType::InstanceSize(),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(type);
      type->untag()->set_flags(d.Read<uint8_t>());
      type->untag()->packed_parameter_counts_ = d.Read<uint32_t>();
      type->untag()->packed_type_parameter_counts_ = d.Read<uint16_t>();
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(
          d, refs, Array::Handle(object_store->canonical_function_types()));
      object_store->set_canonical_function_types(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      AbstractType& type = AbstractType::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        type ^= refs.At(i);
        type = type.Canonicalize(d->thread());
        refs.SetAt(i, type);
      }
    }

    FunctionType& type = FunctionType::Handle(d->zone());
    Code& stub = Code::Handle(d->zone());

    if (Snapshot::IncludesCode(d->kind())) {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        type.UpdateTypeTestingStubEntryPoint();
      }
    } else {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        stub = TypeTestingStubGenerator::DefaultCodeForType(type);
        type.InitializeTypeTestingStubNonAtomic(stub);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class RecordTypeSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalRecordTypeSet,
                                              RecordType,
                                              RecordTypePtr> {
 public:
  RecordTypeSerializationCluster(bool is_canonical,
                                 bool is_deeply_immutable,
                                 bool represents_canonical_set)
      : CanonicalSetSerializationCluster(
            kRecordTypeCid,
            is_canonical,
            is_deeply_immutable,
            represents_canonical_set,
            "RecordType",
            compiler::target::RecordType::InstanceSize()) {}
  ~RecordTypeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    RecordTypePtr type = RecordType::RawCast(object);
    objects_.Add(type);
    PushFromTo(type);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);

    for (intptr_t i = 0; i < count; i++) {
      RecordTypePtr type = objects_[i];
      s->AssignRef(type);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WriteRecordType(s, objects_[i]);
    }
  }

 private:
  void WriteRecordType(Serializer* s, RecordTypePtr type) {
    AutoTraceObject(type);
    WriteFromTo(type);
    ASSERT(Utils::IsUint(8, type->untag()->flags()));
    s->Write<uint8_t>(type->untag()->flags());
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class RecordTypeDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalRecordTypeSet> {
 public:
  RecordTypeDeserializationCluster(bool is_canonical, bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           /*is_deeply_immutable=*/true,
                                           is_root_unit,
                                           "RecordType") {}
  ~RecordTypeDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, RecordType::InstanceSize());
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      RecordTypePtr type = static_cast<RecordTypePtr>(d.Ref(id));
      Deserializer::InitializeHeader(type, kRecordTypeCid,
                                     RecordType::InstanceSize(), mark_canonical,
                                     is_deeply_immutable());
      d.ReadFromTo(type);
      type->untag()->set_flags(d.Read<uint8_t>());
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(d, refs,
                         Array::Handle(object_store->canonical_record_types()));
      object_store->set_canonical_record_types(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      AbstractType& type = AbstractType::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        type ^= refs.At(i);
        type = type.Canonicalize(d->thread());
        refs.SetAt(i, type);
      }
    }

    RecordType& type = RecordType::Handle(d->zone());
    Code& stub = Code::Handle(d->zone());

    if (Snapshot::IncludesCode(d->kind())) {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        type.UpdateTypeTestingStubEntryPoint();
      }
    } else {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type ^= refs.At(id);
        stub = TypeTestingStubGenerator::DefaultCodeForType(type);
        type.InitializeTypeTestingStubNonAtomic(stub);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypeParameterSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalTypeParameterSet,
                                              TypeParameter,
                                              TypeParameterPtr> {
 public:
  TypeParameterSerializationCluster(bool is_canonical,
                                    bool is_deeply_immutable,
                                    bool cluster_represents_canonical_set)
      : CanonicalSetSerializationCluster(
            kTypeParameterCid,
            is_canonical,
            is_deeply_immutable,
            cluster_represents_canonical_set,
            "TypeParameter",
            compiler::target::TypeParameter::InstanceSize()) {}
  ~TypeParameterSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypeParameterPtr type = TypeParameter::RawCast(object);
    objects_.Add(type);

    PushFromTo(type);
  }

  void WriteAlloc(Serializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);
    for (intptr_t i = 0; i < count; i++) {
      TypeParameterPtr type = objects_[i];
      s->AssignRef(type);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WriteTypeParameter(s, objects_[i]);
    }
  }

 private:
  void WriteTypeParameter(Serializer* s, TypeParameterPtr type) {
    AutoTraceObject(type);
    WriteFromTo(type);
    s->Write<uint16_t>(type->untag()->base_);
    s->Write<uint16_t>(type->untag()->index_);
    ASSERT(Utils::IsUint(8, type->untag()->flags()));
    s->Write<uint8_t>(type->untag()->flags());
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypeParameterDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalTypeParameterSet> {
 public:
  TypeParameterDeserializationCluster(bool is_canonical, bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           /*is_deeply_immutable=*/true,
                                           is_root_unit,
                                           "TypeParameter") {}
  ~TypeParameterDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, TypeParameter::InstanceSize());
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypeParameterPtr type = static_cast<TypeParameterPtr>(d.Ref(id));
      Deserializer::InitializeHeader(type, kTypeParameterCid,
                                     TypeParameter::InstanceSize(),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(type);
      type->untag()->base_ = d.Read<uint16_t>();
      type->untag()->index_ = d.Read<uint16_t>();
      type->untag()->set_flags(d.Read<uint8_t>());
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(
          d, refs, Array::Handle(object_store->canonical_type_parameters()));
      object_store->set_canonical_type_parameters(table_);
    } else if (!is_root_unit_ && is_canonical()) {
      TypeParameter& type_param = TypeParameter::Handle(d->zone());
      for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
        type_param ^= refs.At(i);
        type_param ^= type_param.Canonicalize(d->thread());
        refs.SetAt(i, type_param);
      }
    }

    TypeParameter& type_param = TypeParameter::Handle(d->zone());
    Code& stub = Code::Handle(d->zone());

    if (Snapshot::IncludesCode(d->kind())) {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type_param ^= refs.At(id);
        type_param.UpdateTypeTestingStubEntryPoint();
      }
    } else {
      for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
        type_param ^= refs.At(id);
        stub = TypeTestingStubGenerator::DefaultCodeForType(type_param);
        type_param.InitializeTypeTestingStubNonAtomic(stub);
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ClosureSerializationCluster : public SerializationCluster {
 public:
  ClosureSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("Closure",
                             kClosureCid,
                             kSizeVaries,
                             is_canonical,
                             is_deeply_immutable) {}
  ~ClosureSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ClosurePtr closure = Closure::RawCast(object);
    objects_.Add(closure);
    const intptr_t length = UntaggedClosure::LengthBits::decode(
        Smi::Value(closure->untag()->length_and_flags()));
    PushFromTo(closure, length);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ClosurePtr closure = objects_[i];
      s->AssignRef(closure);
      AutoTraceObject(closure);
      const intptr_t length = UntaggedClosure::LengthBits::decode(
          Smi::Value(closure->untag()->length_and_flags()));
      s->WriteUnsigned(length);
      target_memory_size_ += compiler::target::Closure::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ClosurePtr closure = objects_[i];
      const intptr_t length = UntaggedClosure::LengthBits::decode(
          Smi::Value(closure->untag()->length_and_flags()));
      AutoTraceObject(closure);
      s->WriteUnsigned(length);
      WriteFromTo(closure, length);
    }
  }

 private:
  GrowableArray<ClosurePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ClosureDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  ClosureDeserializationCluster(bool is_canonical,
                                bool is_deeply_immutable,
                                bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Closure",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit) {}
  ~ClosureDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(Closure::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ClosurePtr closure = static_cast<ClosurePtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(closure, kClosureCid,
                                     Closure::InstanceSize(length),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(closure, length);
#if defined(DART_PRECOMPILED_RUNTIME)
      closure->untag()->entry_point_ = 0;
#endif
    }
  }

#if defined(DART_PRECOMPILED_RUNTIME)
  void PostLoad(Deserializer* d, const Array& refs) override {
    // We only cache the entry point in bare instructions mode (as we need
    // to load the function anyway otherwise).
    ASSERT(d->kind() == Snapshot::kFullAOT);
    auto& closure = Closure::Handle(d->zone());
    auto& func = Function::Handle(d->zone());
    for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
      closure ^= refs.At(i);
      func = closure.function();
      uword entry_point = func.entry_point();
      ASSERT(entry_point != 0);
      closure.ptr()->untag()->entry_point_ = entry_point;
    }
  }
#endif
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class MintSerializationCluster : public SerializationCluster {
 public:
  MintSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("int",
                             kMintCid,
                             kSizeVaries,
                             is_canonical,
                             is_deeply_immutable) {}
  ~MintSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    if (!object->IsHeapObject()) {
      SmiPtr smi = Smi::RawCast(object);
      smis_.Add(smi);
    } else {
      MintPtr mint = Mint::RawCast(object);
      mints_.Add(mint);
    }
  }

  void WriteAlloc(Serializer* s) {
    s->WriteUnsigned(smis_.length() + mints_.length());
    for (intptr_t i = 0; i < smis_.length(); i++) {
      SmiPtr smi = smis_[i];
      s->AssignRef(smi);
      AutoTraceObject(smi);
      const int64_t value = Smi::Value(smi);
      s->Write<int64_t>(value);
      if (!Smi::IsValid(value)) {
        // This Smi will become a Mint when loaded.
        target_memory_size_ += compiler::target::Mint::InstanceSize();
      }
    }
    for (intptr_t i = 0; i < mints_.length(); i++) {
      MintPtr mint = mints_[i];
      s->AssignRef(mint);
      AutoTraceObject(mint);
      s->Write<int64_t>(mint->untag()->value_);
      // All Mints on the host should be Mints on the target.
      ASSERT(!Smi::IsValid(mint->untag()->value_));
      target_memory_size_ += compiler::target::Mint::InstanceSize();
    }
  }

  void WriteFill(Serializer* s) {}

 private:
  GrowableArray<SmiPtr> smis_;
  GrowableArray<MintPtr> mints_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class MintDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  MintDeserializationCluster(bool is_canonical, bool is_root_unit)
      : AbstractInstanceDeserializationCluster("int",
                                               is_canonical,
                                               /*is_deeply_immutable=*/true,
                                               is_root_unit) {}
  ~MintDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    const bool mark_canonical = is_canonical();
    for (intptr_t i = 0; i < count; i++) {
      int64_t value = d->Read<int64_t>();
      if (Smi::IsValid(value)) {
        d->AssignRef(Smi::New(value));
      } else {
        MintPtr mint = static_cast<MintPtr>(d->Allocate(Mint::InstanceSize()));
        Deserializer::InitializeHeader(mint, kMintCid, Mint::InstanceSize(),
                                       mark_canonical,
                                       /*is_deeply_immutable=*/true);
        mint->untag()->value_ = value;
        d->AssignRef(mint);
      }
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {}
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class DoubleSerializationCluster : public SerializationCluster {
 public:
  DoubleSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("double",
                             kDoubleCid,
                             compiler::target::Double::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~DoubleSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    DoublePtr dbl = Double::RawCast(object);
    objects_.Add(dbl);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      DoublePtr dbl = objects_[i];
      s->AssignRef(dbl);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      DoublePtr dbl = objects_[i];
      AutoTraceObject(dbl);
      s->Write<double>(dbl->untag()->value_);
    }
  }

 private:
  GrowableArray<DoublePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class DoubleDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  DoubleDeserializationCluster(bool is_canonical, bool is_root_unit)
      : AbstractInstanceDeserializationCluster("double",
                                               is_canonical,
                                               /*is_deeply_immutable=*/true,
                                               is_root_unit) {
    ASSERT(Object::ShouldHaveDeeplyImmutabilityBitSet(kDoubleCid));
  }
  ~DoubleDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Double::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);
    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      DoublePtr dbl = static_cast<DoublePtr>(d.Ref(id));
      Deserializer::InitializeHeader(dbl, kDoubleCid, Double::InstanceSize(),
                                     mark_canonical,
                                     /*is_deeply_immutable=*/true);
      dbl->untag()->value_ = d.Read<double>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class Simd128SerializationCluster : public SerializationCluster {
 public:
  Simd128SerializationCluster(intptr_t cid,
                              bool is_canonical,
                              bool is_deeply_immutable)
      : SerializationCluster("Simd128",
                             cid,
                             compiler::target::Int32x4::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {
    ASSERT_EQUAL(compiler::target::Int32x4::InstanceSize(),
                 compiler::target::Float32x4::InstanceSize());
    ASSERT_EQUAL(compiler::target::Int32x4::InstanceSize(),
                 compiler::target::Float64x2::InstanceSize());
  }
  ~Simd128SerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) { objects_.Add(object); }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ObjectPtr vector = objects_[i];
      s->AssignRef(vector);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ObjectPtr vector = objects_[i];
      AutoTraceObject(vector);
      ASSERT_EQUAL(Int32x4::value_offset(), Float32x4::value_offset());
      ASSERT_EQUAL(Int32x4::value_offset(), Float64x2::value_offset());
      s->WriteBytes(&(static_cast<Int32x4Ptr>(vector)->untag()->value_),
                    sizeof(simd128_value_t));
    }
  }

 private:
  GrowableArray<ObjectPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class Simd128DeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  Simd128DeserializationCluster(intptr_t cid,
                                bool is_canonical,
                                bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Simd128",
                                               is_canonical,
                                               /*is_deeply_immutable=*/true,
                                               is_root_unit),
        cid_(cid) {}
  ~Simd128DeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ASSERT_EQUAL(Int32x4::InstanceSize(), Float32x4::InstanceSize());
    ASSERT_EQUAL(Int32x4::InstanceSize(), Float64x2::InstanceSize());
    ReadAllocFixedSize(d, Int32x4::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);
    const intptr_t cid = cid_;
    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ObjectPtr vector = d.Ref(id);
      Deserializer::InitializeHeader(vector, cid, Int32x4::InstanceSize(),
                                     mark_canonical,
                                     /*is_deeply_immutable=*/true);
      d.ReadBytes(&(static_cast<Int32x4Ptr>(vector)->untag()->value_),
                  sizeof(simd128_value_t));
    }
  }

 private:
  intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class GrowableObjectArraySerializationCluster : public SerializationCluster {
 public:
  GrowableObjectArraySerializationCluster(bool is_canonical,
                                          bool is_deeply_immutable)
      : SerializationCluster(
            "GrowableObjectArray",
            kGrowableObjectArrayCid,
            compiler::target::GrowableObjectArray::InstanceSize(),
            is_canonical,
            is_deeply_immutable) {}
  ~GrowableObjectArraySerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    GrowableObjectArrayPtr array = GrowableObjectArray::RawCast(object);
    objects_.Add(array);
    PushFromTo(array);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      GrowableObjectArrayPtr array = objects_[i];
      s->AssignRef(array);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      GrowableObjectArrayPtr array = objects_[i];
      AutoTraceObject(array);
      WriteFromTo(array);
    }
  }

 private:
  GrowableArray<GrowableObjectArrayPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class GrowableObjectArrayDeserializationCluster
    : public DeserializationCluster {
 public:
  GrowableObjectArrayDeserializationCluster(bool is_canonical,
                                            bool is_deeply_immutable)
      : DeserializationCluster("GrowableObjectArray",
                               is_canonical,
                               is_deeply_immutable) {}
  ~GrowableObjectArrayDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, GrowableObjectArray::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      GrowableObjectArrayPtr list =
          static_cast<GrowableObjectArrayPtr>(d.Ref(id));
      Deserializer::InitializeHeader(list, kGrowableObjectArrayCid,
                                     GrowableObjectArray::InstanceSize());
      d.ReadFromTo(list);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class RecordSerializationCluster : public SerializationCluster {
 public:
  RecordSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("Record",
                             kRecordCid,
                             kSizeVaries,
                             is_canonical,
                             is_deeply_immutable) {}
  ~RecordSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    RecordPtr record = Record::RawCast(object);
    objects_.Add(record);

    const intptr_t num_fields = Record::NumFields(record);
    for (intptr_t i = 0; i < num_fields; ++i) {
      s->Push(record->untag()->field(i));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; ++i) {
      RecordPtr record = objects_[i];
      s->AssignRef(record);
      AutoTraceObject(record);
      const intptr_t num_fields = Record::NumFields(record);
      s->WriteUnsigned(num_fields);
      target_memory_size_ += compiler::target::Record::InstanceSize(num_fields);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; ++i) {
      RecordPtr record = objects_[i];
      AutoTraceObject(record);
      const RecordShape shape(record->untag()->shape());
      s->WriteUnsigned(shape.AsInt());
      const intptr_t num_fields = shape.num_fields();
      for (intptr_t j = 0; j < num_fields; ++j) {
        s->WriteElementRef(record->untag()->field(j), j);
      }
    }
  }

 private:
  GrowableArray<RecordPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class RecordDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  RecordDeserializationCluster(bool is_canonical,
                               bool is_deeply_immutable,
                               bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Record",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit) {}
  ~RecordDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t num_fields = d->ReadUnsigned();
      d->AssignRef(d->Allocate(Record::InstanceSize(num_fields)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const bool stamp_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      RecordPtr record = static_cast<RecordPtr>(d.Ref(id));
      const intptr_t shape = d.ReadUnsigned();
      const intptr_t num_fields = RecordShape(shape).num_fields();
      Deserializer::InitializeHeader(record, kRecordCid,
                                     Record::InstanceSize(num_fields),
                                     stamp_canonical, is_deeply_immutable());
      record->untag()->shape_ = Smi::New(shape);
      for (intptr_t j = 0; j < num_fields; ++j) {
        record->untag()->data()[j] = d.ReadRef();
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypedDataSerializationCluster : public SerializationCluster {
 public:
  explicit TypedDataSerializationCluster(intptr_t cid)
      : SerializationCluster("TypedData", cid) {}
  ~TypedDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypedDataPtr data = TypedData::RawCast(object);
    objects_.Add(data);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    const intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    for (intptr_t i = 0; i < count; i++) {
      TypedDataPtr data = objects_[i];
      s->AssignRef(data);
      AutoTraceObject(data);
      const intptr_t length = Smi::Value(data->untag()->length());
      s->WriteUnsigned(length);
      target_memory_size_ +=
          compiler::target::TypedData::InstanceSize(length * element_size);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    for (intptr_t i = 0; i < count; i++) {
      TypedDataPtr data = objects_[i];
      AutoTraceObject(data);
      const intptr_t length = Smi::Value(data->untag()->length());
      s->WriteUnsigned(length);
      uint8_t* cdata = reinterpret_cast<uint8_t*>(data->untag()->data());
      s->WriteBytes(cdata, length * element_size);
    }
  }

 private:
  GrowableArray<TypedDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypedDataDeserializationCluster : public DeserializationCluster {
 public:
  explicit TypedDataDeserializationCluster(intptr_t cid)
      : DeserializationCluster("TypedData"), cid_(cid) {}
  ~TypedDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(TypedData::InstanceSize(length * element_size)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);

    const intptr_t cid = cid_;
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypedDataPtr data = static_cast<TypedDataPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      const intptr_t length_in_bytes = length * element_size;
      Deserializer::InitializeHeader(data, cid,
                                     TypedData::InstanceSize(length_in_bytes));
      data->untag()->length_ = Smi::New(length);
      data->untag()->RecomputeDataField();
      uint8_t* cdata = reinterpret_cast<uint8_t*>(data->untag()->data());
      d.ReadBytes(cdata, length_in_bytes);
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class TypedDataViewSerializationCluster : public SerializationCluster {
 public:
  explicit TypedDataViewSerializationCluster(intptr_t cid)
      : SerializationCluster("TypedDataView",
                             cid,
                             compiler::target::TypedDataView::InstanceSize()) {}
  ~TypedDataViewSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypedDataViewPtr view = TypedDataView::RawCast(object);
    objects_.Add(view);

    PushFromTo(view);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      TypedDataViewPtr view = objects_[i];
      s->AssignRef(view);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      TypedDataViewPtr view = objects_[i];
      AutoTraceObject(view);
      WriteFromTo(view);
    }
  }

 private:
  GrowableArray<TypedDataViewPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class TypedDataViewDeserializationCluster : public DeserializationCluster {
 public:
  explicit TypedDataViewDeserializationCluster(intptr_t cid)
      : DeserializationCluster("TypedDataView"), cid_(cid) {}
  ~TypedDataViewDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, TypedDataView::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const intptr_t cid = cid_;
    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypedDataViewPtr view = static_cast<TypedDataViewPtr>(d.Ref(id));
      Deserializer::InitializeHeader(view, cid, TypedDataView::InstanceSize());
      d.ReadFromTo(view);
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    auto& view = TypedDataView::Handle(d->zone());
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      view ^= refs.At(id);
      view.RecomputeDataField();
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ExternalTypedDataSerializationCluster : public SerializationCluster {
 public:
  explicit ExternalTypedDataSerializationCluster(intptr_t cid)
      : SerializationCluster(
            "ExternalTypedData",
            cid,
            compiler::target::ExternalTypedData::InstanceSize()) {}
  ~ExternalTypedDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ExternalTypedDataPtr data = ExternalTypedData::RawCast(object);
    objects_.Add(data);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ExternalTypedDataPtr data = objects_[i];
      s->AssignRef(data);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid_);
    for (intptr_t i = 0; i < count; i++) {
      ExternalTypedDataPtr data = objects_[i];
      AutoTraceObject(data);
      const intptr_t length = Smi::Value(data->untag()->length());
      s->WriteUnsigned(length);
      uint8_t* cdata = reinterpret_cast<uint8_t*>(data->untag()->data_);
      s->Align(ExternalTypedData::kDataSerializationAlignment);
      s->WriteBytes(cdata, length * element_size);
    }
  }

 private:
  GrowableArray<ExternalTypedDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ExternalTypedDataDeserializationCluster : public DeserializationCluster {
 public:
  explicit ExternalTypedDataDeserializationCluster(intptr_t cid)
      : DeserializationCluster("ExternalTypedData"), cid_(cid) {}
  ~ExternalTypedDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, ExternalTypedData::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    const intptr_t cid = cid_;
    intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid);
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ExternalTypedDataPtr data = static_cast<ExternalTypedDataPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(data, cid,
                                     ExternalTypedData::InstanceSize());
      data->untag()->length_ = Smi::New(length);
      d.Align(ExternalTypedData::kDataSerializationAlignment);
      data->untag()->data_ = const_cast<uint8_t*>(d.AddressOfCurrentPosition());
      d.Advance(length * element_size);
      // No finalizer / external size 0.
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class DeltaEncodedTypedDataSerializationCluster : public SerializationCluster {
 public:
  DeltaEncodedTypedDataSerializationCluster()
      : SerializationCluster("DeltaEncodedTypedData",
                             kDeltaEncodedTypedDataCid) {}
  ~DeltaEncodedTypedDataSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    TypedDataPtr data = TypedData::RawCast(object);
    objects_.Add(data);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      const TypedDataPtr data = objects_[i];
      const intptr_t element_size =
          TypedData::ElementSizeInBytes(data->GetClassIdOfHeapObject());
      s->AssignRef(data);
      AutoTraceObject(data);
      const intptr_t length_in_bytes =
          Smi::Value(data->untag()->length()) * element_size;
      s->WriteUnsigned(length_in_bytes);
      target_memory_size_ +=
          compiler::target::TypedData::InstanceSize(length_in_bytes);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    TypedData& typed_data = TypedData::Handle(s->zone());
    for (intptr_t i = 0; i < count; i++) {
      const TypedDataPtr data = objects_[i];
      AutoTraceObject(data);
      const intptr_t cid = data->GetClassIdOfHeapObject();
      // Only Uint16 and Uint32 typed data is supported at the moment. So encode
      // which this is in the low bit of the length. Uint16 is 0, Uint32 is 1.
      ASSERT(cid == kTypedDataUint16ArrayCid ||
             cid == kTypedDataUint32ArrayCid);
      const intptr_t cid_flag = cid == kTypedDataUint16ArrayCid ? 0 : 1;
      const intptr_t length = Smi::Value(data->untag()->length());
      const intptr_t encoded_length = (length << 1) | cid_flag;
      s->WriteUnsigned(encoded_length);
      intptr_t prev = 0;
      typed_data = data;
      for (intptr_t j = 0; j < length; ++j) {
        const intptr_t value = (cid == kTypedDataUint16ArrayCid)
                                   ? typed_data.GetUint16(j << 1)
                                   : typed_data.GetUint32(j << 2);
        ASSERT(value >= prev);
        s->WriteUnsigned(value - prev);
        prev = value;
      }
    }
  }

 private:
  GrowableArray<TypedDataPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class DeltaEncodedTypedDataDeserializationCluster
    : public DeserializationCluster {
 public:
  DeltaEncodedTypedDataDeserializationCluster()
      : DeserializationCluster("DeltaEncodedTypedData") {}
  ~DeltaEncodedTypedDataDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length_in_bytes = d->ReadUnsigned();
      d->AssignRef(d->Allocate(TypedData::InstanceSize(length_in_bytes)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);
    TypedData& typed_data = TypedData::Handle(d_->zone());

    ASSERT(!is_canonical());  // Never canonical.

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      TypedDataPtr data = static_cast<TypedDataPtr>(d.Ref(id));
      const intptr_t encoded_length = d.ReadUnsigned();
      const intptr_t length = encoded_length >> 1;
      const intptr_t cid = (encoded_length & 0x1) == 0
                               ? kTypedDataUint16ArrayCid
                               : kTypedDataUint32ArrayCid;
      const intptr_t element_size = TypedData::ElementSizeInBytes(cid);
      const intptr_t length_in_bytes = length * element_size;
      Deserializer::InitializeHeader(data, cid,
                                     TypedData::InstanceSize(length_in_bytes));
      data->untag()->length_ = Smi::New(length);
      data->untag()->RecomputeDataField();
      intptr_t value = 0;
      typed_data = data;
      for (intptr_t j = 0; j < length; ++j) {
        value += d.ReadUnsigned();
        if (cid == kTypedDataUint16ArrayCid) {
          typed_data.SetUint16(j << 1, static_cast<uint16_t>(value));
        } else {
          typed_data.SetUint32(j << 2, value);
        }
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class StackTraceSerializationCluster : public SerializationCluster {
 public:
  StackTraceSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("StackTrace",
                             kStackTraceCid,
                             compiler::target::StackTrace::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~StackTraceSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    StackTracePtr trace = StackTrace::RawCast(object);
    objects_.Add(trace);
    PushFromTo(trace);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      StackTracePtr trace = objects_[i];
      s->AssignRef(trace);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      StackTracePtr trace = objects_[i];
      AutoTraceObject(trace);
      WriteFromTo(trace);
    }
  }

 private:
  GrowableArray<StackTracePtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class StackTraceDeserializationCluster : public DeserializationCluster {
 public:
  explicit StackTraceDeserializationCluster(bool is_deeply_immutable)
      : DeserializationCluster("StackTrace",
                               /*is_canonical=*/false,
                               is_deeply_immutable) {}
  ~StackTraceDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, StackTrace::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      StackTracePtr trace = static_cast<StackTracePtr>(d.Ref(id));
      Deserializer::InitializeHeader(trace, kStackTraceCid,
                                     StackTrace::InstanceSize());
      d.ReadFromTo(trace);
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class RegExpSerializationCluster : public SerializationCluster {
 public:
  RegExpSerializationCluster(bool is_canonical, bool is_deeply_immutable)
      : SerializationCluster("RegExp",
                             kRegExpCid,
                             compiler::target::RegExp::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~RegExpSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    RegExpPtr regexp = RegExp::RawCast(object);
    objects_.Add(regexp);
    PushFromTo(regexp);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      RegExpPtr regexp = objects_[i];
      s->AssignRef(regexp);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      RegExpPtr regexp = objects_[i];
      AutoTraceObject(regexp);
      WriteFromTo(regexp);
      s->Write<int32_t>(regexp->untag()->num_one_byte_registers_);
      s->Write<int32_t>(regexp->untag()->num_two_byte_registers_);
      s->Write<uint32_t>(regexp->untag()->flags_);
    }
  }

 private:
  GrowableArray<RegExpPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class RegExpDeserializationCluster : public DeserializationCluster {
 public:
  explicit RegExpDeserializationCluster(bool is_deeply_immutable)
      : DeserializationCluster("RegExp",
                               /*is_canonical=*/false,
                               is_deeply_immutable) {}
  ~RegExpDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, RegExp::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      RegExpPtr regexp = static_cast<RegExpPtr>(d.Ref(id));
      Deserializer::InitializeHeader(regexp, kRegExpCid,
                                     RegExp::InstanceSize());
      d.ReadFromTo(regexp);
      regexp->untag()->num_one_byte_registers_ = d.Read<int32_t>();
      regexp->untag()->num_two_byte_registers_ = d.Read<int32_t>();
      regexp->untag()->flags_ = d.Read<uint32_t>();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class WeakPropertySerializationCluster : public SerializationCluster {
 public:
  WeakPropertySerializationCluster()
      : SerializationCluster("WeakProperty",
                             kWeakPropertyCid,
                             compiler::target::WeakProperty::InstanceSize()) {}
  ~WeakPropertySerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    WeakPropertyPtr property = WeakProperty::RawCast(object);
    objects_.Add(property);

    s->PushWeak(property->untag()->key());
  }

  void RetraceEphemerons(Serializer* s) {
    for (intptr_t i = 0; i < objects_.length(); i++) {
      WeakPropertyPtr property = objects_[i];
      if (s->IsReachable(property->untag()->key())) {
        s->Push(property->untag()->value());
      }
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      WeakPropertyPtr property = objects_[i];
      s->AssignRef(property);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WeakPropertyPtr property = objects_[i];
      AutoTraceObject(property);
      if (s->HasRef(property->untag()->key())) {
        s->WriteOffsetRef(property->untag()->key(), WeakProperty::key_offset());
        s->WriteOffsetRef(property->untag()->value(),
                          WeakProperty::value_offset());
      } else {
        s->WriteOffsetRef(Object::null(), WeakProperty::key_offset());
        s->WriteOffsetRef(Object::null(), WeakProperty::value_offset());
      }
    }
  }

 private:
  GrowableArray<WeakPropertyPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class WeakPropertyDeserializationCluster : public DeserializationCluster {
 public:
  WeakPropertyDeserializationCluster()
      : DeserializationCluster("WeakProperty") {}
  ~WeakPropertyDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, WeakProperty::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    ASSERT(!is_canonical());  // Never canonical.
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      WeakPropertyPtr property = static_cast<WeakPropertyPtr>(d.Ref(id));
      Deserializer::InitializeHeader(property, kWeakPropertyCid,
                                     WeakProperty::InstanceSize());
      d.ReadFromTo(property);
      property->untag()->next_seen_by_gc_ = WeakProperty::null();
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class MapSerializationCluster : public SerializationCluster {
 public:
  MapSerializationCluster(bool is_canonical,
                          bool is_deeply_immutable,
                          intptr_t cid)
      : SerializationCluster("Map",
                             cid,
                             compiler::target::Map::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~MapSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    MapPtr map = Map::RawCast(object);
    // We never have mutable hashmaps in snapshots.
    ASSERT(map->untag()->IsCanonical());
    ASSERT_EQUAL(map.GetClassId(), kConstMapCid);
    objects_.Add(map);
    PushFromTo(map);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      MapPtr map = objects_[i];
      s->AssignRef(map);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      MapPtr map = objects_[i];
      AutoTraceObject(map);
      WriteFromTo(map);
    }
  }

 private:
  GrowableArray<MapPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class MapDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  MapDeserializationCluster(intptr_t cid,
                            bool is_canonical,
                            bool is_deeply_immutable,
                            bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Map",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit),
        cid_(cid) {}
  ~MapDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Map::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const intptr_t cid = cid_;
    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      MapPtr map = static_cast<MapPtr>(d.Ref(id));
      Deserializer::InitializeHeader(map, cid, Map::InstanceSize(),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(map);
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class SetSerializationCluster : public SerializationCluster {
 public:
  SetSerializationCluster(bool is_canonical,
                          bool is_deeply_immutable,
                          intptr_t cid)
      : SerializationCluster("Set",
                             cid,
                             compiler::target::Set::InstanceSize(),
                             is_canonical,
                             is_deeply_immutable) {}
  ~SetSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    SetPtr set = Set::RawCast(object);
    // We never have mutable hashsets in snapshots.
    ASSERT(set->untag()->IsCanonical());
    ASSERT_EQUAL(set.GetClassId(), kConstSetCid);
    objects_.Add(set);
    PushFromTo(set);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      SetPtr set = objects_[i];
      s->AssignRef(set);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      SetPtr set = objects_[i];
      AutoTraceObject(set);
      WriteFromTo(set);
    }
  }

 private:
  GrowableArray<SetPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class SetDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  SetDeserializationCluster(intptr_t cid,
                            bool is_canonical,
                            bool is_deeply_immutable,
                            bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Set",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit),
        cid_(cid) {}
  ~SetDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    ReadAllocFixedSize(d, Set::InstanceSize());
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const intptr_t cid = cid_;
    const bool mark_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      SetPtr set = static_cast<SetPtr>(d.Ref(id));
      Deserializer::InitializeHeader(set, cid, Set::InstanceSize(),
                                     mark_canonical, is_deeply_immutable());
      d.ReadFromTo(set);
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class ArraySerializationCluster : public SerializationCluster {
 public:
  ArraySerializationCluster(bool is_canonical,
                            bool is_deeply_immutable,
                            intptr_t cid)
      : SerializationCluster("Array",
                             cid,
                             kSizeVaries,
                             is_canonical,
                             is_deeply_immutable) {}
  ~ArraySerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    ArrayPtr array = Array::RawCast(object);
    objects_.Add(array);

    s->Push(array->untag()->type_arguments());
    const intptr_t length = Smi::Value(array->untag()->length());
    for (intptr_t i = 0; i < length; i++) {
      s->Push(array->untag()->element(i));
    }
  }

#if defined(DART_PRECOMPILER)
  static bool IsReadOnlyCid(intptr_t cid) {
    switch (cid) {
      case kPcDescriptorsCid:
      case kCodeSourceMapCid:
      case kCompressedStackMapsCid:
      case kOneByteStringCid:
      case kTwoByteStringCid:
        return true;
      default:
        return false;
    }
  }
#endif  // defined(DART_PRECOMPILER)

  void WriteAlloc(Serializer* s) {
#if defined(DART_PRECOMPILER)
    if (FLAG_print_array_optimization_candidates) {
      intptr_t array_count = objects_.length();
      intptr_t array_count_allsmi = 0;
      intptr_t array_count_allro = 0;
      intptr_t array_count_empty = 0;
      intptr_t element_count = 0;
      intptr_t element_count_allsmi = 0;
      intptr_t element_count_allro = 0;
      for (intptr_t i = 0; i < array_count; i++) {
        ArrayPtr array = objects_[i];
        bool allsmi = true;
        bool allro = true;
        const intptr_t length = Smi::Value(array->untag()->length());
        for (intptr_t i = 0; i < length; i++) {
          ObjectPtr element = array->untag()->element(i);
          intptr_t cid = element->GetClassId();
          if (!IsReadOnlyCid(cid)) allro = false;
          if (cid != kSmiCid) allsmi = false;
        }
        element_count += length;
        if (length == 0) {
          array_count_empty++;
        } else if (allsmi) {
          array_count_allsmi++;
          element_count_allsmi += length;
        } else if (allro) {
          array_count_allro++;
          element_count_allro += length;
        }
      }
      OS::PrintErr("Arrays\n");
      OS::PrintErr("  total:  %" Pd ", % " Pd " elements\n", array_count,
                   element_count);
      OS::PrintErr("  smi-only:%" Pd ", % " Pd " elements\n",
                   array_count_allsmi, element_count_allsmi);
      OS::PrintErr("  ro-only:%" Pd " , % " Pd " elements\n", array_count_allro,
                   element_count_allro);
      OS::PrintErr("  empty:%" Pd "\n", array_count_empty);
    }
#endif  // defined(DART_PRECOMPILER)

    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ArrayPtr array = objects_[i];
      s->AssignRef(array);
      AutoTraceObject(array);
      const intptr_t length = Smi::Value(array->untag()->length());
      s->WriteUnsigned(length);
      target_memory_size_ += compiler::target::Array::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      ArrayPtr array = objects_[i];
      AutoTraceObject(array);
      const intptr_t length = Smi::Value(array->untag()->length());
      s->WriteUnsigned(length);
      WriteCompressedField(array, type_arguments);
      for (intptr_t j = 0; j < length; j++) {
        s->WriteElementRef(array->untag()->element(j), j);
      }
    }
  }

 private:
  GrowableArray<ArrayPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ArrayDeserializationCluster
    : public AbstractInstanceDeserializationCluster {
 public:
  ArrayDeserializationCluster(intptr_t cid,
                              bool is_canonical,
                              bool is_deeply_immutable,
                              bool is_root_unit)
      : AbstractInstanceDeserializationCluster("Array",
                                               is_canonical,
                                               is_deeply_immutable,
                                               is_root_unit),
        cid_(cid) {}
  ~ArrayDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(Array::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    const intptr_t cid = cid_;
    const bool stamp_canonical = is_root_unit_ && is_canonical();
    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      ArrayPtr array = static_cast<ArrayPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(array, cid, Array::InstanceSize(length),
                                     stamp_canonical, is_deeply_immutable());
      if (Array::UseCardMarkingForAllocation(length)) [[unlikely]] {
        array->untag()->SetCardRememberedBitUnsynchronized();
        Page::Of(array)->AllocateCardTable();
      }
      array->untag()->type_arguments_ =
          static_cast<TypeArgumentsPtr>(d.ReadRef());
      array->untag()->length_ = Smi::New(length);
      for (intptr_t j = 0; j < length; j++) {
        array->untag()->data()[j] = d.ReadRef();
      }
    }
  }

 private:
  const intptr_t cid_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class WeakArraySerializationCluster : public SerializationCluster {
 public:
  WeakArraySerializationCluster()
      : SerializationCluster("WeakArray", kWeakArrayCid, kSizeVaries) {}
  ~WeakArraySerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    WeakArrayPtr array = WeakArray::RawCast(object);
    objects_.Add(array);

    const intptr_t length = Smi::Value(array->untag()->length());
    for (intptr_t i = 0; i < length; i++) {
      s->PushWeak(array->untag()->element(i));
    }
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      WeakArrayPtr array = objects_[i];
      s->AssignRef(array);
      AutoTraceObject(array);
      const intptr_t length = Smi::Value(array->untag()->length());
      s->WriteUnsigned(length);
      target_memory_size_ += compiler::target::WeakArray::InstanceSize(length);
    }
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      WeakArrayPtr array = objects_[i];
      AutoTraceObject(array);
      const intptr_t length = Smi::Value(array->untag()->length());
      s->WriteUnsigned(length);
      for (intptr_t j = 0; j < length; j++) {
        if (s->HasRef(array->untag()->element(j))) {
          s->WriteElementRef(array->untag()->element(j), j);
        } else {
          s->WriteElementRef(Object::null(), j);
        }
      }
    }
  }

 private:
  GrowableArray<WeakArrayPtr> objects_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class WeakArrayDeserializationCluster : public DeserializationCluster {
 public:
  WeakArrayDeserializationCluster() : DeserializationCluster("WeakArray") {}
  ~WeakArrayDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t length = d->ReadUnsigned();
      d->AssignRef(d->Allocate(WeakArray::InstanceSize(length)));
    }
    stop_index_ = d->next_index();
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      WeakArrayPtr array = static_cast<WeakArrayPtr>(d.Ref(id));
      const intptr_t length = d.ReadUnsigned();
      Deserializer::InitializeHeader(array, kWeakArrayCid,
                                     WeakArray::InstanceSize(length));
      array->untag()->next_seen_by_gc_ = WeakArray::null();
      array->untag()->length_ = Smi::New(length);
      for (intptr_t j = 0; j < length; j++) {
        array->untag()->data()[j] = d.ReadRef();
      }
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class StringSerializationCluster
    : public CanonicalSetSerializationCluster<CanonicalStringSet,
                                              String,
                                              StringPtr> {
 public:
  // To distinguish one and two byte strings, we put a bit in the length to
  // indicate which it is. The length is an unsigned SMI, so we actually have
  // two spare bits available. Keep in sync with DecodeLengthAndCid.
  static intptr_t EncodeLengthAndCid(intptr_t length, intptr_t cid) {
    ASSERT(cid == kOneByteStringCid || cid == kTwoByteStringCid);
    ASSERT(length <= compiler::target::kSmiMax);
    return (length << 1) | (cid == kTwoByteStringCid ? 0x1 : 0x0);
  }

  StringSerializationCluster(bool is_canonical,
                             bool is_deeply_immutable,
                             bool represents_canonical_set)
      : CanonicalSetSerializationCluster(kStringCid,
                                         is_canonical,
                                         is_deeply_immutable,
                                         represents_canonical_set,
                                         "String",
                                         kSizeVaries) {}
  ~StringSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) {
    StringPtr str = static_cast<StringPtr>(object);
    objects_.Add(str);
  }

  void WriteAlloc(Serializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    ReorderObjects(s);
    for (intptr_t i = 0; i < count; i++) {
      StringPtr str = objects_[i];
      s->AssignRef(str);
      AutoTraceObject(str);
      const intptr_t cid = str->GetClassIdOfHeapObject();
      const intptr_t length = Smi::Value(str->untag()->length());
      const intptr_t encoded = EncodeLengthAndCid(length, cid);
      s->WriteUnsigned(encoded);
      target_memory_size_ +=
          cid == kOneByteStringCid
              ? compiler::target::OneByteString::InstanceSize(length)
              : compiler::target::TwoByteString::InstanceSize(length);
    }
    WriteCanonicalSetLayout(s);
  }

  void WriteFill(Serializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      StringPtr str = objects_[i];
      AutoTraceObject(str);
      const intptr_t cid = str->GetClassIdOfHeapObject();
      const intptr_t length = Smi::Value(str->untag()->length());
      const intptr_t encoded = EncodeLengthAndCid(length, cid);
      s->WriteUnsigned(encoded);
      if (cid == kOneByteStringCid) {
        s->WriteBytes(static_cast<OneByteStringPtr>(str)->untag()->data(),
                      length);
      } else {
        s->WriteBytes(reinterpret_cast<uint8_t*>(
                          static_cast<TwoByteStringPtr>(str)->untag()->data()),
                      length * 2);
      }
    }
  }
};
#endif  // !DART_PRECOMPILED_RUNTIME

class StringDeserializationCluster
    : public CanonicalSetDeserializationCluster<CanonicalStringSet> {
 public:
  static intptr_t DecodeLengthAndCid(intptr_t encoded, intptr_t* out_cid) {
    *out_cid = (encoded & 0x1) != 0 ? kTwoByteStringCid : kOneByteStringCid;
    return encoded >> 1;
  }

  static intptr_t InstanceSize(intptr_t length, intptr_t cid) {
    return cid == kOneByteStringCid ? OneByteString::InstanceSize(length)
                                    : TwoByteString::InstanceSize(length);
  }

  StringDeserializationCluster(bool is_canonical, bool is_root_unit)
      : CanonicalSetDeserializationCluster(is_canonical,
                                           /*is_deeply_immutable=*/true,
                                           is_root_unit,
                                           "String") {}
  ~StringDeserializationCluster() {}

  void ReadAlloc(Deserializer* d) override {
    start_index_ = d->next_index();
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      const intptr_t encoded = d->ReadUnsigned();
      intptr_t cid = 0;
      const intptr_t length = DecodeLengthAndCid(encoded, &cid);
      d->AssignRef(d->Allocate(InstanceSize(length, cid)));
    }
    stop_index_ = d->next_index();
    BuildCanonicalSetFromLayout(d);
  }

  void ReadFill(Deserializer* d_) override {
    Deserializer::Local d(d_);

    for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
      StringPtr str = static_cast<StringPtr>(d.Ref(id));
      const intptr_t encoded = d.ReadUnsigned();
      intptr_t cid = 0;
      const intptr_t length = DecodeLengthAndCid(encoded, &cid);
      const intptr_t instance_size = InstanceSize(length, cid);
      // Clean up last two words of the string object to simplify future
      // string comparisons.
      // Objects are rounded up to two-word size boundary.
      *reinterpret_cast<word*>(reinterpret_cast<uint8_t*>(str->untag()) +
                               instance_size - 1 * kWordSize) = 0;
      *reinterpret_cast<word*>(reinterpret_cast<uint8_t*>(str->untag()) +
                               instance_size - 2 * kWordSize) = 0;
      Deserializer::InitializeHeader(str, cid, instance_size, is_canonical(),
                                     /*is_deeply_immutable=*/true);
#if DART_COMPRESSED_POINTERS
      // Gap caused by less-than-a-word length_ smi sitting before data_.
      const intptr_t length_offset =
          reinterpret_cast<intptr_t>(&str->untag()->length_);
      const intptr_t data_offset =
          cid == kOneByteStringCid
              ? reinterpret_cast<intptr_t>(
                    static_cast<OneByteStringPtr>(str)->untag()->data())
              : reinterpret_cast<intptr_t>(
                    static_cast<TwoByteStringPtr>(str)->untag()->data());
      const intptr_t length_with_gap = data_offset - length_offset;
      ASSERT(length_with_gap > kCompressedWordSize);
      ASSERT(length_with_gap == kWordSize);
      memset(reinterpret_cast<void*>(length_offset), 0, length_with_gap);
#endif
      str->untag()->length_ = Smi::New(length);

      StringHasher hasher;
      if (cid == kOneByteStringCid) {
        for (intptr_t j = 0; j < length; j++) {
          uint8_t code_unit = d.Read<uint8_t>();
          static_cast<OneByteStringPtr>(str)->untag()->data()[j] = code_unit;
          hasher.Add(code_unit);
        }

      } else {
        for (intptr_t j = 0; j < length; j++) {
          uint16_t code_unit = d.Read<uint8_t>();
          code_unit = code_unit | (d.Read<uint8_t>() << 8);
          static_cast<TwoByteStringPtr>(str)->untag()->data()[j] = code_unit;
          hasher.Add(code_unit);
        }
      }
      String::SetCachedHash(str, hasher.Finalize());
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    if (!table_.IsNull()) {
      // TODO(bootstrap-only-in-gen-snapshot)
      // This table is incomplete because the cluster only includes written
      // strings, missing the strings that were base objects.
      if (!Snapshot::IncludesCode(d->kind())) return;

      auto object_store = d->isolate_group()->object_store();
      VerifyCanonicalSet(d, refs,
                         WeakArray::Handle(object_store->symbol_table()));
      object_store->set_symbol_table(table_);
#if defined(DEBUG)
      Symbols::New(Thread::Current(), ":some:new:symbol:");
      ASSERT(object_store->symbol_table() == table_.ptr());  // Did not rehash.
#endif
    }
  }
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class FakeSerializationCluster : public SerializationCluster {
 public:
  FakeSerializationCluster(const char* name,
                           intptr_t num_objects,
                           intptr_t size,
                           intptr_t target_memory_size = 0)
      : SerializationCluster(name, -1) {
    num_objects_ = num_objects;
    size_ = size;
    target_memory_size_ = target_memory_size;
  }
  ~FakeSerializationCluster() {}

  void Trace(Serializer* s, ObjectPtr object) { UNREACHABLE(); }
  void WriteAlloc(Serializer* s) { UNREACHABLE(); }
  void WriteFill(Serializer* s) { UNREACHABLE(); }
};
#endif  // !DART_PRECOMPILED_RUNTIME

static bool IsAbsentCid(intptr_t cid) {
  switch (cid) {
    case kObjectCid:
    case kErrorCid:
    case kCallSiteDataCid:

    case kAbstractTypeCid:
    case kFinalizerBaseCid:
    case kTypedDataBaseCid:
    case kTypedDataCid:
    case kExternalTypedDataCid:
    case kTypedDataViewCid:
    case kStringCid:
    case kFfiStructCid:
      return true;
    default:
      return false;
  }
}

#if !defined(DART_PRECOMPILED_RUNTIME)
class ProgramSerializationRoots : public SerializationRoots {
 public:
#define RESET_ROOT_LIST(V)                                                     \
  ONLY_IF_CODE(                                                                \
      V(symbol_table, WeakArray, HashTables::New<CanonicalStringSet>(4)))      \
  V(canonical_types, Array, HashTables::New<CanonicalTypeSet>(4))              \
  V(canonical_function_types, Array,                                           \
    HashTables::New<CanonicalFunctionTypeSet>(4))                              \
  V(canonical_record_types, Array, HashTables::New<CanonicalRecordTypeSet>(4)) \
  V(canonical_type_arguments, Array,                                           \
    HashTables::New<CanonicalTypeArgumentsSet>(4))                             \
  V(canonical_type_parameters, Array,                                          \
    HashTables::New<CanonicalTypeParameterSet>(4))                             \
  ONLY_IN_PRODUCT(ONLY_IN_AOT(                                                 \
      V(closure_functions, GrowableObjectArray, GrowableObjectArray::null()))) \
  ONLY_IN_AOT(V(closure_functions_table, Array, Array::null()))                \
  ONLY_IN_AOT(V(canonicalized_stack_map_entries, CompressedStackMaps,          \
                CompressedStackMaps::null()))

  ProgramSerializationRoots(ObjectStore* object_store,
                            Snapshot::Kind snapshot_kind)
      : object_store_(object_store), snapshot_kind_(snapshot_kind) {
#define ONLY_IF_CODE(code)                                                     \
  if (Snapshot::IncludesCode(snapshot_kind_)) {                                \
    code                                                                       \
  }
#define ONLY_IN_AOT(code)                                                      \
  if (snapshot_kind_ == Snapshot::kFullAOT) {                                  \
    code                                                                       \
  }
#define SAVE_AND_RESET_ROOT(name, Type, init)                                  \
  do {                                                                         \
    saved_##name##_ = object_store->name();                                    \
    object_store->set_##name(Type::Handle(init));                              \
  } while (0);

    RESET_ROOT_LIST(SAVE_AND_RESET_ROOT)
#undef SAVE_AND_RESET_ROOT
#undef ONLY_IN_AOT
#undef ONLY_IF_CODE
  }
  ~ProgramSerializationRoots() {
#define ONLY_IF_CODE(code)                                                     \
  if (Snapshot::IncludesCode(snapshot_kind_)) {                                \
    code                                                                       \
  }
#define ONLY_IN_AOT(code)                                                      \
  if (snapshot_kind_ == Snapshot::kFullAOT) {                                  \
    code                                                                       \
  }
#define RESTORE_ROOT(name, Type, init)                                         \
  object_store_->set_##name(saved_##name##_);
    RESET_ROOT_LIST(RESTORE_ROOT)
#undef RESTORE_ROOT
#undef ONLY_IN_AOT
#undef ONLY_IF_CODE
  }

  void AddBaseObjects(Serializer* s) {
    // TODO(bootstrap-only-in-gen-snapshot): Always case 1.
    if (Snapshot::IncludesCode(s->kind())) {
      s->AddBaseObject(Roots::null_obj(), "Null", "null");
      s->AddBaseObject(Roots::false_obj(), "bool", "false");
      s->AddBaseObject(Roots::true_obj(), "bool", "true");
      s->AddBaseObject(Roots::sentinel_obj(), "Sentinel", "sentinel");
      s->AddBaseObject(Roots::unknown_constant_obj(), "Sentinel",
                       "unknown constant");
      s->AddBaseObject(Roots::non_constant_obj(), "Sentinel", "non constant");
      s->AddBaseObject(Roots::optimized_out_obj(), "Sentinel",
                       "<optimized out>");
    } else {
      Roots* roots = Roots::Current();
      for (ObjectPtr* ptr = roots->from(); ptr <= roots->to(); ptr++) {
        s->AddBaseObject(*ptr, "", "<raw root>");
      }
      for (auto* handle = roots->fromh(); handle <= roots->toh(); handle++) {
        s->AddBaseObject(handle->ptr, "", "<handle root>");
      }
      ClassTable* table = s->isolate_group()->class_table();
      for (intptr_t cid = kObjectCid; cid < kInstanceCid; cid++) {
        if (IsAbsentCid(cid)) continue;
        ASSERT(table->HasValidClassAt(cid));
        s->AddBaseObject(
            table->At(cid), "Class",
            Class::Handle(table->At(cid))
                .NameCString(Object::NameVisibility::kInternalName));
      }
    }
  }

  void PushRoots(Serializer* s) {
    // TODO(bootstrap-only-in-gen-snapshot): Always case 1.
    if (Snapshot::IncludesCode(s->kind())) {
      // Place popular objects into early clusters so their refs will have short
      // encodings.
      // TODO(rmacnak): Should we handle these like null instead?
      s->Push(Object::empty_array().ptr());
      s->Push(Object::empty_instantiations_cache_array().ptr());
      s->Push(Object::empty_subtype_test_cache_array().ptr());
      s->Push(Object::empty_context_scope().ptr());
      s->Push(Object::empty_object_pool().ptr());
      s->Push(Object::empty_compressed_stackmaps().ptr());
      s->Push(Object::empty_descriptors().ptr());
      s->Push(Object::empty_var_descriptors().ptr());
      s->Push(Object::empty_exception_handlers().ptr());
      s->Push(Object::empty_async_exception_handlers().ptr());
      s->CreateEarlyClusters();

      Roots* roots = Roots::Current();
      for (ObjectPtr* ptr = roots->from(); ptr <= roots->to(); ptr++) {
        s->Push(*ptr);
      }
      for (auto* handle = roots->fromh(); handle <= roots->toh(); handle++) {
        s->Push(handle->ptr);
      }
      for (ObjectPtr* ptr = roots->fromah(); ptr <= roots->toah(); ptr++) {
        s->Push(*ptr);
      }
      ClassTable* table = s->isolate_group()->class_table();
      for (intptr_t cid = kObjectCid; cid < kInstanceCid; cid++) {
        if (IsAbsentCid(cid)) continue;
        s->Push(table->At(cid));
      }

      for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
        if (IsAbsentCid(cid)) continue;
        ASSERT(table->HasValidClassAt(cid));
        s->Push(table->At(cid));
      }
    }

    ObjectPtr* from = object_store_->from();
    ObjectPtr* to = object_store_->to_snapshot(s->kind());
    for (ObjectPtr* p = from; p <= to; p++) {
      s->Push(*p);
    }

    FieldTable* initial_field_table =
        s->thread()->isolate_group()->initial_field_table();
    for (intptr_t i = 0, n = initial_field_table->NumFieldIds(); i < n; i++) {
      s->Push(initial_field_table->At(i));
    }

    FieldTable* shared_initial_field_table =
        s->thread()->isolate_group()->shared_initial_field_table();
    for (intptr_t i = 0, n = shared_initial_field_table->NumFieldIds(); i < n;
         i++) {
      s->Push(shared_initial_field_table->At(i));
    }

    dispatch_table_entries_ = object_store_->dispatch_table_code_entries();
    // We should only have a dispatch table in precompiled mode.
    ASSERT(dispatch_table_entries_.IsNull() || s->kind() == Snapshot::kFullAOT);

#if defined(DART_PRECOMPILER)
    // We treat the dispatch table as a root object and trace the Code objects
    // it references. Otherwise, a non-empty entry could be invalid on
    // deserialization if the corresponding Code object was not reachable from
    // the existing snapshot roots.
    if (!dispatch_table_entries_.IsNull()) {
      for (intptr_t i = 0; i < dispatch_table_entries_.Length(); i++) {
        s->Push(dispatch_table_entries_.At(i));
      }
    }
#endif
  }

  void WriteRoots(Serializer* s) {
    // TODO(bootstrap-only-in-gen-snapshot): Always case 1.
    if (Snapshot::IncludesCode(s->kind())) {
      Roots* roots = Roots::Current();
      for (ObjectPtr* ptr = roots->from(); ptr <= roots->to(); ptr++) {
        s->WriteRootRef(*ptr, "<raw root>");
      }
      for (auto* handle = roots->fromh(); handle <= roots->toh(); handle++) {
        s->WriteRootRef(handle->ptr, "<handle root>");
      }
      for (ObjectPtr* ptr = roots->fromah(); ptr <= roots->toah(); ptr++) {
        s->WriteRootRef(*ptr, "<api handle root>");
      }

      // Just to appease v8_snapshot_profile_writer_test.
      ClassTable* table = s->isolate_group()->class_table();
      for (intptr_t cid = kObjectCid; cid < kInstanceCid; cid++) {
        if (IsAbsentCid(cid)) continue;
        s->WriteRootRef(table->At(cid), "<class table root>");
      }
      for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
        if (IsAbsentCid(cid)) continue;
        ASSERT(table->HasValidClassAt(cid));
        s->WriteRootRef(table->At(cid), "<class table root>");
      }
    }

    ObjectPtr* from = object_store_->from();
    ObjectPtr* to = object_store_->to_snapshot(s->kind());
    // A strtab is smaller than an array of strings.
    static const char* const names = ""
#define EMIT_FIELD_NAME(type, name) #name "_\0"
        OBJECT_STORE_FIELD_LIST(
            EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME,
            EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME,
            EMIT_FIELD_NAME)
#undef EMIT_FIELD_NAME
        ;  // NOLINT
    const char* name = names;
    for (ObjectPtr* p = from; p <= to; p++) {
      s->WriteRootRef(*p, name);
      name += strlen(name) + 1;
    }

    FieldTable* initial_field_table =
        s->thread()->isolate_group()->initial_field_table();
    intptr_t n = initial_field_table->NumFieldIds();
    s->WriteUnsigned(n);
    for (intptr_t i = 0; i < n; i++) {
      s->WriteRootRef(initial_field_table->At(i), "some-static-field");
    }

    FieldTable* shared_initial_field_table =
        s->thread()->isolate_group()->shared_initial_field_table();
    intptr_t n_shared = shared_initial_field_table->NumFieldIds();
    s->WriteUnsigned(n_shared);
    for (intptr_t i = 0; i < n_shared; i++) {
      s->WriteRootRef(shared_initial_field_table->At(i),
                      "some-shared-static-field");
    }

    // The dispatch table is serialized only for precompiled snapshots.
    s->WriteDispatchTable(dispatch_table_entries_);
  }

  virtual const CompressedStackMaps& canonicalized_stack_map_entries() const {
    return saved_canonicalized_stack_map_entries_;
  }

 private:
  ObjectStore* const object_store_;
  const Snapshot::Kind snapshot_kind_;
  Array& dispatch_table_entries_ = Array::Handle();

#define ONLY_IF_CODE(code) code
#define ONLY_IN_AOT(code) code
#define DECLARE_FIELD(name, Type, init) Type& saved_##name##_ = Type::Handle();
  RESET_ROOT_LIST(DECLARE_FIELD)
#undef DECLARE_FIELD
#undef ONLY_IN_AOT
#undef ONLY_IF_CODE
};
#endif  // !DART_PRECOMPILED_RUNTIME

class ProgramDeserializationRoots : public DeserializationRoots {
 public:
  explicit ProgramDeserializationRoots(ObjectStore* object_store)
      : object_store_(object_store) {}

  void AddBaseObjects(Deserializer* d) override {
    // TODO(bootstrap-only-in-gen-snapshot): Always case 1.
    if (Snapshot::IncludesCode(d->kind())) {
      d->AddBaseObject(Roots::null_obj());
      d->AddBaseObject(Roots::false_obj());
      d->AddBaseObject(Roots::true_obj());
      d->AddBaseObject(Roots::sentinel_obj());
      d->AddBaseObject(Roots::unknown_constant_obj());
      d->AddBaseObject(Roots::non_constant_obj());
      d->AddBaseObject(Roots::optimized_out_obj());
    } else {
      Roots* roots = Roots::Current();
      for (ObjectPtr* ptr = roots->from(); ptr <= roots->to(); ptr++) {
        d->AddBaseObject(*ptr);
      }
      for (auto* handle = roots->fromh(); handle <= roots->toh(); handle++) {
        d->AddBaseObject(handle->ptr);
      }
      ClassTable* table = d->isolate_group()->class_table();
      for (intptr_t cid = kObjectCid; cid < kInstanceCid; cid++) {
        if (IsAbsentCid(cid)) continue;
        ASSERT(table->HasValidClassAt(cid));
        d->AddBaseObject(table->At(cid));
      }
    }
  }

  void ReadRoots(Deserializer* d) override {
    // TODO(bootstrap-only-in-gen-snapshot): Always case 1.
    if (Snapshot::IncludesCode(d->kind())) {
      Roots* roots = Roots::Current();
      for (ObjectPtr* ptr = roots->from(); ptr <= roots->to(); ptr++) {
        *ptr = d->ReadRef();
      }
      for (auto* handle = roots->fromh(); handle <= roots->toh(); handle++) {
        handle->ptr = d->ReadRef();
      }
      for (ObjectPtr* ptr = roots->fromah(); ptr <= roots->toah(); ptr++) {
        *ptr = d->ReadRef();
      }
      roots->InitVTables();

      // Just to appease v8_snapshot_profile_writer_test.
      ClassTable* table = d->isolate_group()->class_table();
      for (intptr_t cid = kObjectCid; cid < kInstanceCid; cid++) {
        if (IsAbsentCid(cid)) continue;
        table->SetAt(cid, (ClassPtr)d->ReadRef());
      }
      for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
        if (IsAbsentCid(cid)) continue;
        table->SetAt(cid, (ClassPtr)d->ReadRef());
      }
    }

    // Read roots.
    ObjectPtr* from = object_store_->from();
    ObjectPtr* to = object_store_->to_snapshot(d->kind());
    for (ObjectPtr* p = from; p <= to; p++) {
      *p = d->ReadRef();
    }

    {
      FieldTable* initial_field_table =
          d->thread()->isolate_group()->initial_field_table();
      intptr_t n = d->ReadUnsigned();
      initial_field_table->AllocateIndex(n - 1);
      for (intptr_t i = 0; i < n; i++) {
        initial_field_table->SetAt(i, d->ReadRef());
      }
    }

    {
      FieldTable* shared_initial_field_table =
          d->thread()->isolate_group()->shared_initial_field_table();
      intptr_t n_shared = d->ReadUnsigned();
      if (n_shared > 0) {
        shared_initial_field_table->AllocateIndex(n_shared - 1);
        for (intptr_t i = 0; i < n_shared; i++) {
          shared_initial_field_table->SetAt(i, d->ReadRef());
        }
      }
    }

    // Deserialize dispatch table (when applicable)
    d->ReadDispatchTable();
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    auto isolate_group = d->isolate_group();
    {
      isolate_group->class_table()->CopySizesFromClassObjects();
    }
    d->heap()->old_space()->EvaluateAfterLoading();

    if (Snapshot::IncludesCode(d->kind())) {
      d->thread()->InitVMConstants();
    }

    auto object_store = isolate_group->object_store();
    const Array& units = Array::Handle(object_store->loading_units());
    if (!units.IsNull()) {
      LoadingUnit& unit = LoadingUnit::Handle();
      unit ^= units.At(LoadingUnit::kRootId);
      unit.set_base_objects(refs);
    }

    // Setup native resolver for bootstrap impl.
    Bootstrap::SetupNativeResolver();
  }

 private:
  ObjectStore* object_store_;
};

#if !defined(DART_PRECOMPILED_RUNTIME)
class UnitSerializationRoots : public SerializationRoots {
 public:
  explicit UnitSerializationRoots(LoadingUnitSerializationData* unit)
      : unit_(unit) {}

  void AddBaseObjects(Serializer* s) {
    ZoneGrowableArray<Object*>* objects = unit_->parent()->objects();
    for (intptr_t i = 0; i < objects->length(); i++) {
      s->AddBaseObject(objects->At(i)->ptr());
    }
  }

  void PushRoots(Serializer* s) {
    for (auto deferred_object : *unit_->deferred_objects()) {
      ASSERT(deferred_object->IsCode());
      CodePtr code = static_cast<CodePtr>(deferred_object->ptr());
      ObjectPoolPtr pool = code->untag()->object_pool_;
      if (pool != ObjectPool::null()) {
        const intptr_t length = pool->untag()->length_;
        uint8_t* entry_bits = pool->untag()->entry_bits();
        for (intptr_t i = 0; i < length; i++) {
          auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
          if (entry_type == ObjectPool::EntryType::kTaggedObject) {
            s->Push(pool->untag()->data()[i].raw_obj_);
          }
        }
      }
      s->Push(code->untag()->code_source_map_);
    }
  }

  void WriteRoots(Serializer* s) {
#if defined(DART_PRECOMPILER)
    intptr_t start_index = 0;
    intptr_t num_deferred_objects = unit_->deferred_objects()->length();
    if (num_deferred_objects != 0) {
      start_index = s->RefId(unit_->deferred_objects()->At(0)->ptr());
      ASSERT(start_index > 0);
    }
    s->WriteUnsigned(start_index);
    s->WriteUnsigned(num_deferred_objects);
    for (intptr_t i = 0; i < num_deferred_objects; i++) {
      const Object* deferred_object = (*unit_->deferred_objects())[i];
      ASSERT(deferred_object->IsCode());
      CodePtr code = static_cast<CodePtr>(deferred_object->ptr());
      ASSERT(s->RefId(code) == (start_index + i));
      ASSERT(!Code::IsDiscarded(code));
      s->WriteInstructions(code->untag()->instructions_,
                           code->untag()->unchecked_offset_, code, false);
      s->WriteRootRef(code->untag()->code_source_map_, "deferred-code");
    }

    ObjectPoolPtr pool =
        s->isolate_group()->object_store()->global_object_pool();
    const intptr_t length = pool->untag()->length_;
    uint8_t* entry_bits = pool->untag()->entry_bits();
    intptr_t last_write = 0;
    for (intptr_t i = 0; i < length; i++) {
      auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
      if (entry_type == ObjectPool::EntryType::kTaggedObject) {
        if (s->IsWritten(pool->untag()->data()[i].raw_obj_)) {
          intptr_t skip = i - last_write;
          s->WriteUnsigned(skip);
          s->WriteRootRef(pool->untag()->data()[i].raw_obj_,
                          "deferred-literal");
          last_write = i;
        }
      }
    }
    s->WriteUnsigned(length - last_write);
#endif
  }

 private:
  LoadingUnitSerializationData* unit_;
};
#endif  // !DART_PRECOMPILED_RUNTIME

class UnitDeserializationRoots : public DeserializationRoots {
 public:
  explicit UnitDeserializationRoots(const LoadingUnit& unit) : unit_(unit) {}

  void AddBaseObjects(Deserializer* d) override {
    const Array& base_objects =
        Array::Handle(LoadingUnit::Handle(unit_.parent()).base_objects());
    for (intptr_t i = kFirstReference; i < base_objects.Length(); i++) {
      d->AddBaseObject(base_objects.At(i));
    }
  }

  void ReadRoots(Deserializer* d) override {
    deferred_start_index_ = d->ReadUnsigned();
    deferred_stop_index_ = deferred_start_index_ + d->ReadUnsigned();
    for (intptr_t id = deferred_start_index_; id < deferred_stop_index_; id++) {
      CodePtr code = static_cast<CodePtr>(d->Ref(id));
      ASSERT(!Code::IsUnknownDartCode(code));
      d->ReadInstructions(code, /*deferred=*/false);
      if (code->untag()->owner_->IsHeapObject() &&
          code->untag()->owner_->IsFunction()) {
        FunctionPtr func = static_cast<FunctionPtr>(code->untag()->owner_);
        uword entry_point = code->untag()->entry_point_;
        ASSERT(entry_point != 0);
        func->untag()->entry_point_ = entry_point;
        uword unchecked_entry_point = code->untag()->unchecked_entry_point_;
        ASSERT(unchecked_entry_point != 0);
        func->untag()->unchecked_entry_point_ = unchecked_entry_point;
#if defined(DART_PRECOMPILED_RUNTIME)
        if (func->untag()->data()->IsHeapObject() &&
            func->untag()->data()->IsClosureData()) {
          // For closure functions in bare instructions mode, also update the
          // cache inside the static implicit closure object, if any.
          auto data = static_cast<ClosureDataPtr>(func->untag()->data());
          if (data->untag()->closure() != Closure::null()) {
            // Closure functions only have one entry point.
            ASSERT_EQUAL(entry_point, unchecked_entry_point);
            data->untag()->closure()->untag()->entry_point_ = entry_point;
          }
        }
#endif
      }
      code->untag()->code_source_map_ =
          static_cast<CodeSourceMapPtr>(d->ReadRef());
    }

    ObjectPoolPtr pool =
        d->isolate_group()->object_store()->global_object_pool();
    const intptr_t length = pool->untag()->length_;
    uint8_t* entry_bits = pool->untag()->entry_bits();
    for (intptr_t i = d->ReadUnsigned(); i < length; i += d->ReadUnsigned()) {
      auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
      ASSERT(entry_type == ObjectPool::EntryType::kTaggedObject);
      // The existing entry will usually be null, but it might also be an
      // equivalent object that was duplicated in another loading unit.
      pool->untag()->data()[i].raw_obj_ = d->ReadRef();
    }

    // Reinitialize the dispatch table by rereading the table's serialization
    // in the root snapshot.
    auto isolate_group = d->isolate_group();
    if (isolate_group->dispatch_table_snapshot() != nullptr) {
      ReadStream stream(isolate_group->dispatch_table_snapshot(),
                        isolate_group->dispatch_table_snapshot_size());
      const GrowableObjectArray& tables = GrowableObjectArray::Handle(
          isolate_group->object_store()->instructions_tables());
      InstructionsTable& root_table = InstructionsTable::Handle();
      root_table ^= tables.At(0);
      d->ReadDispatchTable(&stream, /*deferred=*/true, root_table,
                           deferred_start_index_, deferred_stop_index_);
    }
  }

  void PostLoad(Deserializer* d, const Array& refs) override {
    d->EndInstructions();
    unit_.set_base_objects(refs);
  }

 private:
  const LoadingUnit& unit_;
  intptr_t deferred_start_index_;
  intptr_t deferred_stop_index_;
};

#if defined(DEBUG)
static constexpr int32_t kSectionMarker = 0xABAB;
#endif

Serializer::Serializer(Thread* thread,
                       Snapshot::Kind kind,
                       NonStreamingWriteStream* stream,
                       ImageWriter* image_writer,
                       V8SnapshotProfileWriter* profile_writer)
    : ThreadStackResource(thread),
      heap_(thread->isolate_group()->heap()),
      zone_(thread->zone()),
      kind_(kind),
      stream_(stream),
      image_writer_(image_writer),
      canonical_clusters_by_cid_(nullptr),
      immutable_clusters_by_cid_(nullptr),
      clusters_by_cid_(nullptr),
      stack_(),
      num_cids_(0),
      num_tlc_cids_(0),
      num_base_objects_(0),
      num_written_objects_(0),
      next_ref_index_(kFirstReference),
      profile_writer_(profile_writer)
#if defined(SNAPSHOT_BACKTRACE)
      ,
      current_parent_(Object::null())
#endif
#if defined(DART_PRECOMPILER)
      ,
      deduped_instructions_sources_(zone_)
#endif
{
  num_cids_ = thread->isolate_group()->class_table()->NumCids();
  num_tlc_cids_ = thread->isolate_group()->class_table()->NumTopLevelCids();
  canonical_clusters_by_cid_ = new SerializationCluster*[num_cids_];
  for (intptr_t i = 0; i < num_cids_; i++) {
    canonical_clusters_by_cid_[i] = nullptr;
  }
  immutable_clusters_by_cid_ = new SerializationCluster*[num_cids_];
  for (intptr_t i = 0; i < num_cids_; i++) {
    immutable_clusters_by_cid_[i] = nullptr;
  }
  clusters_by_cid_ = new SerializationCluster*[num_cids_];
  for (intptr_t i = 0; i < num_cids_; i++) {
    clusters_by_cid_[i] = nullptr;
  }
  if (profile_writer_ != nullptr) {
    offsets_table_ = new (zone_) OffsetsTable(zone_);
  }
}

Serializer::~Serializer() {
  delete[] canonical_clusters_by_cid_;
  delete[] immutable_clusters_by_cid_;
  delete[] clusters_by_cid_;
}

void Serializer::AddBaseObject(ObjectPtr base_object,
                               const char* type,
                               const char* name) {
  // Don't assign references to the discarded code.
  const bool is_discarded_code = base_object->IsHeapObject() &&
                                 base_object->IsCode() &&
                                 Code::IsDiscarded(Code::RawCast(base_object));
  if (!is_discarded_code) {
    AssignRef(base_object);
  }
  num_base_objects_++;

  if ((profile_writer_ != nullptr) && (type != nullptr)) {
    const auto& profile_id = GetProfileId(base_object);
    profile_writer_->SetObjectTypeAndName(profile_id, type, name);
    profile_writer_->AddRoot(profile_id);
  }
}

intptr_t Serializer::AssignRef(ObjectPtr object) {
  ASSERT(IsAllocatedReference(next_ref_index_));

  // The object id weak table holds image offsets for Instructions instead
  // of ref indices.
  ASSERT(!object->IsHeapObject() || !object->IsInstructions());
  heap_->SetObjectId(object, next_ref_index_);
  ASSERT(heap_->GetObjectId(object) == next_ref_index_);

  objects_->Add(&Object::ZoneHandle(object));

  return next_ref_index_++;
}

intptr_t Serializer::AssignArtificialRef(ObjectPtr object) {
  const intptr_t ref = -(next_ref_index_++);
  ASSERT(IsArtificialReference(ref));
  if (object != nullptr) {
    ASSERT(!object.IsHeapObject() || !object.IsInstructions());
    ASSERT(heap_->GetObjectId(object) == kUnreachableReference);
    heap_->SetObjectId(object, ref);
    ASSERT(heap_->GetObjectId(object) == ref);
  }
  return ref;
}

void Serializer::FlushProfile() {
  if (profile_writer_ == nullptr) return;
  const intptr_t bytes =
      stream_->Position() - object_currently_writing_.last_stream_position_;
  profile_writer_->AttributeBytesTo(object_currently_writing_.id_, bytes);
  object_currently_writing_.last_stream_position_ = stream_->Position();
}

V8SnapshotProfileWriter::ObjectId Serializer::GetProfileId(
    ObjectPtr object) const {
  // Instructions are handled separately.
  ASSERT(!object->IsHeapObject() || !object->IsInstructions());
  return GetProfileId(UnsafeRefId(object));
}

V8SnapshotProfileWriter::ObjectId Serializer::GetProfileId(
    intptr_t heap_id) const {
  if (IsArtificialReference(heap_id)) {
    return {IdSpace::kArtificial, -heap_id};
  }
  ASSERT(IsAllocatedReference(heap_id));
  return {IdSpace::kSnapshot, heap_id};
}

void Serializer::AttributeReference(
    ObjectPtr object,
    const V8SnapshotProfileWriter::Reference& reference) {
  if (profile_writer_ == nullptr) return;
  const auto& object_id = GetProfileId(object);
#if defined(DART_PRECOMPILER)
  if (object->IsHeapObject() && object->IsWeakSerializationReference()) {
    auto const wsr = WeakSerializationReference::RawCast(object);
    auto const target = wsr->untag()->target();
    const auto& target_id = GetProfileId(target);
    if (object_id != target_id) {
      const auto& replacement_id = GetProfileId(wsr->untag()->replacement());
      ASSERT(object_id == replacement_id);
      // The target of the WSR will be replaced in the snapshot, so write
      // attributions for both the dropped target and for the replacement.
      profile_writer_->AttributeDroppedReferenceTo(
          object_currently_writing_.id_, reference, target_id, replacement_id);
      return;
    }
    // The replacement isn't used for this WSR in the snapshot, as either the
    // target is strongly referenced or the WSR itself is unreachable, so fall
    // through to attributing a reference to the WSR (which shares the profile
    // ID of the target).
  }
#endif
  profile_writer_->AttributeReferenceTo(object_currently_writing_.id_,
                                        reference, object_id);
}

Serializer::WritingObjectScope::WritingObjectScope(
    Serializer* serializer,
    const V8SnapshotProfileWriter::ObjectId& id,
    ObjectPtr object)
    : serializer_(serializer),
      old_object_(serializer->object_currently_writing_.object_),
      old_id_(serializer->object_currently_writing_.id_),
      old_cid_(serializer->object_currently_writing_.cid_) {
  if (serializer_->profile_writer_ == nullptr) return;
  // The ID should correspond to one already added appropriately to the
  // profile writer.
  ASSERT(serializer_->profile_writer_->HasId(id));
  serializer_->FlushProfile();
  serializer_->object_currently_writing_.object_ = object;
  serializer_->object_currently_writing_.id_ = id;
  serializer_->object_currently_writing_.cid_ =
      object == nullptr ? -1 : object->GetClassId();
}

Serializer::WritingObjectScope::~WritingObjectScope() {
  if (serializer_->profile_writer_ == nullptr) return;
  serializer_->FlushProfile();
  serializer_->object_currently_writing_.object_ = old_object_;
  serializer_->object_currently_writing_.id_ = old_id_;
  serializer_->object_currently_writing_.cid_ = old_cid_;
}

V8SnapshotProfileWriter::ObjectId Serializer::WritingObjectScope::ReserveId(
    Serializer* s,
    const char* type,
    ObjectPtr obj,
    const char* name) {
  if (s->profile_writer_ == nullptr) {
    return V8SnapshotProfileWriter::kArtificialRootId;
  }
  if (name == nullptr) {
    // Handle some cases where there are obvious names to assign.
    switch (obj->GetClassId()) {
      case kSmiCid: {
        name = OS::SCreate(s->zone(), "%" Pd "", Smi::Value(Smi::RawCast(obj)));
        break;
      }
      case kMintCid: {
        name = OS::SCreate(s->zone(), "%" Pd64 "",
                           Mint::RawCast(obj)->untag()->value_);
        break;
      }
      case kOneByteStringCid:
      case kTwoByteStringCid: {
        name = String::ToCString(s->thread(), String::RawCast(obj));
        break;
      }
    }
  }
  const auto& obj_id = s->GetProfileId(obj);
  s->profile_writer_->SetObjectTypeAndName(obj_id, type, name);
  return obj_id;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) {
  ASSERT(profile_writer() != nullptr);

  // UnsafeRefId will do lazy reference allocation for WSRs.
  intptr_t id = UnsafeRefId(obj);
  ASSERT(id != kUnallocatedReference);
  if (id != kUnreachableReference) {
    return IsArtificialReference(id);
  }
  if (obj->IsHeapObject() && obj->IsWeakSerializationReference()) {
    auto const target =
        WeakSerializationReference::RawCast(obj)->untag()->target();
    CreateArtificialNodeIfNeeded(target);
    // Since the WSR is unreachable, we can replace its id with whatever the
    // ID of the target is, whether real or artificial.
    id = heap_->GetObjectId(target);
    heap_->SetObjectId(obj, id);
    return IsArtificialReference(id);
  }

  const char* type = nullptr;
  const char* name = nullptr;
  GrowableArray<std::pair<ObjectPtr, V8SnapshotProfileWriter::Reference>> links;
  const classid_t cid = obj->GetClassId();
  switch (cid) {
    // For profiling static call target tables in AOT mode.
    case kSmiCid: {
      type = "Smi";
      break;
    }
    // For profiling per-code object pools in bare instructions mode.
    case kObjectPoolCid: {
      type = "ObjectPool";
      auto const pool = ObjectPool::RawCast(obj);
      for (intptr_t i = 0; i < pool->untag()->length_; i++) {
        uint8_t bits = pool->untag()->entry_bits()[i];
        if (ObjectPool::TypeBits::decode(bits) ==
            ObjectPool::EntryType::kTaggedObject) {
          auto const elem = pool->untag()->data()[i].raw_obj_;
          // Elements should be reachable from the global object pool.
          ASSERT(HasRef(elem));
          links.Add({elem, V8SnapshotProfileWriter::Reference::Element(i)});
        }
      }
      break;
    }
    // For profiling static call target tables and the dispatch table in AOT.
    case kImmutableArrayCid:
    case kArrayCid: {
      type = "Array";
      auto const array = Array::RawCast(obj);
      for (intptr_t i = 0, n = Smi::Value(array->untag()->length()); i < n;
           i++) {
        ObjectPtr elem = array->untag()->element(i);
        links.Add({elem, V8SnapshotProfileWriter::Reference::Element(i)});
      }
      break;
    }
    // For profiling the dispatch table.
    case kCodeCid: {
      type = "Code";
      auto const code = Code::RawCast(obj);
      name = CodeSerializationCluster::MakeDisambiguatedCodeName(this, code);
      links.Add({code->untag()->owner(),
                 V8SnapshotProfileWriter::Reference::Property("owner_")});
      break;
    }
    case kFunctionCid: {
      FunctionPtr func = static_cast<FunctionPtr>(obj);
      type = "Function";
      name = FunctionSerializationCluster::MakeDisambiguatedFunctionName(this,
                                                                         func);
      links.Add({func->untag()->owner(),
                 V8SnapshotProfileWriter::Reference::Property("owner_")});
      ObjectPtr data = func->untag()->data();
      if (data->GetClassId() == kClosureDataCid) {
        links.Add(
            {data, V8SnapshotProfileWriter::Reference::Property("data_")});
      }
      break;
    }
    case kClosureDataCid: {
      auto data = static_cast<ClosureDataPtr>(obj);
      type = "ClosureData";
      links.Add(
          {data->untag()->parent_function(),
           V8SnapshotProfileWriter::Reference::Property("parent_function_")});
      break;
    }
    case kClassCid: {
      ClassPtr cls = static_cast<ClassPtr>(obj);
      type = "Class";
      name = String::ToCString(thread(), cls->untag()->name());
      links.Add({cls->untag()->library(),
                 V8SnapshotProfileWriter::Reference::Property("library_")});
      break;
    }
    case kPatchClassCid: {
      PatchClassPtr patch_cls = static_cast<PatchClassPtr>(obj);
      type = "PatchClass";
      links.Add(
          {patch_cls->untag()->wrapped_class(),
           V8SnapshotProfileWriter::Reference::Property("wrapped_class_")});
      break;
    }
    case kLibraryCid: {
      LibraryPtr lib = static_cast<LibraryPtr>(obj);
      type = "Library";
      name = String::ToCString(thread(), lib->untag()->url());
      break;
    }
    case kFunctionTypeCid: {
      type = "FunctionType";
      break;
    };
    case kRecordTypeCid: {
      type = "RecordType";
      break;
    };
    // Can appear in weakened constant hash tables.
    case kMintCid: {
      type = "Mint";
      break;
    }
    default:
      FATAL("Request to create artificial node for object with cid %d", cid);
  }

  id = AssignArtificialRef(obj);
  Serializer::WritingObjectScope scope(this, type, obj, name);
  for (const auto& link : links) {
    CreateArtificialNodeIfNeeded(link.first);
    AttributeReference(link.first, link.second);
  }
  return true;
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

intptr_t Serializer::RefId(ObjectPtr object) const {
  auto const id = UnsafeRefId(object);
  if (IsAllocatedReference(id)) {
    return id;
  }
  ASSERT(id == kUnreachableReference || IsArtificialReference(id));
  REUSABLE_OBJECT_HANDLESCOPE(thread());
  auto& handle = thread()->ObjectHandle();
  handle = object;
  FATAL("Reference to unreachable object %s", handle.ToCString());
}

intptr_t Serializer::UnsafeRefId(ObjectPtr object) const {
  // The object id weak table holds image offsets for Instructions instead
  // of ref indices.
  ASSERT(!object->IsHeapObject() || !object->IsInstructions());
  if (!Snapshot::IncludesCode(kind_) && object->GetClassId() == kCodeCid) {
    return RefId(Object::null());
  }
  auto id = heap_->GetObjectId(object);
  if (id != kUnallocatedReference) {
    return id;
  }
  // This is the only case where we may still see unallocated references after
  // WriteAlloc is finished.
  if (object->IsWeakSerializationReference()) {
    // Lazily set the object ID of the WSR to the object which will replace
    // it in the snapshot.
    auto const wsr = static_cast<WeakSerializationReferencePtr>(object);
    // Either the target or the replacement must be allocated, since the
    // WSR is reachable.
    id = HasRef(wsr->untag()->target()) ? RefId(wsr->untag()->target())
                                        : RefId(wsr->untag()->replacement());
    heap_->SetObjectId(wsr, id);
    return id;
  }
  REUSABLE_OBJECT_HANDLESCOPE(thread());
  auto& handle = thread()->ObjectHandle();
  handle = object;
  FATAL("Reference for object %s is unallocated", handle.ToCString());
}

const char* Serializer::ReadOnlyObjectType(intptr_t cid) {
  switch (cid) {
    case kPcDescriptorsCid:
      return "PcDescriptors";
    case kCodeSourceMapCid:
      return "CodeSourceMap";
    case kCompressedStackMapsCid:
      return "CompressedStackMaps";
    case kStringCid:
      return current_loading_unit_id_ <= LoadingUnit::kRootId
                 ? "CanonicalString"
                 : nullptr;
    case kOneByteStringCid:
      return current_loading_unit_id_ <= LoadingUnit::kRootId
                 ? "OneByteStringCid"
                 : nullptr;
    case kTwoByteStringCid:
      return current_loading_unit_id_ <= LoadingUnit::kRootId
                 ? "TwoByteStringCid"
                 : nullptr;
    default:
      return nullptr;
  }
}

SerializationCluster* Serializer::NewClusterForClass(intptr_t cid,
                                                     bool is_canonical,
                                                     bool is_deeply_immutable) {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
  return nullptr;
#else
  Zone* Z = zone_;
  if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
    Push(isolate_group()->class_table()->At(cid));
    return new (Z)
        InstanceSerializationCluster(is_canonical, is_deeply_immutable, cid);
  }
  if (IsTypedDataViewClassId(cid)) {
    return new (Z) TypedDataViewSerializationCluster(cid);
  }
  if (IsExternalTypedDataClassId(cid)) {
    return new (Z) ExternalTypedDataSerializationCluster(cid);
  }
  if (IsTypedDataClassId(cid)) {
    return new (Z) TypedDataSerializationCluster(cid);
  }

#if !defined(DART_COMPRESSED_POINTERS)
  // Sometimes we write memory images for read-only objects that contain no
  // pointers. These can be mmapped directly, needing no relocation, and added
  // to the list of heap pages. This gives us lazy/demand paging from the OS.
  // We do not do this for snapshots without code to keep snapshots portable
  // between machines with different word sizes. We do not do this when we use
  // compressed pointers because we cannot always control the load address of
  // the memory image, and it might be outside the 4GB region addressable by
  // compressed pointers.
  if (Snapshot::IncludesCode(kind_)) {
    if (auto const type = ReadOnlyObjectType(cid)) {
      return new (Z) RODataSerializationCluster(Z, type, cid, is_canonical,
                                                is_deeply_immutable);
    }
  }
#endif

  const bool cluster_represents_canonical_set =
      current_loading_unit_id_ <= LoadingUnit::kRootId && is_canonical;

  switch (cid) {
    case kClassCid:
      return new (Z) ClassSerializationCluster(num_cids_ + num_tlc_cids_);
    case kTypeParametersCid:
      return new (Z)
          TypeParametersSerializationCluster(is_canonical, is_deeply_immutable);
    case kTypeArgumentsCid:
      return new (Z) TypeArgumentsSerializationCluster(
          is_canonical, is_deeply_immutable, cluster_represents_canonical_set);
    case kPatchClassCid:
      return new (Z) PatchClassSerializationCluster();
    case kFunctionCid:
      return new (Z) FunctionSerializationCluster();
    case kClosureDataCid:
      return new (Z) ClosureDataSerializationCluster();
    case kFfiTrampolineDataCid:
      return new (Z) FfiTrampolineDataSerializationCluster();
    case kFieldCid:
      return new (Z) FieldSerializationCluster();
    case kScriptCid:
      return new (Z) ScriptSerializationCluster();
    case kLibraryCid:
      return new (Z) LibrarySerializationCluster();
    case kNamespaceCid:
      return new (Z) NamespaceSerializationCluster();
    case kKernelProgramInfoCid:
      return new (Z) KernelProgramInfoSerializationCluster();
    case kCodeCid:
      return new (Z) CodeSerializationCluster(heap_);
#if defined(DART_DYNAMIC_MODULES)
    case kBytecodeCid:
      return new (Z) BytecodeSerializationCluster();
#endif
    case kObjectPoolCid:
      return new (Z) ObjectPoolSerializationCluster();
    case kPcDescriptorsCid:
      return new (Z) PcDescriptorsSerializationCluster();
    case kCodeSourceMapCid:
      return new (Z) CodeSourceMapSerializationCluster();
    case kCompressedStackMapsCid:
      return new (Z) CompressedStackMapsSerializationCluster();
    case kLocalVarDescriptorsCid:
      return new (Z) LocalVarDescriptorsSerializationCluster();
    case kExceptionHandlersCid:
      return new (Z) ExceptionHandlersSerializationCluster();
    case kContextCid:
      return new (Z) ContextSerializationCluster();
    case kContextScopeCid:
      return new (Z) ContextScopeSerializationCluster();
    case kUnlinkedCallCid:
      return new (Z) UnlinkedCallSerializationCluster();
    case kICDataCid:
      return new (Z) ICDataSerializationCluster();
    case kMegamorphicCacheCid:
      return new (Z) MegamorphicCacheSerializationCluster();
    case kSubtypeTestCacheCid:
      return new (Z) SubtypeTestCacheSerializationCluster();
    case kLoadingUnitCid:
      return new (Z) LoadingUnitSerializationCluster();
    case kApiErrorCid:
      return new (Z) ApiErrorSerializationCluster();
    case kLanguageErrorCid:
      return new (Z) LanguageErrorSerializationCluster();
    case kUnhandledExceptionCid:
      return new (Z) UnhandledExceptionSerializationCluster();
    case kUnwindErrorCid:
      return new (Z) UnwindErrorSerializationCluster();
    case kLibraryPrefixCid:
      return new (Z) LibraryPrefixSerializationCluster();
    case kTypeCid:
      return new (Z) TypeSerializationCluster(is_canonical, is_deeply_immutable,
                                              cluster_represents_canonical_set);
    case kFunctionTypeCid:
      return new (Z) FunctionTypeSerializationCluster(
          is_canonical, is_deeply_immutable, cluster_represents_canonical_set);
    case kRecordTypeCid:
      return new (Z) RecordTypeSerializationCluster(
          is_canonical, is_deeply_immutable, cluster_represents_canonical_set);
    case kTypeParameterCid:
      return new (Z) TypeParameterSerializationCluster(
          is_canonical, is_deeply_immutable, cluster_represents_canonical_set);
    case kClosureCid:
      return new (Z)
          ClosureSerializationCluster(is_canonical, is_deeply_immutable);
    case kMintCid:
      return new (Z)
          MintSerializationCluster(is_canonical, is_deeply_immutable);
    case kDoubleCid:
      return new (Z)
          DoubleSerializationCluster(is_canonical, is_deeply_immutable);
    case kInt32x4Cid:
    case kFloat32x4Cid:
    case kFloat64x2Cid:
      return new (Z)
          Simd128SerializationCluster(cid, is_canonical, is_deeply_immutable);
    case kGrowableObjectArrayCid:
      return new (Z) GrowableObjectArraySerializationCluster(
          is_canonical, is_deeply_immutable);
    case kRecordCid:
      return new (Z)
          RecordSerializationCluster(is_canonical, is_deeply_immutable);
    case kStackTraceCid:
      return new (Z)
          StackTraceSerializationCluster(is_canonical, is_deeply_immutable);
    case kRegExpCid:
      return new (Z)
          RegExpSerializationCluster(is_canonical, is_deeply_immutable);
    case kWeakPropertyCid:
      return new (Z) WeakPropertySerializationCluster();
    case kMapCid:
      // We do not have mutable hash maps in snapshots.
      UNREACHABLE();
    case kConstMapCid:
      return new (Z) MapSerializationCluster(is_canonical, is_deeply_immutable,
                                             kConstMapCid);
    case kSetCid:
      // We do not have mutable hash sets in snapshots.
      UNREACHABLE();
    case kConstSetCid:
      return new (Z) SetSerializationCluster(is_canonical, is_deeply_immutable,
                                             kConstSetCid);
    case kArrayCid:
      return new (Z) ArraySerializationCluster(is_canonical,
                                               is_deeply_immutable, kArrayCid);
    case kImmutableArrayCid:
      return new (Z) ArraySerializationCluster(
          is_canonical, is_deeply_immutable, kImmutableArrayCid);
    case kWeakArrayCid:
      return new (Z) WeakArraySerializationCluster();
    case kStringCid:
      return new (Z) StringSerializationCluster(
          is_canonical, is_deeply_immutable, cluster_represents_canonical_set);
#define CASE_FFI_CID(name) case kFfi##name##Cid:
      CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID)
#undef CASE_FFI_CID
      return new (Z)
          InstanceSerializationCluster(is_canonical, is_deeply_immutable, cid);
    case kDeltaEncodedTypedDataCid:
      return new (Z) DeltaEncodedTypedDataSerializationCluster();
    case kWeakSerializationReferenceCid:
#if defined(DART_PRECOMPILER)
      ASSERT(kind_ == Snapshot::kFullAOT);
      return new (Z) WeakSerializationReferenceSerializationCluster();
#endif
    default:
      break;
  }

  // The caller will check for nullptr and provide an error with more context
  // than is available here.
  return nullptr;
#endif  // !DART_PRECOMPILED_RUNTIME
}

bool Serializer::InCurrentLoadingUnitOrRoot(ObjectPtr obj) {
  if (loading_units_ == nullptr) return true;

  intptr_t unit_id = heap_->GetLoadingUnit(obj);
  if (unit_id == WeakTable::kNoValue) {
    FATAL("Missing loading unit assignment: %s\n",
          Object::Handle(obj).ToCString());
  }
  return unit_id == LoadingUnit::kRootId || unit_id == current_loading_unit_id_;
}

void Serializer::RecordDeferredCode(CodePtr code) {
  const intptr_t unit_id = heap_->GetLoadingUnit(code);
  ASSERT(unit_id != WeakTable::kNoValue && unit_id != LoadingUnit::kRootId);
  (*loading_units_)[unit_id]->AddDeferredObject(code);
}

#if !defined(DART_PRECOMPILED_RUNTIME)
#if defined(DART_PRECOMPILER)
// We use the following encoding schemes when encoding references to Code
// objects.
//
// In AOT mode:
//
// 0        --  LazyCompile stub
// 1        -+
//           |  for non-root-unit/non-VM snapshots
// ...        > reference into parent snapshot objects
//           |  (base is num_base_objects_ in this case, 0 otherwise).
// base     -+
// base + 1 -+
//           |  for non-deferred Code objects (those with instructions)
//            > index in into the instructions table (code_index_).
//           |  (L is code_index_.Length()).
// base + L -+
// ...      -+
//           |  for deferred Code objects (those without instructions)
//            > index of this Code object in the deferred part of the
//           |  Code cluster.
//
// Note that this encoding has the following property: non-discarded
// non-deferred Code objects form the tail of the instruction table
// which makes indices assigned to non-discarded non-deferred Code objects
// and deferred Code objects continuous. This means when decoding
// code_index - (base + 1) - first_entry_with_code yields an index of the
// Code object in the Code cluster both for non-deferred and deferred
// Code objects.
//
// For JIT snapshots we do:
//
// 0        --  LazyCompile stub
// 1        -+
//           |
// ...        > index of the Code object in the Code cluster.
//           |
//
intptr_t Serializer::GetCodeIndex(CodePtr code) {
  // In the precompiled mode Code object is uniquely identified by its
  // instructions (because ProgramVisitor::DedupInstructions will dedup Code
  // objects with the same instructions).
  if (code == StubCode::LazyCompile().ptr()) {
    return 0;
  } else if (FLAG_precompiled_mode) {
    const intptr_t ref = heap_->GetObjectId(code);
    ASSERT(!IsReachableReference(ref) == Code::IsDiscarded(code));

    const intptr_t base = (current_loading_unit_id() == LoadingUnit::kRootId)
                              ? 0
                              : num_base_objects_;

    // Check if we are referring to the Code object which originates from the
    // parent loading unit. In this case we write out the reference of this
    // object.
    if (!Code::IsDiscarded(code) && ref < base) {
      RELEASE_ASSERT(current_loading_unit_id() != LoadingUnit::kRootId);
      return 1 + ref;
    }

    // Otherwise the code object must either be discarded or originate from
    // the Code cluster.
    ASSERT(Code::IsDiscarded(code) || (code_cluster_->first_ref() <= ref &&
                                       ref <= code_cluster_->last_ref()));

    // If Code object is non-deferred then simply write out the index of the
    // entry point, otherwise write out the index of the deferred code object.
    if (ref < code_cluster_->first_deferred_ref()) {
      const intptr_t key = static_cast<intptr_t>(code->untag()->instructions_);
      ASSERT(code_index_.HasKey(key));
      const intptr_t result = code_index_.Lookup(key);
      ASSERT(0 < result && result <= code_index_.Length());
      // Note: result already has + 1.
      return base + result;
    } else {
      // Note: only root snapshot can have deferred Code objects in the
      // cluster.
      const intptr_t cluster_index = ref - code_cluster_->first_deferred_ref();
      return 1 + base + code_index_.Length() + cluster_index;
    }
  } else {
    const intptr_t ref = heap_->GetObjectId(code);
    ASSERT(IsAllocatedReference(ref));
    ASSERT(code_cluster_->first_ref() <= ref &&
           ref <= code_cluster_->last_ref());
    return 1 + (ref - code_cluster_->first_ref());
  }
}
#endif  // defined(DART_PRECOMPILER)

void Serializer::PrepareInstructions(
    const CompressedStackMaps& canonical_stack_map_entries) {
  if (!Snapshot::IncludesCode(kind())) return;

  // Code objects that have identical/duplicate instructions must be adjacent in
  // the order that Code objects are written because the encoding of the
  // reference from the Code to the Instructions assumes monotonically
  // increasing offsets as part of a delta encoding. Also the code order table
  // that allows for mapping return addresses back to Code objects depends on
  // this sorting.
  if (code_cluster_ != nullptr) {
    CodeSerializationCluster::Sort(this, code_cluster_->objects());
  }
  if ((loading_units_ != nullptr) &&
      (current_loading_unit_id_ == LoadingUnit::kRootId)) {
    for (intptr_t i = LoadingUnit::kRootId + 1; i < loading_units_->length();
         i++) {
      auto unit_objects = loading_units_->At(i)->deferred_objects();
      CodeSerializationCluster::Sort(this, unit_objects);
      ASSERT(unit_objects->length() == 0 || code_cluster_ != nullptr);
      for (intptr_t j = 0; j < unit_objects->length(); j++) {
        code_cluster_->deferred_objects()->Add(unit_objects->At(j)->ptr());
      }
    }
  }

#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
  if (kind() == Snapshot::kFullAOT) {
    // Group the code objects whose instructions are not being deferred in this
    // snapshot unit in the order they will be written: first the code objects
    // encountered for this first time in this unit being written by the
    // CodeSerializationCluster, then code object previously deferred whose
    // instructions are now written by UnitSerializationRoots. This order needs
    // to be known to finalize bare-instructions-mode's PC-relative calls.
    GrowableArray<CodePtr> code_objects;
    if (code_cluster_ != nullptr) {
      auto in = code_cluster_->objects();
      for (intptr_t i = 0; i < in->length(); i++) {
        code_objects.Add(in->At(i));
      }
    }
    if (loading_units_ != nullptr) {
      auto in =
          loading_units_->At(current_loading_unit_id_)->deferred_objects();
      for (intptr_t i = 0; i < in->length(); i++) {
        code_objects.Add(in->At(i)->ptr());
      }
    }

    GrowableArray<ImageWriterCommand> writer_commands;
    RelocateCodeObjects(&code_objects, &writer_commands);
    image_writer_->PrepareForSerialization(&writer_commands);

    if (code_objects.length() == 0) {
      return;
    }

    // Build UntaggedInstructionsTable::Data object to be added to the
    // read-only data section of the snapshot. It contains:
    //
    //    - a binary search table mapping an Instructions entry point to its
    //      stack maps (by offset from the beginning of the Data object);
    //    - followed by stack maps bytes;
    //    - followed by canonical stack map entries.
    //
    struct StackMapInfo : public ZoneObject {
      CompressedStackMapsPtr map;
      intptr_t use_count;
      uint32_t offset;
    };

    GrowableArray<StackMapInfo*> stack_maps;
    IntMap<StackMapInfo*> stack_maps_info;

    // Build code_index_ (which maps Instructions object to the order in
    // which they appear in the code section in the end) and collect all
    // stack maps.
    // We also find the first Instructions object which is going to have
    // Code object associated with it. This will allow to reduce the binary
    // search space when searching specifically for the code object in runtime.
    uint32_t total = 0;
    intptr_t not_discarded_count = 0;
    uint32_t first_entry_with_code = 0;
    for (auto& cmd : writer_commands) {
      if (cmd.op == ImageWriterCommand::InsertInstructionOfCode) {
        RELEASE_ASSERT(code_objects[total] ==
                       cmd.insert_instruction_of_code.code);
        ASSERT(!Code::IsDiscarded(cmd.insert_instruction_of_code.code) ||
               (not_discarded_count == 0));
        if (!Code::IsDiscarded(cmd.insert_instruction_of_code.code)) {
          if (not_discarded_count == 0) {
            first_entry_with_code = total;
          }
          not_discarded_count++;
        }
        total++;

        // Update code_index_.
        {
          const intptr_t instr = static_cast<intptr_t>(
              cmd.insert_instruction_of_code.code->untag()->instructions_);
          ASSERT(!code_index_.HasKey(instr));
          code_index_.Insert(instr, total);
        }

        // Collect stack maps.
        CompressedStackMapsPtr stack_map =
            cmd.insert_instruction_of_code.code->untag()->compressed_stackmaps_;
        const intptr_t key = static_cast<intptr_t>(stack_map);

        if (stack_maps_info.HasKey(key)) {
          stack_maps_info.Lookup(key)->use_count++;
        } else {
          auto info = new StackMapInfo();
          info->map = stack_map;
          info->use_count = 1;
          stack_maps.Add(info);
          stack_maps_info.Insert(key, info);
        }
      }
    }
    ASSERT(static_cast<intptr_t>(total) == code_index_.Length());
    instructions_table_len_ = not_discarded_count;

    // Sort stack maps by usage so that most commonly used stack maps are
    // together at the start of the Data object.
    stack_maps.Sort([](StackMapInfo* const* a, StackMapInfo* const* b) {
      if ((*a)->use_count < (*b)->use_count) return 1;
      if ((*a)->use_count > (*b)->use_count) return -1;
      return 0;
    });

    // Build Data object.
    MallocWriteStream pc_mapping(4 * KB);

    // Write the header out.
    {
      UntaggedInstructionsTable::Data header;
      memset(&header, 0, sizeof(header));
      header.length = total;
      header.first_entry_with_code = first_entry_with_code;
      pc_mapping.WriteFixed<UntaggedInstructionsTable::Data>(header);
    }

    // Reserve space for the binary search table.
    for (auto& cmd : writer_commands) {
      if (cmd.op == ImageWriterCommand::InsertInstructionOfCode) {
        pc_mapping.WriteFixed<UntaggedInstructionsTable::DataEntry>({0, 0});
      }
    }

    // Now write collected stack maps after the binary search table.
    auto write_stack_map = [&](CompressedStackMapsPtr smap) {
      const auto flags_and_size = smap->untag()->payload()->flags_and_size();
      const auto payload_size =
          UntaggedCompressedStackMaps::SizeField::decode(flags_and_size);
      pc_mapping.WriteFixed<uint32_t>(flags_and_size);
      pc_mapping.WriteBytes(smap->untag()->payload()->data(), payload_size);
    };

    for (auto sm : stack_maps) {
      sm->offset = pc_mapping.bytes_written();
      write_stack_map(sm->map);
    }

    // Write canonical entries (if any).
    if (!canonical_stack_map_entries.IsNull()) {
      auto header = reinterpret_cast<UntaggedInstructionsTable::Data*>(
          pc_mapping.buffer());
      header->canonical_stack_map_entries_offset = pc_mapping.bytes_written();
      write_stack_map(canonical_stack_map_entries.ptr());
    }
    const auto total_bytes = pc_mapping.bytes_written();

    // Now that we have offsets to all stack maps we can write binary
    // search table.
    pc_mapping.SetPosition(
        sizeof(UntaggedInstructionsTable::Data));  // Skip the header.
    for (auto& cmd : writer_commands) {
      if (cmd.op == ImageWriterCommand::InsertInstructionOfCode) {
        CompressedStackMapsPtr smap =
            cmd.insert_instruction_of_code.code->untag()->compressed_stackmaps_;
        const auto offset =
            stack_maps_info.Lookup(static_cast<intptr_t>(smap))->offset;
        const auto entry = image_writer_->GetTextOffsetFor(
            Code::InstructionsOf(cmd.insert_instruction_of_code.code),
            cmd.insert_instruction_of_code.code);

        pc_mapping.WriteFixed<UntaggedInstructionsTable::DataEntry>(
            {static_cast<uint32_t>(entry), offset});
      }
    }
    // Restore position so that Steal does not truncate the buffer.
    pc_mapping.SetPosition(total_bytes);

    intptr_t length = 0;
    uint8_t* bytes = pc_mapping.Steal(&length);

    instructions_table_rodata_offset_ =
        image_writer_->AddBytesToData(bytes, length);
    // Attribute all bytes in this object to the root for simplicity.
    if (profile_writer_ != nullptr) {
      const auto offset_space = IdSpace::kIsolateData;
      profile_writer_->AttributeReferenceTo(
          V8SnapshotProfileWriter::kArtificialRootId,
          V8SnapshotProfileWriter::Reference::Property(
              "<instructions-table-rodata>"),
          {offset_space, instructions_table_rodata_offset_});
    }
  }
#endif  // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
}

void Serializer::WriteInstructions(InstructionsPtr instr,
                                   uint32_t unchecked_offset,
                                   CodePtr code,
                                   bool deferred) {
  ASSERT(code != Code::null());

  ASSERT(InCurrentLoadingUnitOrRoot(code) != deferred);
  if (deferred) {
    return;
  }

  const intptr_t offset = image_writer_->GetTextOffsetFor(instr, code);
#if defined(DART_PRECOMPILER)
  if (profile_writer_ != nullptr) {
    ASSERT(object_currently_writing_.id_ !=
           V8SnapshotProfileWriter::kArtificialRootId);
    const auto offset_space = IdSpace::kIsolateText;
    profile_writer_->AttributeReferenceTo(
        object_currently_writing_.id_,
        V8SnapshotProfileWriter::Reference::Property("<instructions>"),
        {offset_space, offset});
  }

  if (Code::IsDiscarded(code)) {
    return;
  }

  if (FLAG_precompiled_mode) {
    const uint32_t payload_info =
        (unchecked_offset << 1) | (Code::HasMonomorphicEntry(code) ? 0x1 : 0x0);
    WriteUnsigned(payload_info);
    return;
  }
#endif
  Write<uint32_t>(offset);
  WriteUnsigned(unchecked_offset);
}

void Serializer::TraceDataOffset(uint32_t offset) {
  if (profile_writer_ == nullptr) return;
  // ROData cannot be roots.
  ASSERT(object_currently_writing_.id_ !=
         V8SnapshotProfileWriter::kArtificialRootId);
  auto offset_space = IdSpace::kIsolateData;
  // TODO(sjindel): Give this edge a more appropriate type than element
  // (internal, maybe?).
  profile_writer_->AttributeReferenceTo(
      object_currently_writing_.id_,
      V8SnapshotProfileWriter::Reference::Element(0), {offset_space, offset});
}

uint32_t Serializer::GetDataOffset(ObjectPtr object) const {
#if defined(SNAPSHOT_BACKTRACE)
  return image_writer_->GetDataOffsetFor(object, ParentOf(object));
#else
  return image_writer_->GetDataOffsetFor(object);
#endif
}

intptr_t Serializer::GetDataSize() const {
  if (image_writer_ == nullptr) {
    return 0;
  }
  return image_writer_->data_size();
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

void Serializer::Push(ObjectPtr object, intptr_t cid_override) {
  const bool is_code = object->IsHeapObject() && object->IsCode();
  if (is_code && !Snapshot::IncludesCode(kind_)) {
    return;  // Do not trace, will write null.
  }

  intptr_t id = heap_->GetObjectId(object);
  if (id == kUnreachableReference) {
    // When discovering the transitive closure of objects reachable from the
    // roots we do not trace references, e.g. inside [RawCode], to
    // [RawInstructions], since [RawInstructions] doesn't contain any references
    // and the serialization code uses an [ImageWriter] for those.
    if (object->IsHeapObject() && object->IsInstructions()) {
      UnexpectedObject(object,
                       "Instructions should only be reachable from Code");
    }

    heap_->SetObjectId(object, kUnallocatedReference);
    ASSERT(IsReachableReference(heap_->GetObjectId(object)));
    stack_.Add({object, cid_override});
    if (!(is_code && Code::IsDiscarded(Code::RawCast(object)))) {
      num_written_objects_++;
    }
#if defined(SNAPSHOT_BACKTRACE)
    heap()->SetSnapshotParent(object, &Object::Handle(zone_, current_parent_));
#endif
  }
}

void Serializer::PushWeak(ObjectPtr object) {
  // The GC considers immediate objects to always be alive. This doesn't happen
  // automatically in the serializer because the serializer does not have
  // immediate objects: it handles Smis as ref indices like all other objects.
  // This visit causes the serializer to reproduce the GC's semantics for
  // weakness, which in particular allows the templates in hash_table.h to work
  // with weak arrays because the metadata Smis always survive.
  if (!object->IsHeapObject()) {
    Push(object);
  }
}

void Serializer::Trace(ObjectPtr object, intptr_t cid_override) {
  intptr_t cid;
  bool is_canonical;
  bool is_deeply_immutable;
  if (!object->IsHeapObject()) {
    // Smis are merged into the Mint cluster because Smis for the writer might
    // become Mints for the reader and vice versa.
    cid = kMintCid;
    is_canonical = true;
    is_deeply_immutable = true;
  } else {
    cid = object->GetClassIdOfHeapObject();
    is_canonical = object->untag()->IsCanonical();
    is_deeply_immutable = object->untag()->IsDeeplyImmutable();
  }
  // if is_canonical then has to be is_deeply_immutable
  ASSERT(is_deeply_immutable || !is_canonical);
  if (cid_override != kIllegalCid) {
    cid = cid_override;
  } else if (IsStringClassId(cid)) {
    cid = kStringCid;
  }

  SerializationCluster** cluster_ref =
      is_canonical ? &canonical_clusters_by_cid_[cid]
                   : (is_deeply_immutable ? &immutable_clusters_by_cid_[cid]
                                          : &clusters_by_cid_[cid]);
  if (*cluster_ref == nullptr) {
    *cluster_ref = NewClusterForClass(cid, is_canonical, is_deeply_immutable);
    if (*cluster_ref == nullptr) {
      UnexpectedObject(object, "No serialization cluster defined");
    }
  }
  SerializationCluster* cluster = *cluster_ref;
  ASSERT(cluster != nullptr);
  if (cluster->is_canonical() != is_canonical) {
    FATAL("cluster for %s (cid %" Pd ") %s as canonical, but %s",
          cluster->name(), cid,
          cluster->is_canonical() ? "marked" : "not marked",
          is_canonical ? "should be" : "should not be");
  }

#if defined(SNAPSHOT_BACKTRACE)
  current_parent_ = object;
#endif

  cluster->Trace(this, object);

#if defined(SNAPSHOT_BACKTRACE)
  current_parent_ = Object::null();
#endif
}

void Serializer::UnexpectedObject(ObjectPtr raw_object, const char* message) {
  // Exit the no safepoint scope so we can allocate while printing.
  while (thread()->no_safepoint_scope_depth() > 0) {
    thread()->DecrementNoSafepointScopeDepth();
  }
  Object& object = Object::Handle(raw_object);
  OS::PrintErr("Unexpected object (%s, %s): 0x%" Px " %s\n", message,
               Snapshot::KindToCString(kind_), static_cast<uword>(object.ptr()),
               object.ToCString());
#if defined(SNAPSHOT_BACKTRACE)
  while (!object.IsNull()) {
    object = ParentOf(object);
    OS::PrintErr("referenced by 0x%" Px " %s\n",
                 static_cast<uword>(object.ptr()), object.ToCString());
  }
#endif
  OS::Abort();
}

#if defined(SNAPSHOT_BACKTRACE)
ObjectPtr Serializer::ParentOf(ObjectPtr object) const {
  Object* parent = heap()->GetSnapshotParent(object);
  if (parent != nullptr) {
    return parent->ptr();
  }
  return Object::null();
}

ObjectPtr Serializer::ParentOf(const Object& object) const {
  Object* parent = heap()->GetSnapshotParent(object.ptr());
  if (parent != nullptr) {
    return parent->ptr();
  }
  return Object::null();
}
#endif  // SNAPSHOT_BACKTRACE

void Serializer::WriteVersionAndFeatures() {
  const char* expected_version = Version::SnapshotString();
  ASSERT(expected_version != nullptr);
  const intptr_t version_len = strlen(expected_version);
  WriteBytes(reinterpret_cast<const uint8_t*>(expected_version), version_len);

  char* expected_features = Dart::FeaturesString(isolate_group(), kind_);
  ASSERT(expected_features != nullptr);
  const intptr_t features_len = strlen(expected_features);
  WriteBytes(reinterpret_cast<const uint8_t*>(expected_features),
             features_len + 1);
  free(expected_features);
}

#if !defined(DART_PRECOMPILED_RUNTIME)
static int CompareClusters(SerializationCluster* const* a,
                           SerializationCluster* const* b) {
  if ((*a)->size() > (*b)->size()) {
    return -1;
  } else if ((*a)->size() < (*b)->size()) {
    return 1;
  } else {
    return 0;
  }
}

#define CID_CLUSTER(Type)                                                      \
  reinterpret_cast<Type##SerializationCluster*>(clusters_by_cid_[k##Type##Cid])

const CompressedStackMaps& SerializationRoots::canonicalized_stack_map_entries()
    const {
  return CompressedStackMaps::Handle();
}

void Serializer::CreateEarlyClusters() {
  while (stack_.length() > 0) {
    StackEntry entry = stack_.RemoveLast();
    Trace(entry.obj, entry.cid_override);
  }

  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = canonical_clusters_by_cid_[cid]) {
      clusters_.Add(cluster);
      canonical_clusters_by_cid_[cid] = nullptr;
    }
  }
  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = immutable_clusters_by_cid_[cid]) {
      clusters_.Add(cluster);
      immutable_clusters_by_cid_[cid] = nullptr;
    }
  }
  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = clusters_by_cid_[cid]) {
      clusters_.Add(clusters_by_cid_[cid]);
      clusters_by_cid_[cid] = nullptr;
    }
  }
}

ZoneGrowableArray<Object*>* Serializer::Serialize(SerializationRoots* roots) {
  // While object_currently_writing_ is initialized to the artificial root, we
  // set up a scope to ensure proper flushing to the profile.
  Serializer::WritingObjectScope scope(
      this, V8SnapshotProfileWriter::kArtificialRootId);
  roots->AddBaseObjects(this);

  NoSafepointScope no_safepoint;

  roots->PushRoots(this);

  // Resolving WeakSerializationReferences and WeakProperties may cause new
  // objects to be pushed on the stack, and handling the changes to the stack
  // may cause the targets of WeakSerializationReferences and keys of
  // WeakProperties to become reachable, so we do this as a fixed point
  // computation. Note that reachability is computed monotonically (an object
  // can change from not reachable to reachable, but never the reverse), which
  // is technically a conservative approximation for WSRs, but doing a strict
  // analysis that allows non-monotonic reachability may not halt.
  //
  // To see this, take a WSR whose replacement causes the target of another WSR
  // to become reachable, which then causes the target of the first WSR to
  // become reachable, but the only way to reach the target is through the
  // target of the second WSR, which was only reachable via the replacement
  // the first.
  //
  // In practice, this case doesn't come up as replacements tend to be either
  // null, smis, or singleton objects that do not contain WSRs currently.
  while (stack_.length() > 0) {
    // Strong references.
    while (stack_.length() > 0) {
      StackEntry entry = stack_.RemoveLast();
      Trace(entry.obj, entry.cid_override);
    }

    // Ephemeron references.
#if defined(DART_PRECOMPILER)
    if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) {
      cluster->RetraceEphemerons(this);
    }
#endif
    if (auto const cluster = CID_CLUSTER(WeakProperty)) {
      cluster->RetraceEphemerons(this);
    }
  }

#if defined(DART_PRECOMPILER)
  auto const wsr_cluster = CID_CLUSTER(WeakSerializationReference);
  if (wsr_cluster != nullptr) {
    // Now that we have computed the reachability fixpoint, we remove the
    // count of now-reachable WSRs as they are not actually serialized.
    num_written_objects_ -= wsr_cluster->Count(this);
    // We don't need to write this cluster, so remove it from consideration.
    clusters_by_cid_[kWeakSerializationReferenceCid] = nullptr;
  }
  ASSERT(clusters_by_cid_[kWeakSerializationReferenceCid] == nullptr);
#endif

  code_cluster_ = CID_CLUSTER(Code);

  // The order that PostLoad runs matters for some classes because of
  // assumptions during canonicalization, read filling, or post-load filling of
  // some classes about what has already been read and/or canonicalized.
  // Explicitly add these clusters first, then add the rest ordered by class id.
#define ADD_CANONICAL_NEXT(cid)                                                \
  if (auto const cluster = canonical_clusters_by_cid_[cid]) {                  \
    clusters_.Add(cluster);                                                    \
    canonical_clusters_by_cid_[cid] = nullptr;                                 \
  }
#define ADD_NON_CANONICAL_NEXT(cid)                                            \
  if (auto const cluster = immutable_clusters_by_cid_[cid]) {                  \
    clusters_.Add(cluster);                                                    \
    immutable_clusters_by_cid_[cid] = nullptr;                                 \
  }                                                                            \
  if (auto const cluster = clusters_by_cid_[cid]) {                            \
    clusters_.Add(cluster);                                                    \
    clusters_by_cid_[cid] = nullptr;                                           \
  }
  ADD_CANONICAL_NEXT(kOneByteStringCid)
  ADD_CANONICAL_NEXT(kTwoByteStringCid)
  ADD_CANONICAL_NEXT(kStringCid)
  ADD_CANONICAL_NEXT(kMintCid)
  ADD_CANONICAL_NEXT(kDoubleCid)
  ADD_CANONICAL_NEXT(kTypeParameterCid)
  ADD_CANONICAL_NEXT(kTypeCid)
  ADD_CANONICAL_NEXT(kTypeArgumentsCid)
  // Code cluster should be deserialized before Function as
  // FunctionDeserializationCluster::ReadFill uses instructions table
  // which is filled in CodeDeserializationCluster::ReadFill.
  // Code cluster should also precede ObjectPool as its ReadFill uses
  // entry points of stubs.
  ADD_NON_CANONICAL_NEXT(kCodeCid)
  // The function cluster should be deserialized before any closures, as
  // PostLoad for closures caches the entry point found in the function.
  ADD_NON_CANONICAL_NEXT(kFunctionCid)
  ADD_CANONICAL_NEXT(kClosureCid)
#undef ADD_CANONICAL_NEXT
#undef ADD_NON_CANONICAL_NEXT
  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = canonical_clusters_by_cid_[cid]) {
      clusters_.Add(cluster);
      canonical_clusters_by_cid_[cid] = nullptr;
    }
  }
  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = immutable_clusters_by_cid_[cid]) {
      clusters_.Add(cluster);
      immutable_clusters_by_cid_[cid] = nullptr;
    }
  }
  for (intptr_t cid = 0; cid < num_cids_; cid++) {
    if (auto const cluster = clusters_by_cid_[cid]) {
      clusters_.Add(clusters_by_cid_[cid]);
      clusters_by_cid_[cid] = nullptr;
    }
  }

  PrepareInstructions(roots->canonicalized_stack_map_entries());

  intptr_t num_objects = num_base_objects_ + num_written_objects_;
#if defined(ARCH_IS_64_BIT)
  if (!Utils::IsInt(32, num_objects)) {
    FATAL("Ref overflow");
  }
#endif

  WriteUnsigned(num_base_objects_);
  WriteUnsigned(num_objects);
  WriteUnsigned(clusters_.length());
  ASSERT((instructions_table_len_ == 0) || FLAG_precompiled_mode);
  WriteUnsigned(instructions_table_len_);
  WriteUnsigned(instructions_table_rodata_offset_);

  for (SerializationCluster* cluster : clusters_) {
    cluster->WriteAndMeasureAlloc(this);
    bytes_heap_allocated_ += cluster->target_memory_size();
#if defined(DEBUG)
    Write<int32_t>(next_ref_index_);
#endif
  }

  // We should have assigned a ref to every object we pushed.
  ASSERT((next_ref_index_ - 1) == num_objects);
  // And recorded them all in [objects_].
  ASSERT(objects_->length() == num_objects);

#if defined(DART_PRECOMPILER)
  if (profile_writer_ != nullptr && wsr_cluster != nullptr) {
    // Post-WriteAlloc, we eagerly create artificial nodes for any unreachable
    // targets in reachable WSRs if writing a v8 snapshot profile, since they
    // will be used in AttributeReference().
    //
    // Unreachable WSRs may also need artificial nodes, as they may be members
    // of other unreachable objects that have artificial nodes in the profile,
    // but they are instead lazily handled in CreateArtificialNodeIfNeeded().
    wsr_cluster->CreateArtificialTargetNodesIfNeeded(this);
  }
#endif

  for (SerializationCluster* cluster : clusters_) {
    cluster->WriteAndMeasureFill(this);
#if defined(DEBUG)
    Write<int32_t>(kSectionMarker);
#endif
  }

  roots->WriteRoots(this);

#if defined(DEBUG)
  Write<int32_t>(kSectionMarker);
#endif

  PrintSnapshotSizes();

  heap()->ResetObjectIdTable();
#if defined(SNAPSHOT_BACKTRACE)
  heap()->ResetSnapshotParentTable();
#endif

  return objects_;
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

#if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
// The serialized format of the dispatch table is a sequence of variable-length
// integers (the built-in variable-length integer encoding/decoding of
// the stream). Each encoded integer e is interpreted thus:
// -kRecentCount .. -1   Pick value from the recent values buffer at index -1-e.
// 0                     Empty (unused) entry.
// 1 .. kMaxRepeat       Repeat previous entry e times.
// kIndexBase or higher  Pick entry point from the object at index e-kIndexBase
//                       in the snapshot code cluster. Also put it in the recent
//                       values buffer at the next round-robin index.

// Constants for serialization format. Chosen such that repeats and recent
// values are encoded as single bytes in SLEB128 encoding.
static constexpr intptr_t kDispatchTableSpecialEncodingBits = 6;
static constexpr intptr_t kDispatchTableRecentCount =
    1 << kDispatchTableSpecialEncodingBits;
static constexpr intptr_t kDispatchTableRecentMask =
    (1 << kDispatchTableSpecialEncodingBits) - 1;
static constexpr intptr_t kDispatchTableMaxRepeat =
    (1 << kDispatchTableSpecialEncodingBits) - 1;
static constexpr intptr_t kDispatchTableIndexBase = kDispatchTableMaxRepeat + 1;
#endif  // defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)

void Serializer::WriteDispatchTable(const Array& entries) {
#if defined(DART_PRECOMPILER)
  if (kind() != Snapshot::kFullAOT) return;

  // Create an artificial node to which the bytes should be attributed. We
  // don't attribute them to entries.ptr(), as we don't want to attribute the
  // bytes for printing out a length of 0 to Object::null() when the dispatch
  // table is empty.
  const intptr_t profile_ref = AssignArtificialRef();
  const auto& dispatch_table_profile_id = GetProfileId(profile_ref);
  if (profile_writer_ != nullptr) {
    profile_writer_->SetObjectTypeAndName(dispatch_table_profile_id,
                                          "DispatchTable", "dispatch_table");
    profile_writer_->AddRoot(dispatch_table_profile_id);
  }
  WritingObjectScope scope(this, dispatch_table_profile_id);
  if (profile_writer_ != nullptr) {
    // We'll write the Array object as a property of the artificial dispatch
    // table node, so Code objects otherwise unreferenced will have it as an
    // ancestor.
    CreateArtificialNodeIfNeeded(entries.ptr());
    AttributePropertyRef(entries.ptr(), "<code entries>");
  }

  const intptr_t bytes_before = bytes_written();
  const intptr_t table_length = entries.IsNull() ? 0 : entries.Length();

  ASSERT(table_length <= compiler::target::kWordMax);
  WriteUnsigned(table_length);
  if (table_length == 0) {
    dispatch_table_size_ = bytes_written() - bytes_before;
    return;
  }

  ASSERT(code_cluster_ != nullptr);
  // If instructions can be deduped, the code order table in the deserializer
  // may not contain all Code objects in the snapshot. Thus, we write the ID
  // for the first code object here so we can retrieve it during deserialization
  // and calculate the snapshot ID for Code objects from the cluster index.
  //
  // We could just use the snapshot reference ID of the Code object itself
  // instead of the cluster index and avoid this. However, since entries are
  // SLEB128 encoded, the size delta for serializing the first ID once is less
  // than the size delta of serializing the ID plus kIndexBase for each entry,
  // even when Code objects are allocated before all other non-base objects.
  //
  // We could also map Code objects to the first Code object in the cluster with
  // the same entry point and serialize that ID instead, but that loses
  // information about which Code object was originally referenced.
  WriteUnsigned(code_cluster_->first_ref());

  CodePtr previous_code = nullptr;
  CodePtr recent[kDispatchTableRecentCount] = {nullptr};
  intptr_t recent_index = 0;
  intptr_t repeat_count = 0;
  for (intptr_t i = 0; i < table_length; i++) {
    auto const code = Code::RawCast(entries.At(i));
    // First, see if we're repeating the previous entry (invalid, recent, or
    // encoded).
    if (code == previous_code) {
      if (++repeat_count == kDispatchTableMaxRepeat) {
        Write(kDispatchTableMaxRepeat);
        repeat_count = 0;
      }
      continue;
    }
    // Emit any outstanding repeat count before handling the new code value.
    if (repeat_count > 0) {
      Write(repeat_count);
      repeat_count = 0;
    }
    previous_code = code;
    // The invalid entry can be repeated, but is never part of the recent list
    // since it already encodes to a single byte..
    if (code == Code::null()) {
      Write(0);
      continue;
    }
    // Check against the recent entries, and write an encoded reference to
    // the recent entry if found.
    intptr_t found_index = 0;
    for (; found_index < kDispatchTableRecentCount; found_index++) {
      if (recent[found_index] == code) break;
    }
    if (found_index < kDispatchTableRecentCount) {
      Write(~found_index);
      continue;
    }
    // We have a non-repeated, non-recent entry, so encode the reference ID of
    // the code object and emit that.
    auto const code_index = GetCodeIndex(code);
    // Use the index in the code cluster, not in the snapshot..
    auto const encoded = kDispatchTableIndexBase + code_index;
    ASSERT(encoded <= compiler::target::kWordMax);
    Write(encoded);
    recent[recent_index] = code;
    recent_index = (recent_index + 1) & kDispatchTableRecentMask;
  }
  if (repeat_count > 0) {
    Write(repeat_count);
  }
  dispatch_table_size_ = bytes_written() - bytes_before;
#endif  // defined(DART_PRECOMPILER)
}

void Serializer::PrintSnapshotSizes() {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (FLAG_print_snapshot_sizes_verbose) {
    TextBuffer buffer(1024);
    // Header, using format sizes matching those below to ensure alignment.
    buffer.Printf("%25s", "Cluster");
    buffer.Printf(" %6s", "Objs");
    buffer.Printf(" %8s", "Size");
    buffer.Printf(" %8s", "Fraction");
    buffer.Printf(" %10s", "Cumulative");
    buffer.Printf(" %8s", "HeapSize");
    buffer.Printf(" %5s", "Cid");
    buffer.Printf(" %9s", "Canonical");
    buffer.AddString("\n");
    GrowableArray<SerializationCluster*> clusters_by_size;
    for (intptr_t cid = 1; cid < num_cids_; cid++) {
      if (auto const cluster = canonical_clusters_by_cid_[cid]) {
        clusters_by_size.Add(cluster);
      }
      if (auto const cluster = immutable_clusters_by_cid_[cid]) {
        clusters_by_size.Add(cluster);
      }
      if (auto const cluster = clusters_by_cid_[cid]) {
        clusters_by_size.Add(cluster);
      }
    }
    intptr_t text_size = 0;
    if (image_writer_ != nullptr) {
      auto const text_object_count = image_writer_->GetTextObjectCount();
      text_size = image_writer_->text_size();
      intptr_t trampoline_count, trampoline_size;
      image_writer_->GetTrampolineInfo(&trampoline_count, &trampoline_size);
      auto const instructions_count = text_object_count - trampoline_count;
      auto const instructions_size = text_size - trampoline_size;
      clusters_by_size.Add(new (zone_) FakeSerializationCluster(
          ImageWriter::TagObjectTypeAsReadOnly(zone_, "Instructions"),
          instructions_count, instructions_size));
      if (trampoline_size > 0) {
        clusters_by_size.Add(new (zone_) FakeSerializationCluster(
            ImageWriter::TagObjectTypeAsReadOnly(zone_, "Trampoline"),
            trampoline_count, trampoline_size));
      }
    }
    // The dispatch_table_size_ will be 0 if the snapshot did not include a
    // dispatch table (i.e., the VM snapshot). For a precompiled isolate
    // snapshot, we always serialize at least _one_ byte for the DispatchTable.
    if (dispatch_table_size_ > 0) {
      const auto& dispatch_table_entries = Array::Handle(
          zone_,
          isolate_group()->object_store()->dispatch_table_code_entries());
      auto const entry_count =
          dispatch_table_entries.IsNull() ? 0 : dispatch_table_entries.Length();
      clusters_by_size.Add(new (zone_) FakeSerializationCluster(
          "DispatchTable", entry_count, dispatch_table_size_));
    }
    if (instructions_table_len_ > 0) {
      const intptr_t memory_size =
          compiler::target::InstructionsTable::InstanceSize() +
          compiler::target::Array::InstanceSize(instructions_table_len_);
      clusters_by_size.Add(new (zone_) FakeSerializationCluster(
          "InstructionsTable", instructions_table_len_, 0, memory_size));
    }
    clusters_by_size.Sort(CompareClusters);
    double total_size =
        static_cast<double>(bytes_written() + GetDataSize() + text_size);
    double cumulative_fraction = 0.0;
    for (intptr_t i = 0; i < clusters_by_size.length(); i++) {
      SerializationCluster* cluster = clusters_by_size[i];
      double fraction = static_cast<double>(cluster->size()) / total_size;
      cumulative_fraction += fraction;
      buffer.Printf("%25s", cluster->name());
      buffer.Printf(" %6" Pd "", cluster->num_objects());
      buffer.Printf(" %8" Pd "", cluster->size());
      buffer.Printf(" %1.6lf", fraction);
      buffer.Printf(" %1.8lf", cumulative_fraction);
      buffer.Printf(" %8" Pd "", cluster->target_memory_size());
      if (cluster->cid() != -1) {
        buffer.Printf(" %5" Pd "", cluster->cid());
      } else {
        buffer.Printf(" %5s", "");
      }
      if (cluster->is_canonical()) {
        buffer.Printf(" %9s", "canonical");
      } else {
        buffer.Printf(" %9s", "");
      }
      buffer.AddString("\n");
    }
    OS::PrintErr("%s", buffer.buffer());
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
}

Deserializer::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)
    : ThreadStackResource(thread),
      heap_(thread->isolate_group()->heap()),
      old_space_(heap_->old_space()),
      freelist_(old_space_->DataFreeList()),
      zone_(thread->zone()),
      kind_(kind),
      stream_(buffer, size),
      image_reader_(nullptr),
      refs_(nullptr),
      next_ref_index_(kFirstReference),
      clusters_(nullptr),
      is_non_root_unit_(is_non_root_unit),
      instructions_table_(InstructionsTable::Handle(thread->zone())) {
  if (Snapshot::IncludesCode(kind)) {
    ASSERT(instructions_buffer != nullptr);
    ASSERT(data_buffer != nullptr);
    image_reader_ = new (zone_) ImageReader(data_buffer, instructions_buffer);
  }
  stream_.SetPosition(offset);
}

Deserializer::~Deserializer() {
  delete[] clusters_;
}

DeserializationCluster* Deserializer::ReadCluster() {
  const uint32_t tags = Read<uint32_t>();
  const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
  const bool is_canonical = UntaggedObject::CanonicalBit::decode(tags);
  const bool is_deeply_immutable =
      UntaggedObject::DeeplyImmutableBit::decode(tags);
  Zone* Z = zone_;
  if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
    return new (Z) InstanceDeserializationCluster(
        cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
  }
  if (IsTypedDataViewClassId(cid)) {
    ASSERT(!is_canonical);
    ASSERT(!is_deeply_immutable);
    return new (Z) TypedDataViewDeserializationCluster(cid);
  }
  if (IsExternalTypedDataClassId(cid)) {
    ASSERT(!is_canonical);
    ASSERT(!is_deeply_immutable);
    return new (Z) ExternalTypedDataDeserializationCluster(cid);
  }
  if (IsTypedDataClassId(cid)) {
    ASSERT(!is_canonical);
    ASSERT(!is_deeply_immutable);
    return new (Z) TypedDataDeserializationCluster(cid);
  }

#if !defined(DART_COMPRESSED_POINTERS)
  if (Snapshot::IncludesCode(kind_)) {
    switch (cid) {
      case kPcDescriptorsCid:
      case kCodeSourceMapCid:
      case kCompressedStackMapsCid:
        return new (Z) RODataDeserializationCluster(
            cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
      case kOneByteStringCid:
      case kTwoByteStringCid:
      case kStringCid:
        if (!is_non_root_unit_) {
          return new (Z) RODataDeserializationCluster(
              cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
        }
        break;
    }
  }
#endif

  switch (cid) {
    case kClassCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ClassDeserializationCluster();
    case kTypeParametersCid:
      return new (Z) TypeParametersDeserializationCluster(is_canonical,
                                                          is_deeply_immutable);
    case kTypeArgumentsCid:
      return new (Z) TypeArgumentsDeserializationCluster(
          is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kPatchClassCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) PatchClassDeserializationCluster();
    case kFunctionCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) FunctionDeserializationCluster();
    case kClosureDataCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ClosureDataDeserializationCluster();
    case kFfiTrampolineDataCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) FfiTrampolineDataDeserializationCluster();
    case kFieldCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) FieldDeserializationCluster();
    case kScriptCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ScriptDeserializationCluster();
    case kLibraryCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) LibraryDeserializationCluster();
    case kNamespaceCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) NamespaceDeserializationCluster();
#if !defined(DART_PRECOMPILED_RUNTIME)
    case kKernelProgramInfoCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) KernelProgramInfoDeserializationCluster();
#endif  // !DART_PRECOMPILED_RUNTIME
    case kCodeCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) CodeDeserializationCluster();
#if defined(DART_DYNAMIC_MODULES)
    case kBytecodeCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) BytecodeDeserializationCluster();
#endif
    case kObjectPoolCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ObjectPoolDeserializationCluster();
    case kPcDescriptorsCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) PcDescriptorsDeserializationCluster();
    case kCodeSourceMapCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) CodeSourceMapDeserializationCluster();
    case kCompressedStackMapsCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) CompressedStackMapsDeserializationCluster();
    case kLocalVarDescriptorsCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) LocalVarDescriptorsDeserializationCluster();
    case kExceptionHandlersCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ExceptionHandlersDeserializationCluster();
    case kContextCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ContextDeserializationCluster();
    case kContextScopeCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ContextScopeDeserializationCluster();
    case kUnlinkedCallCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) UnlinkedCallDeserializationCluster();
    case kICDataCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ICDataDeserializationCluster();
    case kMegamorphicCacheCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) MegamorphicCacheDeserializationCluster();
    case kSubtypeTestCacheCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) SubtypeTestCacheDeserializationCluster();
    case kLoadingUnitCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) LoadingUnitDeserializationCluster();
    case kApiErrorCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) ApiErrorDeserializationCluster();
    case kLanguageErrorCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) LanguageErrorDeserializationCluster();
    case kUnhandledExceptionCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) UnhandledExceptionDeserializationCluster();
    case kUnwindErrorCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) UnwindErrorDeserializationCluster();
    case kLibraryPrefixCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) LibraryPrefixDeserializationCluster();
    case kTypeCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          TypeDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kFunctionTypeCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          FunctionTypeDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kRecordTypeCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          RecordTypeDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kTypeParameterCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          TypeParameterDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kClosureCid:
      return new (Z) ClosureDeserializationCluster(
          is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kMintCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          MintDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kDoubleCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          DoubleDeserializationCluster(is_canonical, !is_non_root_unit_);
    case kInt32x4Cid:
    case kFloat32x4Cid:
    case kFloat64x2Cid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          Simd128DeserializationCluster(cid, is_canonical, !is_non_root_unit_);
    case kGrowableObjectArrayCid:
      ASSERT(!is_canonical);
      return new (Z) GrowableObjectArrayDeserializationCluster(
          is_canonical, is_deeply_immutable);
    case kRecordCid:
      return new (Z) RecordDeserializationCluster(
          is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kStackTraceCid:
      ASSERT(!is_canonical);
      return new (Z) StackTraceDeserializationCluster(is_deeply_immutable);
    case kRegExpCid:
      ASSERT(!is_canonical);
      return new (Z) RegExpDeserializationCluster(is_deeply_immutable);
    case kWeakPropertyCid:
      ASSERT(!is_canonical);
      ASSERT(!is_deeply_immutable);
      return new (Z) WeakPropertyDeserializationCluster();
    case kMapCid:
      // We do not have mutable hash maps in snapshots.
      UNREACHABLE();
    case kConstMapCid:
      return new (Z) MapDeserializationCluster(
          kConstMapCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kSetCid:
      // We do not have mutable hash sets in snapshots.
      UNREACHABLE();
    case kConstSetCid:
      return new (Z) SetDeserializationCluster(
          kConstSetCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kArrayCid:
      return new (Z) ArrayDeserializationCluster(
          kArrayCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kImmutableArrayCid:
      return new (Z)
          ArrayDeserializationCluster(kImmutableArrayCid, is_canonical,
                                      is_deeply_immutable, !is_non_root_unit_);
    case kWeakArrayCid:
      return new (Z) WeakArrayDeserializationCluster();
    case kStringCid:
      ASSERT(is_deeply_immutable);
      return new (Z)
          StringDeserializationCluster(is_canonical, !is_non_root_unit_);
#define CASE_FFI_CID(name) case kFfi##name##Cid:
      CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID)
#undef CASE_FFI_CID
      return new (Z) InstanceDeserializationCluster(
          cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
    case kDeltaEncodedTypedDataCid:
      return new (Z) DeltaEncodedTypedDataDeserializationCluster();
    default:
      break;
  }
  FATAL("No cluster defined for cid %" Pd, cid);
  return nullptr;
}

void Deserializer::ReadDispatchTable(
    ReadStream* stream,
    bool deferred,
    const InstructionsTable& root_instruction_table,
    intptr_t deferred_code_start_index,
    intptr_t deferred_code_end_index) {
#if defined(DART_PRECOMPILED_RUNTIME)
  const uint8_t* table_snapshot_start = stream->AddressOfCurrentPosition();
  const intptr_t length = stream->ReadUnsigned();
  if (length == 0) return;

  const intptr_t first_code_id = stream->ReadUnsigned();
  deferred_code_start_index -= first_code_id;
  deferred_code_end_index -= first_code_id;

  auto const IG = isolate_group();
  auto& code = StubCode::DispatchTableNullError();
  ASSERT(!code.IsNull());
  uword null_entry = Code::EntryPointOf(code.ptr());

  DispatchTable* table;
  if (deferred) {
    table = IG->dispatch_table();
    ASSERT(table != nullptr && table->length() == length);
  } else {
    ASSERT(IG->dispatch_table() == nullptr);
    table = new DispatchTable(length);
  }
  auto const array = table->array();
  uword value = 0;
  uword recent[kDispatchTableRecentCount] = {0};
  intptr_t recent_index = 0;
  intptr_t repeat_count = 0;
  for (intptr_t i = 0; i < length; i++) {
    if (repeat_count > 0) {
      array[i] = value;
      repeat_count--;
      continue;
    }
    auto const encoded = stream->Read<intptr_t>();
    if (encoded == 0) {
      value = null_entry;
    } else if (encoded < 0) {
      intptr_t r = ~encoded;
      ASSERT(r < kDispatchTableRecentCount);
      value = recent[r];
    } else if (encoded <= kDispatchTableMaxRepeat) {
      repeat_count = encoded - 1;
    } else {
      const intptr_t code_index = encoded - kDispatchTableIndexBase;
      if (deferred) {
        const intptr_t code_id =
            CodeIndexToClusterIndex(root_instruction_table, code_index);
        if ((deferred_code_start_index <= code_id) &&
            (code_id < deferred_code_end_index)) {
          auto code = static_cast<CodePtr>(Ref(first_code_id + code_id));
          value = Code::EntryPointOf(code);
        } else {
          // Reuse old value from the dispatch table.
          value = array[i];
        }
      } else {
        value = GetEntryPointByCodeIndex(code_index);
      }
      recent[recent_index] = value;
      recent_index = (recent_index + 1) & kDispatchTableRecentMask;
    }
    array[i] = value;
  }
  ASSERT(repeat_count == 0);

  if (!deferred) {
    IG->set_dispatch_table(table);
    intptr_t table_snapshot_size =
        stream->AddressOfCurrentPosition() - table_snapshot_start;
    IG->set_dispatch_table_snapshot(table_snapshot_start);
    IG->set_dispatch_table_snapshot_size(table_snapshot_size);
  }
#endif
}

char* Deserializer::VerifyImageAlignment() {
  if (image_reader_ != nullptr) {
    return image_reader_->VerifyAlignment();
  }
  return nullptr;
}

char* SnapshotHeaderReader::VerifyVersionAndFeatures(
    IsolateGroup* isolate_group,
    intptr_t* offset) {
  char* error = VerifyVersion();
  if (error == nullptr) {
    error = VerifyFeatures(isolate_group);
  }
  if (error == nullptr) {
    *offset = stream_.Position();
  }
  return error;
}

char* SnapshotHeaderReader::VerifyVersion() {
  // If the version string doesn't match, return an error.
  // Note: New things are allocated only if we're going to return an error.

  const char* expected_version = Version::SnapshotString();
  ASSERT(expected_version != nullptr);
  const intptr_t version_len = strlen(expected_version);
  if (stream_.PendingBytes() < version_len) {
    const intptr_t kMessageBufferSize = 128;
    char message_buffer[kMessageBufferSize];
    Utils::SNPrint(message_buffer, kMessageBufferSize,
                   "No full snapshot version found, expected '%s'",
                   expected_version);
    return BuildError(message_buffer);
  }

  const char* version =
      reinterpret_cast<const char*>(stream_.AddressOfCurrentPosition());
  ASSERT(version != nullptr);
  if (strncmp(version, expected_version, version_len) != 0) {
    const intptr_t kMessageBufferSize = 256;
    char message_buffer[kMessageBufferSize];
    char* actual_version = Utils::StrNDup(version, version_len);
    Utils::SNPrint(message_buffer, kMessageBufferSize,
                   "Wrong %s snapshot version, expected '%s' found '%s'",
                   (Snapshot::IsFull(kind_)) ? "full" : "script",
                   expected_version, actual_version);
    free(actual_version);
    return BuildError(message_buffer);
  }
  stream_.Advance(version_len);

  return nullptr;
}

char* SnapshotHeaderReader::VerifyFeatures(IsolateGroup* isolate_group) {
  const char* expected_features = Dart::FeaturesString(isolate_group, kind_);
  ASSERT(expected_features != nullptr);
  const intptr_t expected_len = strlen(expected_features);

  const char* features = nullptr;
  intptr_t features_length = 0;

  auto error = ReadFeatures(&features, &features_length);
  if (error != nullptr) {
    return error;
  }

  if (features_length != expected_len ||
      (strncmp(features, expected_features, expected_len) != 0)) {
    const intptr_t kMessageBufferSize = 1024;
    char message_buffer[kMessageBufferSize];
    char* actual_features = Utils::StrNDup(
        features, features_length < 1024 ? features_length : 1024);
    Utils::SNPrint(message_buffer, kMessageBufferSize,
                   "Snapshot not compatible with the current VM configuration: "
                   "the snapshot requires '%s' but the VM has '%s'",
                   actual_features, expected_features);
    free(const_cast<char*>(expected_features));
    free(actual_features);
    return BuildError(message_buffer);
  }
  free(const_cast<char*>(expected_features));
  return nullptr;
}

char* SnapshotHeaderReader::ReadFeatures(const char** features,
                                         intptr_t* features_length) {
  const char* cursor =
      reinterpret_cast<const char*>(stream_.AddressOfCurrentPosition());
  const intptr_t length = Utils::StrNLen(cursor, stream_.PendingBytes());
  if (length == stream_.PendingBytes()) {
    return BuildError(
        "The features string in the snapshot was not '\\0'-terminated.");
  }
  *features = cursor;
  *features_length = length;
  stream_.Advance(length + 1);
  return nullptr;
}

char* SnapshotHeaderReader::BuildError(const char* message) {
  return Utils::StrDup(message);
}

void Deserializer::ReadInstructions(CodePtr code, bool deferred) {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (deferred) {
    // TODO(rmacnak): Fill in for better errors.
    // uword entry_point = StubCode::NotLoaded().EntryPoint();
    uword entry_point = 1;
    // There are no serialized RawInstructions objects in this mode.
    code->untag()->instructions_ = Instructions::null();
    code->untag()->entry_point_ = entry_point;
    code->untag()->unchecked_entry_point_ = entry_point;
    code->untag()->monomorphic_entry_point_ = entry_point;
    code->untag()->monomorphic_unchecked_entry_point_ = entry_point;
    code->untag()->instructions_length_ = 0;
    return;
  }

  const uword payload_start = instructions_table_.EntryPointAt(
      instructions_table_.rodata()->first_entry_with_code +
      instructions_index_);
  const uint32_t payload_info = ReadUnsigned();
  const uint32_t unchecked_offset = payload_info >> 1;
  const bool has_monomorphic_entrypoint = (payload_info & 0x1) == 0x1;

  const uword entry_offset =
      has_monomorphic_entrypoint ? Instructions::kPolymorphicEntryOffsetAOT : 0;
  const uword monomorphic_entry_offset =
      has_monomorphic_entrypoint ? Instructions::kMonomorphicEntryOffsetAOT : 0;

  const uword entry_point = payload_start + entry_offset;
  const uword monomorphic_entry_point =
      payload_start + monomorphic_entry_offset;

  instructions_table_.SetCodeAt(instructions_index_++, code);

  // There are no serialized RawInstructions objects in this mode.
  code->untag()->instructions_ = Instructions::null();
  code->untag()->entry_point_ = entry_point;
  code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset;
  code->untag()->monomorphic_entry_point_ = monomorphic_entry_point;
  code->untag()->monomorphic_unchecked_entry_point_ =
      monomorphic_entry_point + unchecked_offset;
#else
  ASSERT(!deferred);
  InstructionsPtr instr = image_reader_->GetInstructionsAt(Read<uint32_t>());
  uint32_t unchecked_offset = ReadUnsigned();
  code->untag()->instructions_ = instr;
  code->untag()->unchecked_offset_ = unchecked_offset;
  ASSERT(kind() == Snapshot::kFullJIT);
  const uint32_t active_offset = Read<uint32_t>();
  instr = image_reader_->GetInstructionsAt(active_offset);
  unchecked_offset = ReadUnsigned();
  code->untag()->active_instructions_ = instr;
  Code::InitializeCachedEntryPointsFrom(code, instr, unchecked_offset);
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

void Deserializer::EndInstructions() {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (instructions_table_.IsNull()) {
    ASSERT(instructions_index_ == 0);
    return;
  }

  const auto& code_objects =
      Array::Handle(instructions_table_.ptr()->untag()->code_objects());
  ASSERT(code_objects.Length() == instructions_index_);

  uword previous_end = image_reader_->GetBareInstructionsEnd();
  for (intptr_t i = instructions_index_ - 1; i >= 0; --i) {
    CodePtr code = Code::RawCast(code_objects.At(i));
    uword start = Code::PayloadStartOf(code);
    ASSERT(start <= previous_end);
    code->untag()->instructions_length_ = previous_end - start;
    previous_end = start;
  }

  ObjectStore* object_store = isolate_group()->object_store();
  GrowableObjectArray& tables =
      GrowableObjectArray::Handle(zone_, object_store->instructions_tables());
  if (tables.IsNull()) {
    tables = GrowableObjectArray::New(Heap::kOld);
    object_store->set_instructions_tables(tables);
  }
  if ((tables.Length() == 0) ||
      (tables.At(tables.Length() - 1) != instructions_table_.ptr())) {
    ASSERT((!is_non_root_unit_ && tables.Length() == 0) ||
           (is_non_root_unit_ && tables.Length() > 0));
    tables.Add(instructions_table_, Heap::kOld);
  }
#endif
}

ObjectPtr Deserializer::GetObjectAt(uint32_t offset) const {
  return image_reader_->GetObjectAt(offset);
}

class HeapLocker : public StackResource {
 public:
  HeapLocker(Thread* thread, PageSpace* page_space)
      : StackResource(thread),
        page_space_(page_space),
        freelist_(page_space->DataFreeList()) {
    page_space_->AcquireLock(freelist_);
  }
  ~HeapLocker() { page_space_->ReleaseLock(freelist_); }

 private:
  PageSpace* page_space_;
  FreeList* freelist_;
};

void Deserializer::Deserialize(DeserializationRoots* roots) {
  const void* clustered_start = AddressOfCurrentPosition();

  Array& refs = Array::Handle(zone_);
  num_base_objects_ = ReadUnsigned();
  num_objects_ = ReadUnsigned();
  num_clusters_ = ReadUnsigned();
  const intptr_t instructions_table_len = ReadUnsigned();
  const uint32_t instruction_table_data_offset = ReadUnsigned();
  USE(instruction_table_data_offset);

  clusters_ = new DeserializationCluster*[num_clusters_];
  refs = Array::New(num_objects_ + kFirstReference, Heap::kOld);

#if defined(DART_PRECOMPILED_RUNTIME)
  if (instructions_table_len > 0) {
    ASSERT(FLAG_precompiled_mode);
    const uword start_pc = image_reader_->GetBareInstructionsAt(0);
    const uword end_pc = image_reader_->GetBareInstructionsEnd();
    uword instruction_table_data = 0;
    if (instruction_table_data_offset != 0) {
      // NoSafepointScope to satisfy assertion in DataStart. InstructionsTable
      // data resides in RO memory and is immovable and immortal making it
      // safe to use DataStart result outside of NoSafepointScope.
      NoSafepointScope no_safepoint;
      instruction_table_data = reinterpret_cast<uword>(
          OneByteString::DataStart(String::Handle(static_cast<StringPtr>(
              image_reader_->GetObjectAt(instruction_table_data_offset)))));
    }
    instructions_table_ = InstructionsTable::New(
        instructions_table_len, start_pc, end_pc, instruction_table_data);
  }
#else
  ASSERT(instructions_table_len == 0);
#endif  // defined(DART_PRECOMPILED_RUNTIME)

  {
    // The deserializer initializes objects without using the write barrier,
    // partly for speed since we know all the deserialized objects will be
    // long-lived and partly because the target objects can be not yet
    // initialized at the time of the write. To make this safe, we must ensure
    // there are no other threads mutating this heap, and that incremental
    // marking is not in progress. This is normally the case anyway for the
    // main snapshot being deserialized at isolate load, but needs checks for
    // loading secondary snapshots are part of deferred loading.
    HeapIterationScope iter(thread());
    // For bump-pointer allocation in old-space.
    HeapLocker hl(thread(), heap_->old_space());
    // Must not perform any other type of allocation, which might trigger GC
    // while there are still uninitialized objects.
    NoSafepointScope no_safepoint;
    refs_ = refs.ptr();

    roots->AddBaseObjects(this);

    if (num_base_objects_ != (next_ref_index_ - kFirstReference)) {
      FATAL("Snapshot expects %" Pd
            " base objects, but deserializer provided %" Pd,
            num_base_objects_, next_ref_index_ - kFirstReference);
    }

    {
      TIMELINE_DURATION(thread(), Isolate, "ReadAlloc");
      for (intptr_t i = 0; i < num_clusters_; i++) {
        clusters_[i] = ReadCluster();
        clusters_[i]->ReadAlloc(this);
#if defined(DEBUG)
        intptr_t serializers_next_ref_index_ = Read<int32_t>();
        ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
#endif
      }
    }

    // We should have completely filled the ref array.
    ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects_);

    {
      TIMELINE_DURATION(thread(), Isolate, "ReadFill");
      for (intptr_t i = 0; i < num_clusters_; i++) {
        clusters_[i]->ReadFill(this);
#if defined(DEBUG)
        int32_t section_marker = Read<int32_t>();
        ASSERT(section_marker == kSectionMarker);
#endif
      }
    }

    roots->ReadRoots(this);

#if defined(DEBUG)
    int32_t section_marker = Read<int32_t>();
    ASSERT(section_marker == kSectionMarker);
#endif

    refs_ = nullptr;
  }

  roots->PostLoad(this, refs);

  auto isolate_group = thread()->isolate_group();
#if defined(DEBUG)
  isolate_group->ValidateClassTable();
  isolate_group->heap()->Verify("Deserializer::Deserialize");
#endif

  {
    TIMELINE_DURATION(thread(), Isolate, "PostLoad");
    for (intptr_t i = 0; i < num_clusters_; i++) {
      clusters_[i]->PostLoad(this, refs);
    }
  }

  if (isolate_group->snapshot_is_dontneed_safe()) {
    size_t clustered_length =
        reinterpret_cast<uword>(AddressOfCurrentPosition()) -
        reinterpret_cast<uword>(clustered_start);
    VirtualMemory::DontNeed(const_cast<void*>(clustered_start),
                            clustered_length);
  }
}

#if !defined(DART_PRECOMPILED_RUNTIME)
FullSnapshotWriter::FullSnapshotWriter(Snapshot::Kind kind,
                                       NonStreamingWriteStream* snapshot_data,
                                       ImageWriter* image_writer)
    : thread_(Thread::Current()),
      kind_(kind),
      snapshot_data_(snapshot_data),
      image_writer_(image_writer) {
  ASSERT(isolate_group() != nullptr);
  ASSERT(heap() != nullptr);
  ObjectStore* object_store = isolate_group()->object_store();
  ASSERT(object_store != nullptr);

#if defined(DEBUG)
  isolate_group()->ValidateClassTable();
#endif  // DEBUG

#if defined(DART_PRECOMPILER)
  if (FLAG_write_v8_snapshot_profile_to != nullptr) {
    profile_writer_ = new (zone()) V8SnapshotProfileWriter(zone());
  }
#endif
}

FullSnapshotWriter::~FullSnapshotWriter() {}

void FullSnapshotWriter::WriteProgramSnapshot(
    GrowableArray<LoadingUnitSerializationData*>* units) {
  TIMELINE_DURATION(thread(), Isolate, "WriteProgramSnapshot");

  ASSERT(snapshot_data_ != nullptr);
  Serializer serializer(thread(), kind_, snapshot_data_, image_writer_,
                        profile_writer_);
  serializer.set_loading_units(units);
  serializer.set_current_loading_unit_id(LoadingUnit::kRootId);
  ObjectStore* object_store = isolate_group()->object_store();
  ASSERT(object_store != nullptr);

  // These type arguments must always be retained.
  ASSERT(object_store->type_argument_int()->untag()->IsCanonical());
  ASSERT(object_store->type_argument_double()->untag()->IsCanonical());
  ASSERT(object_store->type_argument_string()->untag()->IsCanonical());
  ASSERT(object_store->type_argument_string_dynamic()->untag()->IsCanonical());
  ASSERT(object_store->type_argument_string_string()->untag()->IsCanonical());

  serializer.ReserveHeader();
  serializer.WriteVersionAndFeatures();
  ProgramSerializationRoots roots(object_store, kind_);
  ZoneGrowableArray<Object*>* objects = serializer.Serialize(&roots);
  if (units != nullptr) {
    (*units)[LoadingUnit::kRootId]->set_objects(objects);
  }
  serializer.FillHeader(serializer.kind());
  clustered_isolate_size_ = serializer.bytes_written();
  heap_isolate_size_ = serializer.bytes_heap_allocated();

  if (Snapshot::IncludesCode(kind_)) {
    image_writer_->SetProfileWriter(profile_writer_);
    image_writer_->Write(serializer.stream(), false);
#if defined(DART_PRECOMPILER)
    image_writer_->DumpStatistics();
#endif

    mapped_data_size_ += image_writer_->data_size();
    mapped_text_size_ += image_writer_->text_size();
    image_writer_->ResetOffsets();
    image_writer_->ClearProfileWriter();
  }
}

void FullSnapshotWriter::WriteUnitSnapshot(
    GrowableArray<LoadingUnitSerializationData*>* units,
    LoadingUnitSerializationData* unit,
    uint32_t program_hash) {
  TIMELINE_DURATION(thread(), Isolate, "WriteUnitSnapshot");

  Serializer serializer(thread(), kind_, snapshot_data_, image_writer_,
                        profile_writer_);
  serializer.set_loading_units(units);
  serializer.set_current_loading_unit_id(unit->id());

  serializer.ReserveHeader();
  serializer.WriteVersionAndFeatures();
  serializer.Write(program_hash);

  UnitSerializationRoots roots(unit);
  unit->set_objects(serializer.Serialize(&roots));

  serializer.FillHeader(serializer.kind());
  clustered_isolate_size_ = serializer.bytes_written();

  if (Snapshot::IncludesCode(kind_)) {
    image_writer_->SetProfileWriter(profile_writer_);
    image_writer_->Write(serializer.stream(), false);
#if defined(DART_PRECOMPILER)
    image_writer_->DumpStatistics();
#endif

    mapped_data_size_ += image_writer_->data_size();
    mapped_text_size_ += image_writer_->text_size();
    image_writer_->ResetOffsets();
    image_writer_->ClearProfileWriter();
  }
}

void FullSnapshotWriter::WriteFullSnapshot(
    GrowableArray<LoadingUnitSerializationData*>* data) {
  WriteProgramSnapshot(data);

  if (FLAG_print_snapshot_sizes) {
    OS::Print("VMIsolate(CodeSize): %" Pd "\n", clustered_vm_size_);
    OS::Print("Isolate(CodeSize): %" Pd "\n", clustered_isolate_size_);
    OS::Print("ReadOnlyData(CodeSize): %" Pd "\n", mapped_data_size_);
    OS::Print("Instructions(CodeSize): %" Pd "\n", mapped_text_size_);
    OS::Print("Total(CodeSize): %" Pd "\n",
              clustered_vm_size_ + clustered_isolate_size_ + mapped_data_size_ +
                  mapped_text_size_);
    OS::Print("VMIsolate(HeapSize): %" Pd "\n", heap_vm_size_);
    OS::Print("Isolate(HeapSize): %" Pd "\n", heap_isolate_size_);
    OS::Print("Total(HeapSize): %" Pd "\n", heap_vm_size_ + heap_isolate_size_);
  }

#if defined(DART_PRECOMPILER)
  if (FLAG_write_v8_snapshot_profile_to != nullptr) {
    profile_writer_->Write(FLAG_write_v8_snapshot_profile_to);
  }
#endif
}
#endif  // defined(DART_PRECOMPILED_RUNTIME)

FullSnapshotReader::FullSnapshotReader(const Snapshot* snapshot,
                                       const uint8_t* instructions_buffer,
                                       Thread* thread)
    : kind_(snapshot->kind()),
      thread_(thread),
      buffer_(snapshot->Addr()),
      size_(snapshot->length()),
      data_image_(snapshot->DataImage()),
      instructions_image_(instructions_buffer) {}

char* SnapshotHeaderReader::InitializeIsolateGroupFlagsFromSnapshot(
    const Snapshot* snapshot) {
  SnapshotHeaderReader header_reader(snapshot);

  char* error = header_reader.VerifyVersion();
  if (error != nullptr) {
    return error;
  }

  const char* features = nullptr;
  intptr_t features_length = 0;
  error = header_reader.ReadFeatures(&features, &features_length);
  if (error != nullptr) {
    return error;
  }

  IsolateGroup* group = IsolateGroup::Current();

  ASSERT(features[features_length] == '\0');
  const char* cursor = features;
  while (*cursor != '\0') {
    while (*cursor == ' ') {
      cursor++;
    }

    const char* end = strstr(cursor, " ");
    if (end == nullptr) {
      end = features + features_length;
    }

#define SET_FLAG(name, isolate_flag)                                           \
  if (strncmp(cursor, #name, end - cursor) == 0) {                             \
    group->set_##isolate_flag(true);                                           \
    cursor = end;                                                              \
    continue;                                                                  \
  }                                                                            \
  if (strncmp(cursor, "no-" #name, end - cursor) == 0) {                       \
    group->set_##isolate_flag(false);                                          \
    cursor = end;                                                              \
    continue;                                                                  \
  }

#define ISOLATE_GROUP_FLAG_LIST(V)                                             \
  V(asserts, asserts)                                                          \
  V(use_field_guards, use_field_guards)                                        \
  V(use_osr, use_osr)                                                          \
  V(branch_coverage, branch_coverage)                                          \
  V(coverage, coverage)                                                        \
  V(code_comments, code_comments)                                              \
  V(dwarf_stack_traces, dwarf_stack_traces)

    ISOLATE_GROUP_FLAG_LIST(SET_FLAG)
#undef ISOLATE_GROUP_FLAG_LIST

    cursor = end;
  }

  return nullptr;
}

char* FullSnapshotReader::ReadProgramSnapshot() {
  SnapshotHeaderReader header_reader(kind_, buffer_, size_);
  intptr_t offset = 0;
  char* error =
      header_reader.VerifyVersionAndFeatures(thread_->isolate_group(), &offset);
  if (error != nullptr) {
    return error;
  }

  // Even though there's no concurrent threads we have to guard agains, some
  // logic we do in deserialization triggers common code that asserts the
  // program lock is held.
  SafepointWriteRwLocker ml(thread_, isolate_group()->program_lock());

  Deserializer deserializer(thread_, kind_, buffer_, size_, data_image_,
                            instructions_image_, /*is_non_root_unit=*/false,
                            offset);
  error = deserializer.VerifyImageAlignment();
  if (error != nullptr) {
    return error;
  }

  if (Snapshot::IncludesCode(kind_)) {
    ASSERT(data_image_ != nullptr);
    thread_->isolate_group()->SetupImagePage(data_image_,
                                             /* is_executable */ false);
    ASSERT(instructions_image_ != nullptr);
    thread_->isolate_group()->SetupImagePage(instructions_image_,
                                             /* is_executable */ true);
  }

  ProgramDeserializationRoots roots(thread_->isolate_group()->object_store());
  deserializer.Deserialize(&roots);

  if (Snapshot::IncludesCode(kind_)) {
    const auto& units = Array::Handle(
        thread_->isolate_group()->object_store()->loading_units());
    if (!units.IsNull()) {
      const auto& unit = LoadingUnit::Handle(
          LoadingUnit::RawCast(units.At(LoadingUnit::kRootId)));
      // Unlike other units, we don't explicitly load the root loading unit,
      // so we mark it as loaded here, setting the instructions image as well.
      unit.set_load_outstanding();
      unit.set_instructions_image(instructions_image_);
      unit.set_loaded(true);
    }
  }

  InitializeBSS();

  return nullptr;
}

char* FullSnapshotReader::ReadUnitSnapshot(const LoadingUnit& unit) {
  SnapshotHeaderReader header_reader(kind_, buffer_, size_);
  intptr_t offset = 0;
  char* error =
      header_reader.VerifyVersionAndFeatures(thread_->isolate_group(), &offset);
  if (error != nullptr) {
    return error;
  }

  Deserializer deserializer(
      thread_, kind_, buffer_, size_, data_image_, instructions_image_,
      /*is_non_root_unit=*/unit.id() != LoadingUnit::kRootId, offset);
  error = deserializer.VerifyImageAlignment();
  if (error != nullptr) {
    return nullptr;
  }
  {
    Array& units =
        Array::Handle(isolate_group()->object_store()->loading_units());
    uint32_t main_program_hash = Smi::Value(Smi::RawCast(units.At(0)));
    uint32_t unit_program_hash = deserializer.Read<uint32_t>();
    if (main_program_hash != unit_program_hash) {
      return Utils::StrDup(
          "Deferred loading unit is from a different "
          "program than the main loading unit");
    }
  }

  if (Snapshot::IncludesCode(kind_)) {
    ASSERT(data_image_ != nullptr);
    thread_->isolate_group()->SetupImagePage(data_image_,
                                             /* is_executable */ false);
    ASSERT(instructions_image_ != nullptr);
    thread_->isolate_group()->SetupImagePage(instructions_image_,
                                             /* is_executable */ true);
    unit.set_instructions_image(instructions_image_);
  }

  UnitDeserializationRoots roots(unit);
  deserializer.Deserialize(&roots);

  InitializeBSS();

  return nullptr;
}

void FullSnapshotReader::InitializeBSS() {
#if defined(DART_PRECOMPILED_RUNTIME)
  // Initialize entries in the isolate portion of the BSS segment.
  ASSERT(Snapshot::IncludesCode(kind_));
  Image image(instructions_image_);
  if (auto const bss = image.bss()) {
    BSS::Initialize(thread_, bss, /*vm=*/false);
  }
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

}  // namespace dart
