Version 2.10.0-65.0.dev

Merge commit '29c0795f48ff8c12aa0793c0fbfef201eb423aed' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6a1a79c..f605320 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,11 @@
     unsoundness with only a minimal regression. To explicitly disable
     deferred loading of types, pass `--no-defer-class-types`. See the original
     post on the [unsoundness in the deferred loading algorithm][].
+*   Enables a new sound deferred splitting algorithm. To explicitly disable
+    the new deferred splitting algorithm, pass `--no-new-deferred-split'.
+    See the original post on the
+    [unsoundness in the deferred loading algorithm][].
+
 
 [#42982]: https://github.com/dart-lang/sdk/issues/42982
 [unsoundness in the deferred loading algorithm]: https://github.com/dart-lang/sdk/blob/302ad7ab2cd2de936254850550aad128ae76bbb7/CHANGELOG.md#dart2js-3
diff --git a/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart b/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
index 4481354..193e37c 100644
--- a/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
+++ b/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
@@ -12,12 +12,23 @@
 import '../../world.dart';
 import '../abstract_value_domain.dart';
 
+/// This class is used to store bits information about class entities.
+class ClassInfo {
+  final int exactBits;
+  final int strictSubtypeBits;
+  final int strictSubclassBits;
+
+  const ClassInfo(
+      this.exactBits, this.strictSubtypeBits, this.strictSubclassBits);
+}
+
 /// This class is used as an API by the powerset abstract value domain to help
 /// implement some queries. It stores the bitmasks as integers and has the
 /// advantage that the operations needed are relatively fast. This will pack
 /// multiple powerset domains into a single integer.
 class PowersetBitsDomain {
   final JClosedWorld _closedWorld;
+  final Map<ClassEntity, ClassInfo> _storedClassInfo = {};
 
   static const int _trueIndex = 0;
   static const int _falseIndex = 1;
@@ -31,7 +42,7 @@
     _nullIndex,
   ];
 
-  const PowersetBitsDomain(this._closedWorld);
+  PowersetBitsDomain(this._closedWorld);
 
   CommonElements get commonElements => _closedWorld.commonElements;
 
@@ -242,50 +253,102 @@
 
   AbstractBool isTypedArray(int value) => AbstractBool.Maybe;
 
-  bool isBoolSubtype(ClassEntity cls) {
+  bool _isBoolSubtype(ClassEntity cls) {
     return cls == commonElements.jsBoolClass || cls == commonElements.boolClass;
   }
 
-  int createNullableSubtype(ClassEntity cls) {
-    if (isBoolSubtype(cls)) {
-      return boolOrNullMask;
+  bool _isNullSubtype(ClassEntity cls) {
+    return cls == commonElements.jsNullClass || cls == commonElements.nullClass;
+  }
+
+  // This function checks if cls is one of those classes other than bool or null
+  // that are live by default in a program.
+  bool _isLiveByDefault(ClassEntity cls) {
+    return cls == commonElements.intClass ||
+        cls == commonElements.numClass ||
+        cls == commonElements.doubleClass ||
+        cls == commonElements.stringClass;
+  }
+
+  ClassInfo _computeClassInfo(ClassEntity cls) {
+    ClassInfo classInfo = _storedClassInfo[cls];
+    if (classInfo != null) {
+      return classInfo;
     }
-    return nullOrOtherMask;
+
+    // Bool and Null are handled specially because the powerset bits
+    // contain information about values being bool or null.
+    if (_isNullSubtype(cls)) {
+      classInfo = ClassInfo(nullMask, powersetBottom, powersetBottom);
+      _storedClassInfo[cls] = classInfo;
+      return classInfo;
+    }
+    if (_isBoolSubtype(cls)) {
+      classInfo = ClassInfo(boolMask, powersetBottom, powersetBottom);
+      _storedClassInfo[cls] = classInfo;
+      return classInfo;
+    }
+
+    // If cls is instantiated or live by default the 'other' bit should be set to 1.
+    int exactBits = powersetBottom;
+    if (_isLiveByDefault(cls) ||
+        _closedWorld.classHierarchy.isInstantiated(cls)) {
+      exactBits = otherMask;
+    }
+
+    int strictSubtypeBits = powersetBottom;
+    for (ClassEntity strictSubtype
+        in _closedWorld.classHierarchy.strictSubtypesOf(cls)) {
+      // Currently null is a subtype of Object in the class hierarchy but we don't
+      // want to consider it as a subtype of a nonnull class
+      if (!_isNullSubtype(strictSubtype)) {
+        strictSubtypeBits |= _computeClassInfo(strictSubtype).exactBits;
+      }
+    }
+
+    int strictSubclassBits = powersetBottom;
+    for (ClassEntity strictSubclass
+        in _closedWorld.classHierarchy.strictSubclassesOf(cls)) {
+      // Currently null is a subtype of Object in the class hierarchy but we don't
+      // want to consider it as a subtype of a nonnull class
+      if (!_isNullSubtype(strictSubclass)) {
+        strictSubclassBits |= _computeClassInfo(strictSubclass).exactBits;
+      }
+    }
+
+    classInfo = ClassInfo(exactBits, strictSubtypeBits, strictSubclassBits);
+    _storedClassInfo[cls] = classInfo;
+    return classInfo;
+  }
+
+  int createNullableSubtype(ClassEntity cls) {
+    return includeNull(createNonNullSubtype(cls));
   }
 
   int createNonNullSubtype(ClassEntity cls) {
-    if (isBoolSubtype(cls)) {
-      return boolMask;
-    }
-    return otherMask;
+    ClassInfo classInfo = _computeClassInfo(cls);
+    return classInfo.exactBits | classInfo.strictSubtypeBits;
   }
 
   int createNonNullSubclass(ClassEntity cls) {
-    if (isBoolSubtype(cls)) {
-      return boolMask;
-    }
-    return otherMask;
+    ClassInfo classInfo = _computeClassInfo(cls);
+    return classInfo.exactBits | classInfo.strictSubclassBits;
   }
 
   int createNullableExact(ClassEntity cls) {
-    if (isBoolSubtype(cls)) {
-      return boolOrNullMask;
-    }
-    return nullOrOtherMask;
+    return includeNull(createNonNullExact(cls));
   }
 
   int createNonNullExact(ClassEntity cls) {
-    if (isBoolSubtype(cls)) {
-      return boolMask;
-    }
-    return otherMask;
+    ClassInfo classInfo = _computeClassInfo(cls);
+    return classInfo.exactBits;
   }
 
   int createFromStaticType(DartType type,
       {ClassRelation classRelation = ClassRelation.subtype, bool nullable}) {
     // TODO(coam): This only works for bool
     int bits = otherMask;
-    if (type is InterfaceType && isBoolSubtype(type.element)) {
+    if (type is InterfaceType && _isBoolSubtype(type.element)) {
       bits = boolMask;
     }
     if (nullable) {
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 40d9dc6..1d6bad2 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -146,7 +146,7 @@
   /// When [reportInvalidInferredDeferredTypes] shows no errors, we expect this
   /// flag to produce the same or better results than the current unsound
   /// implementation.
-  bool newDeferredSplit = false; // default value.
+  bool newDeferredSplit = true; // default value.
   bool _newDeferredSplit = false;
   bool _noNewDeferredSplit = false;
 
diff --git a/pkg/compiler/test/deferred_loading/data/regress_43055/libb.dart b/pkg/compiler/test/deferred_loading/data/regress_43055/libb.dart
index bb954ec..b72819b 100644
--- a/pkg/compiler/test/deferred_loading/data/regress_43055/libb.dart
+++ b/pkg/compiler/test/deferred_loading/data/regress_43055/libb.dart
@@ -6,6 +6,6 @@
 
 import 'libc.dart';
 
-/*class: C1:OutputUnit(1, {libb}), type=OutputUnit(1, {libb})*/
+/*class: C1:OutputUnit(1, {libb}), type=OutputUnit(main, {})*/
 /*member: C1.:OutputUnit(1, {libb})*/
 class C1 extends C2 implements C3 {}
diff --git a/pkg/compiler/test/inference/powerset_bits2_test.dart b/pkg/compiler/test/inference/powerset_bits2_test.dart
new file mode 100644
index 0000000..3ed02cd
--- /dev/null
+++ b/pkg/compiler/test/inference/powerset_bits2_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.
+
+// @dart = 2.7
+
+import 'package:async_helper/async_helper.dart';
+import 'package:compiler/src/common.dart';
+import 'package:compiler/src/common_elements.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/inferrer/powersets/powersets.dart';
+import 'package:compiler/src/inferrer/powersets/powerset_bits.dart';
+import 'package:compiler/src/world.dart';
+import 'package:expect/expect.dart';
+import '../helpers/memory_compiler.dart';
+
+const String CODE = """
+class A {}
+main() {
+  A a = A();
+}
+""";
+
+main() {
+  retainDataForTesting = true;
+
+  runTests() async {
+    CompilationResult result = await runCompiler(memorySourceFiles: {
+      'main.dart': CODE
+    }, options: [
+      '--experimental-powersets',
+    ]);
+    Expect.isTrue(result.isSuccess);
+    Compiler compiler = result.compiler;
+    var results = compiler.globalInference.resultsForTesting;
+    JClosedWorld closedWorld = results.closedWorld;
+    CommonElements commonElements = closedWorld.commonElements;
+    ElementEnvironment elementEnvironment = closedWorld.elementEnvironment;
+    PowersetDomain powersetDomain = closedWorld.abstractValueDomain;
+    PowersetBitsDomain powersetBitsDomain = powersetDomain.powersetBitsDomain;
+
+    var exactTrue = powersetBitsDomain.trueMask;
+    var exactFalse = powersetBitsDomain.falseMask;
+    dynamic classA =
+        elementEnvironment.lookupClass(elementEnvironment.mainLibrary, 'A');
+    var exactA = powersetBitsDomain.createNonNullExact(classA);
+    var subtypeObject =
+        powersetBitsDomain.createNonNullSubtype(commonElements.objectClass);
+    var exactNull =
+        powersetBitsDomain.createNonNullExact(commonElements.jsNullClass);
+    var exactBool =
+        powersetBitsDomain.createNonNullExact(commonElements.jsBoolClass);
+    var nullableBool =
+        powersetBitsDomain.createNullableExact(commonElements.jsBoolClass);
+
+    var unionTrueFalse = powersetBitsDomain.union(exactTrue, exactFalse);
+    var unionBoolNull = powersetBitsDomain.union(exactBool, exactNull);
+    var unionBoolNullOther = powersetBitsDomain.union(unionBoolNull, exactA);
+
+    Expect.equals(unionTrueFalse, exactBool);
+    Expect.equals(unionBoolNull, nullableBool);
+    Expect.equals(unionBoolNullOther, powersetBitsDomain.powersetTop);
+
+    checkDisjoint(int v1, int v2) {
+      Expect.isTrue(powersetBitsDomain.areDisjoint(v1, v2).isDefinitelyTrue);
+    }
+
+    checkDisjoint(exactTrue, exactFalse);
+    checkDisjoint(exactA, exactNull);
+    checkDisjoint(subtypeObject, exactNull);
+    checkDisjoint(exactBool, exactNull);
+    checkDisjoint(exactA, exactBool);
+    checkDisjoint(exactA, nullableBool);
+
+    checkisIn(int v1, int v2) {
+      Expect.isTrue(powersetBitsDomain.isIn(v1, v2).isDefinitelyTrue);
+    }
+
+    checkisIn(exactTrue, exactBool);
+    checkisIn(exactFalse, exactBool);
+    checkisIn(exactNull, nullableBool);
+    checkisIn(exactBool, nullableBool);
+  }
+
+  asyncTest(() async {
+    print('--test from kernel------------------------------------------------');
+    await runTests();
+  });
+}
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index 1ac8113..0b17ed9 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -115,7 +115,6 @@
     // Before calling to run, send the first ping to analytics to have the first
     // ping, as well as the command itself, running in parallel.
     if (analytics.enabled) {
-      analytics.setSessionValue(flagsParam, getFlags(args));
       commandName = getCommandStr(args, runner.commands.keys.toList());
       // ignore: unawaited_futures
       analytics.sendEvent(eventCategory, commandName);
diff --git a/pkg/dartdev/lib/src/analytics.dart b/pkg/dartdev/lib/src/analytics.dart
index f909916..5a1037a 100644
--- a/pkg/dartdev/lib/src/analytics.dart
+++ b/pkg/dartdev/lib/src/analytics.dart
@@ -39,7 +39,6 @@
 
 const String eventCategory = 'dartdev';
 const String exitCodeParam = 'exitCode';
-const String flagsParam = 'flags';
 
 Analytics instance;
 
@@ -104,25 +103,6 @@
       orElse: () => _unknownCommand);
 }
 
-/// Given some set of arguments and parameters, this returns a proper subset
-/// of the arguments that start with '-', joined by a space character.
-String getFlags(List<String> args) {
-  if (args == null || args.isEmpty) {
-    return '';
-  }
-  var argSubset = <String>[];
-  for (var arg in args) {
-    if (arg.startsWith('-')) {
-      if (arg.contains('=')) {
-        argSubset.add(arg.substring(0, arg.indexOf('=') + 1));
-      } else {
-        argSubset.add(arg);
-      }
-    }
-  }
-  return argSubset.join(' ');
-}
-
 /// The directory used to store the analytics settings file.
 ///
 /// Typically, the directory is `~/.dart/` (and the settings file is
diff --git a/pkg/dartdev/test/analytics_test.dart b/pkg/dartdev/test/analytics_test.dart
index 8632697..3ce90de 100644
--- a/pkg/dartdev/test/analytics_test.dart
+++ b/pkg/dartdev/test/analytics_test.dart
@@ -21,30 +21,6 @@
 }
 
 void utils() {
-  test('getFlags', () {
-    // base cases
-    expect(getFlags(null), '');
-    expect(getFlags(['']), '');
-    expect(getFlags(['', '']), '');
-
-    // non-trivial tests
-    expect(getFlags(['help', 'foo', 'bar']), '');
-    expect(getFlags(['--some-flag', '--some-option=1', '-v']),
-        '--some-flag --some-option= -v');
-    expect(
-        getFlags(['help', '--some-flag', '--some-option=two', '-v', 'analyze']),
-        '--some-flag --some-option= -v');
-    expect(
-        getFlags([
-          'help',
-          '--some-flag',
-          'analyze',
-          '--some-option=three=three',
-          '-v'
-        ]),
-        '--some-flag --some-option= -v');
-  });
-
   test('getCommandStr', () {
     var commands = <String>['help', 'foo', 'bar', 'baz'];
 
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index ba76ca6..955226e 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -2170,15 +2170,15 @@
     // its canonical counterpart's object ID. This ensures that any reference to
     // it is serialized as a reference to the canonicalized one.
     for (auto const ref : objects_) {
-      ASSERT(Serializer::IsReachableReference(heap_->GetObjectId(ref)));
+      ASSERT(IsReachableReference(heap_->GetObjectId(ref)));
       if (ShouldDrop(ref)) {
         // For dropped references, reset their ID to be the unreachable
         // reference value, so RefId retrieves the target ID instead.
-        heap_->SetObjectId(ref, Serializer::kUnreachableReference);
+        heap_->SetObjectId(ref, kUnreachableReference);
         continue;
       }
       // Skip if we've already allocated a reference (this is a canonical WSR).
-      if (Serializer::IsAllocatedReference(heap_->GetObjectId(ref))) continue;
+      if (IsAllocatedReference(heap_->GetObjectId(ref))) continue;
       auto const target_cid = WeakSerializationReference::TargetClassIdOf(ref);
       ASSERT(canonical_wsr_map_.HasKey(target_cid));
       auto const canonical_index = canonical_wsr_map_.Lookup(target_cid) - 1;
@@ -2187,7 +2187,7 @@
       // canonical WSR entry, so we'll reference the canonical WSR when
       // serializing references to this object.
       auto const canonical_heap_id = heap_->GetObjectId(canonical_wsr);
-      ASSERT(Serializer::IsAllocatedReference(canonical_heap_id));
+      ASSERT(IsAllocatedReference(canonical_heap_id));
       heap_->SetObjectId(ref, canonical_heap_id);
     }
   }
@@ -2233,7 +2233,7 @@
   // the object ID from the heap directly.
   bool ShouldDrop(WeakSerializationReferencePtr ref) const {
     auto const target = WeakSerializationReference::TargetOf(ref);
-    return Serializer::IsReachableReference(heap_->GetObjectId(target));
+    return IsReachableReference(heap_->GetObjectId(target));
   }
 
   Heap* const heap_;
@@ -4944,6 +4944,432 @@
 };
 #endif  // !DART_PRECOMPILED_RUNTIME
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+class VMSerializationRoots : public SerializationRoots {
+ public:
+  explicit VMSerializationRoots(const Array& symbols)
+      : symbols_(symbols), zone_(Thread::Current()->zone()) {}
+
+  void AddBaseObjects(Serializer* s) {
+    // These objects are always allocated by Object::InitOnce, so they are not
+    // written into the snapshot.
+
+    s->AddBaseObject(Object::null(), "Null", "null");
+    s->AddBaseObject(Object::sentinel().raw(), "Null", "sentinel");
+    s->AddBaseObject(Object::transition_sentinel().raw(), "Null",
+                     "transition_sentinel");
+    s->AddBaseObject(Object::empty_array().raw(), "Array", "<empty_array>");
+    s->AddBaseObject(Object::zero_array().raw(), "Array", "<zero_array>");
+    s->AddBaseObject(Object::dynamic_type().raw(), "Type", "<dynamic type>");
+    s->AddBaseObject(Object::void_type().raw(), "Type", "<void type>");
+    s->AddBaseObject(Object::empty_type_arguments().raw(), "TypeArguments",
+                     "[]");
+    s->AddBaseObject(Bool::True().raw(), "bool", "true");
+    s->AddBaseObject(Bool::False().raw(), "bool", "false");
+    ASSERT(Object::extractor_parameter_types().raw() != Object::null());
+    s->AddBaseObject(Object::extractor_parameter_types().raw(), "Array",
+                     "<extractor parameter types>");
+    ASSERT(Object::extractor_parameter_names().raw() != Object::null());
+    s->AddBaseObject(Object::extractor_parameter_names().raw(), "Array",
+                     "<extractor parameter names>");
+    s->AddBaseObject(Object::empty_context_scope().raw(), "ContextScope",
+                     "<empty>");
+    s->AddBaseObject(Object::empty_descriptors().raw(), "PcDescriptors",
+                     "<empty>");
+    s->AddBaseObject(Object::empty_var_descriptors().raw(),
+                     "LocalVarDescriptors", "<empty>");
+    s->AddBaseObject(Object::empty_exception_handlers().raw(),
+                     "ExceptionHandlers", "<empty>");
+    s->AddBaseObject(Object::implicit_getter_bytecode().raw(), "Bytecode",
+                     "<implicit getter>");
+    s->AddBaseObject(Object::implicit_setter_bytecode().raw(), "Bytecode",
+                     "<implicit setter>");
+    s->AddBaseObject(Object::implicit_static_getter_bytecode().raw(),
+                     "Bytecode", "<implicit static getter>");
+    s->AddBaseObject(Object::method_extractor_bytecode().raw(), "Bytecode",
+                     "<method extractor>");
+    s->AddBaseObject(Object::invoke_closure_bytecode().raw(), "Bytecode",
+                     "<invoke closure>");
+    s->AddBaseObject(Object::invoke_field_bytecode().raw(), "Bytecode",
+                     "<invoke field>");
+    s->AddBaseObject(Object::nsm_dispatcher_bytecode().raw(), "Bytecode",
+                     "<nsm dispatcher>");
+    s->AddBaseObject(Object::dynamic_invocation_forwarder_bytecode().raw(),
+                     "Bytecode", "<dyn forwarder>");
+
+    for (intptr_t i = 0; i < ArgumentsDescriptor::kCachedDescriptorCount; i++) {
+      s->AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i],
+                       "ArgumentsDescriptor", "<cached arguments descriptor>");
+    }
+    for (intptr_t i = 0; i < ICData::kCachedICDataArrayCount; i++) {
+      s->AddBaseObject(ICData::cached_icdata_arrays_[i], "Array",
+                       "<empty icdata entries>");
+    }
+    s->AddBaseObject(SubtypeTestCache::cached_array_, "Array",
+                     "<empty subtype entries>");
+
+    ClassTable* table = s->isolate()->class_table();
+    for (intptr_t cid = kClassCid; cid < kInstanceCid; cid++) {
+      // Error, CallSiteData has no class object.
+      if (cid != kErrorCid && cid != kCallSiteDataCid) {
+        ASSERT(table->HasValidClassAt(cid));
+        s->AddBaseObject(table->At(cid), "Class");
+      }
+    }
+    s->AddBaseObject(table->At(kDynamicCid), "Class");
+    s->AddBaseObject(table->At(kVoidCid), "Class");
+
+    if (!Snapshot::IncludesCode(s->kind())) {
+      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
+        s->AddBaseObject(StubCode::EntryAt(i).raw(), "Code", "<stub code>");
+      }
+    }
+  }
+
+  void PushRoots(Serializer* s) {
+    s->Push(symbols_.raw());
+    if (Snapshot::IncludesCode(s->kind())) {
+      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
+        s->Push(StubCode::EntryAt(i).raw());
+      }
+    }
+  }
+
+  void WriteRoots(Serializer* s) {
+    s->WriteRootRef(symbols_.raw(), "symbol-table");
+    if (Snapshot::IncludesCode(s->kind())) {
+      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
+        s->WriteRootRef(StubCode::EntryAt(i).raw(),
+                        zone_->PrintToString("Stub:%s", StubCode::NameAt(i)));
+      }
+    }
+  }
+
+ private:
+  const Array& symbols_;
+  Zone* zone_;
+};
+#endif  // !DART_PRECOMPILED_RUNTIME
+
+class VMDeserializationRoots : public DeserializationRoots {
+ public:
+  VMDeserializationRoots() : symbol_table_(Array::Handle()) {}
+
+  void AddBaseObjects(Deserializer* d) {
+    // These objects are always allocated by Object::InitOnce, so they are not
+    // written into the snapshot.
+
+    d->AddBaseObject(Object::null());
+    d->AddBaseObject(Object::sentinel().raw());
+    d->AddBaseObject(Object::transition_sentinel().raw());
+    d->AddBaseObject(Object::empty_array().raw());
+    d->AddBaseObject(Object::zero_array().raw());
+    d->AddBaseObject(Object::dynamic_type().raw());
+    d->AddBaseObject(Object::void_type().raw());
+    d->AddBaseObject(Object::empty_type_arguments().raw());
+    d->AddBaseObject(Bool::True().raw());
+    d->AddBaseObject(Bool::False().raw());
+    ASSERT(Object::extractor_parameter_types().raw() != Object::null());
+    d->AddBaseObject(Object::extractor_parameter_types().raw());
+    ASSERT(Object::extractor_parameter_names().raw() != Object::null());
+    d->AddBaseObject(Object::extractor_parameter_names().raw());
+    d->AddBaseObject(Object::empty_context_scope().raw());
+    d->AddBaseObject(Object::empty_descriptors().raw());
+    d->AddBaseObject(Object::empty_var_descriptors().raw());
+    d->AddBaseObject(Object::empty_exception_handlers().raw());
+    d->AddBaseObject(Object::implicit_getter_bytecode().raw());
+    d->AddBaseObject(Object::implicit_setter_bytecode().raw());
+    d->AddBaseObject(Object::implicit_static_getter_bytecode().raw());
+    d->AddBaseObject(Object::method_extractor_bytecode().raw());
+    d->AddBaseObject(Object::invoke_closure_bytecode().raw());
+    d->AddBaseObject(Object::invoke_field_bytecode().raw());
+    d->AddBaseObject(Object::nsm_dispatcher_bytecode().raw());
+    d->AddBaseObject(Object::dynamic_invocation_forwarder_bytecode().raw());
+
+    for (intptr_t i = 0; i < ArgumentsDescriptor::kCachedDescriptorCount; i++) {
+      d->AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i]);
+    }
+    for (intptr_t i = 0; i < ICData::kCachedICDataArrayCount; i++) {
+      d->AddBaseObject(ICData::cached_icdata_arrays_[i]);
+    }
+    d->AddBaseObject(SubtypeTestCache::cached_array_);
+
+    ClassTable* table = d->isolate()->class_table();
+    for (intptr_t cid = kClassCid; cid <= kUnwindErrorCid; cid++) {
+      // Error, CallSiteData has no class object.
+      if (cid != kErrorCid && cid != kCallSiteDataCid) {
+        ASSERT(table->HasValidClassAt(cid));
+        d->AddBaseObject(table->At(cid));
+      }
+    }
+    d->AddBaseObject(table->At(kDynamicCid));
+    d->AddBaseObject(table->At(kVoidCid));
+
+    if (!Snapshot::IncludesCode(d->kind())) {
+      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
+        d->AddBaseObject(StubCode::EntryAt(i).raw());
+      }
+    }
+  }
+
+  void ReadRoots(Deserializer* d) {
+    symbol_table_ ^= d->ReadRef();
+    d->isolate()->object_store()->set_symbol_table(symbol_table_);
+    if (Snapshot::IncludesCode(d->kind())) {
+      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
+        Code* code = Code::ReadOnlyHandle();
+        *code ^= d->ReadRef();
+        StubCode::EntryAtPut(i, code);
+      }
+    }
+  }
+
+  void PostLoad(Deserializer* d, const Array& refs) {
+    // Move remaining bump allocation space to the freelist so it used by C++
+    // allocations (e.g., FinalizeVMIsolate) before allocating new pages.
+    d->heap()->old_space()->AbandonBumpAllocation();
+
+    Symbols::InitFromSnapshot(d->isolate());
+
+    Object::set_vm_isolate_snapshot_object_table(refs);
+  }
+
+ private:
+  Array& symbol_table_;
+};
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
+static const char* kObjectStoreFieldNames[] = {
+#define DECLARE_OBJECT_STORE_FIELD(Type, Name) #Name,
+    OBJECT_STORE_FIELD_LIST(DECLARE_OBJECT_STORE_FIELD,
+                            DECLARE_OBJECT_STORE_FIELD,
+                            DECLARE_OBJECT_STORE_FIELD,
+                            DECLARE_OBJECT_STORE_FIELD)
+#undef DECLARE_OBJECT_STORE_FIELD
+};
+
+class ProgramSerializationRoots : public SerializationRoots {
+ public:
+  ProgramSerializationRoots(intptr_t num_base_objects,
+                            ObjectStore* object_store)
+      : num_base_objects_(num_base_objects),
+        object_store_(object_store),
+        dispatch_table_entries_(Array::Handle()) {}
+
+  void AddBaseObjects(Serializer* s) {
+    if (num_base_objects_ == 0) {
+      // Not writing a new vm isolate: use the one this VM was loaded from.
+      const Array& base_objects = Object::vm_isolate_snapshot_object_table();
+      for (intptr_t i = kFirstReference; i < base_objects.Length(); i++) {
+        s->AddBaseObject(base_objects.At(i));
+      }
+    } else {
+      // Base objects carried over from WriteVMSnapshot.
+      s->CarryOverBaseObjects(num_base_objects_);
+    }
+  }
+
+  void PushRoots(Serializer* s) {
+    ObjectPtr* from = object_store_->from();
+    ObjectPtr* to = object_store_->to_snapshot(s->kind());
+    for (ObjectPtr* p = from; p <= to; p++) {
+      s->Push(*p);
+    }
+
+    dispatch_table_entries_ = object_store_->dispatch_table_code_entries();
+    // We should only have a dispatch table in precompiled mode.
+    ASSERT(dispatch_table_entries_.IsNull() || s->kind() == Snapshot::kFullAOT);
+
+#if defined(DART_PRECOMPILER)
+    // We treat the dispatch table as a root object and trace the Code objects
+    // it references. Otherwise, a non-empty entry could be invalid on
+    // deserialization if the corresponding Code object was not reachable from
+    // the existing snapshot roots.
+    if (!dispatch_table_entries_.IsNull()) {
+      for (intptr_t i = 0; i < dispatch_table_entries_.Length(); i++) {
+        s->Push(dispatch_table_entries_.At(i));
+      }
+    }
+#endif
+  }
+
+  void WriteRoots(Serializer* s) {
+    ObjectPtr* from = object_store_->from();
+    ObjectPtr* to = object_store_->to_snapshot(s->kind());
+    for (ObjectPtr* p = from; p <= to; p++) {
+      s->WriteRootRef(*p, kObjectStoreFieldNames[p - from]);
+    }
+
+    // The dispatch table is serialized only for precompiled snapshots.
+    s->WriteDispatchTable(dispatch_table_entries_);
+  }
+
+ private:
+  intptr_t num_base_objects_;
+  ObjectStore* object_store_;
+  Array& dispatch_table_entries_;
+};
+#endif  // !DART_PRECOMPILED_RUNTIME
+
+class ProgramDeserializationRoots : public DeserializationRoots {
+ public:
+  explicit ProgramDeserializationRoots(ObjectStore* object_store)
+      : object_store_(object_store) {}
+
+  void AddBaseObjects(Deserializer* d) {
+    // N.B.: Skipping index 0 because ref 0 is illegal.
+    const Array& base_objects = Object::vm_isolate_snapshot_object_table();
+    for (intptr_t i = kFirstReference; i < base_objects.Length(); i++) {
+      d->AddBaseObject(base_objects.At(i));
+    }
+  }
+
+  void ReadRoots(Deserializer* d) {
+    // Read roots.
+    ObjectPtr* from = object_store_->from();
+    ObjectPtr* to = object_store_->to_snapshot(d->kind());
+    for (ObjectPtr* p = from; p <= to; p++) {
+      *p = d->ReadRef();
+    }
+
+    // Deserialize dispatch table (when applicable)
+    d->ReadDispatchTable();
+  }
+
+  void PostLoad(Deserializer* d, const Array& refs) {
+    Isolate* isolate = d->thread()->isolate();
+    isolate->class_table()->CopySizesFromClassObjects();
+    d->heap()->old_space()->EvaluateAfterLoading();
+
+    const Array& units =
+        Array::Handle(isolate->object_store()->loading_units());
+    if (!units.IsNull()) {
+      LoadingUnit& unit = LoadingUnit::Handle();
+      unit ^= units.At(LoadingUnit::kRootId);
+      unit.set_base_objects(refs);
+    }
+    isolate->isolate_object_store()->PreallocateObjects();
+
+    // Setup native resolver for bootstrap impl.
+    Bootstrap::SetupNativeResolver();
+  }
+
+ private:
+  ObjectStore* object_store_;
+};
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
+class UnitSerializationRoots : public SerializationRoots {
+ public:
+  explicit UnitSerializationRoots(LoadingUnitSerializationData* unit)
+      : unit_(unit) {}
+
+  void AddBaseObjects(Serializer* s) {
+    intptr_t num_base_objects = unit_->parent()->num_objects();
+    ASSERT(num_base_objects != 0);
+    s->CarryOverBaseObjects(num_base_objects);
+  }
+
+  void PushRoots(Serializer* s) {
+    intptr_t num_deferred_objects = unit_->deferred_objects()->length();
+    for (intptr_t i = 0; i < num_deferred_objects; i++) {
+      const Object* deferred_object = (*unit_->deferred_objects())[i];
+      ASSERT(deferred_object->IsCode());
+      CodePtr code = static_cast<CodePtr>(deferred_object->raw());
+      s->Push(code->ptr()->compressed_stackmaps_);
+      s->Push(code->ptr()->code_source_map_);
+    }
+    {
+      GrowableArray<CodePtr> raw_codes(num_deferred_objects);
+      for (intptr_t i = 0; i < num_deferred_objects; i++) {
+        raw_codes.Add((*unit_->deferred_objects())[i]->raw());
+      }
+      s->PrepareInstructions(&raw_codes);
+    }
+  }
+
+  void WriteRoots(Serializer* s) {
+#if defined(DART_PRECOMPILER)
+    intptr_t start_index = 0;
+    intptr_t num_deferred_objects = unit_->deferred_objects()->length();
+    if (num_deferred_objects != 0) {
+      start_index = s->RefId(unit_->deferred_objects()->At(0)->raw());
+      ASSERT(start_index > 0);
+    }
+    s->WriteUnsigned(start_index);
+    s->WriteUnsigned(num_deferred_objects);
+    for (intptr_t i = 0; i < num_deferred_objects; i++) {
+      const Object* deferred_object = (*unit_->deferred_objects())[i];
+      ASSERT(deferred_object->IsCode());
+      CodePtr code = static_cast<CodePtr>(deferred_object->raw());
+      ASSERT(s->RefId(code) == (start_index + i));
+      s->WriteInstructions(code->ptr()->instructions_,
+                           code->ptr()->unchecked_offset_, code, false);
+      s->WriteRootRef(code->ptr()->compressed_stackmaps_, "deferred-code");
+      s->WriteRootRef(code->ptr()->code_source_map_, "deferred-code");
+    }
+#endif
+  }
+
+ private:
+  LoadingUnitSerializationData* unit_;
+};
+#endif  // !DART_PRECOMPILED_RUNTIME
+
+class UnitDeserializationRoots : public DeserializationRoots {
+ public:
+  explicit UnitDeserializationRoots(const LoadingUnit& unit) : unit_(unit) {}
+
+  void AddBaseObjects(Deserializer* d) {
+    const Array& base_objects =
+        Array::Handle(LoadingUnit::Handle(unit_.parent()).base_objects());
+    for (intptr_t i = kFirstReference; i < base_objects.Length(); i++) {
+      d->AddBaseObject(base_objects.At(i));
+    }
+  }
+
+  void ReadRoots(Deserializer* d) {
+    deferred_start_index_ = d->ReadUnsigned();
+    deferred_stop_index_ = deferred_start_index_ + d->ReadUnsigned();
+    for (intptr_t id = deferred_start_index_; id < deferred_stop_index_; id++) {
+      CodePtr code = static_cast<CodePtr>(d->Ref(id));
+      d->ReadInstructions(code, false);
+      if (code->ptr()->owner_->IsFunction()) {
+        FunctionPtr func = static_cast<FunctionPtr>(code->ptr()->owner_);
+        uword entry_point = code->ptr()->entry_point_;
+        ASSERT(entry_point != 0);
+        func->ptr()->entry_point_ = entry_point;
+        uword unchecked_entry_point = code->ptr()->unchecked_entry_point_;
+        ASSERT(unchecked_entry_point != 0);
+        func->ptr()->unchecked_entry_point_ = unchecked_entry_point;
+      }
+      code->ptr()->compressed_stackmaps_ =
+          static_cast<CompressedStackMapsPtr>(d->ReadRef());
+      code->ptr()->code_source_map_ =
+          static_cast<CodeSourceMapPtr>(d->ReadRef());
+    }
+
+    // Reinitialize the dispatch table by rereading the table's serialization
+    // in the root snapshot.
+    IsolateGroup* group = d->thread()->isolate()->group();
+    if (group->dispatch_table_snapshot() != nullptr) {
+      ReadStream stream(group->dispatch_table_snapshot(),
+                        group->dispatch_table_snapshot_size());
+      d->ReadDispatchTable(&stream);
+    }
+  }
+
+  void PostLoad(Deserializer* d, const Array& refs) {
+    d->EndInstructions(refs, deferred_start_index_, deferred_stop_index_);
+    unit_.set_base_objects(refs);
+  }
+
+ private:
+  const LoadingUnit& unit_;
+  intptr_t deferred_start_index_;
+  intptr_t deferred_stop_index_;
+};
+
 #if defined(DEBUG)
 static const int32_t kSectionMarker = 0xABAB;
 #endif
@@ -4968,7 +5394,7 @@
       num_tlc_cids_(0),
       num_base_objects_(0),
       num_written_objects_(0),
-      next_ref_index_(1),
+      next_ref_index_(kFirstReference),
       previous_text_offset_(0),
       field_table_(thread->isolate()->field_table()),
       vm_(vm),
@@ -5071,13 +5497,13 @@
   ASSERT(profile_writer() != nullptr);
 
   intptr_t id = heap_->GetObjectId(obj);
-  if (Serializer::IsAllocatedReference(id)) {
+  if (IsAllocatedReference(id)) {
     return false;
   }
-  if (Serializer::IsArtificialReference(id)) {
+  if (IsArtificialReference(id)) {
     return true;
   }
-  ASSERT(id == Serializer::kUnreachableReference);
+  ASSERT_EQUAL(id, kUnreachableReference);
   id = AssignArtificialRef(obj);
 
   const char* type = nullptr;
@@ -5383,6 +5809,7 @@
   }
   return image_writer_->data_size();
 }
+#endif
 
 void Serializer::Push(ObjectPtr object) {
   if (!object->IsHeapObject()) {
@@ -5505,6 +5932,7 @@
   free(const_cast<char*>(expected_features));
 }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 static int CompareClusters(SerializationCluster* const* a,
                            SerializationCluster* const* b) {
   if ((*a)->size() > (*b)->size()) {
@@ -5516,7 +5944,13 @@
   }
 }
 
-void Serializer::Serialize() {
+intptr_t Serializer::Serialize(SerializationRoots* roots) {
+  roots->AddBaseObjects(this);
+
+  NoSafepointScope no_safepoint;
+
+  roots->PushRoots(this);
+
   while (stack_.length() > 0) {
     Trace(stack_.RemoveLast());
   }
@@ -5593,6 +6027,24 @@
 #endif
     }
   }
+
+  roots->WriteRoots(this);
+
+#if defined(DEBUG)
+  Write<int32_t>(kSectionMarker);
+#endif
+
+  FlushBytesWrittenToRoot();
+  object_currently_writing_.stream_start_ = stream_.Position();
+
+  PrintSnapshotSizes();
+
+  // Note we are not clearing the object id table. The full ref table
+  // of the vm isolate snapshot serves as the base objects for the
+  // regular isolate snapshot.
+
+  // Return the number of objects, -1 accounts for unused ref 0.
+  return next_ref_index_ - kFirstReference;
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
@@ -5713,6 +6165,33 @@
     Write(repeat_count);
   }
   dispatch_table_size_ = bytes_written() - bytes_before;
+
+  object_currently_writing_.stream_start_ = stream_.Position();
+  // If any bytes were written for the dispatch table, add it to the profile.
+  if (dispatch_table_size_ > 0 && profile_writer_ != nullptr) {
+    // Grab an unused ref index for a unique object id for the dispatch table.
+    const auto dispatch_table_id = next_ref_index_++;
+    const V8SnapshotProfileWriter::ObjectId dispatch_table_snapshot_id(
+        V8SnapshotProfileWriter::kSnapshot, dispatch_table_id);
+    profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table");
+    profile_writer_->SetObjectTypeAndName(dispatch_table_snapshot_id,
+                                          "DispatchTable", nullptr);
+    profile_writer_->AttributeBytesTo(dispatch_table_snapshot_id,
+                                      dispatch_table_size_);
+
+    if (!entries.IsNull()) {
+      for (intptr_t i = 0; i < entries.Length(); i++) {
+        auto const code = Code::RawCast(entries.At(i));
+        if (code == Code::null()) continue;
+        const V8SnapshotProfileWriter::ObjectId code_id(
+            V8SnapshotProfileWriter::kSnapshot, RefId(code));
+        profile_writer_->AttributeReferenceTo(
+            dispatch_table_snapshot_id,
+            {code_id, V8SnapshotProfileWriter::Reference::kElement, i});
+      }
+    }
+  }
+
 #endif  // defined(DART_PRECOMPILER)
 }
 
@@ -5772,279 +6251,6 @@
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 }
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
-void Serializer::AddVMIsolateBaseObjects() {
-  // These objects are always allocated by Object::InitOnce, so they are not
-  // written into the snapshot.
-
-  AddBaseObject(Object::null(), "Null", "null");
-  AddBaseObject(Object::sentinel().raw(), "Null", "sentinel");
-  AddBaseObject(Object::transition_sentinel().raw(), "Null",
-                "transition_sentinel");
-  AddBaseObject(Object::empty_array().raw(), "Array", "<empty_array>");
-  AddBaseObject(Object::zero_array().raw(), "Array", "<zero_array>");
-  AddBaseObject(Object::dynamic_type().raw(), "Type", "<dynamic type>");
-  AddBaseObject(Object::void_type().raw(), "Type", "<void type>");
-  AddBaseObject(Object::empty_type_arguments().raw(), "TypeArguments", "[]");
-  AddBaseObject(Bool::True().raw(), "bool", "true");
-  AddBaseObject(Bool::False().raw(), "bool", "false");
-  ASSERT(Object::extractor_parameter_types().raw() != Object::null());
-  AddBaseObject(Object::extractor_parameter_types().raw(), "Array",
-                "<extractor parameter types>");
-  ASSERT(Object::extractor_parameter_names().raw() != Object::null());
-  AddBaseObject(Object::extractor_parameter_names().raw(), "Array",
-                "<extractor parameter names>");
-  AddBaseObject(Object::empty_context_scope().raw(), "ContextScope", "<empty>");
-  AddBaseObject(Object::empty_descriptors().raw(), "PcDescriptors", "<empty>");
-  AddBaseObject(Object::empty_var_descriptors().raw(), "LocalVarDescriptors",
-                "<empty>");
-  AddBaseObject(Object::empty_exception_handlers().raw(), "ExceptionHandlers",
-                "<empty>");
-  AddBaseObject(Object::implicit_getter_bytecode().raw(), "Bytecode",
-                "<implicit getter>");
-  AddBaseObject(Object::implicit_setter_bytecode().raw(), "Bytecode",
-                "<implicit setter>");
-  AddBaseObject(Object::implicit_static_getter_bytecode().raw(), "Bytecode",
-                "<implicit static getter>");
-  AddBaseObject(Object::method_extractor_bytecode().raw(), "Bytecode",
-                "<method extractor>");
-  AddBaseObject(Object::invoke_closure_bytecode().raw(), "Bytecode",
-                "<invoke closure>");
-  AddBaseObject(Object::invoke_field_bytecode().raw(), "Bytecode",
-                "<invoke field>");
-  AddBaseObject(Object::nsm_dispatcher_bytecode().raw(), "Bytecode",
-                "<nsm dispatcher>");
-  AddBaseObject(Object::dynamic_invocation_forwarder_bytecode().raw(),
-                "Bytecode", "<dyn forwarder>");
-
-  for (intptr_t i = 0; i < ArgumentsDescriptor::kCachedDescriptorCount; i++) {
-    AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i],
-                  "ArgumentsDescriptor", "<cached arguments descriptor>");
-  }
-  for (intptr_t i = 0; i < ICData::kCachedICDataArrayCount; i++) {
-    AddBaseObject(ICData::cached_icdata_arrays_[i], "Array",
-                  "<empty icdata entries>");
-  }
-  AddBaseObject(SubtypeTestCache::cached_array_, "Array",
-                "<empty subtype entries>");
-
-  ClassTable* table = isolate()->class_table();
-  for (intptr_t cid = kClassCid; cid < kInstanceCid; cid++) {
-    // Error, CallSiteData has no class object.
-    if (cid != kErrorCid && cid != kCallSiteDataCid) {
-      ASSERT(table->HasValidClassAt(cid));
-      AddBaseObject(table->At(cid), "Class");
-    }
-  }
-  AddBaseObject(table->At(kDynamicCid), "Class");
-  AddBaseObject(table->At(kVoidCid), "Class");
-
-  if (!Snapshot::IncludesCode(kind_)) {
-    for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-      AddBaseObject(StubCode::EntryAt(i).raw(), "Code", "<stub code>");
-    }
-  }
-}
-
-intptr_t Serializer::WriteVMSnapshot(const Array& symbols) {
-  NoSafepointScope no_safepoint;
-
-  AddVMIsolateBaseObjects();
-
-  // Push roots.
-  Push(symbols.raw());
-  if (Snapshot::IncludesCode(kind_)) {
-    for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-      Push(StubCode::EntryAt(i).raw());
-    }
-  }
-
-  Serialize();
-
-  // Write roots.
-  WriteRootRef(symbols.raw(), "symbol-table");
-  if (Snapshot::IncludesCode(kind_)) {
-    for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-      WriteRootRef(StubCode::EntryAt(i).raw(),
-                   zone_->PrintToString("Stub:%s", StubCode::NameAt(i)));
-    }
-  }
-
-#if defined(DEBUG)
-  Write<int32_t>(kSectionMarker);
-#endif
-
-  FlushBytesWrittenToRoot();
-
-  PrintSnapshotSizes();
-
-  // Note we are not clearing the object id table. The full ref table
-  // of the vm isolate snapshot serves as the base objects for the
-  // regular isolate snapshot.
-
-  // Return the number of objects, -1 accounts for unused ref 0.
-  return next_ref_index_ - 1;
-}
-
-static const char* kObjectStoreFieldNames[] = {
-#define DECLARE_OBJECT_STORE_FIELD(Type, Name) #Name,
-    OBJECT_STORE_FIELD_LIST(DECLARE_OBJECT_STORE_FIELD,
-                            DECLARE_OBJECT_STORE_FIELD,
-                            DECLARE_OBJECT_STORE_FIELD,
-                            DECLARE_OBJECT_STORE_FIELD)
-#undef DECLARE_OBJECT_STORE_FIELD
-};
-
-void Serializer::WriteProgramSnapshot(intptr_t num_base_objects,
-                                      ObjectStore* object_store) {
-  NoSafepointScope no_safepoint;
-
-  if (num_base_objects == 0) {
-    // Not writing a new vm isolate: use the one this VM was loaded from.
-    const Array& base_objects = Object::vm_isolate_snapshot_object_table();
-    for (intptr_t i = 1; i < base_objects.Length(); i++) {
-      AddBaseObject(base_objects.At(i));
-    }
-  } else {
-    // Base objects carried over from WriteVMSnapshot.
-    num_base_objects_ = num_base_objects;
-    next_ref_index_ = num_base_objects + 1;
-  }
-
-  // Push roots.
-  ObjectPtr* from = object_store->from();
-  ObjectPtr* to = object_store->to_snapshot(kind_);
-  for (ObjectPtr* p = from; p <= to; p++) {
-    Push(*p);
-  }
-
-  const auto& dispatch_table_entries =
-      Array::Handle(zone_, object_store->dispatch_table_code_entries());
-  // We should only have a dispatch table in precompiled mode.
-  ASSERT(dispatch_table_entries.IsNull() || kind() == Snapshot::kFullAOT);
-
-#if defined(DART_PRECOMPILER)
-  // We treat the dispatch table as a root object and trace the Code objects it
-  // references. Otherwise, a non-empty entry could be invalid on
-  // deserialization if the corresponding Code object was not reachable from the
-  // existing snapshot roots.
-  if (!dispatch_table_entries.IsNull()) {
-    for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) {
-      Push(dispatch_table_entries.At(i));
-    }
-  }
-#endif
-
-  Serialize();
-
-  // Write roots.
-  for (ObjectPtr* p = from; p <= to; p++) {
-    WriteRootRef(*p, kObjectStoreFieldNames[p - from]);
-  }
-
-  FlushBytesWrittenToRoot();
-  // The dispatch table is serialized only for precompiled snapshots.
-  WriteDispatchTable(dispatch_table_entries);
-  object_currently_writing_.stream_start_ = stream_.Position();
-#if defined(DART_PRECOMPILER)
-  // If any bytes were written for the dispatch table, add it to the profile.
-  if (dispatch_table_size_ > 0 && profile_writer_ != nullptr) {
-    // Grab an unused ref index for a unique object id for the dispatch table.
-    const auto dispatch_table_id = next_ref_index_++;
-    const V8SnapshotProfileWriter::ObjectId dispatch_table_snapshot_id(
-        V8SnapshotProfileWriter::kSnapshot, dispatch_table_id);
-    profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table");
-    profile_writer_->SetObjectTypeAndName(dispatch_table_snapshot_id,
-                                          "DispatchTable", nullptr);
-    profile_writer_->AttributeBytesTo(dispatch_table_snapshot_id,
-                                      dispatch_table_size_);
-
-    if (!dispatch_table_entries.IsNull()) {
-      for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) {
-        auto const code = Code::RawCast(dispatch_table_entries.At(i));
-        if (code == Code::null()) continue;
-        const V8SnapshotProfileWriter::ObjectId code_id(
-            V8SnapshotProfileWriter::kSnapshot, RefId(code));
-        profile_writer_->AttributeReferenceTo(
-            dispatch_table_snapshot_id,
-            {code_id, V8SnapshotProfileWriter::Reference::kElement, i});
-      }
-    }
-  }
-#endif
-
-#if defined(DEBUG)
-  Write<int32_t>(kSectionMarker);
-#endif
-
-  PrintSnapshotSizes();
-
-  // TODO(rmacnak): This also carries over object ids from loading units that
-  // aren't dominators. It would be more robust to remember the written objects
-  // in each loading and re-assign objects when setting up the base objects.
-  // Then a reference to a non-dominating object would produce an error instead
-  // of corruption.
-  if (kind() != Snapshot::kFullAOT) {
-    heap_->ResetObjectIdTable();
-  }
-}
-
-void Serializer::WriteUnitSnapshot(LoadingUnitSerializationData* unit,
-                                   uint32_t program_hash) {
-  Write(program_hash);
-
-  NoSafepointScope no_safepoint;
-
-  intptr_t num_base_objects = unit->parent()->num_objects();
-  ASSERT(num_base_objects != 0);
-  num_base_objects_ = num_base_objects;
-  next_ref_index_ = num_base_objects + 1;
-
-  intptr_t num_deferred_objects = unit->deferred_objects()->length();
-  for (intptr_t i = 0; i < num_deferred_objects; i++) {
-    const Object* deferred_object = (*unit->deferred_objects())[i];
-    ASSERT(deferred_object->IsCode());
-    CodePtr code = static_cast<CodePtr>(deferred_object->raw());
-    Push(code->ptr()->compressed_stackmaps_);
-    Push(code->ptr()->code_source_map_);
-  }
-  {
-    GrowableArray<CodePtr> raw_codes(num_deferred_objects);
-    for (intptr_t i = 0; i < num_deferred_objects; i++) {
-      raw_codes.Add((*unit->deferred_objects())[i]->raw());
-    }
-    PrepareInstructions(&raw_codes);
-  }
-
-  Serialize();
-
-  intptr_t start_index = 0;
-  if (num_deferred_objects != 0) {
-    start_index = RefId(unit->deferred_objects()->At(0)->raw());
-    ASSERT(start_index > 0);
-  }
-  WriteUnsigned(start_index);
-  WriteUnsigned(num_deferred_objects);
-  for (intptr_t i = 0; i < num_deferred_objects; i++) {
-    const Object* deferred_object = (*unit->deferred_objects())[i];
-    ASSERT(deferred_object->IsCode());
-    CodePtr code = static_cast<CodePtr>(deferred_object->raw());
-    ASSERT(RefId(code) == (start_index + i));
-    WriteInstructions(code->ptr()->instructions_,
-                      code->ptr()->unchecked_offset_, code, false);
-    WriteRootRef(code->ptr()->compressed_stackmaps_, "deferred-code");
-    WriteRootRef(code->ptr()->code_source_map_, "deferred-code");
-  }
-
-  FlushBytesWrittenToRoot();
-  object_currently_writing_.stream_start_ = stream_.Position();
-
-#if defined(DEBUG)
-  Write<int32_t>(kSectionMarker);
-#endif
-}
-#endif  // !defined(DART_PRECOMPILED_RUNTIME)
-
 Deserializer::Deserializer(Thread* thread,
                            Snapshot::Kind kind,
                            const uint8_t* buffer,
@@ -6059,7 +6265,7 @@
       stream_(buffer, size),
       image_reader_(NULL),
       refs_(nullptr),
-      next_ref_index_(1),
+      next_ref_index_(kFirstReference),
       previous_text_offset_(0),
       clusters_(NULL),
       field_table_(thread->isolate()->field_table()) {
@@ -6491,48 +6697,6 @@
   return image_reader_->GetObjectAt(offset);
 }
 
-void Deserializer::Prepare() {
-  num_base_objects_ = ReadUnsigned();
-  num_objects_ = ReadUnsigned();
-  num_clusters_ = ReadUnsigned();
-  const intptr_t field_table_len = ReadUnsigned();
-
-  clusters_ = new DeserializationCluster*[num_clusters_];
-  refs_ = Array::New(num_objects_ + 1, Heap::kOld);
-  if (field_table_len > 0) {
-    field_table_->AllocateIndex(field_table_len - 1);
-  }
-  ASSERT(field_table_->NumFieldIds() == field_table_len);
-}
-
-void Deserializer::Deserialize() {
-  if (num_base_objects_ != (next_ref_index_ - 1)) {
-    FATAL2("Snapshot expects %" Pd
-           " base objects, but deserializer provided %" Pd,
-           num_base_objects_, next_ref_index_ - 1);
-  }
-
-  for (intptr_t i = 0; i < num_clusters_; i++) {
-    clusters_[i] = ReadCluster();
-    clusters_[i]->ReadAlloc(this);
-#if defined(DEBUG)
-    intptr_t serializers_next_ref_index_ = Read<int32_t>();
-    ASSERT(serializers_next_ref_index_ == next_ref_index_);
-#endif
-  }
-
-  // We should have completely filled the ref array.
-  ASSERT((next_ref_index_ - 1) == num_objects_);
-
-  for (intptr_t i = 0; i < num_clusters_; i++) {
-    clusters_[i]->ReadFill(this);
-#if defined(DEBUG)
-    int32_t section_marker = Read<int32_t>();
-    ASSERT(section_marker == kSectionMarker);
-#endif
-  }
-}
-
 class HeapLocker : public StackResource {
  public:
   HeapLocker(Thread* thread, PageSpace* page_space)
@@ -6548,87 +6712,54 @@
   FreeList* freelist_;
 };
 
-void Deserializer::AddVMIsolateBaseObjects() {
-  // These objects are always allocated by Object::InitOnce, so they are not
-  // written into the snapshot.
-
-  AddBaseObject(Object::null());
-  AddBaseObject(Object::sentinel().raw());
-  AddBaseObject(Object::transition_sentinel().raw());
-  AddBaseObject(Object::empty_array().raw());
-  AddBaseObject(Object::zero_array().raw());
-  AddBaseObject(Object::dynamic_type().raw());
-  AddBaseObject(Object::void_type().raw());
-  AddBaseObject(Object::empty_type_arguments().raw());
-  AddBaseObject(Bool::True().raw());
-  AddBaseObject(Bool::False().raw());
-  ASSERT(Object::extractor_parameter_types().raw() != Object::null());
-  AddBaseObject(Object::extractor_parameter_types().raw());
-  ASSERT(Object::extractor_parameter_names().raw() != Object::null());
-  AddBaseObject(Object::extractor_parameter_names().raw());
-  AddBaseObject(Object::empty_context_scope().raw());
-  AddBaseObject(Object::empty_descriptors().raw());
-  AddBaseObject(Object::empty_var_descriptors().raw());
-  AddBaseObject(Object::empty_exception_handlers().raw());
-  AddBaseObject(Object::implicit_getter_bytecode().raw());
-  AddBaseObject(Object::implicit_setter_bytecode().raw());
-  AddBaseObject(Object::implicit_static_getter_bytecode().raw());
-  AddBaseObject(Object::method_extractor_bytecode().raw());
-  AddBaseObject(Object::invoke_closure_bytecode().raw());
-  AddBaseObject(Object::invoke_field_bytecode().raw());
-  AddBaseObject(Object::nsm_dispatcher_bytecode().raw());
-  AddBaseObject(Object::dynamic_invocation_forwarder_bytecode().raw());
-
-  for (intptr_t i = 0; i < ArgumentsDescriptor::kCachedDescriptorCount; i++) {
-    AddBaseObject(ArgumentsDescriptor::cached_args_descriptors_[i]);
-  }
-  for (intptr_t i = 0; i < ICData::kCachedICDataArrayCount; i++) {
-    AddBaseObject(ICData::cached_icdata_arrays_[i]);
-  }
-  AddBaseObject(SubtypeTestCache::cached_array_);
-
-  ClassTable* table = isolate()->class_table();
-  for (intptr_t cid = kClassCid; cid <= kUnwindErrorCid; cid++) {
-    // Error, CallSiteData has no class object.
-    if (cid != kErrorCid && cid != kCallSiteDataCid) {
-      ASSERT(table->HasValidClassAt(cid));
-      AddBaseObject(table->At(cid));
-    }
-  }
-  AddBaseObject(table->At(kDynamicCid));
-  AddBaseObject(table->At(kVoidCid));
-
-  if (!Snapshot::IncludesCode(kind_)) {
-    for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-      AddBaseObject(StubCode::EntryAt(i).raw());
-    }
-  }
-}
-
-void Deserializer::ReadVMSnapshot() {
-  Array& symbol_table = Array::Handle(zone_);
+void Deserializer::Deserialize(DeserializationRoots* roots) {
   Array& refs = Array::Handle(zone_);
-  Prepare();
+  num_base_objects_ = ReadUnsigned();
+  num_objects_ = ReadUnsigned();
+  num_clusters_ = ReadUnsigned();
+  const intptr_t field_table_len = ReadUnsigned();
+
+  clusters_ = new DeserializationCluster*[num_clusters_];
+  refs_ = Array::New(num_objects_ + kFirstReference, Heap::kOld);
+  if (field_table_len > 0) {
+    field_table_->AllocateIndex(field_table_len - 1);
+  }
+  ASSERT_EQUAL(field_table_->NumFieldIds(), field_table_len);
 
   {
     NoSafepointScope no_safepoint;
     HeapLocker hl(thread(), heap_->old_space());
 
-    AddVMIsolateBaseObjects();
+    roots->AddBaseObjects(this);
 
-    Deserialize();
-
-    // Read roots.
-    symbol_table ^= ReadRef();
-    isolate()->object_store()->set_symbol_table(symbol_table);
-    if (Snapshot::IncludesCode(kind_)) {
-      for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-        Code* code = Code::ReadOnlyHandle();
-        *code ^= ReadRef();
-        StubCode::EntryAtPut(i, code);
-      }
+    if (num_base_objects_ != (next_ref_index_ - kFirstReference)) {
+      FATAL2("Snapshot expects %" Pd
+             " base objects, but deserializer provided %" Pd,
+             num_base_objects_, next_ref_index_ - kFirstReference);
     }
 
+    for (intptr_t i = 0; i < num_clusters_; i++) {
+      clusters_[i] = ReadCluster();
+      clusters_[i]->ReadAlloc(this);
+#if defined(DEBUG)
+      intptr_t serializers_next_ref_index_ = Read<int32_t>();
+      ASSERT_EQUAL(serializers_next_ref_index_, next_ref_index_);
+#endif
+    }
+
+    // We should have completely filled the ref array.
+    ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects_);
+
+    for (intptr_t i = 0; i < num_clusters_; i++) {
+      clusters_[i]->ReadFill(this);
+#if defined(DEBUG)
+    int32_t section_marker = Read<int32_t>();
+    ASSERT(section_marker == kSectionMarker);
+#endif
+    }
+
+    roots->ReadRoots(this);
+
 #if defined(DEBUG)
     int32_t section_marker = Read<int32_t>();
     ASSERT(section_marker == kSectionMarker);
@@ -6638,162 +6769,19 @@
     refs_ = NULL;
   }
 
-  // Move remaining bump allocation space to the freelist so it used by C++
-  // allocations (e.g., FinalizeVMIsolate) before allocating new pages.
-  heap_->old_space()->AbandonBumpAllocation();
-
-  Symbols::InitFromSnapshot(isolate());
-
-  Object::set_vm_isolate_snapshot_object_table(refs);
-
-#if defined(DEBUG)
-  isolate()->ValidateClassTable();
-#endif
-
-  for (intptr_t i = 0; i < num_clusters_; i++) {
-    clusters_[i]->PostLoad(this, refs);
-  }
-}
-
-void Deserializer::ReadProgramSnapshot(ObjectStore* object_store) {
-  Array& refs = Array::Handle(zone_);
-  Prepare();
-
-  {
-    NoSafepointScope no_safepoint;
-    HeapLocker hl(thread(), heap_->old_space());
-
-    // N.B.: Skipping index 0 because ref 0 is illegal.
-    const Array& base_objects = Object::vm_isolate_snapshot_object_table();
-    for (intptr_t i = 1; i < base_objects.Length(); i++) {
-      AddBaseObject(base_objects.At(i));
-    }
-
-    Deserialize();
-
-    // Read roots.
-    ObjectPtr* from = object_store->from();
-    ObjectPtr* to = object_store->to_snapshot(kind_);
-    for (ObjectPtr* p = from; p <= to; p++) {
-      *p = ReadRef();
-    }
-
-    // Deserialize dispatch table (when applicable)
-    ReadDispatchTable(&stream_);
-
-#if defined(DEBUG)
-    int32_t section_marker = Read<int32_t>();
-    ASSERT(section_marker == kSectionMarker);
-#endif
-
-    refs = refs_;
-    refs_ = NULL;
-  }
-
-  thread()->isolate()->class_table()->CopySizesFromClassObjects();
-  heap_->old_space()->EvaluateAfterLoading();
-
-  Isolate* isolate = thread()->isolate();
-#if defined(DEBUG)
-  isolate->ValidateClassTable();
-  isolate->heap()->Verify();
-#endif
-
-  for (intptr_t i = 0; i < num_clusters_; i++) {
-    clusters_[i]->PostLoad(this, refs);
-  }
-  const Array& units =
-      Array::Handle(zone_, isolate->object_store()->loading_units());
-  if (!units.IsNull()) {
-    LoadingUnit& unit = LoadingUnit::Handle(zone_);
-    unit ^= units.At(LoadingUnit::kRootId);
-    unit.set_base_objects(refs);
-  }
-  isolate->isolate_object_store()->PreallocateObjects();
-
-  // Setup native resolver for bootstrap impl.
-  Bootstrap::SetupNativeResolver();
-}
-
-ApiErrorPtr Deserializer::ReadUnitSnapshot(const LoadingUnit& unit) {
-  Array& units = Array::Handle(
-      zone_, thread()->isolate()->object_store()->loading_units());
-  uint32_t main_program_hash = Smi::Value(Smi::RawCast(units.At(0)));
-  uint32_t unit_program_hash = Read<uint32_t>();
-  if (main_program_hash != unit_program_hash) {
-    return ApiError::New(
-        String::Handle(String::New("Deferred loading unit is from a different "
-                                   "program than the main loading unit")));
-  }
-
-  Array& refs = Array::Handle(zone_);
-  Prepare();
-
-  intptr_t deferred_start_index;
-  intptr_t deferred_stop_index;
-  {
-    NoSafepointScope no_safepoint;
-    HeapLocker hl(thread(), heap_->old_space());
-
-    // N.B.: Skipping index 0 because ref 0 is illegal.
-    const Array& base_objects = Array::Handle(
-        zone_, LoadingUnit::Handle(zone_, unit.parent()).base_objects());
-    for (intptr_t i = 1; i < base_objects.Length(); i++) {
-      AddBaseObject(base_objects.At(i));
-    }
-
-    Deserialize();
-
-    deferred_start_index = ReadUnsigned();
-    deferred_stop_index = deferred_start_index + ReadUnsigned();
-    for (intptr_t id = deferred_start_index; id < deferred_stop_index; id++) {
-      CodePtr code = static_cast<CodePtr>(Ref(id));
-      ReadInstructions(code, false);
-      if (code->ptr()->owner_->IsFunction()) {
-        FunctionPtr func = static_cast<FunctionPtr>(code->ptr()->owner_);
-        uword entry_point = code->ptr()->entry_point_;
-        ASSERT(entry_point != 0);
-        func->ptr()->entry_point_ = entry_point;
-        uword unchecked_entry_point = code->ptr()->unchecked_entry_point_;
-        ASSERT(unchecked_entry_point != 0);
-        func->ptr()->unchecked_entry_point_ = unchecked_entry_point;
-      }
-      code->ptr()->compressed_stackmaps_ =
-          static_cast<CompressedStackMapsPtr>(ReadRef());
-      code->ptr()->code_source_map_ = static_cast<CodeSourceMapPtr>(ReadRef());
-    }
-
-    // Reinitialize the dispatch table by rereading the table's serialization
-    // in the root snapshot.
-    IsolateGroup* group = thread()->isolate()->group();
-    if (group->dispatch_table_snapshot() != nullptr) {
-      ReadStream stream(group->dispatch_table_snapshot(),
-                        group->dispatch_table_snapshot_size());
-      ReadDispatchTable(&stream);
-    }
-
-#if defined(DEBUG)
-    int32_t section_marker = Read<int32_t>();
-    ASSERT(section_marker == kSectionMarker);
-#endif
-
-    refs = refs_;
-    refs_ = NULL;
-  }
+  roots->PostLoad(this, refs);
 
 #if defined(DEBUG)
   Isolate* isolate = thread()->isolate();
   isolate->ValidateClassTable();
-  isolate->heap()->Verify();
+  if (isolate != Dart::vm_isolate()) {
+    isolate->heap()->Verify();
+  }
 #endif
 
-  EndInstructions(refs, deferred_start_index, deferred_stop_index);
   for (intptr_t i = 0; i < num_clusters_; i++) {
     clusters_[i]->PostLoad(this, refs);
   }
-  unit.set_base_objects(refs);
-
-  return ApiError::null();
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
@@ -6846,13 +6834,9 @@
 
   serializer.ReserveHeader();
   serializer.WriteVersionAndFeatures(true);
-  // VM snapshot roots are:
-  // - the symbol table
-  // - the stub code (App-AOT, App-JIT or Core-JIT)
-
-  const Array& symbols =
-      Array::Handle(Dart::vm_isolate()->object_store()->symbol_table());
-  intptr_t num_objects = serializer.WriteVMSnapshot(symbols);
+  VMSerializationRoots roots(
+      Array::Handle(Dart::vm_isolate()->object_store()->symbol_table()));
+  intptr_t num_objects = serializer.Serialize(&roots);
   serializer.FillHeader(serializer.kind());
   clustered_vm_size_ = serializer.bytes_written();
 
@@ -6892,9 +6876,8 @@
 
   serializer.ReserveHeader();
   serializer.WriteVersionAndFeatures(false);
-  // Isolate snapshot roots are:
-  // - the object store
-  serializer.WriteProgramSnapshot(num_base_objects, object_store);
+  ProgramSerializationRoots roots(num_base_objects, object_store);
+  serializer.Serialize(&roots);
   serializer.FillHeader(serializer.kind());
   clustered_isolate_size_ = serializer.bytes_written();
 
@@ -6929,7 +6912,10 @@
 
   serializer.ReserveHeader();
   serializer.WriteVersionAndFeatures(false);
-  serializer.WriteUnitSnapshot(unit, program_hash);
+  serializer.Write(program_hash);
+
+  UnitSerializationRoots roots(unit);
+  serializer.Serialize(&roots);
   serializer.FillHeader(serializer.kind());
   clustered_isolate_size_ = serializer.bytes_written();
 
@@ -7174,7 +7160,8 @@
                                        /* is_executable */ true);
   }
 
-  deserializer.ReadVMSnapshot();
+  VMDeserializationRoots roots;
+  deserializer.Deserialize(&roots);
 
 #if defined(DART_PRECOMPILED_RUNTIME)
   // Initialize entries in the VM portion of the BSS segment.
@@ -7217,8 +7204,8 @@
                                        /* is_executable */ true);
   }
 
-  auto object_store = thread_->isolate()->object_store();
-  deserializer.ReadProgramSnapshot(object_store);
+  ProgramDeserializationRoots roots(thread_->isolate()->object_store());
+  deserializer.Deserialize(&roots);
 
   PatchGlobalObjectPool();
   InitializeBSS();
@@ -7241,6 +7228,17 @@
   if (api_error != ApiError::null()) {
     return api_error;
   }
+  {
+    Array& units =
+        Array::Handle(thread_->isolate()->object_store()->loading_units());
+    uint32_t main_program_hash = Smi::Value(Smi::RawCast(units.At(0)));
+    uint32_t unit_program_hash = deserializer.Read<uint32_t>();
+    if (main_program_hash != unit_program_hash) {
+      return ApiError::New(String::Handle(
+          String::New("Deferred loading unit is from a different "
+                      "program than the main loading unit")));
+    }
+  }
 
   if (Snapshot::IncludesCode(kind_)) {
     ASSERT(data_image_ != NULL);
@@ -7251,10 +7249,8 @@
                                        /* is_executable */ true);
   }
 
-  api_error = deserializer.ReadUnitSnapshot(unit);
-  if (api_error != ApiError::null()) {
-    return api_error;
-  }
+  UnitDeserializationRoots roots(unit);
+  deserializer.Deserialize(&roots);
 
   PatchGlobalObjectPool();
   InitializeBSS();
diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h
index 26e3f38..2dd7253 100644
--- a/runtime/vm/clustered_snapshot.h
+++ b/runtime/vm/clustered_snapshot.h
@@ -124,6 +124,22 @@
   intptr_t stop_index_;
 };
 
+class SerializationRoots {
+ public:
+  virtual ~SerializationRoots() {}
+  virtual void AddBaseObjects(Serializer* serializer) = 0;
+  virtual void PushRoots(Serializer* serializer) = 0;
+  virtual void WriteRoots(Serializer* serializer) = 0;
+};
+
+class DeserializationRoots {
+ public:
+  virtual ~DeserializationRoots() {}
+  virtual void AddBaseObjects(Deserializer* deserializer) = 0;
+  virtual void ReadRoots(Deserializer* deserializer) = 0;
+  virtual void PostLoad(Deserializer* deserializer, const Array& refs) = 0;
+};
+
 class SmiObjectIdPair {
  public:
   SmiObjectIdPair() : smi_(nullptr), id_(0) {}
@@ -149,6 +165,29 @@
 
 typedef DirectChainedHashMap<SmiObjectIdPairTrait> SmiObjectIdMap;
 
+// Reference value for objects that either are not reachable from the roots or
+// should never have a reference in the snapshot (because they are dropped,
+// for example). Should be the default value for Heap::GetObjectId.
+static constexpr intptr_t kUnreachableReference = 0;
+COMPILE_ASSERT(kUnreachableReference == WeakTable::kNoValue);
+static constexpr intptr_t kFirstReference = 1;
+
+// Reference value for traced objects that have not been allocated their final
+// reference ID.
+static const intptr_t kUnallocatedReference = -1;
+
+static constexpr bool IsAllocatedReference(intptr_t ref) {
+  return ref > kUnreachableReference;
+}
+
+static constexpr bool IsArtificialReference(intptr_t ref) {
+  return ref < kUnallocatedReference;
+}
+
+static constexpr bool IsReachableReference(intptr_t ref) {
+  return ref == kUnallocatedReference || IsAllocatedReference(ref);
+}
+
 class Serializer : public ThreadStackResource {
  public:
   Serializer(Thread* thread,
@@ -161,36 +200,6 @@
              V8SnapshotProfileWriter* profile_writer = nullptr);
   ~Serializer();
 
-  // Reference value for objects that either are not reachable from the roots or
-  // should never have a reference in the snapshot (because they are dropped,
-  // for example). Should be the default value for Heap::GetObjectId.
-  static constexpr intptr_t kUnreachableReference = 0;
-  COMPILE_ASSERT(kUnreachableReference == WeakTable::kNoValue);
-
-  static constexpr bool IsReachableReference(intptr_t ref) {
-    return ref == kUnallocatedReference || IsAllocatedReference(ref);
-  }
-
-  // Reference value for traced objects that have not been allocated their final
-  // reference ID.
-  static const intptr_t kUnallocatedReference = -1;
-
-  static constexpr bool IsAllocatedReference(intptr_t ref) {
-    return ref > kUnreachableReference;
-  }
-
-  static constexpr bool IsArtificialReference(intptr_t ref) {
-    return ref < kUnallocatedReference;
-  }
-
-  intptr_t WriteVMSnapshot(const Array& symbols);
-  void WriteProgramSnapshot(intptr_t num_base_objects,
-                            ObjectStore* object_store);
-  void WriteUnitSnapshot(LoadingUnitSerializationData* unit,
-                         uint32_t program_hash);
-
-  void AddVMIsolateBaseObjects();
-
   void AddBaseObject(ObjectPtr base_object,
                      const char* type = nullptr,
                      const char* name = nullptr) {
@@ -209,6 +218,10 @@
       profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, ref});
     }
   }
+  void CarryOverBaseObjects(intptr_t num_base_objects) {
+    num_base_objects_ = num_base_objects;
+    next_ref_index_ = num_base_objects + 1;
+  }
 
   intptr_t AssignRef(ObjectPtr object) {
     ASSERT(IsAllocatedReference(next_ref_index_));
@@ -270,7 +283,7 @@
 
   void WriteVersionAndFeatures(bool is_vm_snapshot);
 
-  void Serialize();
+  intptr_t Serialize(SerializationRoots* roots);
   void PrintSnapshotSizes();
 
   FieldTable* field_table() { return field_table_; }
@@ -438,9 +451,6 @@
     current_loading_unit_id_ = id;
   }
 
- private:
-  static const char* ReadOnlyObjectType(intptr_t cid);
-
   // Returns the reference ID for the object. Fails for objects that have not
   // been allocated a reference ID yet, so should be used only after all
   // WriteAlloc calls.
@@ -482,6 +492,9 @@
     FATAL("Missing ref");
   }
 
+ private:
+  static const char* ReadOnlyObjectType(intptr_t cid);
+
   Heap* heap_;
   Zone* zone_;
   Snapshot::Kind kind_;
@@ -619,12 +632,6 @@
   // message otherwise.
   ApiErrorPtr VerifyImageAlignment();
 
-  void ReadProgramSnapshot(ObjectStore* object_store);
-  ApiErrorPtr ReadUnitSnapshot(const LoadingUnit& unit);
-  void ReadVMSnapshot();
-
-  void AddVMIsolateBaseObjects();
-
   static void InitializeHeader(ObjectPtr raw,
                                intptr_t cid,
                                intptr_t size,
@@ -697,13 +704,11 @@
                        intptr_t stop_index);
   ObjectPtr GetObjectAt(uint32_t offset) const;
 
-  void SkipHeader() { stream_.SetPosition(Snapshot::kHeaderSize); }
-
-  void Prepare();
-  void Deserialize();
+  void Deserialize(DeserializationRoots* roots);
 
   DeserializationCluster* ReadCluster();
 
+  void ReadDispatchTable() { ReadDispatchTable(&stream_); }
   void ReadDispatchTable(ReadStream* stream);
 
   intptr_t next_index() const { return next_ref_index_; }
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 60a78a7..360d974 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2403,10 +2403,10 @@
   friend class CallSiteResetter;
   friend class CallTargets;
   friend class Class;
-  friend class Deserializer;
+  friend class VMDeserializationRoots;
   friend class ICDataTestTask;
   friend class Interpreter;
-  friend class Serializer;
+  friend class VMSerializationRoots;
   friend class SnapshotWriter;
 };
 
@@ -7104,8 +7104,8 @@
 
   FINAL_HEAP_OBJECT_IMPLEMENTATION(SubtypeTestCache, Object);
   friend class Class;
-  friend class Serializer;
-  friend class Deserializer;
+  friend class VMSerializationRoots;
+  friend class VMDeserializationRoots;
 };
 
 class LoadingUnit : public Object {
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 07ee7d6..1314622 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -460,8 +460,8 @@
     return NULL;
   }
 
-  friend class Serializer;
-  friend class Deserializer;
+  friend class ProgramSerializationRoots;
+  friend class ProgramDeserializationRoots;
   friend class ProgramVisitor;
 
   DISALLOW_COPY_AND_ASSIGN(ObjectStore);
diff --git a/runtime/vm/os_fuchsia.cc b/runtime/vm/os_fuchsia.cc
index c30ee49..6ad304e 100644
--- a/runtime/vm/os_fuchsia.cc
+++ b/runtime/vm/os_fuchsia.cc
@@ -12,9 +12,9 @@
 #include <stdint.h>
 
 #include <fuchsia/deprecatedtimezone/cpp/fidl.h>
-#include <lib/async/default.h>
-#include <lib/async-loop/loop.h>
 #include <lib/async-loop/default.h>
+#include <lib/async-loop/loop.h>
+#include <lib/async/default.h>
 #include <lib/inspect/cpp/inspect.h>
 #include <lib/sys/cpp/component_context.h>
 #include <lib/sys/cpp/service_directory.h>
@@ -22,6 +22,7 @@
 #include <zircon/process.h>
 #include <zircon/syscalls.h>
 #include <zircon/syscalls/object.h>
+#include <zircon/time.h>
 #include <zircon/types.h>
 
 #include "platform/assert.h"
@@ -115,6 +116,15 @@
   return true;
 }
 
+int64_t GetCurrentTimeNanos() {
+  struct timespec ts;
+  if (timespec_get(&ts, TIME_UTC) == 0) {
+    FATAL("timespec_get failed");
+    return 0;
+  }
+  return zx_time_add_duration(ZX_SEC(ts.tv_sec), ZX_NSEC(ts.tv_nsec));
+}
+
 }  // namespace
 
 namespace dart {
@@ -177,21 +187,18 @@
 
 int OS::GetLocalTimeZoneAdjustmentInSeconds() {
   int32_t local_offset, dst_offset;
-  zx_time_t now = 0;
-  zx_clock_get(ZX_CLOCK_UTC, &now);
-  zx_status_t status = GetLocalAndDstOffsetInSeconds(
-      now / ZX_SEC(1), &local_offset, &dst_offset);
+  int64_t now_seconds = GetCurrentTimeNanos() / ZX_SEC(1);
+  zx_status_t status =
+      GetLocalAndDstOffsetInSeconds(now_seconds, &local_offset, &dst_offset);
   return status == ZX_OK ? local_offset : 0;
 }
 
 int64_t OS::GetCurrentTimeMillis() {
-  return GetCurrentTimeMicros() / 1000;
+  return GetCurrentTimeNanos() / ZX_MSEC(1);
 }
 
 int64_t OS::GetCurrentTimeMicros() {
-  zx_time_t now = 0;
-  zx_clock_get(ZX_CLOCK_UTC, &now);
-  return now / kNanosecondsPerMicrosecond;
+  return GetCurrentTimeNanos() / ZX_USEC(1);
 }
 
 int64_t OS::GetCurrentMonotonicTicks() {
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index ccf4d45..dcd7f99 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -994,6 +994,7 @@
 
  private:
   friend class Class;
+  friend class UnitDeserializationRoots;
 
   RAW_HEAP_OBJECT_IMPLEMENTATION(Function);
 
@@ -1526,6 +1527,8 @@
   friend class StackFrame;
   friend class Profiler;
   friend class FunctionDeserializationCluster;
+  friend class UnitSerializationRoots;
+  friend class UnitDeserializationRoots;
   friend class CallSiteResetter;
 };
 
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 9d1ebd6..e979cd8 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -17971,12 +17971,8 @@
   *
   * ## Other resources
   *
-  * * [Fetch Data Dynamically](https://www.dartlang.org/docs/tutorials/fetchdata/),
-  * a tutorial from _A Game of Darts_,
-  * shows two different ways to use HttpRequest to get a JSON file.
-  * * [Get Input from a Form](https://www.dartlang.org/docs/tutorials/forms/),
-  * another tutorial from _A Game of Darts_,
-  * shows using HttpRequest with a custom server.
+  * * [Fetch data dynamically](https://dart.dev/tutorials/web/fetch-data/),
+  * a tutorial shows how to load data from a static file or from a server.
   * * [Dart article on using HttpRequests](http://www.dartlang.org/articles/json-web-service/#getting-data)
   * * [JS XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
   * * [Using XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest)
diff --git a/tools/VERSION b/tools/VERSION
index a2403c7..6e94838 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 64
+PRERELEASE 65
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate b/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
index f61c75d..27e5bcf 100644
--- a/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
+++ b/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
@@ -39,12 +39,8 @@
   *
   * ## Other resources
   *
-  * * [Fetch Data Dynamically](https://www.dartlang.org/docs/tutorials/fetchdata/),
-  * a tutorial from _A Game of Darts_,
-  * shows two different ways to use HttpRequest to get a JSON file.
-  * * [Get Input from a Form](https://www.dartlang.org/docs/tutorials/forms/),
-  * another tutorial from _A Game of Darts_,
-  * shows using HttpRequest with a custom server.
+  * * [Fetch data dynamically](https://dart.dev/tutorials/web/fetch-data/),
+  * a tutorial shows how to load data from a static file or from a server.
   * * [Dart article on using HttpRequests](http://www.dartlang.org/articles/json-web-service/#getting-data)
   * * [JS XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
   * * [Using XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest)