// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/message_snapshot.h"

#include <memory>

#include "platform/assert.h"
#include "platform/unicode.h"
#include "vm/class_finalizer.h"
#include "vm/class_id.h"
#include "vm/dart_api_message.h"
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/flags.h"
#include "vm/growable_array.h"
#include "vm/heap/heap.h"
#include "vm/heap/weak_table.h"
#include "vm/longjump.h"
#include "vm/object.h"
#include "vm/object_graph_copy.h"
#include "vm/object_store.h"
#include "vm/symbols.h"
#include "vm/type_testing_stubs.h"

namespace dart {

static Dart_CObject cobj_sentinel = {.type = Dart_CObject_kUnsupported};
static Dart_CObject cobj_dynamic_type = {.type = Dart_CObject_kUnsupported};
static Dart_CObject cobj_void_type = {.type = Dart_CObject_kUnsupported};
static Dart_CObject cobj_empty_type_arguments = {.type =
                                                     Dart_CObject_kUnsupported};
static Dart_CObject cobj_true = {.type = Dart_CObject_kBool,
                                 .value = {.as_bool = true}};
static Dart_CObject cobj_false = {.type = Dart_CObject_kBool,
                                  .value = {.as_bool = false}};
static Dart_CObject cobj_null = {.type = Dart_CObject_kNull,
                                 .value = {.as_int64 = 0}};
static Dart_CObject cobj_empty_array = {.type = Dart_CObject_kArray,
                                        .value = {.as_array = {0, nullptr}}};

enum class MessagePhase {
  kBeforeTypes = 0,
  kTypes = 1,
  kCanonicalInstances = 2,
  kNonCanonicalInstances = 3,

  kNumPhases = 4,
};

class MessageSerializer;
class MessageDeserializer;
class ApiMessageSerializer;
class ApiMessageDeserializer;

class MessageSerializationCluster : public ZoneObject {
 public:
  explicit MessageSerializationCluster(const char* name,
                                       MessagePhase phase,
                                       intptr_t cid,
                                       bool is_canonical = false)
      : name_(name), phase_(phase), cid_(cid), is_canonical_(is_canonical) {}
  virtual ~MessageSerializationCluster() {}

  virtual void Trace(MessageSerializer* s, Object* object) = 0;
  virtual void WriteNodes(MessageSerializer* s) = 0;
  virtual void WriteEdges(MessageSerializer* s) {}

  virtual void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {}
  virtual void WriteNodesApi(ApiMessageSerializer* s) {}
  virtual void WriteEdgesApi(ApiMessageSerializer* s) {}

  const char* name() const { return name_; }
  MessagePhase phase() const { return phase_; }
  intptr_t cid() const { return cid_; }
  bool is_canonical() const { return is_canonical_; }

 protected:
  const char* const name_;
  const MessagePhase phase_;
  const intptr_t cid_;
  const bool is_canonical_;

  DISALLOW_COPY_AND_ASSIGN(MessageSerializationCluster);
};

class MessageDeserializationCluster : public ZoneObject {
 public:
  explicit MessageDeserializationCluster(const char* name,
                                         bool is_canonical = false)
      : name_(name),
        is_canonical_(is_canonical),
        start_index_(0),
        stop_index_(0) {}
  virtual ~MessageDeserializationCluster() {}

  virtual void ReadNodes(MessageDeserializer* d) = 0;
  virtual void ReadEdges(MessageDeserializer* d) {}
  virtual ObjectPtr PostLoad(MessageDeserializer* d) { return nullptr; }
  virtual void ReadNodesApi(ApiMessageDeserializer* d) {}
  virtual void ReadEdgesApi(ApiMessageDeserializer* d) {}
  virtual void PostLoadApi(ApiMessageDeserializer* d) {}

  void ReadNodesWrapped(MessageDeserializer* d);
  void ReadNodesWrappedApi(ApiMessageDeserializer* d);

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

 protected:
  ObjectPtr PostLoadAbstractType(MessageDeserializer* d);
  ObjectPtr PostLoadLinkedHash(MessageDeserializer* d);

  const char* const name_;
  const bool is_canonical_;
  // The range of the ref array that belongs to this cluster.
  intptr_t start_index_;
  intptr_t stop_index_;

  DISALLOW_COPY_AND_ASSIGN(MessageDeserializationCluster);
};

class BaseSerializer : public StackResource {
 public:
  BaseSerializer(Thread* thread, Zone* zone);
  ~BaseSerializer();

  // Writes raw data to the stream (basic type).
  // sizeof(T) must be in {1,2,4,8}.
  template <typename T>
  void Write(T value) {
    BaseWriteStream::Raw<sizeof(T), T>::Write(&stream_, value);
  }
  void WriteUnsigned(intptr_t value) { stream_.WriteUnsigned(value); }
  void WriteWordWith32BitWrites(uword value) {
    stream_.WriteWordWith32BitWrites(value);
  }
  void WriteBytes(const void* addr, intptr_t len) {
    stream_.WriteBytes(addr, len);
  }
  void WriteAscii(const String& str) {
    intptr_t len = str.Length();
    WriteUnsigned(len);
    for (intptr_t i = 0; i < len; i++) {
      int64_t c = str.CharAt(i);
      ASSERT(c < 128);
      Write<uint8_t>(c);
    }
    Write<uint8_t>(0);
  }

  MessageSerializationCluster* NewClusterForClass(intptr_t cid,
                                                  bool is_canonical);
  void WriteCluster(MessageSerializationCluster* cluster);

  std::unique_ptr<Message> Finish(Dart_Port dest_port,
                                  Message::Priority priority) {
    MessageFinalizableData* finalizable_data = finalizable_data_;
    finalizable_data_ = nullptr;
    finalizable_data->SerializationSucceeded();
    intptr_t size;
    uint8_t* buffer = stream_.Steal(&size);
    return Message::New(dest_port, buffer, size, finalizable_data, priority);
  }

  Zone* zone() const { return zone_; }
  MessageFinalizableData* finalizable_data() const { return finalizable_data_; }
  intptr_t next_ref_index() const { return next_ref_index_; }

 protected:
  Zone* const zone_;
  MallocWriteStream stream_;
  MessageFinalizableData* finalizable_data_;
  GrowableArray<MessageSerializationCluster*> clusters_;
  intptr_t num_base_objects_;
  intptr_t num_written_objects_;
  intptr_t next_ref_index_;
};

class MessageSerializer : public BaseSerializer {
 public:
  explicit MessageSerializer(Thread* thread);
  ~MessageSerializer();

  bool MarkObjectId(ObjectPtr object, intptr_t id) {
    ASSERT(id != WeakTable::kNoValue);
    WeakTable* table;
    if (object->IsImmediateOrOldObject()) {
      table = thread()->forward_table_old();
    } else {
      table = thread()->forward_table_new();
    }
    return table->MarkValueExclusive(object, id);
  }

  void SetObjectId(ObjectPtr object, intptr_t id) {
    ASSERT(id != WeakTable::kNoValue);
    WeakTable* table;
    if (object->IsImmediateOrOldObject()) {
      table = thread()->forward_table_old();
    } else {
      table = thread()->forward_table_new();
    }
    table->SetValueExclusive(object, id);
  }

  intptr_t GetObjectId(ObjectPtr object) const {
    const WeakTable* table;
    if (object->IsImmediateOrOldObject()) {
      table = thread()->forward_table_old();
    } else {
      table = thread()->forward_table_new();
    }
    return table->GetValueExclusive(object);
  }

  DART_NOINLINE void AddBaseObject(ObjectPtr base_object) {
    AssignRef(base_object);
    num_base_objects_++;
  }
  DART_NOINLINE void AssignRef(ObjectPtr object) {
    SetObjectId(object, next_ref_index_);
    next_ref_index_++;
  }
  void AssignRef(Object* object) { AssignRef(object->ptr()); }

  void Push(ObjectPtr object);

  void Trace(const Object& root, Object* object);

  void IllegalObject(const Object& object, const char* message);

  void AddBaseObjects();
  void Serialize(const Object& root);

  DART_NOINLINE void WriteRef(ObjectPtr object) {
    intptr_t index = GetObjectId(object);
    ASSERT(index != WeakTable::kNoValue);
    WriteUnsigned(index);
  }

  Thread* thread() const {
    return static_cast<Thread*>(StackResource::thread());
  }
  IsolateGroup* isolate_group() const { return thread()->isolate_group(); }

  bool HasRef(ObjectPtr object) const {
    return GetObjectId(object) != WeakTable::kNoValue;
  }

 private:
  Thread* thread_;
  WeakTable* forward_table_new_;
  WeakTable* forward_table_old_;
  GrowableArray<Object*> stack_;
};

class ApiMessageSerializer : public BaseSerializer {
 public:
  explicit ApiMessageSerializer(Zone* zone);
  ~ApiMessageSerializer();

  bool MarkObjectId(Dart_CObject* object, intptr_t id) {
    ASSERT(id != WeakTable::kNoValue);
    return forward_table_.MarkValueExclusive(
        static_cast<ObjectPtr>(reinterpret_cast<uword>(object)), id);
  }

  void SetObjectId(Dart_CObject* object, intptr_t id) {
    ASSERT(id != WeakTable::kNoValue);
    forward_table_.SetValueExclusive(
        static_cast<ObjectPtr>(reinterpret_cast<uword>(object)), id);
  }

  intptr_t GetObjectId(Dart_CObject* object) const {
    return forward_table_.GetValueExclusive(
        static_cast<ObjectPtr>(reinterpret_cast<uword>(object)));
  }

  DART_NOINLINE void AddBaseObject(Dart_CObject* base_object) {
    AssignRef(base_object);
    num_base_objects_++;
  }
  DART_NOINLINE intptr_t AssignRef(Dart_CObject* object) {
    SetObjectId(object, next_ref_index_);
    return next_ref_index_++;
  }
  void ForwardRef(Dart_CObject* old, Dart_CObject* nue) {
    intptr_t id = GetObjectId(nue);
    ASSERT(id != WeakTable::kNoValue);
    SetObjectId(old, id);
    num_written_objects_--;
  }

  void Push(Dart_CObject* object);

  bool Trace(Dart_CObject* object);

  void AddBaseObjects();
  bool Serialize(Dart_CObject* root);

  void WriteRef(Dart_CObject* object) {
    intptr_t index = GetObjectId(object);
    ASSERT(index != WeakTable::kNoValue);
    WriteUnsigned(index);
  }

  bool Fail(const char* message) {
    exception_message_ = message;
    return false;
  }

 private:
  WeakTable forward_table_;
  GrowableArray<Dart_CObject*> stack_;
  const char* exception_message_;
};

class BaseDeserializer : public ValueObject {
 public:
  BaseDeserializer(Zone* zone, Message* message);
  ~BaseDeserializer();

  // Reads raw data (for basic types).
  // sizeof(T) must be in {1,2,4,8}.
  template <typename T>
  T Read() {
    return ReadStream::Raw<sizeof(T), T>::Read(&stream_);
  }
  intptr_t ReadUnsigned() { return stream_.ReadUnsigned(); }
  uword ReadWordWith32BitReads() { return stream_.ReadWordWith32BitReads(); }
  void ReadBytes(void* addr, intptr_t len) { stream_.ReadBytes(addr, len); }
  const char* ReadAscii() {
    intptr_t len = ReadUnsigned();
    const char* result = reinterpret_cast<const char*>(CurrentBufferAddress());
    Advance(len + 1);
    return result;
  }

  const uint8_t* CurrentBufferAddress() const {
    return stream_.AddressOfCurrentPosition();
  }

  void Advance(intptr_t value) { stream_.Advance(value); }

  MessageDeserializationCluster* ReadCluster();

  Zone* zone() const { return zone_; }
  intptr_t next_index() const { return next_ref_index_; }
  MessageFinalizableData* finalizable_data() const { return finalizable_data_; }

 protected:
  Zone* zone_;
  ReadStream stream_;
  MessageFinalizableData* finalizable_data_;
  intptr_t next_ref_index_;
};

class MessageDeserializer : public BaseDeserializer {
 public:
  MessageDeserializer(Thread* thread, Message* message)
      : BaseDeserializer(thread->zone(), message),
        thread_(thread),
        refs_(Array::Handle(thread->zone())) {}
  ~MessageDeserializer() {}

  DART_NOINLINE void AddBaseObject(ObjectPtr base_object) {
    AssignRef(base_object);
  }
  void AssignRef(ObjectPtr object) {
    refs_.untag()->set_element(next_ref_index_, object);
    next_ref_index_++;
  }

  ObjectPtr Ref(intptr_t index) const {
    ASSERT(index > 0);
    ASSERT(index <= next_ref_index_);
    return refs_.At(index);
  }
  void UpdateRef(intptr_t index, const Object& new_object) {
    ASSERT(index > 0);
    ASSERT(index <= next_ref_index_);
    refs_.SetAt(index, new_object);
  }

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

  void AddBaseObjects();
  ObjectPtr Deserialize();

  Thread* thread() const { return thread_; }
  IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
  ArrayPtr refs() const { return refs_.ptr(); }

 private:
  Thread* const thread_;
  Array& refs_;
};

class ApiMessageDeserializer : public BaseDeserializer {
 public:
  ApiMessageDeserializer(Zone* zone, Message* message)
      : BaseDeserializer(zone, message), refs_(nullptr) {}
  ~ApiMessageDeserializer() {}

  void AddBaseObject(Dart_CObject* base_object) { AssignRef(base_object); }
  void AssignRef(Dart_CObject* object) {
    refs_[next_ref_index_] = object;
    next_ref_index_++;
  }

  Dart_CObject* Allocate(Dart_CObject_Type type) {
    Dart_CObject* result = zone()->Alloc<Dart_CObject>(1);
    result->type = type;
    return result;
  }

  Dart_CObject* Ref(intptr_t index) const {
    ASSERT(index > 0);
    ASSERT(index <= next_ref_index_);
    return refs_[index];
  }

  Dart_CObject* ReadRef() { return Ref(ReadUnsigned()); }

  void AddBaseObjects();
  Dart_CObject* Deserialize();

 private:
  Dart_CObject** refs_;
};

void MessageDeserializationCluster::ReadNodesWrapped(MessageDeserializer* d) {
  start_index_ = d->next_index();
  this->ReadNodes(d);
  stop_index_ = d->next_index();
}

void MessageDeserializationCluster::ReadNodesWrappedApi(
    ApiMessageDeserializer* d) {
  start_index_ = d->next_index();
  this->ReadNodesApi(d);
  stop_index_ = d->next_index();
}

ObjectPtr MessageDeserializationCluster::PostLoadAbstractType(
    MessageDeserializer* d) {
  ClassFinalizer::FinalizationKind finalization =
      is_canonical() ? ClassFinalizer::kCanonicalize
                     : ClassFinalizer::kFinalize;
  AbstractType& type = AbstractType::Handle(d->zone());
  Code& code = Code::Handle(d->zone());
  for (intptr_t id = start_index_; id < stop_index_; id++) {
    type ^= d->Ref(id);

    code = TypeTestingStubGenerator::DefaultCodeForType(type);
    type.InitializeTypeTestingStubNonAtomic(code);

    type ^= ClassFinalizer::FinalizeType(type, finalization);
    d->UpdateRef(id, type);
  }
  return nullptr;
}

ObjectPtr MessageDeserializationCluster::PostLoadLinkedHash(
    MessageDeserializer* d) {
  ASSERT(!is_canonical());
  Array& maps = Array::Handle(d->zone(), d->refs());
  maps = maps.Slice(start_index_, stop_index_ - start_index_,
                    /*with_type_argument=*/false);
  return DartLibraryCalls::RehashObjectsInDartCompactHash(d->thread(), maps);
}

class ClassMessageSerializationCluster : public MessageSerializationCluster {
 public:
  ClassMessageSerializationCluster()
      : MessageSerializationCluster("Class",
                                    MessagePhase::kBeforeTypes,
                                    kClassCid),
        objects_() {}
  ~ClassMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Class* cls = static_cast<Class*>(object);
    objects_.Add(cls);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    Library& lib = Library::Handle(s->zone());
    String& str = String::Handle(s->zone());
    for (intptr_t i = 0; i < count; i++) {
      Class* cls = objects_[i];
      s->AssignRef(cls);
      intptr_t cid = cls->id();
      if (cid < kNumPredefinedCids) {
        ASSERT(cid != 0);
        s->WriteUnsigned(cid);
      } else {
        s->WriteUnsigned(0);
        lib = cls->library();
        str = lib.url();
        s->WriteAscii(str);
        str = cls->Name();
        s->WriteAscii(str);
      }
    }
  }

 private:
  GrowableArray<Class*> objects_;
};

class ClassMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  ClassMessageDeserializationCluster()
      : MessageDeserializationCluster("Class") {}
  ~ClassMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    auto* class_table = d->isolate_group()->class_table();
    String& uri = String::Handle(d->zone());
    Library& lib = Library::Handle(d->zone());
    String& name = String::Handle(d->zone());
    Class& cls = Class::Handle(d->zone());
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t cid = d->ReadUnsigned();
      if (cid != 0) {
        cls = class_table->At(cid);
      } else {
        uri = String::New(d->ReadAscii());   // Library URI.
        name = String::New(d->ReadAscii());  // Class name.
        lib = Library::LookupLibrary(d->thread(), uri);
        if (lib.IsNull()) [[unlikely]] {
          FATAL("Not found: %s %s\n", uri.ToCString(), name.ToCString());
        }
        if (name.Equals(Symbols::TopLevel())) {
          cls = lib.toplevel_class();
        } else {
          cls = lib.LookupClass(name);
        }
        if (cls.IsNull()) [[unlikely]] {
          FATAL("Not found: %s %s\n", uri.ToCString(), name.ToCString());
        }
        cls.EnsureIsFinalized(d->thread());
      }
      d->AssignRef(cls.ptr());
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t cid = d->ReadUnsigned();
      if (cid == 0) {
        d->ReadAscii();  // Library URI.
        d->ReadAscii();  // Class name.
      }
      d->AssignRef(nullptr);
    }
  }
};

class TypeArgumentsMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit TypeArgumentsMessageSerializationCluster(bool is_canonical)
      : MessageSerializationCluster("TypeArguments",
                                    MessagePhase::kTypes,
                                    kTypeArgumentsCid,
                                    is_canonical) {}
  ~TypeArgumentsMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    TypeArguments* type_args = static_cast<TypeArguments*>(object);
    objects_.Add(type_args);

    s->Push(type_args->untag()->instantiations());
    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 WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      TypeArguments* type_args = objects_[i];
      s->AssignRef(type_args);
      intptr_t length = Smi::Value(type_args->untag()->length());
      s->WriteUnsigned(length);
    }
  }

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      TypeArguments* type_args = objects_[i];
      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);

      intptr_t length = Smi::Value(type_args->untag()->length());
      s->WriteUnsigned(length);
      for (intptr_t j = 0; j < length; j++) {
        s->WriteRef(type_args->untag()->element(j));
      }
    }
  }

 private:
  GrowableArray<TypeArguments*> objects_;
};

class TypeArgumentsMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit TypeArgumentsMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("TypeArguments", is_canonical) {}
  ~TypeArgumentsMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      d->AssignRef(TypeArguments::New(length));
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      TypeArgumentsPtr type_args = static_cast<TypeArgumentsPtr>(d->Ref(id));

      type_args->untag()->hash_ = Smi::New(d->Read<int32_t>());
      type_args->untag()->nullability_ = Smi::New(d->ReadUnsigned());

      intptr_t length = d->ReadUnsigned();
      for (intptr_t j = 0; j < length; j++) {
        type_args->untag()->types()[j] =
            static_cast<AbstractTypePtr>(d->ReadRef());
      }
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    if (is_canonical()) {
      TypeArguments& type_args = TypeArguments::Handle(d->zone());
      for (intptr_t id = start_index_; id < stop_index_; id++) {
        type_args ^= d->Ref(id);
        type_args ^= type_args.Canonicalize(d->thread());
        d->UpdateRef(id, type_args);
      }
    }
    return nullptr;
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->ReadUnsigned();  // Length.
      d->AssignRef(nullptr);
    }
  }

  void ReadEdgesApi(ApiMessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      d->Read<int32_t>();  // Hash.
      d->ReadUnsigned();   // Nullability.
      intptr_t length = d->ReadUnsigned();
      for (intptr_t j = 0; j < length; j++) {
        d->ReadRef();  // Element.
      }
    }
  }
};

class TypeMessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit TypeMessageSerializationCluster(bool is_canonical)
      : MessageSerializationCluster("Type",
                                    MessagePhase::kTypes,
                                    kTypeCid,
                                    is_canonical) {}
  ~TypeMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Type* type = static_cast<Type*>(object);

    if (!type->IsTypeClassAllowedBySpawnUri()) {
      s->IllegalObject(*object, "is a Type");
    }

    objects_.Add(type);

    s->Push(type->type_class());
    s->Push(type->arguments());
  }

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

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      Type* type = objects_[i];
      s->WriteRef(type->type_class());
      s->WriteRef(type->arguments());
      s->Write<uint8_t>(static_cast<uint8_t>(type->nullability()));
    }
  }

 private:
  GrowableArray<Type*> objects_;
};

class TypeMessageDeserializationCluster : public MessageDeserializationCluster {
 public:
  explicit TypeMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("Type", is_canonical) {}
  ~TypeMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(Type::New());
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    Class& cls = Class::Handle(d->zone());
    Type& type = Type::Handle(d->zone());
    TypeArguments& type_args = TypeArguments::Handle(d->zone());
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      type ^= d->Ref(id);
      cls ^= d->ReadRef();
      type.set_type_class(cls);
      type_args ^= d->ReadRef();
      type.set_arguments(type_args);
      type.untag()->set_hash(Smi::New(0));
      type.set_nullability(static_cast<Nullability>(d->Read<uint8_t>()));
      type.SetIsFinalized();
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) { return PostLoadAbstractType(d); }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(nullptr);
    }
  }

  void ReadEdgesApi(ApiMessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      d->ReadRef();        // Class.
      d->ReadRef();        // Type arguments.
      d->Read<uint8_t>();  // Nullability.
    }
  }
};

class SmiMessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit SmiMessageSerializationCluster(Zone* zone)
      : MessageSerializationCluster("Smi",
                                    MessagePhase::kBeforeTypes,
                                    kSmiCid,
                                    true),
        objects_(zone, 0) {}
  ~SmiMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Smi* smi = static_cast<Smi*>(object);
    objects_.Add(smi);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Smi* smi = static_cast<Smi*>(objects_[i]);
      s->AssignRef(smi);
      s->Write<intptr_t>(smi->Value());
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<Smi*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* smi = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(smi);
      intptr_t value = smi->type == Dart_CObject_kInt32 ? smi->value.as_int32
                                                        : smi->value.as_int64;
      s->Write<intptr_t>(value);
    }
  }

 private:
  GrowableArray<Smi*> objects_;
};

class SmiMessageDeserializationCluster : public MessageDeserializationCluster {
 public:
  SmiMessageDeserializationCluster()
      : MessageDeserializationCluster("Smi", true) {}
  ~SmiMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(Smi::New(d->Read<intptr_t>()));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t value = d->Read<intptr_t>();
      Dart_CObject* smi;
      if ((kMinInt32 <= value) && (value <= kMaxInt32)) {
        smi = d->Allocate(Dart_CObject_kInt32);
        smi->value.as_int32 = value;
      } else {
        smi = d->Allocate(Dart_CObject_kInt64);
        smi->value.as_int64 = value;
      }
      d->AssignRef(smi);
    }
  }
};

class MintMessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit MintMessageSerializationCluster(Zone* zone, bool is_canonical)
      : MessageSerializationCluster("Mint",
                                    MessagePhase::kBeforeTypes,
                                    kMintCid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~MintMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Mint* mint = static_cast<Mint*>(object);
    objects_.Add(mint);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Mint* mint = static_cast<Mint*>(objects_[i]);
      s->AssignRef(mint);
      s->Write<int64_t>(mint->Value());
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<Mint*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* mint = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(mint);
      int64_t value = mint->type == Dart_CObject_kInt32 ? mint->value.as_int32
                                                        : mint->value.as_int64;
      s->Write<int64_t>(value);
    }
  }

 private:
  GrowableArray<Mint*> objects_;
};

class MintMessageDeserializationCluster : public MessageDeserializationCluster {
 public:
  explicit MintMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("int", is_canonical) {}
  ~MintMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      int64_t value = d->Read<int64_t>();
      d->AssignRef(is_canonical() ? Mint::NewCanonical(value)
                                  : Mint::New(value));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      int64_t value = d->Read<int64_t>();
      Dart_CObject* mint;
      if ((kMinInt32 <= value) && (value <= kMaxInt32)) {
        mint = d->Allocate(Dart_CObject_kInt32);
        mint->value.as_int32 = value;
      } else {
        mint = d->Allocate(Dart_CObject_kInt64);
        mint->value.as_int64 = value;
      }
      d->AssignRef(mint);
    }
  }
};

class DoubleMessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit DoubleMessageSerializationCluster(Zone* zone, bool is_canonical)
      : MessageSerializationCluster("double",
                                    MessagePhase::kBeforeTypes,
                                    kDoubleCid,
                                    is_canonical),
        objects_(zone, 0) {}

  ~DoubleMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Double* dbl = static_cast<Double*>(object);
    objects_.Add(dbl);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Double* dbl = objects_[i];
      s->AssignRef(dbl);
      s->Write<double>(dbl->untag()->value_);
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<Double*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* dbl = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(dbl);
      s->Write<double>(dbl->value.as_double);
    }
  }

 private:
  GrowableArray<Double*> objects_;
};

class DoubleMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit DoubleMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("double", is_canonical) {}
  ~DoubleMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      double value = d->Read<double>();
      d->AssignRef(is_canonical() ? Double::NewCanonical(value)
                                  : Double::New(value));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* dbl = d->Allocate(Dart_CObject_kDouble);
      dbl->value.as_double = d->Read<double>();
      d->AssignRef(dbl);
    }
  }
};

class GrowableObjectArrayMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  GrowableObjectArrayMessageSerializationCluster()
      : MessageSerializationCluster("GrowableObjectArray",
                                    MessagePhase::kNonCanonicalInstances,
                                    kGrowableObjectArrayCid) {}
  ~GrowableObjectArrayMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    GrowableObjectArray* array = static_cast<GrowableObjectArray*>(object);
    objects_.Add(array);

    // Compensation for bogus type prefix optimization.
    TypeArguments& args =
        TypeArguments::Handle(s->zone(), array->untag()->type_arguments());
    if (!args.IsNull() && (args.Length() != 1)) {
      args = args.TruncatedTo(1);
      array->untag()->set_type_arguments(args.ptr());
    }

    s->Push(array->untag()->type_arguments());
    for (intptr_t i = 0, n = array->Length(); i < n; i++) {
      s->Push(array->At(i));
    }
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      GrowableObjectArray* array = objects_[i];
      s->WriteUnsigned(array->Length());
      s->AssignRef(array);
    }
  }

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      GrowableObjectArray* array = objects_[i];
      s->WriteRef(array->untag()->type_arguments());
      for (intptr_t i = 0, n = array->Length(); i < n; i++) {
        s->WriteRef(array->At(i));
      }
    }
  }

 private:
  GrowableArray<GrowableObjectArray*> objects_;
};

class GrowableObjectArrayMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  GrowableObjectArrayMessageDeserializationCluster()
      : MessageDeserializationCluster("GrowableObjectArray") {}
  ~GrowableObjectArrayMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    GrowableObjectArray& array = GrowableObjectArray::Handle(d->zone());
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      array = GrowableObjectArray::New(length);  // Here length is capacity.
      array.SetLength(length);
      d->AssignRef(array.ptr());
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    GrowableObjectArray& array = GrowableObjectArray::Handle(d->zone());
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      array ^= d->Ref(id);
      array.untag()->set_type_arguments(
          static_cast<TypeArgumentsPtr>(d->ReadRef()));
      for (intptr_t i = 0, n = array.Length(); i < n; i++) {
        array.untag()->data()->untag()->set_element(i, d->ReadRef());
      }
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    ASSERT(!is_canonical());
    return nullptr;
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* array = d->Allocate(Dart_CObject_kArray);
      intptr_t length = d->ReadUnsigned();
      array->value.as_array.length = length;
      if (length > 0) {
        array->value.as_array.values = d->zone()->Alloc<Dart_CObject*>(length);
      } else {
        ASSERT(length == 0);
        array->value.as_array.values = nullptr;
      }
      d->AssignRef(array);
    }
  }

  void ReadEdgesApi(ApiMessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      Dart_CObject* array = d->Ref(id);
      intptr_t length = array->value.as_array.length;
      d->ReadRef();  // type_arguments
      for (intptr_t i = 0; i < length; i++) {
        array->value.as_array.values[i] = d->ReadRef();
      }
    }
  }
};

class TypedDataMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit TypedDataMessageSerializationCluster(Zone* zone, intptr_t cid)
      : MessageSerializationCluster("TypedData",
                                    MessagePhase::kNonCanonicalInstances,
                                    cid),
        objects_(zone, 0) {}
  ~TypedDataMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    TypedData* data = static_cast<TypedData*>(object);
    objects_.Add(data);
  }

  void WriteNodes(MessageSerializer* s) {
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      TypedData* data = objects_[i];
      s->AssignRef(data);
      intptr_t length = data->Length();
      s->WriteUnsigned(length);
      NoSafepointScope no_safepoint;
      uint8_t* cdata = reinterpret_cast<uint8_t*>(data->untag()->data());
      s->WriteBytes(cdata, length * element_size);
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<TypedData*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(data);
      intptr_t length = data->value.as_external_typed_data.length;
      s->WriteUnsigned(length);
      const uint8_t* cdata = data->value.as_typed_data.values;
      s->WriteBytes(cdata, length * element_size);
    }
  }

 private:
  GrowableArray<TypedData*> objects_;
};

class TypedDataMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit TypedDataMessageDeserializationCluster(intptr_t cid)
      : MessageDeserializationCluster("TypedData"), cid_(cid) {}
  ~TypedDataMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    intptr_t count = d->ReadUnsigned();
    TypedData& data = TypedData::Handle(d->zone());
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      data = TypedData::New(cid_, length);
      d->AssignRef(data.ptr());
      const intptr_t length_in_bytes = length * element_size;
      NoSafepointScope no_safepoint;
      d->ReadBytes(data.untag()->data(), length_in_bytes);
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    Dart_TypedData_Type type;
    switch (cid_) {
      case kTypedDataInt8ArrayCid:
        type = Dart_TypedData_kInt8;
        break;
      case kTypedDataUint8ArrayCid:
        type = Dart_TypedData_kUint8;
        break;
      case kTypedDataUint8ClampedArrayCid:
        type = Dart_TypedData_kUint8Clamped;
        break;
      case kTypedDataInt16ArrayCid:
        type = Dart_TypedData_kInt16;
        break;
      case kTypedDataUint16ArrayCid:
        type = Dart_TypedData_kUint16;
        break;
      case kTypedDataInt32ArrayCid:
        type = Dart_TypedData_kInt32;
        break;
      case kTypedDataUint32ArrayCid:
        type = Dart_TypedData_kUint32;
        break;
      case kTypedDataInt64ArrayCid:
        type = Dart_TypedData_kInt64;
        break;
      case kTypedDataUint64ArrayCid:
        type = Dart_TypedData_kUint64;
        break;
      case kTypedDataFloat32ArrayCid:
        type = Dart_TypedData_kFloat32;
        break;
      case kTypedDataFloat64ArrayCid:
        type = Dart_TypedData_kFloat64;
        break;
      case kTypedDataInt32x4ArrayCid:
        type = Dart_TypedData_kInt32x4;
        break;
      case kTypedDataFloat32x4ArrayCid:
        type = Dart_TypedData_kFloat32x4;
        break;
      case kTypedDataFloat64x2ArrayCid:
        type = Dart_TypedData_kFloat64x2;
        break;
      default:
        UNREACHABLE();
    }

    intptr_t element_size = TypedData::ElementSizeInBytes(cid_);
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = d->Allocate(Dart_CObject_kTypedData);
      intptr_t length = d->ReadUnsigned();
      data->value.as_typed_data.type = type;
      data->value.as_typed_data.length = length;
      if (length == 0) {
        data->value.as_typed_data.values = nullptr;
      } else {
        data->value.as_typed_data.values = d->CurrentBufferAddress();
        d->Advance(length * element_size);
      }
      d->AssignRef(data);
    }
  }

 private:
  const intptr_t cid_;
};

// This function's name can appear in VM service responses.
static void IsolateMessageTypedDataFinalizer(void* isolate_callback_data,
                                             void* buffer) {
  free(buffer);
}

class ExternalTypedDataMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit ExternalTypedDataMessageSerializationCluster(Zone* zone,
                                                        intptr_t cid)
      : MessageSerializationCluster("ExternalTypedData",
                                    MessagePhase::kNonCanonicalInstances,
                                    cid),
        objects_(zone, 0) {}
  ~ExternalTypedDataMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    ExternalTypedData* data = static_cast<ExternalTypedData*>(object);
    objects_.Add(data);
  }

  void WriteNodes(MessageSerializer* s) {
    intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid_);

    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      ExternalTypedData* data = objects_[i];
      s->AssignRef(data);
      intptr_t length = Smi::Value(data->untag()->length_);
      s->WriteUnsigned(length);

      intptr_t length_in_bytes = length * element_size;
      void* passed_data = malloc(length_in_bytes);
      memmove(passed_data, data->untag()->data_, length_in_bytes);
      s->finalizable_data()->Put(length_in_bytes,
                                 passed_data,  // data
                                 passed_data,  // peer,
                                 IsolateMessageTypedDataFinalizer);
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<ExternalTypedData*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid_);

    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(data);

      intptr_t length = data->value.as_external_typed_data.length;
      s->WriteUnsigned(length);

      s->finalizable_data()->Put(length * element_size,
                                 data->value.as_external_typed_data.data,
                                 data->value.as_external_typed_data.peer,
                                 data->value.as_external_typed_data.callback);
    }
  }

 private:
  GrowableArray<ExternalTypedData*> objects_;
};

class ExternalTypedDataMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit ExternalTypedDataMessageDeserializationCluster(intptr_t cid)
      : MessageDeserializationCluster("ExternalTypedData"), cid_(cid) {}
  ~ExternalTypedDataMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    intptr_t element_size = ExternalTypedData::ElementSizeInBytes(cid_);
    intptr_t count = d->ReadUnsigned();
    ExternalTypedData& data = ExternalTypedData::Handle(d->zone());
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      FinalizableData finalizable_data = d->finalizable_data()->Take();
      data = ExternalTypedData::New(
          cid_, reinterpret_cast<uint8_t*>(finalizable_data.data), length);
      intptr_t external_size = length * element_size;
      data.AddFinalizer(finalizable_data.peer, finalizable_data.callback,
                        external_size);
      d->AssignRef(data.ptr());
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    Dart_TypedData_Type type;
    switch (cid_) {
      case kExternalTypedDataInt8ArrayCid:
        type = Dart_TypedData_kInt8;
        break;
      case kExternalTypedDataUint8ArrayCid:
        type = Dart_TypedData_kUint8;
        break;
      case kExternalTypedDataUint8ClampedArrayCid:
        type = Dart_TypedData_kUint8Clamped;
        break;
      case kExternalTypedDataInt16ArrayCid:
        type = Dart_TypedData_kInt16;
        break;
      case kExternalTypedDataUint16ArrayCid:
        type = Dart_TypedData_kUint16;
        break;
      case kExternalTypedDataInt32ArrayCid:
        type = Dart_TypedData_kInt32;
        break;
      case kExternalTypedDataUint32ArrayCid:
        type = Dart_TypedData_kUint32;
        break;
      case kExternalTypedDataInt64ArrayCid:
        type = Dart_TypedData_kInt64;
        break;
      case kExternalTypedDataUint64ArrayCid:
        type = Dart_TypedData_kUint64;
        break;
      case kExternalTypedDataFloat32ArrayCid:
        type = Dart_TypedData_kFloat32;
        break;
      case kExternalTypedDataFloat64ArrayCid:
        type = Dart_TypedData_kFloat64;
        break;
      case kExternalTypedDataInt32x4ArrayCid:
        type = Dart_TypedData_kInt32x4;
        break;
      case kExternalTypedDataFloat32x4ArrayCid:
        type = Dart_TypedData_kFloat32x4;
        break;
      case kExternalTypedDataFloat64x2ArrayCid:
        type = Dart_TypedData_kFloat64x2;
        break;
      default:
        UNREACHABLE();
    }

    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = d->Allocate(Dart_CObject_kTypedData);
      intptr_t length = d->ReadUnsigned();
      FinalizableData finalizable_data = d->finalizable_data()->Get();
      data->value.as_typed_data.type = type;
      data->value.as_typed_data.length = length;
      data->value.as_typed_data.values =
          reinterpret_cast<uint8_t*>(finalizable_data.data);
      d->AssignRef(data);
    }
  }

 private:
  const intptr_t cid_;
};

class NativePointerMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit NativePointerMessageSerializationCluster(Zone* zone)
      : MessageSerializationCluster("NativePointer",
                                    MessagePhase::kNonCanonicalInstances,
                                    kNativePointer),
        objects_(zone, 0) {}
  ~NativePointerMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) { UNREACHABLE(); }

  void WriteNodes(MessageSerializer* s) { UNREACHABLE(); }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(object);
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = objects_[i];
      s->AssignRef(data);

      s->finalizable_data()->Put(
          data->value.as_native_pointer.size,
          reinterpret_cast<void*>(data->value.as_native_pointer.ptr),
          reinterpret_cast<void*>(data->value.as_native_pointer.ptr),
          data->value.as_native_pointer.callback);
    }
  }

 private:
  GrowableArray<Dart_CObject*> objects_;
};

class NativePointerMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  NativePointerMessageDeserializationCluster()
      : MessageDeserializationCluster("NativePointer"), cid_(kNativePointer) {}
  ~NativePointerMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      FinalizableData finalizable_data = d->finalizable_data()->Take();
      intptr_t ptr = reinterpret_cast<intptr_t>(finalizable_data.data);
      d->AssignRef(Integer::New(ptr));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) { UNREACHABLE(); }

 private:
  const intptr_t cid_;
};

enum TypedDataViewFormat {
  kTypedDataViewFromC,
  kTypedDataViewFromDart,
};

class TypedDataViewMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit TypedDataViewMessageSerializationCluster(Zone* zone, intptr_t cid)
      : MessageSerializationCluster("TypedDataView",
                                    MessagePhase::kNonCanonicalInstances,
                                    cid),
        objects_(zone, 0) {}
  ~TypedDataViewMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    TypedDataView* view = static_cast<TypedDataView*>(object);
    objects_.Add(view);

    s->Push(view->untag()->length());
    s->Push(view->untag()->typed_data());
    s->Push(view->untag()->offset_in_bytes());
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    s->Write<TypedDataViewFormat>(kTypedDataViewFromDart);
    for (intptr_t i = 0; i < count; i++) {
      TypedDataView* view = objects_[i];
      s->AssignRef(view);
    }
  }

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      TypedDataView* view = objects_[i];
      s->WriteRef(view->untag()->length());
      s->WriteRef(view->untag()->typed_data());
      s->WriteRef(view->untag()->offset_in_bytes());
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    ASSERT(object->type == Dart_CObject_kUnmodifiableExternalTypedData);
    objects_.Add(reinterpret_cast<TypedDataView*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t element_size = TypedDataView::ElementSizeInBytes(cid_);

    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    s->Write<TypedDataViewFormat>(kTypedDataViewFromC);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(data);

      intptr_t length = data->value.as_external_typed_data.length;
      s->WriteUnsigned(length);

      s->finalizable_data()->Put(length * element_size,
                                 data->value.as_external_typed_data.data,
                                 data->value.as_external_typed_data.peer,
                                 data->value.as_external_typed_data.callback);
    }
  }

 private:
  GrowableArray<TypedDataView*> objects_;
};

class TypedDataViewMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit TypedDataViewMessageDeserializationCluster(intptr_t cid)
      : MessageDeserializationCluster("TypedDataView"), cid_(cid) {}
  ~TypedDataViewMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    format_ = d->Read<TypedDataViewFormat>();
    if (format_ == kTypedDataViewFromC) {
      intptr_t view_cid = cid_;
      ASSERT(IsUnmodifiableTypedDataViewClassId(view_cid));
      intptr_t backing_cid = cid_ - kTypedDataCidRemainderUnmodifiable +
                             kTypedDataCidRemainderExternal;
      ASSERT(IsExternalTypedDataClassId(backing_cid));
      intptr_t element_size =
          ExternalTypedData::ElementSizeInBytes(backing_cid);
      ExternalTypedData& data = ExternalTypedData::Handle(d->zone());
      TypedDataView& view = TypedDataView::Handle(d->zone());
      for (intptr_t i = 0; i < count; i++) {
        intptr_t length = d->ReadUnsigned();
        FinalizableData finalizable_data = d->finalizable_data()->Take();
        data = ExternalTypedData::New(
            backing_cid, reinterpret_cast<uint8_t*>(finalizable_data.data),
            length);
        data.SetDeeplyImmutable();  // Can pass by reference.
        intptr_t external_size = length * element_size;
        data.AddFinalizer(finalizable_data.peer, finalizable_data.callback,
                          external_size);
        view = TypedDataView::New(view_cid, data, 0, length);
        d->AssignRef(data.ptr());
      }
    } else {
      for (intptr_t i = 0; i < count; i++) {
        d->AssignRef(TypedDataView::New(cid_));
      }
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    if (format_ == kTypedDataViewFromC) return;
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      TypedDataViewPtr view = static_cast<TypedDataViewPtr>(d->Ref(id));
      view->untag()->set_length(static_cast<SmiPtr>(d->ReadRef()));
      view->untag()->set_typed_data(
          static_cast<TypedDataBasePtr>(d->ReadRef()));
      view->untag()->set_offset_in_bytes(static_cast<SmiPtr>(d->ReadRef()));
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    if (format_ == kTypedDataViewFromC) return nullptr;
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      TypedDataViewPtr view = static_cast<TypedDataViewPtr>(d->Ref(id));
      view->untag()->RecomputeDataField();
    }
    return nullptr;
  }

  struct Dart_CTypedDataView : public Dart_CObject {
    Dart_CObject* length;
    Dart_CObject* typed_data;
    Dart_CObject* offset_in_bytes;
  };

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    format_ = d->Read<TypedDataViewFormat>();
    if (format_ == kTypedDataViewFromC) {
      Dart_TypedData_Type type;
      switch (cid_) {
        case kUnmodifiableTypedDataInt8ArrayViewCid:
          type = Dart_TypedData_kInt8;
          break;
        case kUnmodifiableTypedDataUint8ArrayViewCid:
          type = Dart_TypedData_kUint8;
          break;
        case kUnmodifiableTypedDataUint8ClampedArrayViewCid:
          type = Dart_TypedData_kUint8Clamped;
          break;
        case kUnmodifiableTypedDataInt16ArrayViewCid:
          type = Dart_TypedData_kInt16;
          break;
        case kUnmodifiableTypedDataUint16ArrayViewCid:
          type = Dart_TypedData_kUint16;
          break;
        case kUnmodifiableTypedDataInt32ArrayViewCid:
          type = Dart_TypedData_kInt32;
          break;
        case kUnmodifiableTypedDataUint32ArrayViewCid:
          type = Dart_TypedData_kUint32;
          break;
        case kUnmodifiableTypedDataInt64ArrayViewCid:
          type = Dart_TypedData_kInt64;
          break;
        case kUnmodifiableTypedDataUint64ArrayViewCid:
          type = Dart_TypedData_kUint64;
          break;
        case kUnmodifiableTypedDataFloat32ArrayViewCid:
          type = Dart_TypedData_kFloat32;
          break;
        case kUnmodifiableTypedDataFloat64ArrayViewCid:
          type = Dart_TypedData_kFloat64;
          break;
        case kUnmodifiableTypedDataInt32x4ArrayViewCid:
          type = Dart_TypedData_kInt32x4;
          break;
        case kUnmodifiableTypedDataFloat32x4ArrayViewCid:
          type = Dart_TypedData_kFloat32x4;
          break;
        case kUnmodifiableTypedDataFloat64x2ArrayViewCid:
          type = Dart_TypedData_kFloat64x2;
          break;
        default:
          UNREACHABLE();
      }

      Dart_CObject* data =
          d->Allocate(Dart_CObject_kUnmodifiableExternalTypedData);
      intptr_t length = d->ReadUnsigned();
      FinalizableData finalizable_data = d->finalizable_data()->Get();
      data->value.as_typed_data.type = type;
      data->value.as_typed_data.length = length;
      data->value.as_typed_data.values =
          reinterpret_cast<uint8_t*>(finalizable_data.data);
      d->AssignRef(data);
    } else {
      for (intptr_t i = 0; i < count; i++) {
        Dart_CTypedDataView* view = d->zone()->Alloc<Dart_CTypedDataView>(1);
        d->AssignRef(view);
      }
    }
  }

  void ReadEdgesApi(ApiMessageDeserializer* d) {
    if (format_ == kTypedDataViewFromC) return;
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      Dart_CTypedDataView* view = static_cast<Dart_CTypedDataView*>(d->Ref(id));
      view->length = d->ReadRef();
      view->typed_data = d->ReadRef();
      view->offset_in_bytes = d->ReadRef();
    }
  }

  void PostLoadApi(ApiMessageDeserializer* d) {
    if (format_ == kTypedDataViewFromC) return;
    Dart_TypedData_Type type;
    switch (cid_) {
      case kTypedDataInt8ArrayViewCid:
      case kUnmodifiableTypedDataInt8ArrayViewCid:
        type = Dart_TypedData_kInt8;
        break;
      case kTypedDataUint8ArrayViewCid:
      case kUnmodifiableTypedDataUint8ArrayViewCid:
        type = Dart_TypedData_kUint8;
        break;
      case kTypedDataUint8ClampedArrayViewCid:
      case kUnmodifiableTypedDataUint8ClampedArrayViewCid:
        type = Dart_TypedData_kUint8Clamped;
        break;
      case kTypedDataInt16ArrayViewCid:
      case kUnmodifiableTypedDataInt16ArrayViewCid:
        type = Dart_TypedData_kInt16;
        break;
      case kTypedDataUint16ArrayViewCid:
      case kUnmodifiableTypedDataUint16ArrayViewCid:
        type = Dart_TypedData_kUint16;
        break;
      case kTypedDataInt32ArrayViewCid:
      case kUnmodifiableTypedDataInt32ArrayViewCid:
        type = Dart_TypedData_kInt32;
        break;
      case kTypedDataUint32ArrayViewCid:
      case kUnmodifiableTypedDataUint32ArrayViewCid:
        type = Dart_TypedData_kUint32;
        break;
      case kTypedDataInt64ArrayViewCid:
      case kUnmodifiableTypedDataInt64ArrayViewCid:
        type = Dart_TypedData_kInt64;
        break;
      case kTypedDataUint64ArrayViewCid:
      case kUnmodifiableTypedDataUint64ArrayViewCid:
        type = Dart_TypedData_kUint64;
        break;
      case kTypedDataFloat32ArrayViewCid:
      case kUnmodifiableTypedDataFloat32ArrayViewCid:
        type = Dart_TypedData_kFloat32;
        break;
      case kTypedDataFloat64ArrayViewCid:
      case kUnmodifiableTypedDataFloat64ArrayViewCid:
        type = Dart_TypedData_kFloat64;
        break;
      case kTypedDataInt32x4ArrayViewCid:
      case kUnmodifiableTypedDataInt32x4ArrayViewCid:
        type = Dart_TypedData_kInt32x4;
        break;
      case kTypedDataFloat32x4ArrayViewCid:
      case kUnmodifiableTypedDataFloat32x4ArrayViewCid:
        type = Dart_TypedData_kFloat32x4;
        break;
      case kTypedDataFloat64x2ArrayViewCid:
      case kUnmodifiableTypedDataFloat64x2ArrayViewCid:
        type = Dart_TypedData_kFloat64x2;
        break;
      default:
        UNREACHABLE();
    }

    for (intptr_t id = start_index_; id < stop_index_; id++) {
      Dart_CTypedDataView* view = static_cast<Dart_CTypedDataView*>(d->Ref(id));
      if (view->typed_data->type == Dart_CObject_kTypedData) {
        view->type = Dart_CObject_kTypedData;
        view->value.as_typed_data.type = type;
        view->value.as_typed_data.length = view->length->value.as_int32;
        view->value.as_typed_data.values =
            view->typed_data->value.as_typed_data.values +
            view->offset_in_bytes->value.as_int32;
      } else {
        UNREACHABLE();
      }
    }
  }

 private:
  const intptr_t cid_;
  TypedDataViewFormat format_;
};

class TransferableTypedDataMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  TransferableTypedDataMessageSerializationCluster()
      : MessageSerializationCluster("TransferableTypedData",
                                    MessagePhase::kNonCanonicalInstances,
                                    kTransferableTypedDataCid) {}
  ~TransferableTypedDataMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    TransferableTypedData* transferable =
        static_cast<TransferableTypedData*>(object);
    objects_.Add(transferable);

    void* peer = s->thread()->heap()->GetPeer(transferable->ptr());
    // Assume that object's Peer is only used to track transferability state.
    ASSERT(peer != nullptr);
    TransferableTypedDataPeer* tpeer =
        reinterpret_cast<TransferableTypedDataPeer*>(peer);
    if (tpeer->data() == nullptr) {
      s->IllegalObject(*object,
                       "TransferableTypedData has been transferred already");
    }
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      TransferableTypedData* transferable = objects_[i];
      s->AssignRef(transferable);

      void* peer = s->thread()->heap()->GetPeer(transferable->ptr());
      // Assume that object's Peer is only used to track transferability state.
      ASSERT(peer != nullptr);
      TransferableTypedDataPeer* tpeer =
          reinterpret_cast<TransferableTypedDataPeer*>(peer);
      intptr_t length = tpeer->length();  // In bytes.
      void* data = tpeer->data();
      ASSERT(data != nullptr);
      s->WriteUnsigned(length);
      s->finalizable_data()->Put(
          length, data, tpeer,
          // Finalizer does nothing - in case of failure to serialize,
          // [data] remains wrapped in sender's [TransferableTypedData].
          [](void* data, void* peer) {},
          // This is invoked on successful serialization of the message
          [](void* data, void* peer) {
            TransferableTypedDataPeer* ttpeer =
                reinterpret_cast<TransferableTypedDataPeer*>(peer);
            ttpeer->handle()->EnsureFreedExternal(IsolateGroup::Current());
            ttpeer->ClearData();
          });
    }
  }

 private:
  GrowableArray<TransferableTypedData*> objects_;
};

class TransferableTypedDataMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  TransferableTypedDataMessageDeserializationCluster()
      : MessageDeserializationCluster("TransferableTypedData") {}
  ~TransferableTypedDataMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      const FinalizableData finalizable_data = d->finalizable_data()->Take();
      d->AssignRef(TransferableTypedData::New(
          reinterpret_cast<uint8_t*>(finalizable_data.data), length));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* data = d->Allocate(Dart_CObject_kTypedData);
      data->value.as_typed_data.length = d->ReadUnsigned();
      data->value.as_typed_data.type = Dart_TypedData_kUint8;
      FinalizableData finalizable_data = d->finalizable_data()->Get();
      data->value.as_typed_data.values =
          reinterpret_cast<const uint8_t*>(finalizable_data.data);
      d->AssignRef(data);
    }
  }
};

class Simd128MessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit Simd128MessageSerializationCluster(intptr_t cid)
      : MessageSerializationCluster("Simd128",
                                    MessagePhase::kBeforeTypes,
                                    cid) {}
  ~Simd128MessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) { objects_.Add(object); }

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

 private:
  GrowableArray<Object*> objects_;
};

class Simd128MessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit Simd128MessageDeserializationCluster(intptr_t cid)
      : MessageDeserializationCluster("Simd128"), cid_(cid) {
    ASSERT(cid_ == kInt32x4Cid || cid_ == kFloat32x4Cid ||
           cid_ == kFloat64x2Cid);
#if defined(DEBUG)
    // If not for Int32x4, check that all the Int32x4-specific arguments used in
    // ReadNodes match those for the actual class.
    if (cid_ == kFloat32x4Cid) {
      AssertSameStructure<Int32x4, Float32x4>();
    } else if (cid_ == kFloat64x2Cid) {
      AssertSameStructure<Int32x4, Float64x2>();
    }
#endif
  }
  ~Simd128MessageDeserializationCluster() {}

#if defined(DEBUG)
  template <typename Expected, typename Got>
  static void AssertSameStructure() {
    ASSERT_EQUAL(Got::InstanceSize(), Expected::InstanceSize());
    ASSERT_EQUAL(Got::ContainsCompressedPointers(),
                 Expected::ContainsCompressedPointers());
    ASSERT_EQUAL(Object::from_offset<Got>(), Object::from_offset<Expected>());
    ASSERT_EQUAL(Object::to_offset<Got>(), Object::to_offset<Expected>());
    ASSERT_EQUAL(Got::value_offset(), Expected::value_offset());
  }
#endif

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      ObjectPtr vector = Object::Allocate(
          cid_, Int32x4::InstanceSize(), Heap::kNew,
          Int32x4::ContainsCompressedPointers(), Object::from_offset<Int32x4>(),
          Object::to_offset<Int32x4>());
      d->AssignRef(vector);
      d->ReadBytes(&(static_cast<Int32x4Ptr>(vector)->untag()->value_),
                   sizeof(simd128_value_t));
    }
  }

 private:
  const intptr_t cid_;
};

class SendPortMessageSerializationCluster : public MessageSerializationCluster {
 public:
  explicit SendPortMessageSerializationCluster(Zone* zone)
      : MessageSerializationCluster("SendPort",
                                    MessagePhase::kNonCanonicalInstances,
                                    kSendPortCid),
        objects_(zone, 0) {}
  ~SendPortMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    SendPort* port = static_cast<SendPort*>(object);
    objects_.Add(port);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      SendPort* port = objects_[i];
      s->AssignRef(port);
      s->Write<Dart_Port>(port->untag()->id_);
      s->Write<Dart_Port>(port->untag()->origin_id_);
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<SendPort*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* port = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(port);
      s->Write<Dart_Port>(port->value.as_send_port.id);
      s->Write<Dart_Port>(port->value.as_send_port.origin_id);
    }
  }

 private:
  GrowableArray<SendPort*> objects_;
};

class SendPortMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  SendPortMessageDeserializationCluster()
      : MessageDeserializationCluster("SendPort") {}
  ~SendPortMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_Port id = d->Read<Dart_Port>();
      Dart_Port origin_id = d->Read<Dart_Port>();
      d->AssignRef(SendPort::New(id, origin_id));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* port = d->Allocate(Dart_CObject_kSendPort);
      port->value.as_send_port.id = d->Read<Dart_Port>();
      port->value.as_send_port.origin_id = d->Read<Dart_Port>();
      d->AssignRef(port);
    }
  }
};

class CapabilityMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit CapabilityMessageSerializationCluster(Zone* zone)
      : MessageSerializationCluster("Capability",
                                    MessagePhase::kNonCanonicalInstances,
                                    kCapabilityCid),
        objects_(zone, 0) {}
  ~CapabilityMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Capability* cap = static_cast<Capability*>(object);
    objects_.Add(cap);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Capability* cap = objects_[i];
      s->AssignRef(cap);
      s->Write<uint64_t>(cap->untag()->id_);
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<Capability*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* cap = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(cap);
      s->Write<Dart_Port>(cap->value.as_capability.id);
    }
  }

 private:
  GrowableArray<Capability*> objects_;
};

class CapabilityMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  CapabilityMessageDeserializationCluster()
      : MessageDeserializationCluster("Capability") {}
  ~CapabilityMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      uint64_t id = d->Read<uint64_t>();
      d->AssignRef(Capability::New(id));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* cap = d->Allocate(Dart_CObject_kCapability);
      cap->value.as_capability.id = d->Read<uint64_t>();
      d->AssignRef(cap);
    }
  }
};

class MapMessageSerializationCluster : public MessageSerializationCluster {
 public:
  MapMessageSerializationCluster(Zone* zone, bool is_canonical, intptr_t cid)
      : MessageSerializationCluster("Map",
                                    is_canonical
                                        ? MessagePhase::kCanonicalInstances
                                        : MessagePhase::kNonCanonicalInstances,
                                    cid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~MapMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Map* map = static_cast<Map*>(object);
    objects_.Add(map);

    // Compensation for bogus type prefix optimization.
    TypeArguments& args =
        TypeArguments::Handle(s->zone(), map->untag()->type_arguments());
    if (!args.IsNull() && (args.Length() != 2)) {
      args = args.TruncatedTo(2);
      map->untag()->set_type_arguments(args.ptr());
    }

    s->Push(map->untag()->type_arguments());
    s->Push(map->untag()->data());
    s->Push(map->untag()->used_data());
  }

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

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      Map* map = objects_[i];
      s->WriteRef(map->untag()->type_arguments());
      s->WriteRef(map->untag()->data());
      s->WriteRef(map->untag()->used_data());
    }
  }

 private:
  GrowableArray<Map*> objects_;
};

class MapMessageDeserializationCluster : public MessageDeserializationCluster {
 public:
  MapMessageDeserializationCluster(bool is_canonical, intptr_t cid)
      : MessageDeserializationCluster("Map", is_canonical), cid_(cid) {}
  ~MapMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(Map::NewUninitialized(cid_));
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      MapPtr map = static_cast<MapPtr>(d->Ref(id));
      map->untag()->set_hash_mask(Smi::New(0));
      map->untag()->set_type_arguments(
          static_cast<TypeArgumentsPtr>(d->ReadRef()));
      map->untag()->set_data(static_cast<ArrayPtr>(d->ReadRef()));
      map->untag()->set_used_data(static_cast<SmiPtr>(d->ReadRef()));
      map->untag()->set_deleted_keys(Smi::New(0));
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    if (!is_canonical()) {
      ASSERT(cid_ == kMapCid);
      return PostLoadLinkedHash(d);
    }

    ASSERT(cid_ == kConstMapCid);
    SafepointMutexLocker ml(
        d->isolate_group()->constant_canonicalization_mutex());
    Map& instance = Map::Handle(d->zone());
    for (intptr_t i = start_index_; i < stop_index_; i++) {
      instance ^= d->Ref(i);
      instance ^= instance.CanonicalizeLocked(d->thread());
      d->UpdateRef(i, instance);
    }
    return nullptr;
  }

 private:
  const intptr_t cid_;
};

class SetMessageSerializationCluster : public MessageSerializationCluster {
 public:
  SetMessageSerializationCluster(Zone* zone, bool is_canonical, intptr_t cid)
      : MessageSerializationCluster("Set",
                                    is_canonical
                                        ? MessagePhase::kCanonicalInstances
                                        : MessagePhase::kNonCanonicalInstances,
                                    cid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~SetMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Set* set = static_cast<Set*>(object);
    objects_.Add(set);

    // Compensation for bogus type prefix optimization.
    TypeArguments& args =
        TypeArguments::Handle(s->zone(), set->untag()->type_arguments());
    if (!args.IsNull() && (args.Length() != 1)) {
      args = args.TruncatedTo(1);
      set->untag()->set_type_arguments(args.ptr());
    }

    s->Push(set->untag()->type_arguments());
    s->Push(set->untag()->data());
    s->Push(set->untag()->used_data());
  }

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

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      Set* set = objects_[i];
      s->WriteRef(set->untag()->type_arguments());
      s->WriteRef(set->untag()->data());
      s->WriteRef(set->untag()->used_data());
    }
  }

 private:
  GrowableArray<Set*> objects_;
};

class SetMessageDeserializationCluster : public MessageDeserializationCluster {
 public:
  SetMessageDeserializationCluster(bool is_canonical, intptr_t cid)
      : MessageDeserializationCluster("Set", is_canonical), cid_(cid) {}
  ~SetMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      d->AssignRef(Set::NewUninitialized(cid_));
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      SetPtr map = static_cast<SetPtr>(d->Ref(id));
      map->untag()->set_hash_mask(Smi::New(0));
      map->untag()->set_type_arguments(
          static_cast<TypeArgumentsPtr>(d->ReadRef()));
      map->untag()->set_data(static_cast<ArrayPtr>(d->ReadRef()));
      map->untag()->set_used_data(static_cast<SmiPtr>(d->ReadRef()));
      map->untag()->set_deleted_keys(Smi::New(0));
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    if (!is_canonical()) {
      ASSERT(cid_ == kSetCid);
      return PostLoadLinkedHash(d);
    }

    ASSERT(cid_ == kConstSetCid);
    SafepointMutexLocker ml(
        d->isolate_group()->constant_canonicalization_mutex());
    Set& instance = Set::Handle(d->zone());
    for (intptr_t i = start_index_; i < stop_index_; i++) {
      instance ^= d->Ref(i);
      instance ^= instance.CanonicalizeLocked(d->thread());
      d->UpdateRef(i, instance);
    }
    return nullptr;
  }

 private:
  const intptr_t cid_;
};

class ArrayMessageSerializationCluster : public MessageSerializationCluster {
 public:
  ArrayMessageSerializationCluster(Zone* zone, bool is_canonical, intptr_t cid)
      : MessageSerializationCluster("Array",
                                    is_canonical
                                        ? MessagePhase::kCanonicalInstances
                                        : MessagePhase::kNonCanonicalInstances,
                                    cid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~ArrayMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    Array* array = static_cast<Array*>(object);
    objects_.Add(array);

    // Compensation for bogus type prefix optimization.
    TypeArguments& args =
        TypeArguments::Handle(s->zone(), array->untag()->type_arguments());
    if (!args.IsNull() && (args.Length() != 1)) {
      args = args.TruncatedTo(1);
      array->untag()->set_type_arguments(args.ptr());
    }

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

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Array* array = objects_[i];
      s->AssignRef(array);
      intptr_t length = Smi::Value(array->untag()->length());
      s->WriteUnsigned(length);
    }
  }

  void WriteEdges(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      Array* array = objects_[i];
      intptr_t length = array->Length();
      s->WriteRef(array->untag()->type_arguments());
      for (intptr_t j = 0; j < length; j++) {
        s->WriteRef(array->untag()->element(j));
      }
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<Array*>(object));

    for (intptr_t i = 0, n = object->value.as_array.length; i < n; i++) {
      s->Push(object->value.as_array.values[i]);
    }
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* array = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(array);
      s->WriteUnsigned(array->value.as_array.length);
    }
  }

  void WriteEdgesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* array = reinterpret_cast<Dart_CObject*>(objects_[i]);
      intptr_t length = array->value.as_array.length;
      s->WriteRef(&cobj_null);  // TypeArguments
      for (intptr_t j = 0; j < length; j++) {
        s->WriteRef(array->value.as_array.values[j]);
      }
    }
  }

 private:
  GrowableArray<Array*> objects_;
};

class ArrayMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit ArrayMessageDeserializationCluster(bool is_canonical, intptr_t cid)
      : MessageDeserializationCluster("Array", is_canonical), cid_(cid) {}
  ~ArrayMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      d->AssignRef(Array::NewUninitialized(cid_, length));
    }
  }

  void ReadEdges(MessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      ArrayPtr array = static_cast<ArrayPtr>(d->Ref(id));
      intptr_t length = Smi::Value(array->untag()->length());
      array->untag()->set_type_arguments(
          static_cast<TypeArgumentsPtr>(d->ReadRef()));
      for (intptr_t j = 0; j < length; j++) {
        array->untag()->set_element(j, d->ReadRef());
      }
    }
  }

  ObjectPtr PostLoad(MessageDeserializer* d) {
    if (is_canonical()) {
      SafepointMutexLocker ml(
          d->isolate_group()->constant_canonicalization_mutex());
      Instance& instance = Instance::Handle(d->zone());
      for (intptr_t i = start_index_; i < stop_index_; i++) {
        instance ^= d->Ref(i);
        instance = instance.CanonicalizeLocked(d->thread());
        d->UpdateRef(i, instance);
      }
    }
    return nullptr;
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* array = d->Allocate(Dart_CObject_kArray);
      intptr_t length = d->ReadUnsigned();
      array->value.as_array.length = length;
      if (length == 0) {
        array->value.as_array.values = nullptr;
      } else {
        array->value.as_array.values = d->zone()->Alloc<Dart_CObject*>(length);
      }
      d->AssignRef(array);
    }
  }

  void ReadEdgesApi(ApiMessageDeserializer* d) {
    for (intptr_t id = start_index_; id < stop_index_; id++) {
      Dart_CObject* array = d->Ref(id);
      intptr_t length = array->value.as_array.length;
      d->ReadRef();  // type_arguments
      for (intptr_t i = 0; i < length; i++) {
        array->value.as_array.values[i] = d->ReadRef();
      }
    }
  }

 private:
  const intptr_t cid_;
};

class OneByteStringMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit OneByteStringMessageSerializationCluster(Zone* zone,
                                                    bool is_canonical)
      : MessageSerializationCluster("OneByteString",
                                    MessagePhase::kBeforeTypes,
                                    kOneByteStringCid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~OneByteStringMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    String* str = static_cast<String*>(object);
    objects_.Add(str);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      String* str = objects_[i];
      s->AssignRef(str);
      intptr_t length = str->Length();
      s->WriteUnsigned(length);
      NoSafepointScope no_safepoint;
      s->WriteBytes(OneByteString::DataStart(*str), length * sizeof(uint8_t));
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<String*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* str = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(str);

      const uint8_t* utf8_str =
          reinterpret_cast<const uint8_t*>(str->value.as_string);
      intptr_t utf8_len = strlen(str->value.as_string);
      Utf8::Type type = Utf8::kLatin1;
      intptr_t latin1_len = Utf8::CodeUnitCount(utf8_str, utf8_len, &type);

      uint8_t* latin1_str = reinterpret_cast<uint8_t*>(
          dart::malloc(latin1_len * sizeof(uint8_t)));
      bool success =
          Utf8::DecodeToLatin1(utf8_str, utf8_len, latin1_str, latin1_len);
      ASSERT(success);
      s->WriteUnsigned(latin1_len);
      s->WriteBytes(latin1_str, latin1_len);
      ::free(latin1_str);
    }
  }

 private:
  GrowableArray<String*> objects_;
};

class OneByteStringMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit OneByteStringMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("OneByteString", is_canonical) {}
  ~OneByteStringMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      const uint8_t* data = d->CurrentBufferAddress();
      d->Advance(length * sizeof(uint8_t));
      d->AssignRef(is_canonical()
                       ? Symbols::FromLatin1(d->thread(), data, length)
                       : String::FromLatin1(data, length));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* str = d->Allocate(Dart_CObject_kString);
      intptr_t latin1_length = d->ReadUnsigned();
      const uint8_t* data = d->CurrentBufferAddress();

      d->Advance(latin1_length * sizeof(uint8_t));

      intptr_t utf8_len = 0;
      for (intptr_t i = 0; i < latin1_length; i++) {
        utf8_len += Utf8::Length(data[i]);
      }
      char* utf8_data = d->zone()->Alloc<char>(utf8_len + 1);
      str->value.as_string = utf8_data;
      for (intptr_t i = 0; i < latin1_length; i++) {
        utf8_data += Utf8::Encode(data[i], utf8_data);
      }
      *utf8_data = '\0';

      d->AssignRef(str);
    }
  }
};

class TwoByteStringMessageSerializationCluster
    : public MessageSerializationCluster {
 public:
  explicit TwoByteStringMessageSerializationCluster(Zone* zone,
                                                    bool is_canonical)
      : MessageSerializationCluster("TwoByteString",
                                    MessagePhase::kBeforeTypes,
                                    kTwoByteStringCid,
                                    is_canonical),
        objects_(zone, 0) {}
  ~TwoByteStringMessageSerializationCluster() {}

  void Trace(MessageSerializer* s, Object* object) {
    String* str = static_cast<String*>(object);
    objects_.Add(str);
  }

  void WriteNodes(MessageSerializer* s) {
    const intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      String* str = objects_[i];
      s->AssignRef(str);
      intptr_t length = str->Length();
      s->WriteUnsigned(length);
      NoSafepointScope no_safepoint;
      uint16_t* utf16 = TwoByteString::DataStart(*str);
      s->WriteBytes(reinterpret_cast<const uint8_t*>(utf16),
                    length * sizeof(uint16_t));
    }
  }

  void TraceApi(ApiMessageSerializer* s, Dart_CObject* object) {
    objects_.Add(reinterpret_cast<String*>(object));
  }

  void WriteNodesApi(ApiMessageSerializer* s) {
    intptr_t count = objects_.length();
    s->WriteUnsigned(count);
    for (intptr_t i = 0; i < count; i++) {
      Dart_CObject* str = reinterpret_cast<Dart_CObject*>(objects_[i]);
      s->AssignRef(str);

      const uint8_t* utf8_str =
          reinterpret_cast<const uint8_t*>(str->value.as_string);
      intptr_t utf8_len = strlen(str->value.as_string);
      Utf8::Type type = Utf8::kLatin1;
      intptr_t utf16_len = Utf8::CodeUnitCount(utf8_str, utf8_len, &type);

      uint16_t* utf16_str = reinterpret_cast<uint16_t*>(
          dart::malloc(utf16_len * sizeof(uint16_t)));
      bool success =
          Utf8::DecodeToUTF16(utf8_str, utf8_len, utf16_str, utf16_len);
      ASSERT(success);
      s->WriteUnsigned(utf16_len);
      s->WriteBytes(reinterpret_cast<const uint8_t*>(utf16_str),
                    utf16_len * sizeof(uint16_t));
      ::free(utf16_str);
    }
  }

 private:
  GrowableArray<String*> objects_;
};

class TwoByteStringMessageDeserializationCluster
    : public MessageDeserializationCluster {
 public:
  explicit TwoByteStringMessageDeserializationCluster(bool is_canonical)
      : MessageDeserializationCluster("TwoByteString", is_canonical) {}
  ~TwoByteStringMessageDeserializationCluster() {}

  void ReadNodes(MessageDeserializer* d) {
    const intptr_t count = d->ReadUnsigned();
    for (intptr_t i = 0; i < count; i++) {
      intptr_t length = d->ReadUnsigned();
      const uint16_t* data =
          reinterpret_cast<const uint16_t*>(d->CurrentBufferAddress());
      d->Advance(length * sizeof(uint16_t));
      d->AssignRef(is_canonical()
                       ? Symbols::FromUTF16(d->thread(), data, length)
                       : String::FromUTF16(data, length));
    }
  }

  void ReadNodesApi(ApiMessageDeserializer* d) {
    intptr_t count = d->ReadUnsigned();
    for (intptr_t j = 0; j < count; j++) {
      // Read all the UTF-16 code units.
      intptr_t utf16_length = d->ReadUnsigned();
      const uint16_t* utf16 =
          reinterpret_cast<const uint16_t*>(d->CurrentBufferAddress());
      d->Advance(utf16_length * sizeof(uint16_t));

      // Calculate the UTF-8 length and check if the string can be
      // UTF-8 encoded.
      intptr_t utf8_len = 0;
      bool valid = true;
      intptr_t i = 0;
      while (i < utf16_length && valid) {
        int32_t ch = Utf16::Next(utf16, &i, utf16_length);
        utf8_len += Utf8::Length(ch);
        valid = !Utf16::IsSurrogate(ch);
      }
      if (!valid) {
        d->AssignRef(d->Allocate(Dart_CObject_kUnsupported));
      } else {
        Dart_CObject* str = d->Allocate(Dart_CObject_kString);
        char* utf8 = d->zone()->Alloc<char>(utf8_len + 1);
        str->value.as_string = utf8;
        i = 0;
        while (i < utf16_length) {
          utf8 += Utf8::Encode(Utf16::Next(utf16, &i, utf16_length), utf8);
        }
        *utf8 = '\0';
        d->AssignRef(str);
      }
    }
  }
};

static constexpr intptr_t kFirstReference = 1;
static constexpr intptr_t kUnallocatedReference = -1;

BaseSerializer::BaseSerializer(Thread* thread, Zone* zone)
    : StackResource(thread),
      zone_(zone),
      stream_(100),
      finalizable_data_(new MessageFinalizableData()),
      clusters_(zone, 0),
      num_base_objects_(0),
      num_written_objects_(0),
      next_ref_index_(kFirstReference) {}

BaseSerializer::~BaseSerializer() {
  delete finalizable_data_;
}

MessageSerializer::MessageSerializer(Thread* thread)
    : BaseSerializer(thread, thread->zone()),
      forward_table_new_(),
      forward_table_old_(),
      stack_(thread->zone(), 0) {
  thread->set_forward_table_new(new WeakTable());
  thread->set_forward_table_old(new WeakTable());
}

MessageSerializer::~MessageSerializer() {
  thread()->set_forward_table_new(nullptr);
  thread()->set_forward_table_old(nullptr);
}

ApiMessageSerializer::ApiMessageSerializer(Zone* zone)
    : BaseSerializer(nullptr, zone), forward_table_(), stack_(zone, 0) {}

ApiMessageSerializer::~ApiMessageSerializer() {}

void MessageSerializer::Push(ObjectPtr object) {
  if (MarkObjectId(object, kUnallocatedReference)) {
    stack_.Add(&Object::ZoneHandle(zone_, object));
    num_written_objects_++;
  }
}

void ApiMessageSerializer::Push(Dart_CObject* object) {
  if (MarkObjectId(object, kUnallocatedReference)) {
    stack_.Add(object);
    num_written_objects_++;
  }
}

void MessageSerializer::Trace(const Object& root, Object* object) {
  intptr_t cid;
  bool is_canonical;
  if (!object->ptr()->IsHeapObject()) {
    cid = kSmiCid;
    is_canonical = true;
  } else {
    cid = object->GetClassId();
    is_canonical = object->ptr()->untag()->IsCanonical();
  }

  MessageSerializationCluster* cluster = nullptr;
  for (MessageSerializationCluster* c : clusters_) {
    if ((c->cid() == cid) && (c->is_canonical() == is_canonical)) {
      cluster = c;
      break;
    }
  }
  if (cluster == nullptr) {
    if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
      // Will stomp over forward_table_new/old WeakTables, which should be ok,
      // as they are not going to be used again here.
      const char* message = OS::SCreate(
          zone_, "is a regular instance reachable via %s",
          FindRetainingPath(zone_, thread(), root, *object,
                            TraversalRules::kExternalBetweenIsolateGroups));
      IllegalObject(*object, message);
    }

    const char* illegal_cid_string = nullptr;
    // Keep the list in sync with the one in lib/isolate.cc,
    // vm/object_graph_copy.cc
#define ILLEGAL(type)                                                          \
  case k##type##Cid:                                                           \
    illegal_cid_string = #type;                                                \
    break;

    switch (cid) {
      ILLEGAL(Closure)
      ILLEGAL(Finalizer)
      ILLEGAL(FinalizerEntry)
      ILLEGAL(FunctionType)
      ILLEGAL(MirrorReference)
      ILLEGAL(NativeFinalizer)
      ILLEGAL(ReceivePort)
      ILLEGAL(Record)
      ILLEGAL(RecordType)
      ILLEGAL(RegExp)
      ILLEGAL(StackTrace)
      ILLEGAL(SuspendState)
      ILLEGAL(UserTag)
      ILLEGAL(WeakProperty)
      ILLEGAL(WeakReference)
      ILLEGAL(WeakArray)

      // From "dart:ffi" we handle only Pointer/DynamicLibrary specially, since
      // those are the only non-abstract classes (so we avoid checking more cids
      // here that cannot happen in reality)
      ILLEGAL(DynamicLibrary)
      ILLEGAL(Pointer)

#undef ILLEGAL
    }

    if (illegal_cid_string != nullptr) {
      // Will stomp over forward_table_new/old WeakTables, which should be ok,
      // as they are not going to be used again here.
      const char* message = OS::SCreate(
          zone_, "is a %s reachable via %s", illegal_cid_string,
          FindRetainingPath(zone_, thread(), root, *object,
                            TraversalRules::kExternalBetweenIsolateGroups));
      IllegalObject(*object, message);
    }

    cluster = NewClusterForClass(cid, is_canonical);
    clusters_.Add(cluster);
  }

  cluster->Trace(this, object);
}

bool ApiMessageSerializer::Trace(Dart_CObject* object) {
  const bool is_canonical = false;
  intptr_t cid;
  switch (object->type) {
    case Dart_CObject_kNull:
      ForwardRef(object, &cobj_null);
      return true;
    case Dart_CObject_kBool:
      ForwardRef(object, object->value.as_bool ? &cobj_true : &cobj_false);
      return true;
    case Dart_CObject_kInt32:
      cid = Smi::IsValid(object->value.as_int32) ? kSmiCid : kMintCid;
      break;
    case Dart_CObject_kInt64:
      cid = Smi::IsValid(object->value.as_int64) ? kSmiCid : kMintCid;
      break;
    case Dart_CObject_kDouble:
      cid = kDoubleCid;
      break;
    case Dart_CObject_kString: {
      RELEASE_ASSERT(object->value.as_string != nullptr);
      const uint8_t* utf8_str =
          reinterpret_cast<const uint8_t*>(object->value.as_string);
      intptr_t utf8_len = strlen(object->value.as_string);
      if (!Utf8::IsValid(utf8_str, utf8_len)) {
        return Fail("invalid utf8");
      }
      Utf8::Type type = Utf8::kLatin1;
      intptr_t len = Utf8::CodeUnitCount(utf8_str, utf8_len, &type);
      if (len > String::kMaxElements) {
        return Fail("invalid string length");
      }
      cid = type == Utf8::kLatin1 ? kOneByteStringCid : kTwoByteStringCid;
      break;
    }
    case Dart_CObject_kArray:
      cid = kArrayCid;
      if (!Array::IsValidLength(object->value.as_array.length)) {
        return Fail("invalid array length");
      }
      break;
    case Dart_CObject_kTypedData:
      switch (object->value.as_typed_data.type) {
        case Dart_TypedData_kInt8:
          cid = kTypedDataInt8ArrayCid;
          break;
        case Dart_TypedData_kUint8:
          cid = kTypedDataUint8ArrayCid;
          break;
        case Dart_TypedData_kUint8Clamped:
          cid = kTypedDataUint8ClampedArrayCid;
          break;
        case Dart_TypedData_kInt16:
          cid = kTypedDataInt16ArrayCid;
          break;
        case Dart_TypedData_kUint16:
          cid = kTypedDataUint16ArrayCid;
          break;
        case Dart_TypedData_kInt32:
          cid = kTypedDataInt32ArrayCid;
          break;
        case Dart_TypedData_kUint32:
          cid = kTypedDataUint32ArrayCid;
          break;
        case Dart_TypedData_kInt64:
          cid = kTypedDataInt64ArrayCid;
          break;
        case Dart_TypedData_kUint64:
          cid = kTypedDataUint64ArrayCid;
          break;
        case Dart_TypedData_kFloat32:
          cid = kTypedDataFloat32ArrayCid;
          break;
        case Dart_TypedData_kFloat64:
          cid = kTypedDataFloat64ArrayCid;
          break;
        case Dart_TypedData_kInt32x4:
          cid = kTypedDataInt32x4ArrayCid;
          break;
        case Dart_TypedData_kFloat32x4:
          cid = kTypedDataFloat32x4ArrayCid;
          break;
        case Dart_TypedData_kFloat64x2:
          cid = kTypedDataFloat64x2ArrayCid;
          break;
        default:
          return Fail("invalid TypedData type");
      }
      {
        intptr_t len = object->value.as_typed_data.length;
        if (len < 0 || len > TypedData::MaxElements(cid)) {
          return Fail("invalid typeddata length");
        }
      }
      break;
    case Dart_CObject_kExternalTypedData:
      switch (object->value.as_external_typed_data.type) {
        case Dart_TypedData_kInt8:
          cid = kExternalTypedDataInt8ArrayCid;
          break;
        case Dart_TypedData_kUint8:
          cid = kExternalTypedDataUint8ArrayCid;
          break;
        case Dart_TypedData_kUint8Clamped:
          cid = kExternalTypedDataUint8ClampedArrayCid;
          break;
        case Dart_TypedData_kInt16:
          cid = kExternalTypedDataInt16ArrayCid;
          break;
        case Dart_TypedData_kUint16:
          cid = kExternalTypedDataUint16ArrayCid;
          break;
        case Dart_TypedData_kInt32:
          cid = kExternalTypedDataInt32ArrayCid;
          break;
        case Dart_TypedData_kUint32:
          cid = kExternalTypedDataUint32ArrayCid;
          break;
        case Dart_TypedData_kInt64:
          cid = kExternalTypedDataInt64ArrayCid;
          break;
        case Dart_TypedData_kUint64:
          cid = kExternalTypedDataUint64ArrayCid;
          break;
        case Dart_TypedData_kFloat32:
          cid = kExternalTypedDataFloat32ArrayCid;
          break;
        case Dart_TypedData_kFloat64:
          cid = kExternalTypedDataFloat64ArrayCid;
          break;
        case Dart_TypedData_kInt32x4:
          cid = kExternalTypedDataInt32x4ArrayCid;
          break;
        case Dart_TypedData_kFloat32x4:
          cid = kExternalTypedDataFloat32x4ArrayCid;
          break;
        case Dart_TypedData_kFloat64x2:
          cid = kExternalTypedDataFloat64x2ArrayCid;
          break;
        default:
          return Fail("invalid TypedData type");
      }
      {
        intptr_t len = object->value.as_typed_data.length;
        if (len < 0 || len > ExternalTypedData::MaxElements(cid)) {
          return Fail("invalid typeddata length");
        }
      }
      break;
    case Dart_CObject_kUnmodifiableExternalTypedData:
      switch (object->value.as_external_typed_data.type) {
        case Dart_TypedData_kInt8:
          cid = kUnmodifiableTypedDataInt8ArrayViewCid;
          break;
        case Dart_TypedData_kUint8:
          cid = kUnmodifiableTypedDataUint8ArrayViewCid;
          break;
        case Dart_TypedData_kUint8Clamped:
          cid = kUnmodifiableTypedDataUint8ClampedArrayViewCid;
          break;
        case Dart_TypedData_kInt16:
          cid = kUnmodifiableTypedDataInt16ArrayViewCid;
          break;
        case Dart_TypedData_kUint16:
          cid = kUnmodifiableTypedDataUint16ArrayViewCid;
          break;
        case Dart_TypedData_kInt32:
          cid = kUnmodifiableTypedDataInt32ArrayViewCid;
          break;
        case Dart_TypedData_kUint32:
          cid = kUnmodifiableTypedDataUint32ArrayViewCid;
          break;
        case Dart_TypedData_kInt64:
          cid = kUnmodifiableTypedDataInt64ArrayViewCid;
          break;
        case Dart_TypedData_kUint64:
          cid = kUnmodifiableTypedDataUint64ArrayViewCid;
          break;
        case Dart_TypedData_kFloat32:
          cid = kUnmodifiableTypedDataFloat32ArrayViewCid;
          break;
        case Dart_TypedData_kFloat64:
          cid = kUnmodifiableTypedDataFloat64ArrayViewCid;
          break;
        case Dart_TypedData_kInt32x4:
          cid = kUnmodifiableTypedDataInt32x4ArrayViewCid;
          break;
        case Dart_TypedData_kFloat32x4:
          cid = kUnmodifiableTypedDataFloat32x4ArrayViewCid;
          break;
        case Dart_TypedData_kFloat64x2:
          cid = kUnmodifiableTypedDataFloat64x2ArrayViewCid;
          break;
        default:
          return Fail("invalid TypedData type");
      }
      {
        intptr_t len = object->value.as_typed_data.length;
        if (len < 0 || len > TypedData::MaxElements(
                                 cid - kTypedDataCidRemainderUnmodifiable +
                                 kTypedDataCidRemainderInternal)) {
          return Fail("invalid typeddata length");
        }
      }
      break;
    case Dart_CObject_kSendPort:
      cid = kSendPortCid;
      break;
    case Dart_CObject_kCapability:
      cid = kCapabilityCid;
      break;
    case Dart_CObject_kNativePointer:
      cid = kNativePointer;
      break;
    default:
      return Fail("invalid Dart_CObject type");
  }

  MessageSerializationCluster* cluster = nullptr;
  for (MessageSerializationCluster* c : clusters_) {
    if (c->cid() == cid) {
      cluster = c;
      break;
    }
  }
  if (cluster == nullptr) {
    cluster = NewClusterForClass(cid, is_canonical);
    clusters_.Add(cluster);
  }

  cluster->TraceApi(this, object);
  return true;
}

void MessageSerializer::IllegalObject(const Object& object,
                                      const char* message) {
  const Array& args = Array::Handle(zone(), Array::New(3));
  args.SetAt(0, object);
  args.SetAt(2, String::Handle(zone(), String::New(message)));
  Exceptions::ThrowByType(Exceptions::kArgumentValue, args);
}

BaseDeserializer::BaseDeserializer(Zone* zone, Message* message)
    : zone_(zone),
      stream_(message->snapshot(), message->snapshot_length()),
      finalizable_data_(message->finalizable_data()),
      next_ref_index_(kFirstReference) {}

BaseDeserializer::~BaseDeserializer() {}

MessageSerializationCluster* BaseSerializer::NewClusterForClass(
    intptr_t cid,
    bool is_canonical) {
  Zone* Z = zone_;
  if (IsTypedDataViewClassId(cid) || cid == kByteDataViewCid ||
      IsUnmodifiableTypedDataViewClassId(cid) ||
      cid == kUnmodifiableByteDataViewCid) {
    return new (Z) TypedDataViewMessageSerializationCluster(Z, cid);
  }
  if (IsExternalTypedDataClassId(cid)) {
    return new (Z) ExternalTypedDataMessageSerializationCluster(Z, cid);
  }
  if (IsTypedDataClassId(cid)) {
    return new (Z) TypedDataMessageSerializationCluster(Z, cid);
  }

  switch (cid) {
    case kNativePointer:
      return new (Z) NativePointerMessageSerializationCluster(Z);
    case kClassCid:
      return new (Z) ClassMessageSerializationCluster();
    case kTypeArgumentsCid:
      return new (Z) TypeArgumentsMessageSerializationCluster(is_canonical);
    case kTypeCid:
      return new (Z) TypeMessageSerializationCluster(is_canonical);
    case kSmiCid:
      return new (Z) SmiMessageSerializationCluster(Z);
    case kMintCid:
      return new (Z) MintMessageSerializationCluster(Z, is_canonical);
    case kDoubleCid:
      return new (Z) DoubleMessageSerializationCluster(Z, is_canonical);
    case kGrowableObjectArrayCid:
      return new (Z) GrowableObjectArrayMessageSerializationCluster();
    case kSendPortCid:
      return new (Z) SendPortMessageSerializationCluster(Z);
    case kCapabilityCid:
      return new (Z) CapabilityMessageSerializationCluster(Z);
    case kTransferableTypedDataCid:
      return new (Z) TransferableTypedDataMessageSerializationCluster();
    case kMapCid:
    case kConstMapCid:
      return new (Z) MapMessageSerializationCluster(Z, is_canonical, cid);
    case kSetCid:
    case kConstSetCid:
      return new (Z) SetMessageSerializationCluster(Z, is_canonical, cid);
    case kArrayCid:
    case kImmutableArrayCid:
      return new (Z) ArrayMessageSerializationCluster(Z, is_canonical, cid);
    case kOneByteStringCid:
      return new (Z) OneByteStringMessageSerializationCluster(Z, is_canonical);
    case kTwoByteStringCid:
      return new (Z) TwoByteStringMessageSerializationCluster(Z, is_canonical);
    case kInt32x4Cid:
    case kFloat32x4Cid:
    case kFloat64x2Cid:
      return new (Z) Simd128MessageSerializationCluster(cid);
    default:
      break;
  }

  FATAL("No cluster defined for cid %" Pd, cid);
  return nullptr;
}

void BaseSerializer::WriteCluster(MessageSerializationCluster* cluster) {
  uint64_t cid_and_canonical = (static_cast<uint64_t>(cluster->cid()) << 1) |
                               (cluster->is_canonical() ? 0x1 : 0x0);
  WriteUnsigned(cid_and_canonical);
}

MessageDeserializationCluster* BaseDeserializer::ReadCluster() {
  const uint64_t cid_and_canonical = ReadUnsigned();
  const intptr_t cid = (cid_and_canonical >> 1) & kMaxUint32;
  const bool is_canonical = (cid_and_canonical & 0x1) == 0x1;

  Zone* Z = zone_;
  if (IsTypedDataViewClassId(cid) || cid == kByteDataViewCid ||
      IsUnmodifiableTypedDataViewClassId(cid) ||
      cid == kUnmodifiableByteDataViewCid) {
    ASSERT(!is_canonical);
    return new (Z) TypedDataViewMessageDeserializationCluster(cid);
  }
  if (IsExternalTypedDataClassId(cid)) {
    ASSERT(!is_canonical);
    return new (Z) ExternalTypedDataMessageDeserializationCluster(cid);
  }
  if (IsTypedDataClassId(cid)) {
    ASSERT(!is_canonical);
    return new (Z) TypedDataMessageDeserializationCluster(cid);
  }

  switch (cid) {
    case kNativePointer:
      ASSERT(!is_canonical);
      return new (Z) NativePointerMessageDeserializationCluster();
    case kClassCid:
      ASSERT(!is_canonical);
      return new (Z) ClassMessageDeserializationCluster();
    case kTypeArgumentsCid:
      return new (Z) TypeArgumentsMessageDeserializationCluster(is_canonical);
    case kTypeCid:
      return new (Z) TypeMessageDeserializationCluster(is_canonical);
    case kSmiCid:
      ASSERT(is_canonical);
      return new (Z) SmiMessageDeserializationCluster();
    case kMintCid:
      return new (Z) MintMessageDeserializationCluster(is_canonical);
    case kDoubleCid:
      return new (Z) DoubleMessageDeserializationCluster(is_canonical);
    case kGrowableObjectArrayCid:
      ASSERT(!is_canonical);
      return new (Z) GrowableObjectArrayMessageDeserializationCluster();
    case kSendPortCid:
      ASSERT(!is_canonical);
      return new (Z) SendPortMessageDeserializationCluster();
    case kCapabilityCid:
      ASSERT(!is_canonical);
      return new (Z) CapabilityMessageDeserializationCluster();
    case kTransferableTypedDataCid:
      ASSERT(!is_canonical);
      return new (Z) TransferableTypedDataMessageDeserializationCluster();
    case kMapCid:
    case kConstMapCid:
      return new (Z) MapMessageDeserializationCluster(is_canonical, cid);
    case kSetCid:
    case kConstSetCid:
      return new (Z) SetMessageDeserializationCluster(is_canonical, cid);
    case kArrayCid:
    case kImmutableArrayCid:
      return new (Z) ArrayMessageDeserializationCluster(is_canonical, cid);
    case kOneByteStringCid:
      return new (Z) OneByteStringMessageDeserializationCluster(is_canonical);
    case kTwoByteStringCid:
      return new (Z) TwoByteStringMessageDeserializationCluster(is_canonical);
    case kInt32x4Cid:
    case kFloat32x4Cid:
    case kFloat64x2Cid:
      ASSERT(!is_canonical);
      return new (Z) Simd128MessageDeserializationCluster(cid);
    default:
      break;
  }

  FATAL("No cluster defined for cid %" Pd, cid);
  return nullptr;
}

void MessageSerializer::AddBaseObjects() {
  AddBaseObject(Object::null());
  AddBaseObject(Object::sentinel().ptr());
  AddBaseObject(Object::empty_array().ptr());
  AddBaseObject(Object::dynamic_type().ptr());
  AddBaseObject(Object::void_type().ptr());
  AddBaseObject(Object::empty_type_arguments().ptr());
  AddBaseObject(Bool::True().ptr());
  AddBaseObject(Bool::False().ptr());
}

void MessageDeserializer::AddBaseObjects() {
  AddBaseObject(Object::null());
  AddBaseObject(Object::sentinel().ptr());
  AddBaseObject(Object::empty_array().ptr());
  AddBaseObject(Object::dynamic_type().ptr());
  AddBaseObject(Object::void_type().ptr());
  AddBaseObject(Object::empty_type_arguments().ptr());
  AddBaseObject(Bool::True().ptr());
  AddBaseObject(Bool::False().ptr());
}

void ApiMessageSerializer::AddBaseObjects() {
  AddBaseObject(&cobj_null);
  AddBaseObject(&cobj_sentinel);
  AddBaseObject(&cobj_empty_array);
  AddBaseObject(&cobj_dynamic_type);
  AddBaseObject(&cobj_void_type);
  AddBaseObject(&cobj_empty_type_arguments);
  AddBaseObject(&cobj_true);
  AddBaseObject(&cobj_false);
}

void ApiMessageDeserializer::AddBaseObjects() {
  AddBaseObject(&cobj_null);
  AddBaseObject(&cobj_sentinel);
  AddBaseObject(&cobj_empty_array);
  AddBaseObject(&cobj_dynamic_type);
  AddBaseObject(&cobj_void_type);
  AddBaseObject(&cobj_empty_type_arguments);
  AddBaseObject(&cobj_true);
  AddBaseObject(&cobj_false);
}

void MessageSerializer::Serialize(const Object& root) {
  AddBaseObjects();

  Push(root.ptr());

  while (stack_.length() > 0) {
    Trace(root, stack_.RemoveLast());
  }

  intptr_t num_objects = num_base_objects_ + num_written_objects_;
  WriteUnsigned(num_base_objects_);
  WriteUnsigned(num_objects);

  for (intptr_t i = 0; i < static_cast<intptr_t>(MessagePhase::kNumPhases);
       i++) {
    intptr_t num_clusters = 0;
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      num_clusters++;
    }
    WriteUnsigned(num_clusters);
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      WriteCluster(cluster);
      cluster->WriteNodes(this);
    }
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      cluster->WriteEdges(this);
    }
  }

  // We should have assigned a ref to every object we pushed.
  ASSERT((next_ref_index_ - 1) == num_objects);

  WriteRef(root.ptr());
}

bool ApiMessageSerializer::Serialize(Dart_CObject* root) {
  AddBaseObjects();

  Push(root);

  // Strong references only.
  while (stack_.length() > 0) {
    if (!Trace(stack_.RemoveLast())) {
      return false;
    }
  }

  intptr_t num_objects = num_base_objects_ + num_written_objects_;
  WriteUnsigned(num_base_objects_);
  WriteUnsigned(num_objects);

  for (intptr_t i = 0; i < static_cast<intptr_t>(MessagePhase::kNumPhases);
       i++) {
    intptr_t num_clusters = 0;
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      num_clusters++;
    }
    WriteUnsigned(num_clusters);
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      WriteCluster(cluster);
      cluster->WriteNodesApi(this);
    }
    for (MessageSerializationCluster* cluster : clusters_) {
      if (static_cast<intptr_t>(cluster->phase()) != i) continue;
      cluster->WriteEdgesApi(this);
    }
  }

  // We should have assigned a ref to every object we pushed.
  ASSERT((next_ref_index_ - 1) == num_objects);

  WriteRef(root);
  return true;
}

ObjectPtr MessageDeserializer::Deserialize() {
  intptr_t num_base_objects = ReadUnsigned();
  intptr_t num_objects = ReadUnsigned();

  refs_ = Array::New(num_objects + kFirstReference);

  AddBaseObjects();

  // Writer and reader must agree on number of base objects.
  ASSERT_EQUAL(num_base_objects, (next_ref_index_ - kFirstReference));

  Object& error = Object::Handle(zone());
  for (intptr_t i = 0; i < static_cast<intptr_t>(MessagePhase::kNumPhases);
       i++) {
    intptr_t num_clusters = ReadUnsigned();
    MessageDeserializationCluster** clusters =
        zone()->Alloc<MessageDeserializationCluster*>(num_clusters);
    for (intptr_t i = 0; i < num_clusters; i++) {
      clusters[i] = ReadCluster();
      clusters[i]->ReadNodesWrapped(this);
    }
    for (intptr_t i = 0; i < num_clusters; i++) {
      clusters[i]->ReadEdges(this);
    }
    for (intptr_t i = 0; i < num_clusters; i++) {
      error = clusters[i]->PostLoad(this);
      if (error.IsError()) {
        return error.ptr();  // E.g., an UnwindError during rehashing.
      }
    }
  }

  // We should have completely filled the ref array.
  ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects);

  return ReadRef();
}

Dart_CObject* ApiMessageDeserializer::Deserialize() {
  intptr_t num_base_objects = ReadUnsigned();
  intptr_t num_objects = ReadUnsigned();

  refs_ = zone()->Alloc<Dart_CObject*>(num_objects + kFirstReference);

  AddBaseObjects();

  // Writer and reader must agree on number of base objects.
  ASSERT_EQUAL(num_base_objects, (next_ref_index_ - kFirstReference));

  for (intptr_t i = 0; i < static_cast<intptr_t>(MessagePhase::kNumPhases);
       i++) {
    intptr_t num_clusters = ReadUnsigned();
    MessageDeserializationCluster** clusters =
        zone()->Alloc<MessageDeserializationCluster*>(num_clusters);
    for (intptr_t i = 0; i < num_clusters; i++) {
      clusters[i] = ReadCluster();
      clusters[i]->ReadNodesWrappedApi(this);
    }
    for (intptr_t i = 0; i < num_clusters; i++) {
      clusters[i]->ReadEdgesApi(this);
    }
    for (intptr_t i = 0; i < num_clusters; i++) {
      clusters[i]->PostLoadApi(this);
    }
  }

  // We should have completely filled the ref array.
  ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects);

  return ReadRef();
}

std::unique_ptr<Message> WriteMessage(bool same_group,
                                      const Object& obj,
                                      Dart_Port dest_port,
                                      Message::Priority priority) {
  if (ApiObjectConverter::CanConvert(obj.ptr())) {
    return Message::New(dest_port, obj.ptr(), priority);
  } else if (same_group) {
    const Object& copy = Object::Handle(CopyMutableObjectGraph(obj));
    auto handle =
        IsolateGroup::Current()->api_state()->AllocatePersistentHandle();
    handle->set_ptr(copy.ptr());
    return std::make_unique<Message>(dest_port, handle, priority);
  }

  Thread* thread = Thread::Current();
  MessageSerializer serializer(thread);
  serializer.Serialize(obj);
  return serializer.Finish(dest_port, priority);
}

std::unique_ptr<Message> WriteApiMessage(Zone* zone,
                                         Dart_CObject* obj,
                                         Dart_Port dest_port,
                                         Message::Priority priority) {
  ApiMessageSerializer serializer(zone);
  if (!serializer.Serialize(obj)) {
    return nullptr;
  }
  return serializer.Finish(dest_port, priority);
}

ObjectPtr ReadObjectGraphCopyMessage(Thread* thread, PersistentHandle* handle) {
  // msg_array = [
  //     <message>,
  //     <collection-lib-objects-to-rehash>,
  //     <core-lib-objects-to-rehash>,
  // ]
  Zone* zone = thread->zone();
  Object& msg_obj = Object::Handle(zone);
  const auto& msg_array = Array::Handle(zone, Array::RawCast(handle->ptr()));
  ASSERT(msg_array.Length() == 3);
  msg_obj = msg_array.At(0);
  if (msg_array.At(1) != Object::null()) {
    const auto& objects_to_rehash = Object::Handle(zone, msg_array.At(1));
    auto& result = Object::Handle(zone);
    result = DartLibraryCalls::RehashObjectsInDartCompactHash(
        thread, objects_to_rehash);
    if (result.ptr() != Object::null()) {
      msg_obj = result.ptr();
    }
  }
  if (msg_array.At(2) != Object::null()) {
    const auto& objects_to_rehash = Object::Handle(zone, msg_array.At(2));
    auto& result = Object::Handle(zone);
    result =
        DartLibraryCalls::RehashObjectsInDartCore(thread, objects_to_rehash);
    if (result.ptr() != Object::null()) {
      msg_obj = result.ptr();
    }
  }
  return msg_obj.ptr();
}

ObjectPtr ReadMessage(Thread* thread, Message* message) {
  if (message->IsRaw()) {
    return message->raw_obj();
  } else if (message->IsFinalizerInvocationRequest()) {
    PersistentHandle* handle = message->persistent_handle();
    Object& msg_obj = Object::Handle(thread->zone(), handle->ptr());
    ASSERT(msg_obj.IsFinalizer() || msg_obj.IsNativeFinalizer());
    return msg_obj.ptr();
  } else if (message->IsPersistentHandle()) {
    return ReadObjectGraphCopyMessage(thread, message->persistent_handle());
  } else {
    RELEASE_ASSERT(message->IsSnapshot());
    LongJumpScope jump(thread);
    if (DART_SETJMP(*jump.Set()) == 0) {
      MessageDeserializer deserializer(thread, message);
      return deserializer.Deserialize();
    } else {
      return thread->StealStickyError();
    }
  }
}

Dart_CObject* ReadApiMessage(Zone* zone, Message* message) {
  if (message->IsRaw()) {
    Dart_CObject* result = zone->Alloc<Dart_CObject>(1);
    ApiObjectConverter::Convert(message->raw_obj(), result);
    return result;
  } else {
    RELEASE_ASSERT(message->IsSnapshot());
    ApiMessageDeserializer deserializer(zone, message);
    return deserializer.Deserialize();
  }
}

}  // namespace dart
