Support post-inference serialization/deserialization

Currently handling Swarm in-memory serialization/deserialization.

Change-Id: I4b01f70d1e42d7462ed079ece227d6c2ed4de597
Reviewed-on: https://dart-review.googlesource.com/c/80445
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Auto-Submit: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/backend_strategy.dart b/pkg/compiler/lib/src/backend_strategy.dart
index 6b2e8f0..c3aefb1 100644
--- a/pkg/compiler/lib/src/backend_strategy.dart
+++ b/pkg/compiler/lib/src/backend_strategy.dart
@@ -13,7 +13,6 @@
 import 'js_backend/inferred_data.dart';
 import 'js_backend/js_backend.dart';
 import 'js_backend/native_data.dart';
-import 'js_emitter/sorter.dart';
 import 'ssa/ssa.dart';
 import 'types/types.dart';
 import 'universe/world_builder.dart';
@@ -26,8 +25,11 @@
   JClosedWorld createJClosedWorld(
       KClosedWorld closedWorld, OutputUnitData outputUnitData);
 
-  /// The [Sorter] used for sorting elements in the generated code.
-  Sorter get sorter;
+  /// Registers [closedWorld] as the current closed world used by this backend
+  /// strategy.
+  ///
+  /// This is used to support serialization after type inference.
+  void registerJClosedWorld(JClosedWorld closedWorld);
 
   /// Creates the [CodegenWorldBuilder] used by the codegen enqueuer.
   CodegenWorldBuilder createCodegenWorldBuilder(
diff --git a/pkg/compiler/lib/src/closure.dart b/pkg/compiler/lib/src/closure.dart
index 8006d88..22b3fc7 100644
--- a/pkg/compiler/lib/src/closure.dart
+++ b/pkg/compiler/lib/src/closure.dart
@@ -7,6 +7,9 @@
 import 'common.dart';
 import 'elements/entities.dart';
 import 'elements/types.dart';
+import 'js_model/closure.dart';
+import 'js_model/element_map.dart';
+import 'serialization/serialization.dart';
 
 abstract class ClosureConversionTask extends CompilerTask {
   ClosureConversionTask(Measurer measurer) : super(measurer);
@@ -17,6 +20,14 @@
 /// node to look up, it returns a information about the internal representation
 /// of how closure conversion is implemented. T is an ir.Node or Node.
 abstract class ClosureData {
+  /// Deserializes a [ClosureData] object from [source].
+  factory ClosureData.readFromDataSource(
+          JsToElementMap elementMap, DataSource source) =
+      ClosureDataImpl.readFromDataSource;
+
+  /// Serializes this [ClosureData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Look up information about the variables that have been mutated and are
   /// used inside the scope of [node].
   ScopeInfo getScopeInfo(MemberEntity member);
@@ -32,6 +43,14 @@
   CapturedScope getCapturedScope(MemberEntity entity);
 }
 
+/// Enum used for identifying [ScopeInfo] subclasses in serialization.
+enum ScopeInfoKind {
+  scopeInfo,
+  capturedScope,
+  capturedLoopScope,
+  closureRepresentationInfo,
+}
+
 /// Class that represents one level of scoping information, whether this scope
 /// is a closure or not. This is specifically used to store information
 /// about the usage of variables in try or sync blocks, because they need to be
@@ -44,6 +63,27 @@
 class ScopeInfo {
   const ScopeInfo();
 
+  /// Deserializes a [ScopeInfo] object from [source].
+  factory ScopeInfo.readFromDataSource(DataSource source) {
+    ScopeInfoKind kind = source.readEnum(ScopeInfoKind.values);
+    switch (kind) {
+      case ScopeInfoKind.scopeInfo:
+        return new JsScopeInfo.readFromDataSource(source);
+      case ScopeInfoKind.capturedScope:
+        return new JsCapturedScope.readFromDataSource(source);
+      case ScopeInfoKind.capturedLoopScope:
+        return new JsCapturedLoopScope.readFromDataSource(source);
+      case ScopeInfoKind.closureRepresentationInfo:
+        return new KernelClosureClassInfo.readFromDataSource(source);
+    }
+    throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
+  }
+
+  /// Serializes this [ScopeInfo] to [sink].
+  void writeToDataSink(DataSink sink) {
+    throw new UnsupportedError('${runtimeType}.writeToDataSink');
+  }
+
   /// Convenience reference pointer to the element representing `this`.
   /// If this scope is not in an instance member, it will be null.
   Local get thisLocal => null;
@@ -81,6 +121,21 @@
 class CapturedScope extends ScopeInfo {
   const CapturedScope();
 
+  /// Deserializes a [CapturedScope] object from [source].
+  factory CapturedScope.readFromDataSource(DataSource source) {
+    ScopeInfoKind kind = source.readEnum(ScopeInfoKind.values);
+    switch (kind) {
+      case ScopeInfoKind.scopeInfo:
+      case ScopeInfoKind.closureRepresentationInfo:
+        throw new UnsupportedError('Unexpected CapturedScope kind $kind');
+      case ScopeInfoKind.capturedScope:
+        return new JsCapturedScope.readFromDataSource(source);
+      case ScopeInfoKind.capturedLoopScope:
+        return new JsCapturedLoopScope.readFromDataSource(source);
+    }
+    throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
+  }
+
   /// If true, this closure accesses a variable that was defined in an outside
   /// scope and this variable gets modified at some point (sometimes we say that
   /// variable has been "captured"). In this situation, access to this variable
@@ -113,6 +168,20 @@
 class CapturedLoopScope extends CapturedScope {
   const CapturedLoopScope();
 
+  /// Deserializes a [CapturedLoopScope] object from [source].
+  factory CapturedLoopScope.readFromDataSource(DataSource source) {
+    ScopeInfoKind kind = source.readEnum(ScopeInfoKind.values);
+    switch (kind) {
+      case ScopeInfoKind.scopeInfo:
+      case ScopeInfoKind.closureRepresentationInfo:
+      case ScopeInfoKind.capturedScope:
+        throw new UnsupportedError('Unexpected CapturedLoopScope kind $kind');
+      case ScopeInfoKind.capturedLoopScope:
+        return new JsCapturedLoopScope.readFromDataSource(source);
+    }
+    throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
+  }
+
   /// True if this loop scope declares in the first part of the loop
   /// `for (<here>;...;...)` any variables that need to be boxed.
   bool get hasBoxedLoopVariables => false;
@@ -166,6 +235,21 @@
 class ClosureRepresentationInfo extends ScopeInfo {
   const ClosureRepresentationInfo();
 
+  /// Deserializes a [ClosureRepresentationInfo] object from [source].
+  factory ClosureRepresentationInfo.readFromDataSource(DataSource source) {
+    ScopeInfoKind kind = source.readEnum(ScopeInfoKind.values);
+    switch (kind) {
+      case ScopeInfoKind.scopeInfo:
+      case ScopeInfoKind.capturedScope:
+      case ScopeInfoKind.capturedLoopScope:
+        throw new UnsupportedError(
+            'Unexpected ClosureRepresentationInfo kind $kind');
+      case ScopeInfoKind.closureRepresentationInfo:
+        return new KernelClosureClassInfo.readFromDataSource(source);
+    }
+    throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
+  }
+
   /// The original local function before any translation.
   ///
   /// Will be null for methods.
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 7f23c57..9140c04 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -360,6 +360,7 @@
   void generateJavaScriptCode(
       GlobalTypeInferenceResults globalInferenceResults) {
     JClosedWorld closedWorld = globalInferenceResults.closedWorld;
+    backendStrategy.registerJClosedWorld(closedWorld);
     FunctionEntity mainFunction = closedWorld.elementEnvironment.mainFunction;
     reporter.log('Compiling...');
     phase = PHASE_COMPILING;
@@ -402,7 +403,7 @@
         enqueuer.createCodegenEnqueuer(closedWorld, globalInferenceResults);
     _codegenWorldBuilder = codegenEnqueuer.worldBuilder;
     codegenEnqueuer.applyImpact(backend.onCodegenStart(
-        closedWorld, _codegenWorldBuilder, backendStrategy.sorter));
+        closedWorld, _codegenWorldBuilder, closedWorld.sorter));
     return codegenEnqueuer;
   }
 
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index 95f65bf..7243452 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -22,6 +22,7 @@
 import 'elements/entities.dart';
 import 'kernel/kelements.dart' show KLocalFunction;
 import 'library_loader.dart';
+import 'serialization/serialization.dart';
 import 'universe/use.dart';
 import 'universe/world_impact.dart'
     show ImpactUseCase, WorldImpact, WorldImpactVisitorImpl;
@@ -1271,6 +1272,10 @@
 // TODO(sigmund): consider moving here every piece of data used as a result of
 // deferred loading (including hunksToLoad, etc).
 class OutputUnitData {
+  /// Tag used for identifying serialized [OutputUnitData] objects in a
+  /// debugging data stream.
+  static const String tag = 'output-unit-data';
+
   final bool isProgramSplit;
   final OutputUnit mainOutputUnit;
   final Map<ClassEntity, OutputUnit> _classToUnit;
@@ -1288,6 +1293,7 @@
       this._constantToUnit,
       this.outputUnits);
 
+  // Creates J-world data from the K-world data.
   OutputUnitData.from(
       OutputUnitData other,
       Map<ClassEntity, OutputUnit> Function(
@@ -1305,9 +1311,90 @@
             convertMemberMap(other._memberToUnit, other._localFunctionToUnit),
         _classToUnit =
             convertClassMap(other._classToUnit, other._localFunctionToUnit),
+        // Local functions only make sense in the K-world model.
         _localFunctionToUnit = const <Local, OutputUnit>{},
         _constantToUnit = convertConstantMap(other._constantToUnit);
 
+  /// Deserializes an [OutputUnitData] object from [source].
+  factory OutputUnitData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    bool isProgramSplit = source.readBool();
+    List<ImportEntity> imports = source.readList(() {
+      String name = source.readString();
+      Uri uri = source.readUri();
+      Uri enclosingLibraryUri = source.readUri();
+      bool isDeferred = source.readBool();
+      return new ImportEntity(isDeferred, name, uri, enclosingLibraryUri);
+    });
+    List<OutputUnit> outputUnits = source.readList(() {
+      bool isMainOutput = source.readBool();
+      String name = source.readString();
+      Set<ImportEntity> importSet = source.readList(() {
+        return imports[source.readInt()];
+      }).toSet();
+      return new OutputUnit(isMainOutput, name, importSet);
+    });
+    OutputUnit mainOutputUnit = outputUnits[source.readInt()];
+
+    Map<ClassEntity, OutputUnit> classToUnit = source.readClassMap(() {
+      return outputUnits[source.readInt()];
+    });
+    Map<MemberEntity, OutputUnit> memberToUnit = source.readMemberMap(() {
+      return outputUnits[source.readInt()];
+    });
+    Map<ConstantValue, OutputUnit> constantToUnit = source.readConstantMap(() {
+      return outputUnits[source.readInt()];
+    });
+    source.end(tag);
+    return new OutputUnitData(
+        isProgramSplit,
+        mainOutputUnit,
+        classToUnit,
+        memberToUnit,
+        // Local functions only make sense in the K-world model.
+        const <Local, OutputUnit>{},
+        constantToUnit,
+        outputUnits);
+  }
+
+  /// Serializes this [OutputUnitData] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeBool(isProgramSplit);
+    Map<ImportEntity, int> importIndex = {};
+    for (OutputUnit outputUnit in outputUnits) {
+      for (ImportEntity import in outputUnit._imports) {
+        importIndex[import] ??= importIndex.length;
+      }
+    }
+    sink.writeList(importIndex.keys, (ImportEntity import) {
+      sink.writeString(import.name);
+      sink.writeUri(import.uri);
+      sink.writeUri(import.enclosingLibraryUri);
+      sink.writeBool(import.isDeferred);
+    });
+    Map<OutputUnit, int> outputUnitIndices = {};
+    sink.writeList(outputUnits, (OutputUnit outputUnit) {
+      outputUnitIndices[outputUnit] = outputUnitIndices.length;
+      sink.writeBool(outputUnit.isMainOutput);
+      sink.writeString(outputUnit.name);
+      sink.writeList(outputUnit._imports, (ImportEntity import) {
+        sink.writeInt(importIndex[import]);
+      });
+    });
+    sink.writeInt(outputUnitIndices[mainOutputUnit]);
+    sink.writeClassMap(_classToUnit, (OutputUnit outputUnit) {
+      sink.writeInt(outputUnitIndices[outputUnit]);
+    });
+    sink.writeMemberMap(_memberToUnit, (OutputUnit outputUnit) {
+      sink.writeInt(outputUnitIndices[outputUnit]);
+    });
+    sink.writeConstantMap(_constantToUnit, (OutputUnit outputUnit) {
+      sink.writeInt(outputUnitIndices[outputUnit]);
+    });
+    sink.end(tag);
+  }
+
   /// Returns the [OutputUnit] where [cls] belongs.
   OutputUnit outputUnitForClass(ClassEntity cls) {
     if (!isProgramSplit) return mainOutputUnit;
diff --git a/pkg/compiler/lib/src/elements/entities.dart b/pkg/compiler/lib/src/elements/entities.dart
index 2304b62..0adff08 100644
--- a/pkg/compiler/lib/src/elements/entities.dart
+++ b/pkg/compiler/lib/src/elements/entities.dart
@@ -7,6 +7,7 @@
 import 'package:front_end/src/api_unstable/dart2js.dart' show AsyncModifier;
 
 import '../common.dart';
+import '../serialization/serialization.dart';
 import '../universe/call_structure.dart' show CallStructure;
 import '../util/util.dart';
 import 'names.dart';
@@ -258,6 +259,10 @@
 
 /// The structure of function parameters.
 class ParameterStructure {
+  /// Tag used for identifying serialized [ParameterStructure] objects in a
+  /// debugging data stream.
+  static const String tag = 'parameter-structure';
+
   /// The number of required (positional) parameters.
   final int requiredParameters;
 
@@ -285,6 +290,28 @@
         type.typeVariables.length);
   }
 
+  /// Deserializes a [ParameterStructure] object from [source].
+  factory ParameterStructure.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    int requiredParameters = source.readInt();
+    int positionalParameters = source.readInt();
+    List<String> namedParameters = source.readStrings();
+    int typeParameters = source.readInt();
+    source.end(tag);
+    return new ParameterStructure(requiredParameters, positionalParameters,
+        namedParameters, typeParameters);
+  }
+
+  /// Serializes this [ParameterStructure] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(requiredParameters);
+    sink.writeInt(positionalParameters);
+    sink.writeStrings(namedParameters);
+    sink.writeInt(typeParameters);
+    sink.end(tag);
+  }
+
   /// The number of optional parameters (positional or named).
   int get optionalParameters =>
       positionalParameters - requiredParameters + namedParameters.length;
diff --git a/pkg/compiler/lib/src/elements/indexed.dart b/pkg/compiler/lib/src/elements/indexed.dart
index f20a4ffab..56defb0 100644
--- a/pkg/compiler/lib/src/elements/indexed.dart
+++ b/pkg/compiler/lib/src/elements/indexed.dart
@@ -53,12 +53,16 @@
 
 /// Base implementation for an index based map of entities of type [E].
 abstract class EntityMapBase<E extends _Indexed> {
+  int _size = 0;
   List<E> _list = <E>[];
 
   /// Returns the [index]th entity in the map.
   E getEntity(int index) => _list[index];
 
-  /// Returns the number entities in the map.
+  /// Returns the number of non-null entities in the map.
+  int get size => _size;
+
+  /// Returns the number (null and non-null) entities in the map.
   int get length => _list.length;
 }
 
@@ -73,8 +77,26 @@
     assert(entity._index == null);
     entity._index = _list.length;
     _list.add(entity);
+    _size++;
     return entity;
   }
+
+  /// Registers a new [entity] by the given [index].
+  E0 registerByIndex<E0 extends E>(int index, E0 entity) {
+    assert(index >= _list.length);
+    _list.length = index;
+    return register(entity);
+  }
+
+  /// Calls [f] for each non-null entity.
+  void forEach<E0 extends E>(void f(E0 entity)) {
+    for (int index = 0; index < _list.length; index++) {
+      E entity = _list[index];
+      if (entity != null) {
+        f(entity);
+      }
+    }
+  }
 }
 
 /// Base implementation of an index based map of entities of type [E] with a
@@ -86,7 +108,7 @@
   /// Returns the data object stored for the [index]th entity.
   D getData(E entity) {
     int index = entity._index;
-    if (index < length && index >= _data.length) {
+    if (index < _list.length && index >= _data.length) {
       throw new StateError(
           'Data is in the process of being created for ${_list[index]}.');
     }
@@ -111,12 +133,39 @@
   E0 register<E0 extends E, D0 extends D>(E0 entity, D0 data) {
     assert(entity != null);
     assert(entity._index == null);
+    assert(
+        _list.length == _data.length,
+        'Data list length ${_data.length} inconsistent '
+        'with entity list length ${_list.length}.');
     entity._index = _list.length;
     _list.add(entity);
+    _size++;
     assert(data != null);
     _data.add(data);
     return entity;
   }
+
+  /// Registers a new [entity] with an associated [data] object by the given
+  /// [index].
+  E0 registerByIndex<E0 extends E, D0 extends D>(
+      int index, E0 entity, D0 data) {
+    assert(index >= _list.length);
+    _list.length = _data.length = index;
+    return register(entity, data);
+  }
+
+  /// Calls [f] for each non-null entity with its corresponding data object.
+  void forEach<E0 extends E, D0 extends D>(void f(E0 entity, D0 data)) {
+    if (_list.length != _data.length) {
+      throw new StateError('Data is in the process of being created.');
+    }
+    for (int index = 0; index < _list.length; index++) {
+      E entity = _list[index];
+      if (entity != null) {
+        f(entity, _data[index]);
+      }
+    }
+  }
 }
 
 /// Base implementation for an index based of entities of type [E] with a
@@ -128,7 +177,7 @@
   /// Returns the environment object stored for the [index]th entity.
   V getEnv(E entity) {
     int index = entity._index;
-    if (index < length && index >= _env.length) {
+    if (index < _list.length && index >= _env.length) {
       throw new StateError(
           'Env is in the process of being created for ${_list[index]}.');
     }
@@ -149,12 +198,48 @@
       E0 entity, D0 data, V0 env) {
     assert(entity != null);
     assert(entity._index == null);
+    assert(
+        _list.length == _data.length,
+        'Data list length ${_data.length} inconsistent '
+        'with entity list length ${_list.length}.');
+    assert(
+        _list.length == _env.length,
+        'Env list length ${_env.length} inconsistent '
+        'with entity list length ${_list.length}.');
     entity._index = _list.length;
     _list.add(entity);
+    _size++;
     assert(data != null);
     _data.add(data);
     assert(env != null);
     _env.add(env);
     return entity;
   }
+
+  /// Registers a new [entity] with an associated [data] object and environment
+  /// [env] by the given [index].
+  E0 registerByIndex<E0 extends E, D0 extends D, V0 extends V>(
+      int index, E0 entity, D0 data, V0 env) {
+    assert(index >= _list.length);
+    _list.length = _data.length = _env.length = index;
+    return register(entity, data, env);
+  }
+
+  /// Calls [f] for each non-null entity with its corresponding data object and
+  /// environment.
+  void forEach<E0 extends E, D0 extends D, V0 extends V>(
+      void f(E0 entity, D0 data, V0 env)) {
+    if (_list.length != _data.length) {
+      throw new StateError('Data is in the process of being created.');
+    }
+    if (_list.length != _env.length) {
+      throw new StateError('Env is in the process of being created.');
+    }
+    for (int index = 0; index < _list.length; index++) {
+      E entity = _list[index];
+      if (entity != null) {
+        f(entity, _data[index], _env[index]);
+      }
+    }
+  }
 }
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index c608983..5ce0f07 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -16,12 +16,12 @@
 import '../elements/types.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_backend/no_such_method_registry.dart';
-import '../js_emitter/sorter.dart';
 import '../js_model/element_map.dart';
 import '../js_model/js_strategy.dart';
 import '../js_model/locals.dart';
 import '../native/behavior.dart' as native;
 import '../options.dart';
+import '../serialization/serialization.dart';
 import '../types/abstract_value_domain.dart';
 import '../types/types.dart';
 import '../universe/call_structure.dart';
@@ -288,8 +288,6 @@
 
   final NoSuchMethodRegistry noSuchMethodRegistry;
 
-  final Sorter sorter;
-
   InferrerEngineImpl(
       this.options,
       this.progress,
@@ -298,7 +296,6 @@
       this.closedWorld,
       this.noSuchMethodRegistry,
       this.mainElement,
-      this.sorter,
       this.inferredDataBuilder)
       : this.types = new TypeSystem(
             closedWorld, new KernelTypeSystemStrategy(closedWorld));
@@ -1365,6 +1362,10 @@
 
 class KernelGlobalTypeInferenceElementData
     implements GlobalTypeInferenceElementData {
+  /// Tag used for identifying serialized [GlobalTypeInferenceElementData]
+  /// objects in a debugging data stream.
+  static const String tag = 'global-type-inference-element-data';
+
   // TODO(johnniwinther): Rename this together with [typeOfSend].
   Map<ir.TreeNode, AbstractValue> _sendMap;
 
@@ -1372,6 +1373,86 @@
   Map<ir.ForInStatement, AbstractValue> _currentMap;
   Map<ir.ForInStatement, AbstractValue> _moveNextMap;
 
+  KernelGlobalTypeInferenceElementData();
+
+  KernelGlobalTypeInferenceElementData.internal(
+      this._sendMap, this._iteratorMap, this._currentMap, this._moveNextMap);
+
+  /// Deserializes a [GlobalTypeInferenceElementData] object from [source].
+  factory KernelGlobalTypeInferenceElementData.readFromDataSource(
+      DataSource source, AbstractValueDomain abstractValueDomain) {
+    source.begin(tag);
+    Map<ir.TreeNode, AbstractValue> sendMap = source.readTreeNodeMap(
+        () => abstractValueDomain.readAbstractValueFromDataSource(source),
+        emptyAsNull: true);
+    Map<ir.ForInStatement, AbstractValue> iteratorMap = source.readTreeNodeMap(
+        () => abstractValueDomain.readAbstractValueFromDataSource(source),
+        emptyAsNull: true);
+    Map<ir.ForInStatement, AbstractValue> currentMap = source.readTreeNodeMap(
+        () => abstractValueDomain.readAbstractValueFromDataSource(source),
+        emptyAsNull: true);
+    Map<ir.ForInStatement, AbstractValue> moveNextMap = source.readTreeNodeMap(
+        () => abstractValueDomain.readAbstractValueFromDataSource(source),
+        emptyAsNull: true);
+    source.end(tag);
+    return new KernelGlobalTypeInferenceElementData.internal(
+        sendMap, iteratorMap, currentMap, moveNextMap);
+  }
+
+  /// Serializes this [GlobalTypeInferenceElementData] to [sink].
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain) {
+    sink.begin(tag);
+    sink.writeTreeNodeMap(
+        _sendMap,
+        (AbstractValue value) =>
+            abstractValueDomain.writeAbstractValueToDataSink(sink, value),
+        allowNull: true);
+    sink.writeTreeNodeMap(
+        _iteratorMap,
+        (AbstractValue value) =>
+            abstractValueDomain.writeAbstractValueToDataSink(sink, value),
+        allowNull: true);
+    sink.writeTreeNodeMap(
+        _currentMap,
+        (AbstractValue value) =>
+            abstractValueDomain.writeAbstractValueToDataSink(sink, value),
+        allowNull: true);
+    sink.writeTreeNodeMap(
+        _moveNextMap,
+        (AbstractValue value) =>
+            abstractValueDomain.writeAbstractValueToDataSink(sink, value),
+        allowNull: true);
+    sink.end(tag);
+  }
+
+  @override
+  void compress() {
+    if (_sendMap != null) {
+      _sendMap.removeWhere(_mapsToNull);
+      if (_sendMap.isEmpty) {
+        _sendMap = null;
+      }
+    }
+    if (_iteratorMap != null) {
+      _iteratorMap.removeWhere(_mapsToNull);
+      if (_iteratorMap.isEmpty) {
+        _iteratorMap = null;
+      }
+    }
+    if (_currentMap != null) {
+      _currentMap.removeWhere(_mapsToNull);
+      if (_currentMap.isEmpty) {
+        _currentMap = null;
+      }
+    }
+    if (_moveNextMap != null) {
+      _moveNextMap.removeWhere(_mapsToNull);
+      if (_moveNextMap.isEmpty) {
+        _moveNextMap = null;
+      }
+    }
+  }
+
   @override
   AbstractValue typeOfSend(ir.TreeNode node) {
     if (_sendMap == null) return null;
@@ -1429,3 +1510,5 @@
     return _sendMap[node];
   }
 }
+
+bool _mapsToNull(ir.TreeNode node, AbstractValue value) => value == null;
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
index 827f0f4..c8c20c2 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
@@ -79,7 +79,6 @@
         closedWorld,
         _compiler.backend.noSuchMethodRegistry,
         main,
-        _compiler.backendStrategy.sorter,
         _inferredDataBuilder);
   }
 
@@ -109,6 +108,7 @@
     void createMemberResults(
         MemberEntity member, MemberTypeInformation typeInformation) {
       GlobalTypeInferenceElementData data = inferrer.dataOfMember(member);
+      data.compress();
       bool isJsInterop = closedWorld.nativeData.isJsInteropMember(member);
 
       AbstractValue returnType;
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/container_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/container_type_mask.dart
index a0280dc..4b27265 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/container_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/container_type_mask.dart
@@ -8,6 +8,10 @@
 /// site of a container (currently only List) that will get specialized
 /// once the [TypeGraphInferrer] phase finds an element type for it.
 class ContainerTypeMask extends AllocationTypeMask {
+  /// Tag used for identifying serialized [ContainerTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'container-type-mask';
+
   final TypeMask forwardTo;
 
   // The [Node] where this type mask was created.
@@ -25,6 +29,32 @@
   ContainerTypeMask(this.forwardTo, this.allocationNode, this.allocationElement,
       this.elementType, this.length);
 
+  /// Deserializes a [ContainerTypeMask] object from [source].
+  factory ContainerTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    TypeMask forwardTo = new TypeMask.readFromDataSource(source, closedWorld);
+    ir.TreeNode allocationNode = source.readTreeNodeOrNull();
+    MemberEntity allocationElement = source.readMemberOrNull();
+    TypeMask elementType = new TypeMask.readFromDataSource(source, closedWorld);
+    int length = source.readIntOrNull();
+    source.end(tag);
+    return new ContainerTypeMask(
+        forwardTo, allocationNode, allocationElement, elementType, length);
+  }
+
+  /// Serializes this [ContainerTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.container);
+    sink.begin(tag);
+    forwardTo.writeToDataSink(sink);
+    sink.writeTreeNodeOrNull(allocationNode);
+    sink.writeMemberOrNull(allocationElement);
+    elementType.writeToDataSink(sink);
+    sink.writeIntOrNull(length);
+    sink.end(tag);
+  }
+
   TypeMask nullable() {
     return isNullable
         ? this
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/dictionary_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/dictionary_type_mask.dart
index c6e3950..9a64845 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/dictionary_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/dictionary_type_mask.dart
@@ -13,6 +13,10 @@
  * the more general [MapTypeMask] is used.
  */
 class DictionaryTypeMask extends MapTypeMask {
+  /// Tag used for identifying serialized [DictionaryTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'dictionary-type-mask';
+
   // The underlying key/value map of this dictionary.
   final Map<String, AbstractValue> _typeMap;
 
@@ -25,6 +29,38 @@
       this._typeMap)
       : super(forwardTo, allocationNode, allocationElement, keyType, valueType);
 
+  /// Deserializes a [DictionaryTypeMask] object from [source].
+  factory DictionaryTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    TypeMask forwardTo = new TypeMask.readFromDataSource(source, closedWorld);
+    ir.TreeNode allocationNode = source.readTreeNode();
+    MemberEntity allocationElement = source.readMember();
+    TypeMask keyType = new TypeMask.readFromDataSource(source, closedWorld);
+    TypeMask valueType = new TypeMask.readFromDataSource(source, closedWorld);
+    Map<String, AbstractValue> typeMap = source.readStringMap(
+        () => new TypeMask.readFromDataSource(source, closedWorld));
+    source.end(tag);
+    return new DictionaryTypeMask(forwardTo, allocationNode, allocationElement,
+        keyType, valueType, typeMap);
+  }
+
+  /// Serializes this [DictionaryTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.dictionary);
+    sink.begin(tag);
+    forwardTo.writeToDataSink(sink);
+    sink.writeTreeNode(allocationNode);
+    sink.writeMember(allocationElement);
+    valueType.writeToDataSink(sink);
+    keyType.writeToDataSink(sink);
+    sink.writeStringMap(_typeMap, (AbstractValue value) {
+      TypeMask typeMask = value;
+      typeMask.writeToDataSink(sink);
+    });
+    sink.end(tag);
+  }
+
   TypeMask nullable() {
     return isNullable
         ? this
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/flat_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/flat_type_mask.dart
index 20150d5..c74600a 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/flat_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/flat_type_mask.dart
@@ -9,6 +9,10 @@
  * base type.
  */
 class FlatTypeMask implements TypeMask {
+  /// Tag used for identifying serialized [FlatTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'flat-type-mask';
+
   static const int EMPTY = 0;
   static const int EXACT = 1;
   static const int SUBCLASS = 2;
@@ -39,10 +43,6 @@
   FlatTypeMask.nonNullSubtype(ClassEntity base)
       : this.internal(base, SUBTYPE << 1);
 
-  ClassQuery get _classQuery => isExact
-      ? ClassQuery.EXACT
-      : (isSubclass ? ClassQuery.SUBCLASS : ClassQuery.SUBTYPE);
-
   FlatTypeMask.internal(this.base, this.flags);
 
   /**
@@ -69,6 +69,31 @@
         base, flags, () => new FlatTypeMask.internal(base, flags));
   }
 
+  /// Deserializes a [FlatTypeMask] object from [source].
+  factory FlatTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    ClassEntity base = source.readClassOrNull();
+    int flags = source.readInt();
+    source.end(tag);
+    CommonMasks commonMasks = closedWorld.abstractValueDomain;
+    return commonMasks.getCachedMask(
+        base, flags, () => new FlatTypeMask.internal(base, flags));
+  }
+
+  /// Serializes this [FlatTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.flat);
+    sink.begin(tag);
+    sink.writeClassOrNull(base);
+    sink.writeInt(flags);
+    sink.end(tag);
+  }
+
+  ClassQuery get _classQuery => isExact
+      ? ClassQuery.EXACT
+      : (isSubclass ? ClassQuery.SUBCLASS : ClassQuery.SUBTYPE);
+
   bool get isEmpty => isEmptyOrNull && !isNullable;
   bool get isNull => isEmptyOrNull && isNullable;
   bool get isEmptyOrNull => (flags >> 1) == EMPTY;
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/map_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/map_type_mask.dart
index f7b2866..5e6e2fb 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/map_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/map_type_mask.dart
@@ -10,6 +10,10 @@
  * once the [TypeGraphInferrer] phase finds a key and/or value type for it.
  */
 class MapTypeMask extends AllocationTypeMask {
+  /// Tag used for identifying serialized [MapTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'map-type-mask';
+
   final TypeMask forwardTo;
 
   // The [Node] where this type mask was created.
@@ -27,6 +31,32 @@
   MapTypeMask(this.forwardTo, this.allocationNode, this.allocationElement,
       this.keyType, this.valueType);
 
+  /// Deserializes a [MapTypeMask] object from [source].
+  factory MapTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    TypeMask forwardTo = new TypeMask.readFromDataSource(source, closedWorld);
+    ir.TreeNode allocationNode = source.readTreeNode();
+    MemberEntity allocationElement = source.readMember();
+    TypeMask keyType = new TypeMask.readFromDataSource(source, closedWorld);
+    TypeMask valueType = new TypeMask.readFromDataSource(source, closedWorld);
+    source.end(tag);
+    return new MapTypeMask(
+        forwardTo, allocationNode, allocationElement, keyType, valueType);
+  }
+
+  /// Serializes this [MapTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.map);
+    sink.begin(tag);
+    forwardTo.writeToDataSink(sink);
+    sink.writeTreeNode(allocationNode);
+    sink.writeMember(allocationElement);
+    valueType.writeToDataSink(sink);
+    keyType.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   TypeMask nullable() {
     return isNullable
         ? this
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart
index 5b0e920..6987117 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart
@@ -8,8 +8,9 @@
 
 import '../../common.dart';
 import '../../common_elements.dart' show CommonElements;
-import '../../constants/values.dart' show ConstantValue, PrimitiveConstantValue;
+import '../../constants/values.dart';
 import '../../elements/entities.dart';
+import '../../serialization/serialization.dart';
 import '../../types/abstract_value_domain.dart';
 import '../../universe/class_hierarchy.dart';
 import '../../universe/selector.dart' show Selector;
@@ -718,6 +719,16 @@
   String getCompactText(AbstractValue value) {
     return formatType(value);
   }
+
+  @override
+  TypeMask readAbstractValueFromDataSource(DataSource source) {
+    return new TypeMask.readFromDataSource(source, _closedWorld);
+  }
+
+  @override
+  void writeAbstractValueToDataSink(DataSink sink, covariant TypeMask value) {
+    value.writeToDataSink(sink);
+  }
 }
 
 /// Convert the given TypeMask to a compact string format.
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/type_mask.dart
index 225f21c..a08d320 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/type_mask.dart
@@ -91,6 +91,16 @@
   }
 }
 
+/// Enum used for identifying [TypeMask] subclasses in serialization.
+enum TypeMaskKind {
+  flat,
+  union,
+  container,
+  map,
+  dictionary,
+  value,
+}
+
 /**
  * A type mask represents a set of contained classes, but the
  * operations on it are not guaranteed to be precise and they may
@@ -209,6 +219,30 @@
     return UnionTypeMask.unionOf(masks, closedWorld);
   }
 
+  /// Deserializes a [TypeMask] object from [source].
+  factory TypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    TypeMaskKind kind = source.readEnum(TypeMaskKind.values);
+    switch (kind) {
+      case TypeMaskKind.flat:
+        return new FlatTypeMask.readFromDataSource(source, closedWorld);
+      case TypeMaskKind.union:
+        return new UnionTypeMask.readFromDataSource(source, closedWorld);
+      case TypeMaskKind.container:
+        return new ContainerTypeMask.readFromDataSource(source, closedWorld);
+      case TypeMaskKind.map:
+        return new MapTypeMask.readFromDataSource(source, closedWorld);
+      case TypeMaskKind.dictionary:
+        return new DictionaryTypeMask.readFromDataSource(source, closedWorld);
+      case TypeMaskKind.value:
+        return new ValueTypeMask.readFromDataSource(source, closedWorld);
+    }
+    throw new UnsupportedError("Unexpected TypeMaskKind $kind.");
+  }
+
+  /// Serializes this [TypeMask] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /**
    * If [mask] is forwarding, returns the first non-forwarding [TypeMask] in
    * [mask]'s forwarding chain.
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/union_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/union_type_mask.dart
index 0da519c..aba7ea6 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/union_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/union_type_mask.dart
@@ -5,7 +5,9 @@
 part of masks;
 
 class UnionTypeMask implements TypeMask {
-  final Iterable<FlatTypeMask> disjointMasks;
+  /// Tag used for identifying serialized [UnionTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'union-type-mask';
 
   static const int MAX_UNION_LENGTH = 4;
 
@@ -14,11 +16,32 @@
   // helpful in debugging.
   static const bool PERFORM_EXTRA_CONTAINS_CHECK = false;
 
+  final Iterable<FlatTypeMask> disjointMasks;
+
   UnionTypeMask._internal(this.disjointMasks) {
     assert(disjointMasks.length > 1);
     assert(disjointMasks.every((TypeMask mask) => !mask.isUnion));
   }
 
+  /// Deserializes a [UnionTypeMask] object from [source].
+  factory UnionTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    List<FlatTypeMask> disjointMasks = source
+        .readList(() => new TypeMask.readFromDataSource(source, closedWorld));
+    source.end(tag);
+    return new UnionTypeMask._internal(disjointMasks);
+  }
+
+  /// Serializes this [UnionTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.union);
+    sink.begin(tag);
+    sink.writeList(
+        disjointMasks, (FlatTypeMask mask) => mask.writeToDataSink(sink));
+    sink.end(tag);
+  }
+
   static TypeMask unionOf(Iterable<TypeMask> masks, JClosedWorld closedWorld) {
     assert(
         masks.every((mask) => TypeMask.assertIsNormalized(mask, closedWorld)));
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/value_type_mask.dart b/pkg/compiler/lib/src/inferrer/typemasks/value_type_mask.dart
index ebeda52..45c1300 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/value_type_mask.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/value_type_mask.dart
@@ -5,11 +5,34 @@
 part of masks;
 
 class ValueTypeMask extends ForwardingTypeMask {
+  /// Tag used for identifying serialized [ValueTypeMask] objects in a
+  /// debugging data stream.
+  static const String tag = 'value-type-mask';
+
   final TypeMask forwardTo;
   final PrimitiveConstantValue value;
 
   ValueTypeMask(this.forwardTo, this.value);
 
+  /// Deserializes a [ValueTypeMask] object from [source].
+  factory ValueTypeMask.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    TypeMask forwardTo = new TypeMask.readFromDataSource(source, closedWorld);
+    ConstantValue constant = source.readConstant();
+    source.end(tag);
+    return new ValueTypeMask(forwardTo, constant);
+  }
+
+  /// Serializes this [ValueTypeMask] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(TypeMaskKind.value);
+    sink.begin(tag);
+    forwardTo.writeToDataSink(sink);
+    sink.writeConstant(value);
+    sink.end(tag);
+  }
+
   TypeMask nullable() {
     return isNullable ? this : new ValueTypeMask(forwardTo.nullable(), value);
   }
diff --git a/pkg/compiler/lib/src/js_backend/allocator_analysis.dart b/pkg/compiler/lib/src/js_backend/allocator_analysis.dart
index 7415322..6e15c77 100644
--- a/pkg/compiler/lib/src/js_backend/allocator_analysis.dart
+++ b/pkg/compiler/lib/src/js_backend/allocator_analysis.dart
@@ -10,6 +10,7 @@
 import '../kernel/kernel_strategy.dart';
 import '../kernel/kelements.dart' show KClass, KField;
 import '../options.dart';
+import '../serialization/serialization.dart';
 
 abstract class AllocatorAnalysis {}
 
@@ -70,6 +71,10 @@
 }
 
 class JAllocatorAnalysis implements AllocatorAnalysis {
+  /// Tag used for identifying serialized [JAllocatorAnalysis] objects in a
+  /// debugging data stream.
+  static const String tag = 'allocator-analysis';
+
   // --csp and --fast-startup have different constraints to the generated code.
   final CompilerOptions _options;
   final Map<JField, ConstantValue> _fixedInitializers =
@@ -77,6 +82,34 @@
 
   JAllocatorAnalysis._(this._options);
 
+  /// Deserializes a [JAllocatorAnalysis] object from [source].
+  factory JAllocatorAnalysis.readFromDataSource(
+      DataSource source, CompilerOptions options) {
+    source.begin(tag);
+    JAllocatorAnalysis analysis = new JAllocatorAnalysis._(options);
+    int fieldCount = source.readInt();
+    for (int i = 0; i < fieldCount; i++) {
+      JField field = source.readMember();
+      // TODO(sra): Deserialize constant, when non-null is supported.
+      ConstantValue value = const NullConstantValue();
+      analysis._fixedInitializers[field] = value;
+    }
+    source.end(tag);
+    return analysis;
+  }
+
+  /// Serializes this [JAllocatorAnalysis] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(_fixedInitializers.length);
+    _fixedInitializers.forEach((JField field, ConstantValue value) {
+      sink.writeMember(field);
+      // TODO(sra): Serialize constant, when non-null is supported.
+      assert(value.isNull);
+    });
+    sink.end(tag);
+  }
+
   static JAllocatorAnalysis from(KAllocatorAnalysis kAnalysis,
       JsToFrontendMap map, CompilerOptions options) {
     var result = new JAllocatorAnalysis._(options);
diff --git a/pkg/compiler/lib/src/js_backend/annotations.dart b/pkg/compiler/lib/src/js_backend/annotations.dart
index 5f66d8f..025f6d8 100644
--- a/pkg/compiler/lib/src/js_backend/annotations.dart
+++ b/pkg/compiler/lib/src/js_backend/annotations.dart
@@ -10,6 +10,7 @@
 import '../diagnostics/messages.dart';
 import '../elements/entities.dart';
 import '../native/native.dart' as native;
+import '../serialization/serialization.dart';
 
 /// Returns `true` if parameter and returns types should be trusted for
 /// [element].
@@ -179,6 +180,13 @@
 }
 
 abstract class AnnotationsData {
+  /// Deserializes a [AnnotationsData] object from [source].
+  factory AnnotationsData.readFromDataSource(DataSource source) =
+      AnnotationsDataImpl.readFromDataSource;
+
+  /// Serializes this [AnnotationsData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Functions with a `@NoInline()` or `@noInline` annotation.
   Iterable<FunctionEntity> get nonInlinableFunctions;
 
@@ -199,6 +207,10 @@
 }
 
 class AnnotationsDataImpl implements AnnotationsData {
+  /// Tag used for identifying serialized [AnnotationsData] objects in a
+  /// debugging data stream.
+  static const String tag = 'annotations-data';
+
   final Iterable<FunctionEntity> nonInlinableFunctions;
   final Iterable<FunctionEntity> tryInlineFunctions;
   final Iterable<FunctionEntity> cannotThrowFunctions;
@@ -213,6 +225,35 @@
       this.sideEffectFreeFunctions,
       this.trustTypeAnnotationsMembers,
       this.assumeDynamicMembers);
+
+  factory AnnotationsDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Iterable<FunctionEntity> nonInlinableFunctions = source.readMembers();
+    Iterable<FunctionEntity> tryInlineFunctions = source.readMembers();
+    Iterable<FunctionEntity> cannotThrowFunctions = source.readMembers();
+    Iterable<FunctionEntity> sideEffectFreeFunctions = source.readMembers();
+    Iterable<MemberEntity> trustTypeAnnotationsMembers = source.readMembers();
+    Iterable<MemberEntity> assumeDynamicMembers = source.readMembers();
+    source.end(tag);
+    return new AnnotationsDataImpl(
+        nonInlinableFunctions,
+        tryInlineFunctions,
+        cannotThrowFunctions,
+        sideEffectFreeFunctions,
+        trustTypeAnnotationsMembers,
+        assumeDynamicMembers);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMembers(nonInlinableFunctions);
+    sink.writeMembers(tryInlineFunctions);
+    sink.writeMembers(cannotThrowFunctions);
+    sink.writeMembers(sideEffectFreeFunctions);
+    sink.writeMembers(trustTypeAnnotationsMembers);
+    sink.writeMembers(assumeDynamicMembers);
+    sink.end(tag);
+  }
 }
 
 class AnnotationsDataBuilder implements AnnotationsData {
@@ -255,4 +296,8 @@
   Iterable<MemberEntity> get trustTypeAnnotationsMembers =>
       _trustTypeAnnotationsMembers;
   Iterable<MemberEntity> get assumeDynamicMembers => _assumeDynamicMembers;
+
+  void writeToDataSink(DataSink sink) {
+    throw new UnsupportedError('AnnotationsDataBuilder.writeToDataSink');
+  }
 }
diff --git a/pkg/compiler/lib/src/js_backend/backend_usage.dart b/pkg/compiler/lib/src/js_backend/backend_usage.dart
index b3e02ac..efe00d9 100644
--- a/pkg/compiler/lib/src/js_backend/backend_usage.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_usage.dart
@@ -7,11 +7,19 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../frontend_strategy.dart';
+import '../serialization/serialization.dart';
 import '../universe/feature.dart';
 import '../util/util.dart' show Setlet;
 import 'backend_impact.dart';
 
 abstract class BackendUsage {
+  /// Deserializes a [BackendUsage] object from [source].
+  factory BackendUsage.readFromDataSource(DataSource source) =
+      BackendUsageImpl.readFromDataSource;
+
+  /// Serializes this [BackendUsage] to [sink].
+  void writeToDataSink(DataSink sink);
+
   bool needToInitializeIsolateAffinityTag;
   bool needToInitializeDispatchProperty;
 
@@ -262,6 +270,10 @@
 }
 
 class BackendUsageImpl implements BackendUsage {
+  /// Tag used for identifying serialized [BackendUsage] objects in a
+  /// debugging data stream.
+  static const String tag = 'backend-usage';
+
   // TODO(johnniwinther): Remove the need for these.
   final Set<FunctionEntity> _globalFunctionDependencies;
   final Set<ClassEntity> _globalClassDependencies;
@@ -307,6 +319,61 @@
         this._helperClassesUsed = helperClassesUsed,
         this._runtimeTypeUses = runtimeTypeUses;
 
+  factory BackendUsageImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Set<FunctionEntity> globalFunctionDependencies =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<ClassEntity> globalClassDependencies = source.readClasses().toSet();
+    Set<FunctionEntity> helperFunctionsUsed =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<ClassEntity> helperClassesUsed = source.readClasses().toSet();
+    Set<RuntimeTypeUse> runtimeTypeUses = source.readList(() {
+      RuntimeTypeUseKind kind = source.readEnum(RuntimeTypeUseKind.values);
+      DartType receiverType = source.readDartType();
+      DartType argumentType = source.readDartType(allowNull: true);
+      return new RuntimeTypeUse(kind, receiverType, argumentType);
+    }).toSet();
+    bool needToInitializeIsolateAffinityTag = source.readBool();
+    bool needToInitializeDispatchProperty = source.readBool();
+    bool requiresPreamble = source.readBool();
+    bool isFunctionApplyUsed = source.readBool();
+    bool isMirrorsUsed = source.readBool();
+    bool isNoSuchMethodUsed = source.readBool();
+    source.end(tag);
+    return new BackendUsageImpl(
+        globalFunctionDependencies: globalFunctionDependencies,
+        globalClassDependencies: globalClassDependencies,
+        helperFunctionsUsed: helperFunctionsUsed,
+        helperClassesUsed: helperClassesUsed,
+        runtimeTypeUses: runtimeTypeUses,
+        needToInitializeIsolateAffinityTag: needToInitializeIsolateAffinityTag,
+        needToInitializeDispatchProperty: needToInitializeDispatchProperty,
+        requiresPreamble: requiresPreamble,
+        isFunctionApplyUsed: isFunctionApplyUsed,
+        isMirrorsUsed: isMirrorsUsed,
+        isNoSuchMethodUsed: isNoSuchMethodUsed);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMembers(_globalFunctionDependencies);
+    sink.writeClasses(_globalClassDependencies);
+    sink.writeMembers(_helperFunctionsUsed);
+    sink.writeClasses(_helperClassesUsed);
+    sink.writeList(runtimeTypeUses, (RuntimeTypeUse runtimeTypeUse) {
+      sink.writeEnum(runtimeTypeUse.kind);
+      sink.writeDartType(runtimeTypeUse.receiverType);
+      sink.writeDartType(runtimeTypeUse.argumentType, allowNull: true);
+    });
+    sink.writeBool(needToInitializeIsolateAffinityTag);
+    sink.writeBool(needToInitializeDispatchProperty);
+    sink.writeBool(requiresPreamble);
+    sink.writeBool(isFunctionApplyUsed);
+    sink.writeBool(isMirrorsUsed);
+    sink.writeBool(isNoSuchMethodUsed);
+    sink.end(tag);
+  }
+
   @override
   bool isFunctionUsedByBackend(FunctionEntity element) {
     return _helperFunctionsUsed.contains(element);
diff --git a/pkg/compiler/lib/src/js_backend/inferred_data.dart b/pkg/compiler/lib/src/js_backend/inferred_data.dart
index 1f1f272..73bb493 100644
--- a/pkg/compiler/lib/src/js_backend/inferred_data.dart
+++ b/pkg/compiler/lib/src/js_backend/inferred_data.dart
@@ -6,6 +6,7 @@
 
 import '../common.dart';
 import '../elements/entities.dart';
+import '../serialization/serialization.dart';
 import '../types/abstract_value_domain.dart';
 import '../universe/selector.dart';
 import '../universe/side_effects.dart';
@@ -13,6 +14,20 @@
 import 'annotations.dart';
 
 abstract class InferredData {
+  /// Deserializes a [InferredData] object from [source].
+  factory InferredData.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    bool isTrivial = source.readBool();
+    if (isTrivial) {
+      return new TrivialInferredData();
+    } else {
+      return new InferredDataImpl.readFromDataSource(source, closedWorld);
+    }
+  }
+
+  /// Serializes this [InferredData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns the side effects of executing [element].
   SideEffects getSideEffectsOfElement(FunctionEntity element);
 
@@ -62,6 +77,10 @@
 }
 
 class InferredDataImpl implements InferredData {
+  /// Tag used for identifying serialized [InferredData] objects in a
+  /// debugging data stream.
+  static const String tag = 'inferred-data';
+
   final JClosedWorld _closedWorld;
   final Set<MemberEntity> _functionsCalledInLoop;
   final Map<FunctionEntity, SideEffects> _sideEffects;
@@ -80,6 +99,40 @@
       this._elementsThatCannotThrow,
       this._functionsThatMightBePassedToApply);
 
+  factory InferredDataImpl.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld) {
+    source.begin(tag);
+    Set<MemberEntity> functionsCalledInLoop = source.readMembers().toSet();
+    Map<FunctionEntity, SideEffects> sideEffects =
+        source.readMemberMap(() => new SideEffects.readFromDataSource(source));
+    Set<FunctionEntity> sideEffectsFreeElements =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<FunctionEntity> elementsThatCannotThrow =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<FunctionEntity> functionsThatMightBePassedToApply =
+        source.readMembers<FunctionEntity>().toSet();
+    source.end(tag);
+    return new InferredDataImpl(
+        closedWorld,
+        functionsCalledInLoop,
+        sideEffects,
+        sideEffectsFreeElements,
+        elementsThatCannotThrow,
+        functionsThatMightBePassedToApply);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(false); // Is _not_ trivial.
+    sink.begin(tag);
+    sink.writeMembers(_functionsCalledInLoop);
+    sink.writeMemberMap(_sideEffects,
+        (SideEffects sideEffects) => sideEffects.writeToDataSink(sink));
+    sink.writeMembers(_sideEffectsFreeElements);
+    sink.writeMembers(_elementsThatCannotThrow);
+    sink.writeMembers(_functionsThatMightBePassedToApply);
+    sink.end(tag);
+  }
+
   @override
   SideEffects getSideEffectsOfSelector(
       Selector selector, AbstractValue receiver) {
@@ -251,6 +304,11 @@
   final SideEffects _allSideEffects = new SideEffects();
 
   @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(true); // Is trivial.
+  }
+
+  @override
   SideEffects getSideEffectsOfElement(FunctionEntity element) {
     return _allSideEffects;
   }
diff --git a/pkg/compiler/lib/src/js_backend/interceptor_data.dart b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
index f4db4d7..833c7c7 100644
--- a/pkg/compiler/lib/src/js_backend/interceptor_data.dart
+++ b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
@@ -10,6 +10,7 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../js/js.dart' as jsAst;
+import '../serialization/serialization.dart';
 import '../types/abstract_value_domain.dart';
 import '../universe/selector.dart';
 import '../world.dart' show JClosedWorld;
@@ -17,6 +18,15 @@
 import 'native_data.dart';
 
 abstract class InterceptorData {
+  /// Deserializes a [InterceptorData] object from [source].
+  factory InterceptorData.readFromDataSource(
+      DataSource source,
+      NativeData nativeData,
+      CommonElements commonElements) = InterceptorDataImpl.readFromDataSource;
+
+  /// Serializes this [InterceptorData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns `true` if [cls] is an intercepted class.
   bool isInterceptedClass(ClassEntity element);
 
@@ -49,6 +59,10 @@
 }
 
 class InterceptorDataImpl implements InterceptorData {
+  /// Tag used for identifying serialized [InterceptorData] objects in a
+  /// debugging data stream.
+  static const String tag = 'interceptor-data';
+
   final NativeBasicData _nativeData;
   final CommonElements _commonElements;
 
@@ -89,6 +103,40 @@
       this.interceptedClasses,
       this.classesMixedIntoInterceptedClasses);
 
+  factory InterceptorDataImpl.readFromDataSource(
+      DataSource source, NativeData nativeData, CommonElements commonElements) {
+    source.begin(tag);
+    int interceptedMembersCount = source.readInt();
+    Map<String, Set<MemberEntity>> interceptedMembers = {};
+    for (int i = 0; i < interceptedMembersCount; i++) {
+      String name = source.readString();
+      Set<MemberEntity> members = source.readMembers().toSet();
+      interceptedMembers[name] = members;
+    }
+    Set<ClassEntity> interceptedClasses = source.readClasses().toSet();
+    Set<ClassEntity> classesMixedIntoInterceptedClasses =
+        source.readClasses().toSet();
+    source.end(tag);
+    return new InterceptorDataImpl(
+        nativeData,
+        commonElements,
+        interceptedMembers,
+        interceptedClasses,
+        classesMixedIntoInterceptedClasses);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(interceptedMembers.length);
+    interceptedMembers.forEach((String name, Set<MemberEntity> members) {
+      sink.writeString(name);
+      sink.writeMembers(members);
+    });
+    sink.writeClasses(interceptedClasses);
+    sink.writeClasses(classesMixedIntoInterceptedClasses);
+    sink.end(tag);
+  }
+
   bool isInterceptedMethod(MemberEntity element) {
     if (!element.isInstanceMember) return false;
     // TODO(johnniwinther): Avoid this hack.
diff --git a/pkg/compiler/lib/src/js_backend/native_data.dart b/pkg/compiler/lib/src/js_backend/native_data.dart
index 40c3d28..e41e541 100644
--- a/pkg/compiler/lib/src/js_backend/native_data.dart
+++ b/pkg/compiler/lib/src/js_backend/native_data.dart
@@ -8,12 +8,21 @@
 import '../common_elements.dart' show ElementEnvironment;
 import '../elements/entities.dart';
 import '../native/behavior.dart' show NativeBehavior;
+import '../serialization/serialization.dart';
 import '../util/util.dart';
 
 /// Basic information for native classes and js-interop libraries and classes.
 ///
 /// This information is computed during loading using [NativeBasicDataBuilder].
 abstract class NativeBasicData {
+  /// Deserializes a [NativeBasicData] object from [source].
+  factory NativeBasicData.readFromDataSource(
+          DataSource source, ElementEnvironment elementEnvironment) =
+      NativeBasicDataImpl.readFromDataSource;
+
+  /// Serializes this [NativeBasicData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns `true` if [cls] corresponds to a native JavaScript class.
   ///
   /// A class is marked as native either through the `@Native(...)` annotation
@@ -48,6 +57,14 @@
 ///
 /// This information is computed during resolution using [NativeDataBuilder].
 abstract class NativeData extends NativeBasicData {
+  /// Deserializes a [NativeData] object from [source].
+  factory NativeData.readFromDataSource(
+          DataSource source, ElementEnvironment elementEnvironment) =
+      NativeDataImpl.readFromDataSource;
+
+  /// Serializes this [NativeData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns `true` if [element] corresponds to a native JavaScript member.
   ///
   /// A member is marked as native either through the native mechanism
@@ -267,6 +284,10 @@
 }
 
 class NativeBasicDataImpl implements NativeBasicData {
+  /// Tag used for identifying serialized [NativeBasicData] objects in a
+  /// debugging data stream.
+  static const String tag = 'native-basic-data';
+
   final ElementEnvironment _env;
 
   /// Tag info for native JavaScript classes names. See
@@ -293,6 +314,45 @@
       this.anonymousJsInteropClasses,
       this.jsInteropMembers);
 
+  factory NativeBasicDataImpl.readFromDataSource(
+      DataSource source, ElementEnvironment elementEnvironment) {
+    source.begin(tag);
+    Map<ClassEntity, NativeClassTag> nativeClassTagInfo =
+        source.readClassMap(() {
+      List<String> names = source.readStrings();
+      bool isNonLeaf = source.readBool();
+      return new NativeClassTag.internal(names, isNonLeaf);
+    });
+    Map<LibraryEntity, String> jsInteropLibraries =
+        source.readLibraryMap(source.readString);
+    Map<ClassEntity, String> jsInteropClasses =
+        source.readLibraryMap(source.readString);
+    Set<ClassEntity> anonymousJsInteropClasses = source.readClasses().toSet();
+    Map<MemberEntity, String> jsInteropMembers =
+        source.readLibraryMap(source.readString);
+    source.end(tag);
+    return new NativeBasicDataImpl(
+        elementEnvironment,
+        nativeClassTagInfo,
+        jsInteropLibraries,
+        jsInteropClasses,
+        anonymousJsInteropClasses,
+        jsInteropMembers);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeClassMap(nativeClassTagInfo, (NativeClassTag tag) {
+      sink.writeStrings(tag.names);
+      sink.writeBool(tag.isNonLeaf);
+    });
+    sink.writeLibraryMap(jsInteropLibraries, sink.writeString);
+    sink.writeClassMap(jsInteropClasses, sink.writeString);
+    sink.writeClasses(anonymousJsInteropClasses);
+    sink.writeMemberMap(jsInteropMembers, sink.writeString);
+    sink.end(tag);
+  }
+
   @override
   bool isNativeClass(ClassEntity element) {
     if (isJsInteropClass(element)) return true;
@@ -480,7 +540,13 @@
       jsInteropMembers);
 }
 
+// TODO(johnniwinther): Remove fields that overlap with [NativeBasicData], like
+// [anonymousJsInteropClasses].
 class NativeDataImpl implements NativeData, NativeBasicDataImpl {
+  /// Tag used for identifying serialized [NativeData] objects in a
+  /// debugging data stream.
+  static const String tag = 'native-data';
+
   /// Prefix used to escape JS names that are not valid Dart names
   /// when using JSInterop.
   static const String _jsInteropEscapePrefix = r'JS$';
@@ -525,6 +591,63 @@
       this.jsInteropClasses,
       this.jsInteropMembers);
 
+  factory NativeDataImpl.readFromDataSource(
+      DataSource source, ElementEnvironment elementEnvironment) {
+    source.begin(tag);
+    NativeBasicData nativeBasicData =
+        new NativeBasicData.readFromDataSource(source, elementEnvironment);
+    Map<MemberEntity, String> nativeMemberName =
+        source.readMemberMap(source.readString);
+    Map<FunctionEntity, NativeBehavior> nativeMethodBehavior = source
+        .readMemberMap(() => new NativeBehavior.readFromDataSource(source));
+    Map<MemberEntity, NativeBehavior> nativeFieldLoadBehavior = source
+        .readMemberMap(() => new NativeBehavior.readFromDataSource(source));
+    Map<MemberEntity, NativeBehavior> nativeFieldStoreBehavior = source
+        .readMemberMap(() => new NativeBehavior.readFromDataSource(source));
+    Map<LibraryEntity, String> jsInteropLibraries =
+        source.readLibraryMap(source.readString);
+    Set<ClassEntity> anonymousJsInteropClasses = source.readClasses().toSet();
+    Map<ClassEntity, String> jsInteropClasses =
+        source.readClassMap(source.readString);
+    Map<MemberEntity, String> jsInteropMembers =
+        source.readMemberMap(source.readString);
+    source.end(tag);
+    return new NativeDataImpl(
+        nativeBasicData,
+        nativeMemberName,
+        nativeMethodBehavior,
+        nativeFieldLoadBehavior,
+        nativeFieldStoreBehavior,
+        jsInteropLibraries,
+        anonymousJsInteropClasses,
+        jsInteropClasses,
+        jsInteropMembers);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    _nativeBasicData.writeToDataSink(sink);
+
+    sink.writeMemberMap(nativeMemberName, sink.writeString);
+
+    sink.writeMemberMap(nativeMethodBehavior, (NativeBehavior behavior) {
+      behavior.writeToDataSink(sink);
+    });
+
+    sink.writeMemberMap(nativeFieldLoadBehavior, (NativeBehavior behavior) {
+      behavior.writeToDataSink(sink);
+    });
+    sink.writeMemberMap(nativeFieldStoreBehavior, (NativeBehavior behavior) {
+      behavior.writeToDataSink(sink);
+    });
+
+    sink.writeLibraryMap(jsInteropLibraries, sink.writeString);
+    sink.writeClasses(anonymousJsInteropClasses);
+    sink.writeClassMap(jsInteropClasses, sink.writeString);
+    sink.writeMemberMap(jsInteropMembers, sink.writeString);
+    sink.end(tag);
+  }
+
   @override
   bool isAnonymousJsInteropClass(ClassEntity element) {
     return anonymousJsInteropClasses.contains(element);
diff --git a/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart b/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
index 4c47390..e997b3e 100644
--- a/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
+++ b/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
@@ -6,6 +6,7 @@
 import '../common_elements.dart' show CommonElements;
 import '../common/names.dart' show Identifiers, Selectors;
 import '../elements/entities.dart';
+import '../serialization/serialization.dart';
 import '../types/types.dart';
 
 /// [NoSuchMethodRegistry] and [NoSuchMethodData] categorizes `noSuchMethod`
@@ -171,6 +172,13 @@
 /// Post inference collected category `D` methods are into subcategories `D1`
 /// and `D2`.
 abstract class NoSuchMethodData {
+  /// Deserializes a [NoSuchMethodData] object from [source].
+  factory NoSuchMethodData.readFromDataSource(DataSource source) =
+      NoSuchMethodDataImpl.readFromDataSource;
+
+  /// Serializes this [NoSuchMethodData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns [true] if the given element is a complex [noSuchMethod]
   /// implementation. An implementation is complex if it falls into
   /// category D, as described above.
@@ -186,6 +194,10 @@
 }
 
 class NoSuchMethodDataImpl implements NoSuchMethodData {
+  /// Tag used for identifying serialized [NoSuchMethodData] objects in a
+  /// debugging data stream.
+  static const String tag = 'no-such-method-data';
+
   /// The implementations that fall into category B, described above.
   final Set<FunctionEntity> throwingImpls;
 
@@ -203,6 +215,35 @@
   NoSuchMethodDataImpl(
       this.throwingImpls, this.otherImpls, this.forwardingSyntaxImpls);
 
+  factory NoSuchMethodDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Set<FunctionEntity> throwingImpls =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<FunctionEntity> otherImpls =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<FunctionEntity> forwardingSyntaxImpls =
+        source.readMembers<FunctionEntity>().toSet();
+    List<FunctionEntity> complexNoReturnImpls =
+        source.readMembers<FunctionEntity>();
+    List<FunctionEntity> complexReturningImpls =
+        source.readMembers<FunctionEntity>();
+    source.end(tag);
+    return new NoSuchMethodDataImpl(
+        throwingImpls, otherImpls, forwardingSyntaxImpls)
+      ..complexNoReturnImpls.addAll(complexNoReturnImpls)
+      ..complexReturningImpls.addAll(complexReturningImpls);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMembers(throwingImpls);
+    sink.writeMembers(otherImpls);
+    sink.writeMembers(forwardingSyntaxImpls);
+    sink.writeMembers(complexNoReturnImpls);
+    sink.writeMembers(complexReturningImpls);
+    sink.end(tag);
+  }
+
   /// Now that type inference is complete, split category D into two
   /// subcategories: D1, those that have no return type, and D2, those
   /// that have a return type.
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types.dart b/pkg/compiler/lib/src/js_backend/runtime_types.dart
index daa1915..3329424 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types.dart
@@ -19,6 +19,7 @@
 import '../js/js.dart' show js;
 import '../js_emitter/js_emitter.dart' show Emitter;
 import '../options.dart';
+import '../serialization/serialization.dart';
 import '../universe/class_hierarchy.dart';
 import '../universe/feature.dart';
 import '../universe/selector.dart';
@@ -42,6 +43,20 @@
 
 /// Interface for the classes and methods that need runtime types.
 abstract class RuntimeTypesNeed {
+  /// Deserializes a [RuntimeTypesNeed] object from [source].
+  factory RuntimeTypesNeed.readFromDataSource(
+      DataSource source, ElementEnvironment elementEnvironment) {
+    bool isTrivial = source.readBool();
+    if (isTrivial) {
+      return const TrivialRuntimeTypesNeed();
+    }
+    return new RuntimeTypesNeedImpl.readFromDataSource(
+        source, elementEnvironment);
+  }
+
+  /// Serializes this [RuntimeTypesNeed] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns `true` if [cls] needs type arguments at runtime type.
   ///
   /// This is for instance the case for generic classes used in a type test:
@@ -106,6 +121,10 @@
 class TrivialRuntimeTypesNeed implements RuntimeTypesNeed {
   const TrivialRuntimeTypesNeed();
 
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(true); // Is trivial.
+  }
+
   @override
   bool classNeedsTypeArguments(ClassEntity cls) => true;
 
@@ -718,6 +737,10 @@
 }
 
 class RuntimeTypesNeedImpl implements RuntimeTypesNeed {
+  /// Tag used for identifying serialized [RuntimeTypesNeed] objects in a
+  /// debugging data stream.
+  static const String tag = 'runtime-types-need';
+
   final ElementEnvironment _elementEnvironment;
   final Set<ClassEntity> classesNeedingTypeArguments;
   final Set<FunctionEntity> methodsNeedingSignature;
@@ -737,6 +760,45 @@
       this.selectorsNeedingTypeArguments,
       this.instantiationsNeedingTypeArguments);
 
+  factory RuntimeTypesNeedImpl.readFromDataSource(
+      DataSource source, ElementEnvironment elementEnvironment) {
+    source.begin(tag);
+    Set<ClassEntity> classesNeedingTypeArguments =
+        source.readClasses<ClassEntity>().toSet();
+    Set<FunctionEntity> methodsNeedingSignature =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<FunctionEntity> methodsNeedingTypeArguments =
+        source.readMembers<FunctionEntity>().toSet();
+    Set<Selector> selectorsNeedingTypeArguments =
+        source.readList(() => new Selector.readFromDataSource(source)).toSet();
+    Set<int> instantiationsNeedingTypeArguments =
+        source.readList(source.readInt).toSet();
+    source.end(tag);
+    return new RuntimeTypesNeedImpl(
+        elementEnvironment,
+        classesNeedingTypeArguments,
+        methodsNeedingSignature,
+        methodsNeedingTypeArguments,
+        null,
+        null,
+        selectorsNeedingTypeArguments,
+        instantiationsNeedingTypeArguments);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(false); // Is _not_ trivial.
+    sink.begin(tag);
+    sink.writeClasses(classesNeedingTypeArguments);
+    sink.writeMembers(methodsNeedingSignature);
+    sink.writeMembers(methodsNeedingTypeArguments);
+    assert(localFunctionsNeedingSignature == null);
+    assert(localFunctionsNeedingTypeArguments == null);
+    sink.writeList(selectorsNeedingTypeArguments,
+        (Selector selector) => selector.writeToDataSink(sink));
+    sink.writeList(instantiationsNeedingTypeArguments, sink.writeInt);
+    sink.end(tag);
+  }
+
   bool checkClass(covariant ClassEntity cls) => true;
 
   bool classNeedsTypeArguments(ClassEntity cls) {
diff --git a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
index cc96103..063d03f 100644
--- a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
+++ b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
@@ -201,7 +201,7 @@
           closedWorld.allocatorAnalysis,
           inferredData,
           backend.sourceInformationStrategy,
-          compiler.backendStrategy.sorter,
+          closedWorld.sorter,
           typeTestRegistry.rtiNeededClasses,
           closedWorld.elementEnvironment.mainFunction);
       int size = emitter.emitProgram(programBuilder);
diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
index 0a0c184..a500d9d 100644
--- a/pkg/compiler/lib/src/js_model/closure.dart
+++ b/pkg/compiler/lib/src/js_model/closure.dart
@@ -18,6 +18,7 @@
 import '../js_model/env.dart';
 import '../ordered_typeset.dart';
 import '../options.dart';
+import '../serialization/serialization.dart';
 import '../ssa/type_builder.dart';
 import '../universe/selector.dart';
 import 'elements.dart';
@@ -66,6 +67,8 @@
 }
 
 class ClosureDataImpl implements ClosureData {
+  /// Tag used for identifying serialized [ClosureData] objects in a
+  /// debugging data stream.
   static const String tag = 'closure-data';
 
   final JsToElementMap _elementMap;
@@ -84,6 +87,42 @@
   ClosureDataImpl(this._elementMap, this._scopeMap, this._capturedScopesMap,
       this._capturedScopeForSignatureMap, this._localClosureRepresentationMap);
 
+  /// Deserializes a [ClosureData] object from [source].
+  factory ClosureDataImpl.readFromDataSource(
+      JsToElementMap elementMap, DataSource source) {
+    source.begin(tag);
+    // TODO(johnniwinther): Support shared [ScopeInfo].
+    Map<MemberEntity, ScopeInfo> scopeMap =
+        source.readMemberMap(() => new ScopeInfo.readFromDataSource(source));
+    Map<ir.TreeNode, CapturedScope> capturedScopesMap = source
+        .readTreeNodeMap(() => new CapturedScope.readFromDataSource(source));
+    Map<MemberEntity, CapturedScope> capturedScopeForSignatureMap = source
+        .readMemberMap(() => new CapturedScope.readFromDataSource(source));
+    Map<ir.TreeNode, ClosureRepresentationInfo> localClosureRepresentationMap =
+        source.readTreeNodeMap(
+            () => new ClosureRepresentationInfo.readFromDataSource(source));
+    source.end(tag);
+    return new ClosureDataImpl(elementMap, scopeMap, capturedScopesMap,
+        capturedScopeForSignatureMap, localClosureRepresentationMap);
+  }
+
+  /// Serializes this [ClosureData] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMemberMap(
+        _scopeMap, (ScopeInfo info) => info.writeToDataSink(sink));
+    sink.writeTreeNodeMap(_capturedScopesMap, (CapturedScope scope) {
+      scope.writeToDataSink(sink);
+    });
+    sink.writeMemberMap(_capturedScopeForSignatureMap,
+        (CapturedScope scope) => scope.writeToDataSink(sink));
+    sink.writeTreeNodeMap(_localClosureRepresentationMap,
+        (ClosureRepresentationInfo info) {
+      info.writeToDataSink(sink);
+    });
+    sink.end(tag);
+  }
+
   @override
   ScopeInfo getScopeInfo(MemberEntity entity) {
     // TODO(johnniwinther): Remove this check when constructor bodies a created
@@ -659,7 +698,11 @@
 }
 
 class JsScopeInfo extends ScopeInfo {
-  final Set<Local> localsUsedInTryOrSync;
+  /// Tag used for identifying serialized [JsScopeInfo] objects in a
+  /// debugging data stream.
+  static const String tag = 'scope-info';
+
+  final Iterable<Local> localsUsedInTryOrSync;
   final Local thisLocal;
   final Map<Local, JRecordField> boxedVariables;
 
@@ -702,6 +745,29 @@
   }
 
   bool isBoxed(Local variable) => boxedVariables.containsKey(variable);
+
+  factory JsScopeInfo.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Local thisLocal = source.readLocalOrNull();
+    Map<Local, JRecordField> boxedVariables =
+        source.readLocalMap<Local, JRecordField>(() => source.readMember());
+    Set<Local> freeVariables = source.readLocals().toSet();
+    source.end(tag);
+    return new JsScopeInfo.internal(
+        localsUsedInTryOrSync, thisLocal, boxedVariables, freeVariables);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ScopeInfoKind.scopeInfo);
+    sink.begin(tag);
+    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeLocalOrNull(thisLocal);
+    sink.writeLocalMap(boxedVariables, sink.writeMember);
+    sink.writeLocals(freeVariables);
+    sink.end(tag);
+  }
 }
 
 class KernelCapturedScope extends KernelScopeInfo {
@@ -746,8 +812,21 @@
 }
 
 class JsCapturedScope extends JsScopeInfo implements CapturedScope {
+  /// Tag used for identifying serialized [JsCapturedScope] objects in a
+  /// debugging data stream.
+  static const String tag = 'captured-scope';
+
   final Local context;
 
+  JsCapturedScope.internal(
+      Iterable<Local> localsUsedInTryOrSync,
+      Local thisLocal,
+      Map<Local, JRecordField> boxedVariables,
+      Set<Local> freeVariables,
+      this.context)
+      : super.internal(
+            localsUsedInTryOrSync, thisLocal, boxedVariables, freeVariables);
+
   JsCapturedScope.from(
       Map<Local, JRecordField> boxedVariables,
       KernelCapturedScope capturedScope,
@@ -758,6 +837,31 @@
         super.from(boxedVariables, capturedScope, localsMap, elementMap);
 
   bool get requiresContextBox => boxedVariables.isNotEmpty;
+
+  factory JsCapturedScope.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Local thisLocal = source.readLocalOrNull();
+    Map<Local, JRecordField> boxedVariables =
+        source.readLocalMap<Local, JRecordField>(() => source.readMember());
+    Set<Local> freeVariables = source.readLocals().toSet();
+    Local context = source.readLocalOrNull();
+    source.end(tag);
+    return new JsCapturedScope.internal(localsUsedInTryOrSync, thisLocal,
+        boxedVariables, freeVariables, context);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ScopeInfoKind.capturedScope);
+    sink.begin(tag);
+    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeLocalOrNull(thisLocal);
+    sink.writeLocalMap(boxedVariables, sink.writeMember);
+    sink.writeLocals(freeVariables);
+    sink.writeLocalOrNull(context);
+    sink.end(tag);
+  }
 }
 
 class KernelCapturedLoopScope extends KernelCapturedScope {
@@ -788,8 +892,22 @@
 }
 
 class JsCapturedLoopScope extends JsCapturedScope implements CapturedLoopScope {
+  /// Tag used for identifying serialized [JsCapturedLoopScope] objects in a
+  /// debugging data stream.
+  static const String tag = 'captured-loop-scope';
+
   final List<Local> boxedLoopVariables;
 
+  JsCapturedLoopScope.internal(
+      Iterable<Local> localsUsedInTryOrSync,
+      Local thisLocal,
+      Map<Local, JRecordField> boxedVariables,
+      Set<Local> freeVariables,
+      Local context,
+      this.boxedLoopVariables)
+      : super.internal(localsUsedInTryOrSync, thisLocal, boxedVariables,
+            freeVariables, context);
+
   JsCapturedLoopScope.from(
       Map<Local, JRecordField> boxedVariables,
       KernelCapturedLoopScope capturedScope,
@@ -801,11 +919,43 @@
         super.from(boxedVariables, capturedScope, localsMap, elementMap);
 
   bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
+
+  factory JsCapturedLoopScope.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Local thisLocal = source.readLocalOrNull();
+    Map<Local, JRecordField> boxedVariables =
+        source.readLocalMap<Local, JRecordField>(() => source.readMember());
+    Set<Local> freeVariables = source.readLocals().toSet();
+    Local context = source.readLocalOrNull();
+    List<Local> boxedLoopVariables = source.readLocals();
+    source.end(tag);
+    return new JsCapturedLoopScope.internal(localsUsedInTryOrSync, thisLocal,
+        boxedVariables, freeVariables, context, boxedLoopVariables);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ScopeInfoKind.capturedLoopScope);
+    sink.begin(tag);
+    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeLocalOrNull(thisLocal);
+    sink.writeLocalMap(boxedVariables, sink.writeMember);
+    sink.writeLocals(freeVariables);
+    sink.writeLocalOrNull(context);
+    sink.writeLocals(boxedLoopVariables);
+    sink.end(tag);
+  }
 }
 
+// TODO(johnniwinther): Rename this class.
 // TODO(johnniwinther): Add unittest for the computed [ClosureClass].
 class KernelClosureClassInfo extends JsScopeInfo
     implements ClosureRepresentationInfo {
+  /// Tag used for identifying serialized [KernelClosureClassInfo] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-representation-info';
+
   JFunction callMethod;
   JSignatureMethod signatureMethod;
   final Local closureEntity;
@@ -839,6 +989,47 @@
       : localToFieldMap = new Map<Local, JField>(),
         super.from(boxedVariables, info, localsMap, elementMap);
 
+  factory KernelClosureClassInfo.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Local thisLocal = source.readLocalOrNull();
+    Map<Local, JRecordField> boxedVariables =
+        source.readLocalMap<Local, JRecordField>(() => source.readMember());
+    Set<Local> freeVariables = source.readLocals().toSet();
+    JFunction callMethod = source.readMember();
+    JSignatureMethod signatureMethod = source.readMemberOrNull();
+    Local closureEntity = source.readLocalOrNull();
+    JClass closureClassEntity = source.readClass();
+    Map<Local, JField> localToFieldMap =
+        source.readLocalMap(() => source.readMember());
+    source.end(tag);
+    return new KernelClosureClassInfo.internal(
+        localsUsedInTryOrSync,
+        thisLocal,
+        boxedVariables,
+        freeVariables,
+        callMethod,
+        signatureMethod,
+        closureEntity,
+        closureClassEntity,
+        localToFieldMap);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ScopeInfoKind.closureRepresentationInfo);
+    sink.begin(tag);
+    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeLocalOrNull(thisLocal);
+    sink.writeLocalMap(boxedVariables, sink.writeMember);
+    sink.writeLocals(freeVariables);
+    sink.writeMember(callMethod);
+    sink.writeMemberOrNull(signatureMethod);
+    sink.writeLocalOrNull(closureEntity);
+    sink.writeClass(closureClassEntity);
+    sink.writeLocalMap(localToFieldMap, sink.writeMember);
+    sink.end(tag);
+  }
+
   List<Local> get createdFieldEntities => localToFieldMap.keys.toList();
 
   @override
@@ -880,9 +1071,29 @@
 }
 
 class JClosureClass extends JClass {
+  /// Tag used for identifying serialized [JClosureClass] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-class';
+
   JClosureClass(JLibrary library, String name)
       : super(library, name, isAbstract: false);
 
+  factory JClosureClass.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JLibrary library = source.readLibrary();
+    String name = source.readString();
+    source.end(tag);
+    return new JClosureClass(library, name);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassKind.closure);
+    sink.begin(tag);
+    sink.writeLibrary(library);
+    sink.writeString(name);
+    sink.end(tag);
+  }
+
   @override
   bool get isClosure => true;
 
@@ -909,6 +1120,10 @@
 }
 
 class JClosureField extends JField implements PrivatelyNamedJSEntity {
+  /// Tag used for identifying serialized [JClosureClass] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-field';
+
   final String declaredName;
 
   JClosureField(
@@ -927,12 +1142,40 @@
       : super(library, enclosingClass, memberName,
             isAssignable: isAssignable, isConst: isConst, isStatic: false);
 
+  factory JClosureField.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JClass cls = source.readClass();
+    String name = source.readString();
+    String declaredName = source.readString();
+    bool isConst = source.readBool();
+    bool isAssignable = source.readBool();
+    source.end(tag);
+    return new JClosureField.internal(
+        cls.library, cls, new Name(name, cls.library), declaredName,
+        isAssignable: isAssignable, isConst: isConst);
+  }
+
   @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.closureField);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass);
+    sink.writeString(name);
+    sink.writeString(declaredName);
+    sink.writeBool(isConst);
+    sink.writeBool(isAssignable);
+    sink.end(tag);
+  }
+
   @override
   Entity get rootOfScope => enclosingClass;
 }
 
 class RecordClassData implements JClassData {
+  /// Tag used for identifying serialized [RecordClassData] objects in a
+  /// debugging data stream.
+  static const String tag = 'record-class-data';
+
   @override
   final ClassDefinition definition;
 
@@ -948,6 +1191,28 @@
   RecordClassData(
       this.definition, this.thisType, this.supertype, this.orderedTypeSet);
 
+  factory RecordClassData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ClassDefinition definition = new ClassDefinition.readFromDataSource(source);
+    InterfaceType thisType = source.readDartType();
+    InterfaceType supertype = source.readDartType();
+    OrderedTypeSet orderedTypeSet =
+        new OrderedTypeSet.readFromDataSource(source);
+    source.end(tag);
+    return new RecordClassData(definition, thisType, supertype, orderedTypeSet);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassDataKind.record);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartType(thisType);
+    sink.writeDartType(supertype);
+    orderedTypeSet.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   @override
   bool get isMixinApplication => false;
 
@@ -969,12 +1234,32 @@
 
 /// A container for variables declared in a particular scope that are accessed
 /// elsewhere.
-// TODO(efortuna, johnniwinther): Don't implement JClass. This isn't actually a
+// TODO(johnniwinther): Don't implement JClass. This isn't actually a
 // class.
 class JRecord extends JClass {
+  /// Tag used for identifying serialized [JRecord] objects in a
+  /// debugging data stream.
+  static const String tag = 'record';
+
   JRecord(LibraryEntity library, String name)
       : super(library, name, isAbstract: false);
 
+  factory JRecord.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JLibrary library = source.readLibrary();
+    String name = source.readString();
+    source.end(tag);
+    return new JRecord(library, name);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassKind.record);
+    sink.begin(tag);
+    sink.writeLibrary(library);
+    sink.writeString(name);
+    sink.end(tag);
+  }
+
   bool get isClosure => false;
 
   String toString() => '${jsElementPrefix}record_container($name)';
@@ -986,6 +1271,10 @@
 /// This corresponds to BoxFieldElement; we reuse BoxLocal from the original
 /// algorithm to correspond to the actual name of the variable.
 class JRecordField extends JField {
+  /// Tag used for identifying serialized [JRecordField] objects in a
+  /// debugging data stream.
+  static const String tag = 'record-field';
+
   final BoxLocal box;
 
   JRecordField(String name, this.box, {bool isConst})
@@ -993,24 +1282,92 @@
             new Name(name, box.container.library),
             isStatic: false, isAssignable: true, isConst: isConst);
 
+  factory JRecordField.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    String name = source.readString();
+    JClass enclosingClass = source.readClass();
+    bool isConst = source.readBool();
+    source.end(tag);
+    return new JRecordField(name, new BoxLocal(enclosingClass),
+        isConst: isConst);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.recordField);
+    sink.begin(tag);
+    sink.writeString(name);
+    sink.writeClass(enclosingClass);
+    sink.writeBool(isConst);
+    sink.end(tag);
+  }
+
   @override
   bool get isInstanceMember => false;
 }
 
 class ClosureClassData extends RecordClassData {
+  /// Tag used for identifying serialized [ClosureClassData] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-class-data';
+
   @override
   FunctionType callType;
 
   ClosureClassData(ClassDefinition definition, InterfaceType thisType,
       InterfaceType supertype, OrderedTypeSet orderedTypeSet)
       : super(definition, thisType, supertype, orderedTypeSet);
+
+  factory ClosureClassData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ClassDefinition definition = new ClassDefinition.readFromDataSource(source);
+    InterfaceType thisType = source.readDartType();
+    InterfaceType supertype = source.readDartType();
+    OrderedTypeSet orderedTypeSet =
+        new OrderedTypeSet.readFromDataSource(source);
+    FunctionType callType = source.readDartType();
+    source.end(tag);
+    return new ClosureClassData(definition, thisType, supertype, orderedTypeSet)
+      ..callType = callType;
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassDataKind.closure);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartType(thisType);
+    sink.writeDartType(supertype);
+    orderedTypeSet.writeToDataSink(sink);
+    sink.writeDartType(callType);
+    sink.end(tag);
+  }
 }
 
 class ClosureClassDefinition implements ClassDefinition {
+  /// Tag used for identifying serialized [ClosureClassDefinition] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-class-definition';
+
   final SourceSpan location;
 
   ClosureClassDefinition(this.location);
 
+  factory ClosureClassDefinition.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    SourceSpan location = source.readSourceSpan();
+    source.end(tag);
+    return new ClosureClassDefinition(location);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ClassKind.closure);
+    sink.begin(tag);
+    sink.writeSourceSpan(location);
+    sink.end(tag);
+  }
+
   ClassKind get kind => ClassKind.closure;
 
   ir.Node get node =>
@@ -1034,6 +1391,10 @@
 class ClosureFunctionData extends ClosureMemberData
     with FunctionDataMixin
     implements FunctionData {
+  /// Tag used for identifying serialized [ClosureFunctionData] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-function-data';
+
   final FunctionType functionType;
   final ir.FunctionNode functionNode;
   final ClassTypeVariableAccess classTypeVariableAccess;
@@ -1046,6 +1407,31 @@
       this.classTypeVariableAccess)
       : super(definition, memberThisType);
 
+  factory ClosureFunctionData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ClosureMemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    FunctionType functionType = source.readDartType();
+    ir.FunctionNode functionNode = source.readTreeNode();
+    ClassTypeVariableAccess classTypeVariableAccess =
+        source.readEnum(ClassTypeVariableAccess.values);
+    source.end(tag);
+    return new ClosureFunctionData(definition, memberThisType, functionType,
+        functionNode, classTypeVariableAccess);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.closureFunction);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartType(memberThisType, allowNull: true);
+    sink.writeDartType(functionType);
+    sink.writeTreeNode(functionNode);
+    sink.writeEnum(classTypeVariableAccess);
+    sink.end(tag);
+  }
+
   void forEachParameter(JsToElementMap elementMap,
       void f(DartType type, String name, ConstantValue defaultValue)) {
     void handleParameter(ir.VariableDeclaration node, {bool isOptional: true}) {
@@ -1078,10 +1464,32 @@
 }
 
 class ClosureFieldData extends ClosureMemberData implements JFieldData {
+  /// Tag used for identifying serialized [ClosureFieldData] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-field-data';
+
   DartType _type;
+
   ClosureFieldData(MemberDefinition definition, InterfaceType memberThisType)
       : super(definition, memberThisType);
 
+  factory ClosureFieldData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    source.end(tag);
+    return new ClosureFieldData(definition, memberThisType);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.closureField);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartType(memberThisType, allowNull: true);
+    sink.end(tag);
+  }
+
   @override
   DartType getFieldType(IrToElementMap elementMap) {
     if (_type != null) return _type;
@@ -1142,6 +1550,10 @@
 }
 
 class ClosureMemberDefinition implements MemberDefinition {
+  /// Tag used for identifying serialized [ClosureMemberDefinition] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-member-definition';
+
   final SourceSpan location;
   final MemberKind kind;
   final ir.TreeNode node;
@@ -1150,14 +1562,50 @@
       : assert(
             kind == MemberKind.closureCall || kind == MemberKind.closureField);
 
+  factory ClosureMemberDefinition.readFromDataSource(
+      DataSource source, MemberKind kind) {
+    source.begin(tag);
+    SourceSpan location = source.readSourceSpan();
+    ir.TreeNode node = source.readTreeNode();
+    source.end(tag);
+    return new ClosureMemberDefinition(location, kind, node);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(kind);
+    sink.begin(tag);
+    sink.writeSourceSpan(location);
+    sink.writeTreeNode(node);
+    sink.end(tag);
+  }
+
   String toString() => 'ClosureMemberDefinition(kind:$kind,location:$location)';
 }
 
 class RecordContainerDefinition implements ClassDefinition {
+  /// Tag used for identifying serialized [RecordContainerDefinition] objects in
+  /// a debugging data stream.
+  static const String tag = 'record-definition';
+
   final SourceSpan location;
 
   RecordContainerDefinition(this.location);
 
+  factory RecordContainerDefinition.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    SourceSpan location = source.readSourceSpan();
+    source.end(tag);
+    return new RecordContainerDefinition(location);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(ClassKind.record);
+    sink.begin(tag);
+    sink.writeSourceSpan(location);
+    sink.end(tag);
+  }
+
   ClassKind get kind => ClassKind.record;
 
   ir.Node get node => throw new UnsupportedError(
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 13a6bea..22a306b 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -18,11 +18,13 @@
 import '../js_model/closure.dart' show JRecordField, KernelScopeInfo;
 import '../js_model/elements.dart' show JGeneratorBody;
 import '../native/native.dart' as native;
+import '../serialization/serialization.dart';
 import '../ssa/type_builder.dart';
 import '../types/abstract_value_domain.dart';
 import '../universe/call_structure.dart';
 import '../universe/selector.dart';
 import '../world.dart';
+import 'closure.dart';
 
 /// Interface that translates between Kernel IR nodes and entities used for
 /// global type inference and building the SSA graph for members.
@@ -273,6 +275,9 @@
   /// Returns the [JumpTarget] defined by the while statement [node] or `null`
   /// if [node] is not a jump target.
   JumpTarget getJumpTargetForWhile(ir.WhileStatement node);
+
+  /// Serializes this [KernelToLocalsMap] to [sink].
+  void writeToDataSink(DataSink sink);
 }
 
 /// Returns the [ir.FunctionNode] that defines [member] or `null` if [member]
@@ -346,6 +351,27 @@
   /// The canonical location of [member]. This is used for sorting the members
   /// in the emitted code.
   SourceSpan get location;
+
+  /// Deserializes a [MemberDefinition] object from [source].
+  factory MemberDefinition.readFromDataSource(DataSource source) {
+    MemberKind kind = source.readEnum(MemberKind.values);
+    switch (kind) {
+      case MemberKind.regular:
+        return new RegularMemberDefinition.readFromDataSource(source);
+      case MemberKind.constructor:
+      case MemberKind.constructorBody:
+      case MemberKind.signature:
+      case MemberKind.generatorBody:
+        return new SpecialMemberDefinition.readFromDataSource(source, kind);
+      case MemberKind.closureCall:
+      case MemberKind.closureField:
+        return new ClosureMemberDefinition.readFromDataSource(source, kind);
+    }
+    throw new UnsupportedError("Unexpected MemberKind $kind");
+  }
+
+  /// Serializes this [MemberDefinition] to [sink].
+  void writeToDataSink(DataSink sink);
 }
 
 enum ClassKind {
@@ -358,10 +384,29 @@
 
 /// A member directly defined by its [ir.Member] node.
 class RegularMemberDefinition implements MemberDefinition {
+  /// Tag used for identifying serialized [RegularMemberDefinition] objects in a
+  /// debugging data stream.
+  static const String tag = 'regular-member-definition';
+
   final ir.Member node;
 
   RegularMemberDefinition(this.node);
 
+  factory RegularMemberDefinition.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Member node = source.readMemberNode();
+    source.end(tag);
+    return new RegularMemberDefinition(node);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(MemberKind.regular);
+    sink.begin(tag);
+    sink.writeMemberNode(node);
+    sink.end(tag);
+  }
+
   SourceSpan get location => computeSourceSpanFromTreeNode(node);
 
   MemberKind get kind => MemberKind.regular;
@@ -372,11 +417,31 @@
 
 /// The definition of a special kind of member
 class SpecialMemberDefinition implements MemberDefinition {
+  /// Tag used for identifying serialized [SpecialMemberDefinition] objects in a
+  /// debugging data stream.
+  static const String tag = 'special-member-definition';
+
   final ir.TreeNode node;
   final MemberKind kind;
 
   SpecialMemberDefinition(this.node, this.kind);
 
+  factory SpecialMemberDefinition.readFromDataSource(
+      DataSource source, MemberKind kind) {
+    source.begin(tag);
+    ir.TreeNode node = source.readTreeNode();
+    source.end(tag);
+    return new SpecialMemberDefinition(node, kind);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(kind);
+    sink.begin(tag);
+    sink.writeTreeNode(node);
+    sink.end(tag);
+  }
+
   SourceSpan get location => computeSourceSpanFromTreeNode(node);
 
   String toString() => 'SpecialMemberDefinition(kind:$kind,'
@@ -394,14 +459,49 @@
   /// The canonical location of [cls]. This is used for sorting the classes
   /// in the emitted code.
   SourceSpan get location;
+
+  /// Deserializes a [ClassDefinition] object from [source].
+  factory ClassDefinition.readFromDataSource(DataSource source) {
+    ClassKind kind = source.readEnum(ClassKind.values);
+    switch (kind) {
+      case ClassKind.regular:
+        return new RegularClassDefinition.readFromDataSource(source);
+      case ClassKind.closure:
+        return new ClosureClassDefinition.readFromDataSource(source);
+      case ClassKind.record:
+        return new RecordContainerDefinition.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unexpected ClassKind $kind");
+  }
+
+  /// Serializes this [ClassDefinition] to [sink].
+  void writeToDataSink(DataSink sink);
 }
 
 /// A class directly defined by its [ir.Class] node.
 class RegularClassDefinition implements ClassDefinition {
+  /// Tag used for identifying serialized [RegularClassDefinition] objects in a
+  /// debugging data stream.
+  static const String tag = 'regular-class-definition';
+
   final ir.Class node;
 
   RegularClassDefinition(this.node);
 
+  factory RegularClassDefinition.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Class node = source.readClassNode();
+    source.end(tag);
+    return new RegularClassDefinition(node);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(kind);
+    sink.begin(tag);
+    sink.writeClassNode(node);
+    sink.end(tag);
+  }
+
   SourceSpan get location => computeSourceSpanFromTreeNode(node);
 
   ClassKind get kind => ClassKind.regular;
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index bc3122a..dccda12 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -43,6 +43,7 @@
 import '../native/native.dart' as native;
 import '../options.dart';
 import '../ordered_typeset.dart';
+import '../serialization/serialization.dart';
 import '../ssa/type_builder.dart';
 import '../universe/call_structure.dart';
 import '../universe/selector.dart';
@@ -1442,6 +1443,23 @@
 class JsKernelToElementMap extends JsToElementMapBase
     with JsElementCreatorMixin
     implements JsToWorldBuilder, JsToElementMap {
+  /// Tag used for identifying serialized [JsKernelToElementMap] objects in a
+  /// debugging data stream.
+  static const String tag = 'js-kernel-to-element-map';
+
+  /// Tags used for identifying serialized subsections of a
+  /// [JsKernelToElementMap] object in a debugging data stream.
+  static const String libraryTag = 'libraries';
+  static const String classTag = 'classes';
+  static const String typedefTag = 'typedefs';
+  static const String memberTag = 'members';
+  static const String typeVariableTag = 'type-variables';
+  static const String libraryDataTag = 'library-data';
+  static const String classDataTag = 'class-data';
+  static const String typedefDataTag = 'typedef-data';
+  static const String memberDataTag = 'member-data';
+  static const String typeVariableDataTag = 'type-variable-data';
+
   final Map<ir.Library, IndexedLibrary> libraryMap = {};
   final Map<ir.Class, IndexedClass> classMap = {};
   final Map<ir.Typedef, IndexedTypedef> typedefMap = {};
@@ -1574,7 +1592,6 @@
       assert(newTypeVariable.typeVariableIndex ==
           oldTypeVariable.typeVariableIndex);
     }
-    //typeVariableMap.keys.forEach((n) => print(n.parent));
     // TODO(johnniwinther): We should close the environment in the beginning of
     // this constructor but currently we need the [MemberEntity] to query if the
     // member is live, thus potentially creating the [MemberEntity] in the
@@ -1582,6 +1599,207 @@
     _elementMap.envIsClosed = true;
   }
 
+  JsKernelToElementMap.readFromDataSource(
+      CompilerOptions options,
+      DiagnosticReporter reporter,
+      Environment environment,
+      ir.Component component,
+      DataSource source)
+      : super(options, reporter, environment) {
+    source.registerComponentLookup(new ComponentLookup(component));
+    _EntityLookup entityLookup = new _EntityLookup();
+    source.registerEntityLookup(entityLookup);
+
+    source.begin(tag);
+    source.begin(libraryTag);
+    int libraryCount = source.readInt();
+    for (int i = 0; i < libraryCount; i++) {
+      int index = source.readInt();
+      JLibrary library = new JLibrary.readFromDataSource(source);
+      entityLookup.registerLibrary(index, library);
+    }
+    source.end(libraryTag);
+
+    source.begin(classTag);
+    int classCount = source.readInt();
+    for (int i = 0; i < classCount; i++) {
+      int index = source.readInt();
+      JClass cls = new JClass.readFromDataSource(source);
+      entityLookup.registerClass(index, cls);
+    }
+    source.end(classTag);
+
+    source.begin(typedefTag);
+    int typedefCount = source.readInt();
+    for (int i = 0; i < typedefCount; i++) {
+      int index = source.readInt();
+      JTypedef typedef = new JTypedef.readFromDataSource(source);
+      entityLookup.registerTypedef(index, typedef);
+    }
+    source.end(typedefTag);
+
+    source.begin(memberTag);
+    int memberCount = source.readInt();
+    for (int i = 0; i < memberCount; i++) {
+      int index = source.readInt();
+      JMember member = new JMember.readFromDataSource(source);
+      entityLookup.registerMember(index, member);
+    }
+    source.end(memberTag);
+
+    source.begin(typeVariableTag);
+    int typeVariableCount = source.readInt();
+    for (int i = 0; i < typeVariableCount; i++) {
+      int index = source.readInt();
+      JTypeVariable typeVariable = new JTypeVariable.readFromDataSource(source);
+      entityLookup.registerTypeVariable(index, typeVariable);
+    }
+    source.end(typeVariableTag);
+
+    programEnv = new JProgramEnv([component]);
+    source.begin(libraryDataTag);
+    entityLookup.forEachLibrary((int index, JLibrary library) {
+      JLibraryEnv env = new JLibraryEnv.readFromDataSource(source);
+      JLibraryData data = new JLibraryData.readFromDataSource(source);
+      libraryMap[env.library] =
+          libraries.registerByIndex(index, library, data, env);
+      programEnv.registerLibrary(env);
+      assert(index == library.libraryIndex);
+    });
+    source.end(libraryDataTag);
+
+    source.begin(classDataTag);
+    entityLookup.forEachClass((int index, JClass cls) {
+      JClassEnv env = new JClassEnv.readFromDataSource(source);
+      JClassData data = new JClassData.readFromDataSource(source);
+      classMap[env.cls] = classes.registerByIndex(index, cls, data, env);
+      libraries.getEnv(cls.library).registerClass(cls.name, env);
+      assert(index == cls.classIndex);
+    });
+    source.end(classDataTag);
+
+    source.begin(typedefDataTag);
+    entityLookup.forEachTypedef((int index, JTypedef typedef) {
+      JTypedefData data = new JTypedefData.readFromDataSource(source);
+      typedefs.registerByIndex(index, typedef, data);
+      assert(index == typedef.typedefIndex);
+    });
+    source.end(typedefDataTag);
+
+    source.begin(memberDataTag);
+    entityLookup.forEachMember((int index, IndexedMember member) {
+      JMemberData data = new JMemberData.readFromDataSource(source);
+      members.registerByIndex(index, member, data);
+      switch (data.definition.kind) {
+        case MemberKind.regular:
+        case MemberKind.constructor:
+          ir.Member node = data.definition.node;
+          if (member.isField) {
+            fieldMap[node] = member;
+          } else if (member.isConstructor) {
+            constructorMap[node] = member;
+          } else {
+            methodMap[node] = member;
+          }
+          break;
+        default:
+      }
+      assert(index == member.memberIndex);
+    });
+    source.end(memberDataTag);
+
+    source.begin(typeVariableDataTag);
+    entityLookup.forEachTypeVariable((int index, JTypeVariable typeVariable) {
+      JTypeVariableData data = new JTypeVariableData.readFromDataSource(source);
+      typeVariableMap[data.node] =
+          typeVariables.registerByIndex(index, typeVariable, data);
+      assert(index == typeVariable.typeVariableIndex);
+    });
+    source.end(typeVariableDataTag);
+    source.end(tag);
+  }
+
+  /// Serializes this [JsToElementMap] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+
+    // Serialize the entities before serializing the data.
+    sink.begin(libraryTag);
+    sink.writeInt(libraries.size);
+    libraries.forEach((JLibrary library, _, __) {
+      sink.writeInt(library.libraryIndex);
+      library.writeToDataSink(sink);
+    });
+    sink.end(libraryTag);
+
+    sink.begin(classTag);
+    sink.writeInt(classes.size);
+    classes.forEach((JClass cls, _, __) {
+      sink.writeInt(cls.classIndex);
+      cls.writeToDataSink(sink);
+    });
+    sink.end(classTag);
+
+    sink.begin(typedefTag);
+    sink.writeInt(typedefs.size);
+    typedefs.forEach((JTypedef typedef, _) {
+      sink.writeInt(typedef.typedefIndex);
+      typedef.writeToDataSink(sink);
+    });
+    sink.end(typedefTag);
+
+    sink.begin(memberTag);
+    sink.writeInt(members.size);
+    members.forEach((JMember member, _) {
+      sink.writeInt(member.memberIndex);
+      member.writeToDataSink(sink);
+    });
+    sink.end(memberTag);
+
+    sink.begin(typeVariableTag);
+    sink.writeInt(typeVariables.size);
+    typeVariables.forEach((JTypeVariable typeVariable, _) {
+      sink.writeInt(typeVariable.typeVariableIndex);
+      typeVariable.writeToDataSink(sink);
+    });
+    sink.end(typeVariableTag);
+
+    // Serialize the entity data after having serialized the entities.
+    sink.begin(libraryDataTag);
+    libraries.forEach((_, JLibraryData data, JLibraryEnv env) {
+      env.writeToDataSink(sink);
+      data.writeToDataSink(sink);
+    });
+    sink.end(libraryDataTag);
+
+    sink.begin(classDataTag);
+    classes.forEach((_, JClassData data, JClassEnv env) {
+      env.writeToDataSink(sink);
+      data.writeToDataSink(sink);
+    });
+    sink.end(classDataTag);
+
+    sink.begin(typedefDataTag);
+    typedefs.forEach((_, JTypedefData data) {
+      data.writeToDataSink(sink);
+    });
+    sink.end(typedefDataTag);
+
+    sink.begin(memberDataTag);
+    members.forEach((_, JMemberData data) {
+      data.writeToDataSink(sink);
+    });
+    sink.end(memberDataTag);
+
+    sink.begin(typeVariableDataTag);
+    typeVariables.forEach((_, JTypeVariableData data) {
+      data.writeToDataSink(sink);
+    });
+    sink.end(typeVariableDataTag);
+
+    sink.end(tag);
+  }
+
   @override
   void forEachNestedClosure(
       MemberEntity member, void f(FunctionEntity closure)) {
@@ -2223,3 +2441,101 @@
         .getEntity(indexedTypeVariable.typeVariableIndex);
   }
 }
+
+/// [EntityLookup] implementation used to deserialize [JsKernelToElementMap].
+///
+/// Since data objects and environments are registered together with their
+/// entity we need to have a separate lookup-by-index mechanism to allow for
+/// index-based reference within data objects and environments.
+class _EntityLookup implements EntityLookup {
+  final Map<int, JLibrary> _libraries = {};
+  final Map<int, JClass> _classes = {};
+  final Map<int, JTypedef> _typedefs = {};
+  final Map<int, JMember> _members = {};
+  final Map<int, JTypeVariable> _typeVariables = {};
+
+  void registerLibrary(int index, JLibrary library) {
+    assert(!_libraries.containsKey(index),
+        "Library for index $index has already been defined.");
+    _libraries[index] = library;
+  }
+
+  void registerClass(int index, JClass cls) {
+    assert(!_classes.containsKey(index),
+        "Class for index $index has already been defined.");
+    _classes[index] = cls;
+  }
+
+  void registerTypedef(int index, JTypedef typedef) {
+    assert(!_typedefs.containsKey(index),
+        "Typedef for index $index has already been defined.");
+    _typedefs[index] = typedef;
+  }
+
+  void registerMember(int index, JMember member) {
+    assert(!_members.containsKey(index),
+        "Member for index $index has already been defined.");
+    _members[index] = member;
+  }
+
+  void registerTypeVariable(int index, JTypeVariable typeVariable) {
+    assert(!_typeVariables.containsKey(index),
+        "Type variable for index $index has already been defined.");
+    _typeVariables[index] = typeVariable;
+  }
+
+  void forEachLibrary(void f(int index, JLibrary library)) {
+    _libraries.forEach(f);
+  }
+
+  void forEachClass(void f(int index, JClass cls)) {
+    _classes.forEach(f);
+  }
+
+  void forEachTypedef(void f(int index, JTypedef typedef)) {
+    _typedefs.forEach(f);
+  }
+
+  void forEachMember(void f(int index, JMember member)) {
+    _members.forEach(f);
+  }
+
+  void forEachTypeVariable(void f(int index, JTypeVariable typeVariable)) {
+    _typeVariables.forEach(f);
+  }
+
+  @override
+  IndexedLibrary getLibraryByIndex(int index) {
+    IndexedLibrary library = _libraries[index];
+    assert(library != null, "No library found for index $index");
+    return library;
+  }
+
+  @override
+  IndexedClass getClassByIndex(int index) {
+    IndexedClass cls = _classes[index];
+    assert(cls != null, "No class found for index $index");
+    return cls;
+  }
+
+  @override
+  IndexedTypedef getTypedefByIndex(int index) {
+    IndexedTypedef typedef = _typedefs[index];
+    assert(typedef != null, "No typedef found for index $index");
+    return typedef;
+  }
+
+  @override
+  IndexedMember getMemberByIndex(int index) {
+    IndexedMember member = _members[index];
+    assert(member != null, "No member found for index $index");
+    return member;
+  }
+
+  @override
+  IndexedTypeVariable getTypeVariableByIndex(int index) {
+    IndexedTypeVariable typeVariable = _typeVariables[index];
+    assert(typeVariable != null, "No type variable found for index $index");
+    return typeVariable;
+  }
+}
diff --git a/pkg/compiler/lib/src/js_model/elements.dart b/pkg/compiler/lib/src/js_model/elements.dart
index c357702..f964ef9 100644
--- a/pkg/compiler/lib/src/js_model/elements.dart
+++ b/pkg/compiler/lib/src/js_model/elements.dart
@@ -9,7 +9,9 @@
 import '../elements/indexed.dart';
 import '../elements/names.dart';
 import '../elements/types.dart';
+import '../serialization/serialization.dart';
 import '../universe/class_set.dart' show ClassHierarchyNodesMapKey;
+import 'closure.dart';
 
 /// Map from 'frontend' to 'backend' elements.
 ///
@@ -318,15 +320,43 @@
 const String jsElementPrefix = 'j:';
 
 class JLibrary extends IndexedLibrary {
+  /// Tag used for identifying serialized [JLibrary] objects in a
+  /// debugging data stream.
+  static const String tag = 'library';
+
   final String name;
   final Uri canonicalUri;
 
   JLibrary(this.name, this.canonicalUri);
 
+  /// Deserializes a [JLibrary] object from [source].
+  factory JLibrary.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    String name = source.readString();
+    Uri canonicalUri = source.readUri();
+    source.end(tag);
+    return new JLibrary(name, canonicalUri);
+  }
+
+  /// Serializes this [JLibrary] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeString(name);
+    sink.writeUri(canonicalUri);
+    sink.end(tag);
+  }
+
   String toString() => '${jsElementPrefix}library($name)';
 }
 
+/// Enum used for identifying [JClass] subclasses in serialization.
+enum JClassKind { node, closure, record }
+
 class JClass extends IndexedClass with ClassHierarchyNodesMapKey {
+  /// Tag used for identifying serialized [JClass] objects in a
+  /// debugging data stream.
+  static const String tag = 'class';
+
   final JLibrary library;
 
   final String name;
@@ -334,6 +364,35 @@
 
   JClass(this.library, this.name, {this.isAbstract});
 
+  /// Deserializes a [JClass] object from [source].
+  factory JClass.readFromDataSource(DataSource source) {
+    JClassKind kind = source.readEnum(JClassKind.values);
+    switch (kind) {
+      case JClassKind.node:
+        source.begin(tag);
+        JLibrary library = source.readLibrary();
+        String name = source.readString();
+        bool isAbstract = source.readBool();
+        source.end(tag);
+        return new JClass(library, name, isAbstract: isAbstract);
+      case JClassKind.closure:
+        return new JClosureClass.readFromDataSource(source);
+      case JClassKind.record:
+        return new JRecord.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unexpected ClassKind $kind");
+  }
+
+  /// Serializes this [JClass] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassKind.node);
+    sink.begin(tag);
+    sink.writeLibrary(library);
+    sink.writeString(name);
+    sink.writeBool(isAbstract);
+    sink.end(tag);
+  }
+
   @override
   bool get isClosure => false;
 
@@ -341,15 +400,52 @@
 }
 
 class JTypedef extends IndexedTypedef {
+  /// Tag used for identifying serialized [JTypedef] objects in a
+  /// debugging data stream.
+  static const String tag = 'typedef';
+
   final JLibrary library;
 
   final String name;
 
   JTypedef(this.library, this.name);
 
+  /// Deserializes a [JTypedef] object from [source].
+  factory JTypedef.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JLibrary library = source.readLibrary();
+    String name = source.readString();
+    source.end(tag);
+    return new JTypedef(library, name);
+  }
+
+  /// Serializes this [JTypedef] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeLibrary(library);
+    sink.writeString(name);
+    sink.end(tag);
+  }
+
   String toString() => '${jsElementPrefix}typedef($name)';
 }
 
+/// Enum used for identifying [JMember] subclasses in serialization.
+enum JMemberKind {
+  generativeConstructor,
+  factoryConstructor,
+  constructorBody,
+  field,
+  getter,
+  setter,
+  method,
+  closureField,
+  closureCallMethod,
+  generatorBody,
+  signatureMethod,
+  recordField,
+}
+
 abstract class JMember extends IndexedMember {
   final JLibrary library;
   final JClass enclosingClass;
@@ -359,6 +455,41 @@
   JMember(this.library, this.enclosingClass, this._name, {bool isStatic: false})
       : _isStatic = isStatic;
 
+  /// Deserializes a [JMember] object from [source].
+  factory JMember.readFromDataSource(DataSource source) {
+    JMemberKind kind = source.readEnum(JMemberKind.values);
+    switch (kind) {
+      case JMemberKind.generativeConstructor:
+        return new JGenerativeConstructor.readFromDataSource(source);
+      case JMemberKind.factoryConstructor:
+        return new JFactoryConstructor.readFromDataSource(source);
+      case JMemberKind.constructorBody:
+        return new JConstructorBody.readFromDataSource(source);
+      case JMemberKind.field:
+        return new JField.readFromDataSource(source);
+      case JMemberKind.getter:
+        return new JGetter.readFromDataSource(source);
+      case JMemberKind.setter:
+        return new JSetter.readFromDataSource(source);
+      case JMemberKind.method:
+        return new JMethod.readFromDataSource(source);
+      case JMemberKind.closureField:
+        return new JClosureField.readFromDataSource(source);
+      case JMemberKind.closureCallMethod:
+        return new JClosureCallMethod.readFromDataSource(source);
+      case JMemberKind.generatorBody:
+        return new JGeneratorBody.readFromDataSource(source);
+      case JMemberKind.signatureMethod:
+        return new JSignatureMethod.readFromDataSource(source);
+      case JMemberKind.recordField:
+        return new JRecordField.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unexpected JMemberKind $kind");
+  }
+
+  /// Serializes this [JMember] to [sink].
+  void writeToDataSink(DataSink sink);
+
   String get name => _name.text;
 
   Name get memberName => _name;
@@ -444,12 +575,42 @@
 }
 
 class JGenerativeConstructor extends JConstructor {
+  /// Tag used for identifying serialized [JGenerativeConstructor] objects in a
+  /// debugging data stream.
+  static const String tag = 'generative-constructor';
+
   JGenerativeConstructor(
       JClass enclosingClass, Name name, ParameterStructure parameterStructure,
       {bool isExternal, bool isConst})
       : super(enclosingClass, name, parameterStructure,
             isExternal: isExternal, isConst: isConst);
 
+  factory JGenerativeConstructor.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JClass enclosingClass = source.readClass();
+    String name = source.readString();
+    ParameterStructure parameterStructure =
+        new ParameterStructure.readFromDataSource(source);
+    bool isExternal = source.readBool();
+    bool isConst = source.readBool();
+    source.end(tag);
+    return new JGenerativeConstructor(enclosingClass,
+        new Name(name, enclosingClass.library), parameterStructure,
+        isExternal: isExternal, isConst: isConst);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.generativeConstructor);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass);
+    sink.writeString(name);
+    parameterStructure.writeToDataSink(sink);
+    sink.writeBool(isExternal);
+    sink.writeBool(isConst);
+    sink.end(tag);
+  }
+
   @override
   bool get isFactoryConstructor => false;
 
@@ -458,6 +619,10 @@
 }
 
 class JFactoryConstructor extends JConstructor {
+  /// Tag used for identifying serialized [JFactoryConstructor] objects in a
+  /// debugging data stream.
+  static const String tag = 'factory-constructor';
+
   @override
   final bool isFromEnvironmentConstructor;
 
@@ -467,6 +632,36 @@
       : super(enclosingClass, name, parameterStructure,
             isExternal: isExternal, isConst: isConst);
 
+  factory JFactoryConstructor.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JClass enclosingClass = source.readClass();
+    String name = source.readString();
+    ParameterStructure parameterStructure =
+        new ParameterStructure.readFromDataSource(source);
+    bool isExternal = source.readBool();
+    bool isConst = source.readBool();
+    bool isFromEnvironmentConstructor = source.readBool();
+    source.end(tag);
+    return new JFactoryConstructor(enclosingClass,
+        new Name(name, enclosingClass.library), parameterStructure,
+        isExternal: isExternal,
+        isConst: isConst,
+        isFromEnvironmentConstructor: isFromEnvironmentConstructor);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.factoryConstructor);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass);
+    sink.writeString(name);
+    parameterStructure.writeToDataSink(sink);
+    sink.writeBool(isExternal);
+    sink.writeBool(isConst);
+    sink.writeBool(isFromEnvironmentConstructor);
+    sink.end(tag);
+  }
+
   @override
   bool get isFactoryConstructor => true;
 
@@ -475,7 +670,11 @@
 }
 
 class JConstructorBody extends JFunction implements ConstructorBodyEntity {
-  final ConstructorEntity constructor;
+  /// Tag used for identifying serialized [JConstructorBody] objects in a
+  /// debugging data stream.
+  static const String tag = 'constructor-body';
+
+  final JConstructor constructor;
 
   JConstructorBody(this.constructor)
       : super(
@@ -487,10 +686,29 @@
             isStatic: false,
             isExternal: false);
 
+  factory JConstructorBody.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JConstructor constructor = source.readMember();
+    source.end(tag);
+    return new JConstructorBody(constructor);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.constructorBody);
+    sink.begin(tag);
+    sink.writeMember(constructor);
+    sink.end(tag);
+  }
+
   String get _kind => 'constructor_body';
 }
 
 class JMethod extends JFunction {
+  /// Tag used for identifying serialized [JMethod] objects in a
+  /// debugging data stream.
+  static const String tag = 'method';
+
   final bool isAbstract;
 
   JMethod(JLibrary library, JClass enclosingClass, Name name,
@@ -499,6 +717,53 @@
       : super(library, enclosingClass, name, parameterStructure, asyncMarker,
             isStatic: isStatic, isExternal: isExternal);
 
+  factory JMethod.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberContextKind kind = source.readEnum(MemberContextKind.values);
+    JLibrary library;
+    JClass enclosingClass;
+    switch (kind) {
+      case MemberContextKind.library:
+        library = source.readLibrary();
+        break;
+      case MemberContextKind.cls:
+        enclosingClass = source.readClass();
+        library = enclosingClass.library;
+        break;
+    }
+    String name = source.readString();
+    ParameterStructure parameterStructure =
+        new ParameterStructure.readFromDataSource(source);
+    AsyncMarker asyncMarker = source.readEnum(AsyncMarker.values);
+    bool isStatic = source.readBool();
+    bool isExternal = source.readBool();
+    bool isAbstract = source.readBool();
+    source.end(tag);
+    return new JMethod(library, enclosingClass, new Name(name, library),
+        parameterStructure, asyncMarker,
+        isStatic: isStatic, isExternal: isExternal, isAbstract: isAbstract);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.method);
+    sink.begin(tag);
+    if (enclosingClass != null) {
+      sink.writeEnum(MemberContextKind.cls);
+      sink.writeClass(enclosingClass);
+    } else {
+      sink.writeEnum(MemberContextKind.library);
+      sink.writeLibrary(library);
+    }
+    sink.writeString(name);
+    parameterStructure.writeToDataSink(sink);
+    sink.writeEnum(asyncMarker);
+    sink.writeBool(isStatic);
+    sink.writeBool(isExternal);
+    sink.writeBool(isAbstract);
+    sink.end(tag);
+  }
+
   @override
   bool get isFunction => true;
 
@@ -506,7 +771,11 @@
 }
 
 class JGeneratorBody extends JFunction {
-  final FunctionEntity function;
+  /// Tag used for identifying serialized [JGeneratorBody] objects in a
+  /// debugging data stream.
+  static const String tag = 'generator-body';
+
+  final JFunction function;
   final DartType elementType;
   final int hashCode;
 
@@ -516,10 +785,31 @@
             function.parameterStructure, function.asyncMarker,
             isStatic: function.isStatic, isExternal: false);
 
+  factory JGeneratorBody.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JFunction function = source.readMember();
+    DartType elementType = source.readDartType();
+    source.end(tag);
+    return new JGeneratorBody(function, elementType);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.generatorBody);
+    sink.begin(tag);
+    sink.writeMember(function);
+    sink.writeDartType(elementType);
+    sink.end(tag);
+  }
+
   String get _kind => 'generator_body';
 }
 
 class JGetter extends JFunction {
+  /// Tag used for identifying serialized [JGetter] objects in a
+  /// debugging data stream.
+  static const String tag = 'getter';
+
   final bool isAbstract;
 
   JGetter(JLibrary library, JClass enclosingClass, Name name,
@@ -529,6 +819,50 @@
             asyncMarker,
             isStatic: isStatic, isExternal: isExternal);
 
+  factory JGetter.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberContextKind kind = source.readEnum(MemberContextKind.values);
+    JLibrary library;
+    JClass enclosingClass;
+    switch (kind) {
+      case MemberContextKind.library:
+        library = source.readLibrary();
+        break;
+      case MemberContextKind.cls:
+        enclosingClass = source.readClass();
+        library = enclosingClass.library;
+        break;
+    }
+    String name = source.readString();
+    AsyncMarker asyncMarker = source.readEnum(AsyncMarker.values);
+    bool isStatic = source.readBool();
+    bool isExternal = source.readBool();
+    bool isAbstract = source.readBool();
+    source.end(tag);
+    return new JGetter(
+        library, enclosingClass, new Name(name, library), asyncMarker,
+        isStatic: isStatic, isExternal: isExternal, isAbstract: isAbstract);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.getter);
+    sink.begin(tag);
+    if (enclosingClass != null) {
+      sink.writeEnum(MemberContextKind.cls);
+      sink.writeClass(enclosingClass);
+    } else {
+      sink.writeEnum(MemberContextKind.library);
+      sink.writeLibrary(library);
+    }
+    sink.writeString(name);
+    sink.writeEnum(asyncMarker);
+    sink.writeBool(isStatic);
+    sink.writeBool(isExternal);
+    sink.writeBool(isAbstract);
+    sink.end(tag);
+  }
+
   @override
   bool get isGetter => true;
 
@@ -536,6 +870,10 @@
 }
 
 class JSetter extends JFunction {
+  /// Tag used for identifying serialized [JSetter] objects in a
+  /// debugging data stream.
+  static const String tag = 'setter';
+
   final bool isAbstract;
 
   JSetter(JLibrary library, JClass enclosingClass, Name name,
@@ -544,6 +882,47 @@
             AsyncMarker.SYNC,
             isStatic: isStatic, isExternal: isExternal);
 
+  factory JSetter.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberContextKind kind = source.readEnum(MemberContextKind.values);
+    JLibrary library;
+    JClass enclosingClass;
+    switch (kind) {
+      case MemberContextKind.library:
+        library = source.readLibrary();
+        break;
+      case MemberContextKind.cls:
+        enclosingClass = source.readClass();
+        library = enclosingClass.library;
+        break;
+    }
+    String name = source.readString();
+    bool isStatic = source.readBool();
+    bool isExternal = source.readBool();
+    bool isAbstract = source.readBool();
+    source.end(tag);
+    return new JSetter(library, enclosingClass, new Name(name, library),
+        isStatic: isStatic, isExternal: isExternal, isAbstract: isAbstract);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.setter);
+    sink.begin(tag);
+    if (enclosingClass != null) {
+      sink.writeEnum(MemberContextKind.cls);
+      sink.writeClass(enclosingClass);
+    } else {
+      sink.writeEnum(MemberContextKind.library);
+      sink.writeLibrary(library);
+    }
+    sink.writeString(name);
+    sink.writeBool(isStatic);
+    sink.writeBool(isExternal);
+    sink.writeBool(isAbstract);
+    sink.end(tag);
+  }
+
   @override
   bool get isAssignable => true;
 
@@ -554,6 +933,10 @@
 }
 
 class JField extends JMember implements FieldEntity, IndexedField {
+  /// Tag used for identifying serialized [JField] objects in a
+  /// debugging data stream.
+  static const String tag = 'field';
+
   final bool isAssignable;
   final bool isConst;
 
@@ -561,6 +944,47 @@
       {bool isStatic, this.isAssignable, this.isConst})
       : super(library, enclosingClass, name, isStatic: isStatic);
 
+  factory JField.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberContextKind kind = source.readEnum(MemberContextKind.values);
+    JLibrary library;
+    JClass enclosingClass;
+    switch (kind) {
+      case MemberContextKind.library:
+        library = source.readLibrary();
+        break;
+      case MemberContextKind.cls:
+        enclosingClass = source.readClass();
+        library = enclosingClass.library;
+        break;
+    }
+    String name = source.readString();
+    bool isStatic = source.readBool();
+    bool isAssignable = source.readBool();
+    bool isConst = source.readBool();
+    source.end(tag);
+    return new JField(library, enclosingClass, new Name(name, library),
+        isStatic: isStatic, isAssignable: isAssignable, isConst: isConst);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.field);
+    sink.begin(tag);
+    if (enclosingClass != null) {
+      sink.writeEnum(MemberContextKind.cls);
+      sink.writeClass(enclosingClass);
+    } else {
+      sink.writeEnum(MemberContextKind.library);
+      sink.writeLibrary(library);
+    }
+    sink.writeString(name);
+    sink.writeBool(isStatic);
+    sink.writeBool(isAssignable);
+    sink.writeBool(isConst);
+    sink.end(tag);
+  }
+
   @override
   bool get isField => true;
 
@@ -568,33 +992,137 @@
 }
 
 class JClosureCallMethod extends JMethod {
+  /// Tag used for identifying serialized [JClosureCallMethod] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-call-method';
+
   JClosureCallMethod(ClassEntity enclosingClass,
       ParameterStructure parameterStructure, AsyncMarker asyncMarker)
       : super(enclosingClass.library, enclosingClass, Names.call,
             parameterStructure, asyncMarker,
             isStatic: false, isExternal: false, isAbstract: false);
 
+  factory JClosureCallMethod.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JClass enclosingClass = source.readClass();
+    ParameterStructure parameterStructure =
+        new ParameterStructure.readFromDataSource(source);
+    AsyncMarker asyncMarker = source.readEnum(AsyncMarker.values);
+    source.end(tag);
+    return new JClosureCallMethod(
+        enclosingClass, parameterStructure, asyncMarker);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.closureCallMethod);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass);
+    parameterStructure.writeToDataSink(sink);
+    sink.writeEnum(asyncMarker);
+    sink.end(tag);
+  }
+
   String get _kind => 'closure_call';
 }
 
 /// A method that returns the signature of the Dart closure/tearoff that this
 /// method's parent class is representing.
 class JSignatureMethod extends JMethod {
+  /// Tag used for identifying serialized [JSignatureMethod] objects in a
+  /// debugging data stream.
+  static const String tag = 'signature-method';
+
   JSignatureMethod(ClassEntity enclosingClass)
       : super(enclosingClass.library, enclosingClass, Names.signature,
             const ParameterStructure(0, 0, const [], 0), AsyncMarker.SYNC,
             isStatic: false, isExternal: false, isAbstract: false);
 
+  factory JSignatureMethod.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JClass cls = source.readClass();
+    source.end(tag);
+    return new JSignatureMethod(cls);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberKind.signatureMethod);
+    sink.begin(tag);
+    sink.writeClass(enclosingClass);
+    sink.end(tag);
+  }
+
   String get _kind => 'signature';
 }
 
+/// Enum used for identifying [JTypeVariable] variants in serialization.
+enum JTypeVariableKind { cls, member, typedef, local }
+
 class JTypeVariable extends IndexedTypeVariable {
+  /// Tag used for identifying serialized [JTypeVariable] objects in a
+  /// debugging data stream.
+  static const String tag = 'type-variable';
+
   final Entity typeDeclaration;
   final String name;
   final int index;
 
   JTypeVariable(this.typeDeclaration, this.name, this.index);
 
+  /// Deserializes a [JTypeVariable] object from [source].
+  factory JTypeVariable.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    JTypeVariableKind kind = source.readEnum(JTypeVariableKind.values);
+    Entity typeDeclaration;
+    switch (kind) {
+      case JTypeVariableKind.cls:
+        typeDeclaration = source.readClass();
+        break;
+      case JTypeVariableKind.member:
+        typeDeclaration = source.readMember();
+        break;
+      case JTypeVariableKind.typedef:
+        typeDeclaration = source.readTypedef();
+        break;
+      case JTypeVariableKind.local:
+        // Type variables declared by local functions don't point to their
+        // declaration, since the corresponding closure call methods is created
+        // after the type variable.
+        // TODO(johnniwinther): Fix this.
+        break;
+    }
+    String name = source.readString();
+    int index = source.readInt();
+    source.end(tag);
+    return new JTypeVariable(typeDeclaration, name, index);
+  }
+
+  /// Serializes this [JTypeVariable] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    if (typeDeclaration is IndexedClass) {
+      IndexedClass cls = typeDeclaration;
+      sink.writeEnum(JTypeVariableKind.cls);
+      sink.writeClass(cls);
+    } else if (typeDeclaration is IndexedMember) {
+      IndexedMember member = typeDeclaration;
+      sink.writeEnum(JTypeVariableKind.member);
+      sink.writeMember(member);
+    } else if (typeDeclaration is IndexedTypedef) {
+      IndexedTypedef typedef = typeDeclaration;
+      sink.writeEnum(JTypeVariableKind.typedef);
+      sink.writeTypedef(typedef);
+    } else if (typeDeclaration == null) {
+      sink.writeEnum(JTypeVariableKind.local);
+    } else {
+      throw new UnsupportedError(
+          "Unexpected type variable declarer $typeDeclaration.");
+    }
+    sink.writeString(name);
+    sink.writeInt(index);
+    sink.end(tag);
+  }
+
   String toString() =>
       '${jsElementPrefix}type_variable(${typeDeclaration.name}.$name)';
 }
diff --git a/pkg/compiler/lib/src/js_model/env.dart b/pkg/compiler/lib/src/js_model/env.dart
index d2e4f8c..876106a 100644
--- a/pkg/compiler/lib/src/js_model/env.dart
+++ b/pkg/compiler/lib/src/js_model/env.dart
@@ -18,14 +18,16 @@
 import '../ir/util.dart';
 import '../js_model/element_map.dart';
 import '../ordered_typeset.dart';
+import '../serialization/serialization.dart';
 import '../ssa/type_builder.dart';
+import 'closure.dart';
 import 'element_map.dart';
 import 'element_map_impl.dart';
 import 'elements.dart';
 
 /// Environment for fast lookup of component libraries.
 class JProgramEnv {
-  final Set<ir.Component> _components;
+  final Iterable<ir.Component> _components;
   final Map<Uri, JLibraryEnv> _libraryMap = {};
 
   JProgramEnv(this._components);
@@ -57,6 +59,10 @@
 
 /// Environment for fast lookup of library classes and members.
 class JLibraryEnv {
+  /// Tag used for identifying serialized [JLibraryEnv] objects in a
+  /// debugging data stream.
+  static const String tag = 'library-env';
+
   final ir.Library library;
   final Map<String, JClassEnv> _classMap = {};
   final Map<String, ir.Member> _memberMap;
@@ -64,6 +70,27 @@
 
   JLibraryEnv(this.library, this._memberMap, this._setterMap);
 
+  /// Deserializes a [JLibraryEnv] object from [source].
+  factory JLibraryEnv.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Library library = source.readLibraryNode();
+    Map<String, ir.Member> memberMap =
+        source.readStringMap(source.readMemberNode);
+    Map<String, ir.Member> setterMap =
+        source.readStringMap(source.readMemberNode);
+    source.end(tag);
+    return new JLibraryEnv(library, memberMap, setterMap);
+  }
+
+  /// Serializes this [JLibraryEnv] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeLibraryNode(library);
+    sink.writeStringMap(_memberMap, sink.writeMemberNode);
+    sink.writeStringMap(_setterMap, sink.writeMemberNode);
+    sink.end(tag);
+  }
+
   void registerClass(String name, JClassEnv classEnv) {
     _classMap[name] = classEnv;
   }
@@ -96,16 +123,64 @@
 }
 
 class JLibraryData {
+  /// Tag used for identifying serialized [JLibraryData] objects in a
+  /// debugging data stream.
+  static const String tag = 'library-data';
+
   final ir.Library library;
   // TODO(johnniwinther): Avoid direct access to [imports]. It might be null if
   // it hasn't been computed for the corresponding [KLibraryData].
   final Map<ir.LibraryDependency, ImportEntity> imports;
 
   JLibraryData(this.library, this.imports);
+
+  factory JLibraryData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Library library = source.readLibraryNode();
+    int importCount = source.readInt();
+    Map<ir.LibraryDependency, ImportEntity> imports;
+    if (importCount > 0) {
+      // TODO(johnniwinther): Deserialize imports.
+    }
+    source.end(tag);
+    return new JLibraryData(library, imports);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeLibraryNode(library);
+    if (imports == null) {
+      sink.writeInt(0);
+    } else {
+      sink.writeInt(imports.length);
+      // TODO(johnniwinther): Serialize imports.
+    }
+    sink.end(tag);
+  }
 }
 
+/// Enum used for identifying [JClassEnv] subclasses in serialization.
+enum JClassEnvKind { node, closure, record }
+
 /// Member data for a class.
 abstract class JClassEnv {
+  /// Deserializes a [JClassEnv] object from [source].
+  factory JClassEnv.readFromDataSource(DataSource source) {
+    JClassEnvKind kind = source.readEnum(JClassEnvKind.values);
+    switch (kind) {
+      case JClassEnvKind.node:
+        return new JClassEnvImpl.readFromDataSource(source);
+      case JClassEnvKind.closure:
+        return new ClosureClassEnv.readFromDataSource(source);
+      case JClassEnvKind.record:
+        return new RecordEnv.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unsupported JClassEnvKind $kind");
+  }
+
+  /// Serializes this [JClassEnv] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// The [ir.Class] that defined the class, if any.
   ir.Class get cls;
 
@@ -139,6 +214,10 @@
 
 /// Environment for fast lookup of class members.
 class JClassEnvImpl implements JClassEnv {
+  /// Tag used for identifying serialized [JClassEnv] objects in a
+  /// debugging data stream.
+  static const String tag = 'class-env';
+
   final ir.Class cls;
   final Map<String, ir.Member> _constructorMap;
   final Map<String, ir.Member> _memberMap;
@@ -152,6 +231,35 @@
   JClassEnvImpl(this.cls, this._constructorMap, this._memberMap,
       this._setterMap, this._members, this.isSuperMixinApplication);
 
+  factory JClassEnvImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Class cls = source.readClassNode();
+    Map<String, ir.Member> constructorMap =
+        source.readStringMap(source.readMemberNode);
+    Map<String, ir.Member> memberMap =
+        source.readStringMap(source.readMemberNode);
+    Map<String, ir.Member> setterMap =
+        source.readStringMap(source.readMemberNode);
+    List<ir.Member> members = source.readMemberNodes();
+    bool isSuperMixinApplication = source.readBool();
+    source.end(tag);
+    return new JClassEnvImpl(cls, constructorMap, memberMap, setterMap, members,
+        isSuperMixinApplication);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassEnvKind.node);
+    sink.begin(tag);
+    sink.writeClassNode(cls);
+    sink.writeStringMap(_constructorMap, sink.writeMemberNode);
+    sink.writeStringMap(_memberMap, sink.writeMemberNode);
+    sink.writeStringMap(_setterMap, sink.writeMemberNode);
+    sink.writeMemberNodes(_members);
+    sink.writeBool(isSuperMixinApplication);
+    sink.end(tag);
+  }
+
   bool get isUnnamedMixinApplication => cls.isAnonymousMixin;
 
   /// Return the [MemberEntity] for the member [name] in [cls]. If [setter] is
@@ -195,10 +303,31 @@
 }
 
 class RecordEnv implements JClassEnv {
+  /// Tag used for identifying serialized [RecordEnv] objects in a
+  /// debugging data stream.
+  static const String tag = 'record-env';
+
   final Map<String, IndexedMember> _memberMap;
 
   RecordEnv(this._memberMap);
 
+  factory RecordEnv.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Map<String, IndexedMember> _memberMap =
+        source.readStringMap(() => source.readMember());
+    source.end(tag);
+    return new RecordEnv(_memberMap);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassEnvKind.record);
+    sink.begin(tag);
+    sink.writeStringMap(
+        _memberMap, (IndexedMember member) => sink.writeMember(member));
+    sink.end(tag);
+  }
+
   @override
   void forEachConstructorBody(void f(ConstructorBodyEntity constructor)) {
     // We do not create constructor bodies for containers.
@@ -238,8 +367,29 @@
 }
 
 class ClosureClassEnv extends RecordEnv {
+  /// Tag used for identifying serialized [ClosureClassEnv] objects in a
+  /// debugging data stream.
+  static const String tag = 'closure-class-env';
+
   ClosureClassEnv(Map<String, MemberEntity> memberMap) : super(memberMap);
 
+  factory ClosureClassEnv.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Map<String, IndexedMember> _memberMap =
+        source.readStringMap(() => source.readMember());
+    source.end(tag);
+    return new ClosureClassEnv(_memberMap);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassEnvKind.closure);
+    sink.begin(tag);
+    sink.writeStringMap(
+        _memberMap, (IndexedMember member) => sink.writeMember(member));
+    sink.end(tag);
+  }
+
   @override
   MemberEntity lookupMember(IrToElementMap elementMap, String name,
       {bool setter: false}) {
@@ -251,7 +401,27 @@
   }
 }
 
+/// Enum used for identifying [JClassData] subclasses in serialization.
+enum JClassDataKind { node, closure, record }
+
 abstract class JClassData {
+  /// Deserializes a [JClassData] object from [source].
+  factory JClassData.readFromDataSource(DataSource source) {
+    JClassDataKind kind = source.readEnum(JClassDataKind.values);
+    switch (kind) {
+      case JClassDataKind.node:
+        return new JClassDataImpl.readFromDataSource(source);
+      case JClassDataKind.closure:
+        return new ClosureClassData.readFromDataSource(source);
+      case JClassDataKind.record:
+        return new RecordClassData.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unexpected JClassDataKind $kind");
+  }
+
+  /// Serializes this [JClassData] to [sink].
+  void writeToDataSink(DataSink sink);
+
   ClassDefinition get definition;
 
   InterfaceType get thisType;
@@ -267,6 +437,10 @@
 }
 
 class JClassDataImpl implements JClassData {
+  /// Tag used for identifying serialized [JClassDataImpl] objects in a
+  /// debugging data stream.
+  static const String tag = 'class-data';
+
   final ir.Class cls;
   final ClassDefinition definition;
   bool isMixinApplication;
@@ -281,17 +455,75 @@
 
   JClassDataImpl(this.cls, this.definition);
 
+  factory JClassDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Class cls = source.readClassNode();
+    ClassDefinition definition = new ClassDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new JClassDataImpl(cls, definition);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JClassDataKind.node);
+    sink.begin(tag);
+    sink.writeClassNode(cls);
+    definition.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   bool get isEnumClass => cls != null && cls.isEnum;
 
   DartType get callType => null;
 }
 
+/// Enum used for identifying [JMemberData] subclasses in serialization.
+enum JMemberDataKind {
+  function,
+  field,
+  constructor,
+  constructorBody,
+  signature,
+  generatorBody,
+  closureFunction,
+  closureField,
+}
+
 abstract class JMemberData {
   MemberDefinition get definition;
 
   InterfaceType getMemberThisType(JsToElementMap elementMap);
 
   ClassTypeVariableAccess get classTypeVariableAccess;
+
+  JMemberData();
+
+  /// Deserializes a [JMemberData] object from [source].
+  factory JMemberData.readFromDataSource(DataSource source) {
+    JMemberDataKind kind = source.readEnum(JMemberDataKind.values);
+    switch (kind) {
+      case JMemberDataKind.function:
+        return new FunctionDataImpl.readFromDataSource(source);
+      case JMemberDataKind.field:
+        return new JFieldDataImpl.readFromDataSource(source);
+      case JMemberDataKind.constructor:
+        return new JConstructorDataImpl.readFromDataSource(source);
+      case JMemberDataKind.constructorBody:
+        return new ConstructorBodyDataImpl.readFromDataSource(source);
+      case JMemberDataKind.signature:
+        return new SignatureFunctionData.readFromDataSource(source);
+      case JMemberDataKind.generatorBody:
+        return new GeneratorBodyFunctionData.readFromDataSource(source);
+      case JMemberDataKind.closureFunction:
+        return new ClosureFunctionData.readFromDataSource(source);
+      case JMemberDataKind.closureField:
+        return new ClosureFieldData.readFromDataSource(source);
+    }
+    throw new UnsupportedError("Unexpected JMemberDataKind $kind");
+  }
+
+  /// Serializes this [JMemberData] to [sink].
+  void writeToDataSink(DataSink sink);
 }
 
 abstract class JMemberDataImpl implements JMemberData {
@@ -351,6 +583,10 @@
 class FunctionDataImpl extends JMemberDataImpl
     with FunctionDataMixin
     implements FunctionData {
+  /// Tag used for identifying serialized [FunctionDataImpl] objects in a
+  /// debugging data stream.
+  static const String tag = 'function-data';
+
   final ir.FunctionNode functionNode;
   FunctionType _type;
 
@@ -358,6 +594,32 @@
       ir.Member node, this.functionNode, MemberDefinition definition)
       : super(node, definition);
 
+  factory FunctionDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Member node = source.readMemberNode();
+    ir.FunctionNode functionNode;
+    if (node is ir.Procedure) {
+      functionNode = node.function;
+    } else if (node is ir.Constructor) {
+      functionNode = node.function;
+    } else {
+      throw new UnsupportedError(
+          "Unexpected member node $node (${node.runtimeType}).");
+    }
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new FunctionDataImpl(node, functionNode, definition);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.function);
+    sink.begin(tag);
+    sink.writeMemberNode(node);
+    definition.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   FunctionType getFunctionType(covariant JsToElementMapBase elementMap) {
     return _type ??= elementMap.getFunctionType(functionNode);
   }
@@ -395,6 +657,10 @@
 }
 
 class SignatureFunctionData implements FunctionData {
+  /// Tag used for identifying serialized [SignatureFunctionData] objects in a
+  /// debugging data stream.
+  static const String tag = 'signature-function-data';
+
   final MemberDefinition definition;
   final InterfaceType memberThisType;
   final ClassTypeVariableAccess classTypeVariableAccess;
@@ -403,6 +669,29 @@
   SignatureFunctionData(this.definition, this.memberThisType,
       this.typeParameters, this.classTypeVariableAccess);
 
+  factory SignatureFunctionData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    List<ir.TypeParameter> typeParameters = source.readTypeParameterNodes();
+    ClassTypeVariableAccess classTypeVariableAccess =
+        source.readEnum(ClassTypeVariableAccess.values);
+    source.end(tag);
+    return new SignatureFunctionData(
+        definition, memberThisType, typeParameters, classTypeVariableAccess);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.signature);
+    sink.begin(tag);
+    definition.writeToDataSink(sink);
+    sink.writeDartType(memberThisType, allowNull: true);
+    sink.writeTypeParameterNodes(typeParameters);
+    sink.writeEnum(classTypeVariableAccess);
+    sink.end(tag);
+  }
+
   FunctionType getFunctionType(covariant JsToElementMapBase elementMap) {
     throw new UnsupportedError("SignatureFunctionData.getFunctionType");
   }
@@ -451,9 +740,32 @@
 }
 
 class GeneratorBodyFunctionData extends DelegatedFunctionData {
+  /// Tag used for identifying serialized [GeneratorBodyFunctionData] objects in
+  /// a debugging data stream.
+  static const String tag = 'generator-body-data';
+
   final MemberDefinition definition;
+
   GeneratorBodyFunctionData(FunctionData baseData, this.definition)
       : super(baseData);
+
+  factory GeneratorBodyFunctionData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    // TODO(johnniwinther): Share the original base data on deserialization.
+    FunctionData baseData = new JMemberData.readFromDataSource(source);
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new GeneratorBodyFunctionData(baseData, definition);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.generatorBody);
+    sink.begin(tag);
+    baseData.writeToDataSink(sink);
+    definition.writeToDataSink(sink);
+    sink.end(tag);
+  }
 }
 
 abstract class JConstructorData extends FunctionData {
@@ -463,6 +775,10 @@
 
 class JConstructorDataImpl extends FunctionDataImpl
     implements JConstructorData {
+  /// Tag used for identifying serialized [JConstructorDataImpl] objects in a
+  /// debugging data stream.
+  static const String tag = 'constructor-data';
+
   ConstantConstructor _constantConstructor;
   JConstructorBody constructorBody;
 
@@ -470,6 +786,33 @@
       ir.Member node, ir.FunctionNode functionNode, MemberDefinition definition)
       : super(node, functionNode, definition);
 
+  factory JConstructorDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Member node = source.readMemberNode();
+    ir.FunctionNode functionNode;
+    if (node is ir.Procedure) {
+      functionNode = node.function;
+    } else if (node is ir.Constructor) {
+      functionNode = node.function;
+    } else {
+      throw new UnsupportedError(
+          "Unexpected member node $node (${node.runtimeType}).");
+    }
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new JConstructorDataImpl(node, functionNode, definition);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.constructor);
+    sink.begin(tag);
+    sink.writeMemberNode(node);
+    definition.writeToDataSink(sink);
+    assert(constructorBody == null);
+    sink.end(tag);
+  }
+
   ConstantConstructor getConstructorConstant(
       JsToElementMapBase elementMap, ConstructorEntity constructor) {
     if (_constantConstructor == null) {
@@ -492,10 +835,40 @@
 }
 
 class ConstructorBodyDataImpl extends FunctionDataImpl {
+  /// Tag used for identifying serialized [ConstructorBodyDataImpl] objects in
+  /// a debugging data stream.
+  static const String tag = 'constructor-body-data';
+
   ConstructorBodyDataImpl(
       ir.Member node, ir.FunctionNode functionNode, MemberDefinition definition)
       : super(node, functionNode, definition);
 
+  factory ConstructorBodyDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Member node = source.readMemberNode();
+    ir.FunctionNode functionNode;
+    if (node is ir.Procedure) {
+      functionNode = node.function;
+    } else if (node is ir.Constructor) {
+      functionNode = node.function;
+    } else {
+      throw new UnsupportedError(
+          "Unexpected member node $node (${node.runtimeType}).");
+    }
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new ConstructorBodyDataImpl(node, functionNode, definition);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.constructorBody);
+    sink.begin(tag);
+    sink.writeMemberNode(node);
+    definition.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   // TODO(johnniwinther,sra): Constructor bodies should access type variables
   // through `this`.
   @override
@@ -518,6 +891,10 @@
 }
 
 class JFieldDataImpl extends JMemberDataImpl implements JFieldData {
+  /// Tag used for identifying serialized [JFieldDataImpl] objects in
+  /// a debugging data stream.
+  static const String tag = 'field-data';
+
   DartType _type;
   bool _isConstantComputed = false;
   ConstantValue _constantValue;
@@ -526,6 +903,23 @@
   JFieldDataImpl(ir.Field node, MemberDefinition definition)
       : super(node, definition);
 
+  factory JFieldDataImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.Member node = source.readMemberNode();
+    MemberDefinition definition =
+        new MemberDefinition.readFromDataSource(source);
+    source.end(tag);
+    return new JFieldDataImpl(node, definition);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeEnum(JMemberDataKind.field);
+    sink.begin(tag);
+    sink.writeMemberNode(node);
+    definition.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   ir.Field get node => super.node;
 
   DartType getFieldType(covariant JsToElementMapBase elementMap) {
@@ -582,18 +976,52 @@
 }
 
 class JTypedefData {
+  /// Tag used for identifying serialized [JTypedefData] objects in
+  /// a debugging data stream.
+  static const String tag = 'typedef-data';
+
   final TypedefType rawType;
 
   JTypedefData(this.rawType);
+
+  factory JTypedefData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    TypedefType rawType = source.readDartType();
+    source.end(tag);
+    return new JTypedefData(rawType);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeDartType(rawType);
+    sink.end(tag);
+  }
 }
 
 class JTypeVariableData {
+  /// Tag used for identifying serialized [JTypeVariableData] objects in
+  /// a debugging data stream.
+  static const String tag = 'type-variable-data';
+
   final ir.TypeParameter node;
   DartType _bound;
   DartType _defaultType;
 
   JTypeVariableData(this.node);
 
+  factory JTypeVariableData.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.TypeParameter node = source.readTypeParameterNode();
+    source.end(tag);
+    return new JTypeVariableData(node);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeTypeParameterNode(node);
+    sink.end(tag);
+  }
+
   DartType getBound(IrToElementMap elementMap) {
     return _bound ??= elementMap.getDartType(node.bound);
   }
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index 7b9921c..3421f91 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -16,10 +16,12 @@
 import '../constants/constant_system.dart';
 import '../constants/values.dart';
 import '../deferred_load.dart';
+import '../diagnostics/diagnostic_listener.dart';
 import '../elements/entities.dart';
 import '../elements/names.dart';
 import '../elements/types.dart';
 import '../elements/entity_utils.dart' as utils;
+import '../environment.dart';
 import '../enqueue.dart';
 import '../io/kernel_source_information.dart'
     show KernelSourceInformationStrategy;
@@ -42,6 +44,7 @@
 import '../native/behavior.dart';
 import '../ordered_typeset.dart';
 import '../options.dart';
+import '../serialization/serialization.dart';
 import '../ssa/builder_kernel.dart';
 import '../ssa/nodes.dart';
 import '../ssa/ssa.dart';
@@ -64,7 +67,6 @@
 class JsBackendStrategy implements BackendStrategy {
   final Compiler _compiler;
   JsKernelToElementMap _elementMap;
-  Sorter _sorter;
 
   JsBackendStrategy(this._compiler);
 
@@ -101,8 +103,8 @@
   }
 
   @override
-  Sorter get sorter {
-    return _sorter ??= new KernelSorter(elementMap);
+  void registerJClosedWorld(covariant JsClosedWorld closedWorld) {
+    _elementMap = closedWorld.elementMap;
   }
 
   @override
@@ -634,6 +636,8 @@
 }
 
 class JsClosedWorld extends ClosedWorldBase {
+  static const String tag = 'closed-world';
+
   final JsKernelToElementMap elementMap;
   final RuntimeTypesNeed rtiNeed;
   AbstractValueDomain _abstractValueDomain;
@@ -642,6 +646,7 @@
   final GlobalLocalsMap globalLocalsMap;
   final ClosureData closureDataLookup;
   final OutputUnitData outputUnitData;
+  Sorter _sorter;
 
   JsClosedWorld(this.elementMap,
       {ConstantSystem constantSystem,
@@ -656,8 +661,8 @@
       Iterable<MemberEntity> liveInstanceMembers,
       Iterable<MemberEntity> assignedInstanceMembers,
       Iterable<MemberEntity> processedMembers,
-      Map<ClassEntity, Iterable<ClassEntity>> mixinUses,
-      Map<ClassEntity, Iterable<ClassEntity>> typesImplementedBySubclasses,
+      Map<ClassEntity, Set<ClassEntity>> mixinUses,
+      Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses,
       ClassHierarchy classHierarchy,
       AbstractValueStrategy abstractValueStrategy,
       this.annotationsData,
@@ -684,6 +689,113 @@
     _abstractValueDomain = abstractValueStrategy.createDomain(this);
   }
 
+  /// Deserializes a [JsClosedWorld] object from [source].
+  factory JsClosedWorld.readFromDataSource(
+      CompilerOptions options,
+      DiagnosticReporter reporter,
+      Environment environment,
+      AbstractValueStrategy abstractValueStrategy,
+      ir.Component component,
+      DataSource source) {
+    source.begin(tag);
+
+    JsKernelToElementMap elementMap =
+        new JsKernelToElementMap.readFromDataSource(
+            options, reporter, environment, component, source);
+    GlobalLocalsMap globalLocalsMap =
+        new GlobalLocalsMap.readFromDataSource(source);
+    source.registerLocalLookup(new LocalLookupImpl(globalLocalsMap));
+    ClassHierarchy classHierarchy = new ClassHierarchy.readFromDataSource(
+        source, elementMap.commonElements);
+    NativeData nativeData = new NativeData.readFromDataSource(
+        source, elementMap.elementEnvironment);
+    elementMap.nativeBasicData = nativeData;
+    InterceptorData interceptorData = new InterceptorData.readFromDataSource(
+        source, nativeData, elementMap.commonElements);
+    BackendUsage backendUsage = new BackendUsage.readFromDataSource(source);
+    RuntimeTypesNeed rtiNeed = new RuntimeTypesNeed.readFromDataSource(
+        source, elementMap.elementEnvironment);
+    JAllocatorAnalysis allocatorAnalysis =
+        new JAllocatorAnalysis.readFromDataSource(source, options);
+    NoSuchMethodData noSuchMethodData =
+        new NoSuchMethodData.readFromDataSource(source);
+
+    Set<ClassEntity> implementedClasses = source.readClasses().toSet();
+    Iterable<ClassEntity> liveNativeClasses = source.readClasses();
+    Iterable<MemberEntity> liveInstanceMembers = source.readMembers();
+    Iterable<MemberEntity> assignedInstanceMembers = source.readMembers();
+    Iterable<MemberEntity> processedMembers = source.readMembers();
+    Map<ClassEntity, Set<ClassEntity>> mixinUses =
+        source.readClassMap(() => source.readClasses().toSet());
+    Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses =
+        source.readClassMap(() => source.readClasses().toSet());
+
+    AnnotationsData annotationsData =
+        new AnnotationsData.readFromDataSource(source);
+
+    ClosureData closureData =
+        new ClosureData.readFromDataSource(elementMap, source);
+
+    OutputUnitData outputUnitData =
+        new OutputUnitData.readFromDataSource(source);
+
+    source.end(tag);
+
+    return new JsClosedWorld(elementMap,
+        nativeData: nativeData,
+        interceptorData: interceptorData,
+        backendUsage: backendUsage,
+        rtiNeed: rtiNeed,
+        allocatorAnalysis: allocatorAnalysis,
+        noSuchMethodData: noSuchMethodData,
+        implementedClasses: implementedClasses,
+        liveNativeClasses: liveNativeClasses,
+        liveInstanceMembers: liveInstanceMembers,
+        assignedInstanceMembers: assignedInstanceMembers,
+        processedMembers: processedMembers,
+        mixinUses: mixinUses,
+        typesImplementedBySubclasses: typesImplementedBySubclasses,
+        classHierarchy: classHierarchy,
+        abstractValueStrategy: abstractValueStrategy,
+        annotationsData: annotationsData,
+        globalLocalsMap: globalLocalsMap,
+        closureDataLookup: closureData,
+        outputUnitData: outputUnitData);
+  }
+
+  /// Serializes this [JsClosedWorld] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    elementMap.writeToDataSink(sink);
+    globalLocalsMap.writeToDataSink(sink);
+
+    classHierarchy.writeToDataSink(sink);
+    nativeData.writeToDataSink(sink);
+    interceptorData.writeToDataSink(sink);
+    backendUsage.writeToDataSink(sink);
+    rtiNeed.writeToDataSink(sink);
+    allocatorAnalysis.writeToDataSink(sink);
+    noSuchMethodData.writeToDataSink(sink);
+    sink.writeClasses(implementedClasses);
+    sink.writeClasses(liveNativeClasses);
+    sink.writeMembers(liveInstanceMembers);
+    sink.writeMembers(assignedInstanceMembers);
+    sink.writeMembers(processedMembers);
+    sink.writeClassMap(
+        mixinUses, (Set<ClassEntity> set) => sink.writeClasses(set));
+    sink.writeClassMap(typesImplementedBySubclasses,
+        (Set<ClassEntity> set) => sink.writeClasses(set));
+    annotationsData.writeToDataSink(sink);
+    closureDataLookup.writeToDataSink(sink);
+    outputUnitData.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
+  @override
+  Sorter get sorter {
+    return _sorter ??= new KernelSorter(elementMap);
+  }
+
   @override
   AbstractValueDomain get abstractValueDomain {
     return _abstractValueDomain;
@@ -1236,3 +1348,16 @@
         a, definition1.location, b, definition2.location);
   }
 }
+
+/// [LocalLookup] implementation used to deserialize [JsClosedWorld].
+class LocalLookupImpl implements LocalLookup {
+  final GlobalLocalsMap _globalLocalsMap;
+
+  LocalLookupImpl(this._globalLocalsMap);
+
+  @override
+  Local getLocalByIndex(MemberEntity memberContext, int index) {
+    KernelToLocalsMapImpl map = _globalLocalsMap.getLocalsMap(memberContext);
+    return map.getLocalByIndex(index);
+  }
+}
diff --git a/pkg/compiler/lib/src/js_model/locals.dart b/pkg/compiler/lib/src/js_model/locals.dart
index 14a933d..6f66920 100644
--- a/pkg/compiler/lib/src/js_model/locals.dart
+++ b/pkg/compiler/lib/src/js_model/locals.dart
@@ -13,15 +13,60 @@
 import '../elements/jumps.dart';
 import '../elements/types.dart';
 import '../ir/util.dart';
+import '../serialization/serialization.dart';
 
 import 'element_map.dart';
 import 'elements.dart' show JGeneratorBody;
 
 class GlobalLocalsMap {
+  /// Tag used for identifying serialized [GlobalLocalsMap] objects in a
+  /// debugging data stream.
+  static const String tag = 'global-locals-map';
+
   final Map<MemberEntity, KernelToLocalsMap> _localsMaps;
 
   GlobalLocalsMap() : _localsMaps = {};
 
+  GlobalLocalsMap.internal(this._localsMaps);
+
+  /// Deserializes a [GlobalLocalsMap] object from [source].
+  factory GlobalLocalsMap.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Map<MemberEntity, KernelToLocalsMap> _localsMaps = {};
+    int mapCount = source.readInt();
+    for (int i = 0; i < mapCount; i++) {
+      KernelToLocalsMap localsMap =
+          new KernelToLocalsMapImpl.readFromDataSource(source);
+      List<MemberEntity> members = source.readMembers();
+      for (MemberEntity member in members) {
+        _localsMaps[member] = localsMap;
+      }
+    }
+    source.end(tag);
+    return new GlobalLocalsMap.internal(_localsMaps);
+  }
+
+  /// Serializes this [GlobalLocalsMap] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    // [KernelToLocalsMap]s are shared between members and their nested
+    // closures, so we reverse [_localsMaps] to ensure that [KernelToLocalsMap]s
+    // are shared upon deserialization. The sharing is needed for correctness
+    // since captured variables will otherwise have distinct locals for their
+    // non-captured and captured uses.
+    Map<KernelToLocalsMap, List<MemberEntity>> reverseMap = {};
+    _localsMaps.forEach((MemberEntity member, KernelToLocalsMap localsMap) {
+      reverseMap.putIfAbsent(localsMap, () => []).add(member);
+    });
+    sink.writeInt(reverseMap.length);
+    reverseMap
+        .forEach((KernelToLocalsMap localsMap, List<MemberEntity> members) {
+      localsMap.writeToDataSink(sink);
+      sink.writeMembers(members);
+    });
+    sink.end(tag);
+  }
+
   /// Returns the [KernelToLocalsMap] for [member].
   KernelToLocalsMap getLocalsMap(MemberEntity member) {
     // If element is a ConstructorBodyEntity, its localsMap is the same as for
@@ -48,6 +93,10 @@
 }
 
 class KernelToLocalsMapImpl implements KernelToLocalsMap {
+  /// Tag used for identifying serialized [KernelToLocalsMapImpl] objects in a
+  /// debugging data stream.
+  static const String tag = 'locals-map';
+
   MemberEntity _currentMember;
   final EntityDataMap<JLocal, LocalData> _locals =
       new EntityDataMap<JLocal, LocalData>();
@@ -56,6 +105,73 @@
   Map<ir.TreeNode, JJumpTarget> _jumpTargetMap;
   Iterable<ir.BreakStatement> _breaksAsContinue;
 
+  KernelToLocalsMapImpl(this._currentMember);
+
+  /// Deserializes a [KernelToLocalsMapImpl] object from [source].
+  KernelToLocalsMapImpl.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    _currentMember = source.readMember();
+    int localsCount = source.readInt();
+    for (int i = 0; i < localsCount; i++) {
+      int index = source.readInt();
+      String name = source.readStringOrNull();
+      bool isRegularParameter = source.readBool();
+      ir.VariableDeclaration node = source.readTreeNode();
+      JLocal local = new JLocal(name, currentMember,
+          isRegularParameter: isRegularParameter);
+      LocalData data = new LocalData(node);
+      _locals.registerByIndex(index, local, data);
+      _variableMap[node] = local;
+    }
+    int jumpCount = source.readInt();
+    if (jumpCount > 0) {
+      _jumpTargetMap = {};
+      for (int i = 0; i < jumpCount; i++) {
+        JJumpTarget target = new JJumpTarget.readFromDataSource(source);
+        List<ir.TreeNode> nodes = source.readTreeNodes();
+        for (ir.TreeNode node in nodes) {
+          _jumpTargetMap[node] = target;
+        }
+      }
+    }
+    _breaksAsContinue = source.readTreeNodes();
+    source.end(tag);
+  }
+
+  /// Serializes this [KernelToLocalsMapImpl] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMember(currentMember);
+    sink.writeInt(_locals.size);
+    _locals.forEach((JLocal local, LocalData data) {
+      assert(local.memberContext == currentMember);
+      sink.writeInt(local.localIndex);
+      sink.writeStringOrNull(local.name);
+      sink.writeBool(local.isRegularParameter);
+      sink.writeTreeNode(data.node);
+    });
+    if (_jumpTargetMap != null) {
+      // [JJumpTarget]s are shared between nodes, so we reverse
+      // [_jumpTargetMap] to ensure that [JJumpTarget]s are shared upon
+      // deserialization. This sharing is needed for correctness since for
+      // instance a label statement containing a for loop both constitutes the
+      // same jump target and the SSA graph builder dependents on this property.
+      Map<JJumpTarget, List<ir.TreeNode>> reversedMap = {};
+      _jumpTargetMap.forEach((ir.TreeNode node, JJumpTarget target) {
+        reversedMap.putIfAbsent(target, () => []).add(node);
+      });
+      sink.writeInt(reversedMap.length);
+      reversedMap.forEach((JJumpTarget target, List<ir.TreeNode> nodes) {
+        target.writeToDataSink(sink);
+        sink.writeTreeNodes(nodes);
+      });
+    } else {
+      sink.writeInt(0);
+    }
+    sink.writeTreeNodes(_breaksAsContinue, allowNull: true);
+    sink.end(tag);
+  }
+
   // TODO(johnniwinther): Compute this eagerly from the root of the member.
   void _ensureJumpMap(ir.TreeNode node) {
     if (_jumpTargetMap == null) {
@@ -72,9 +188,12 @@
     }
   }
 
-  KernelToLocalsMapImpl(this._currentMember);
-
   MemberEntity get currentMember => _currentMember;
+
+  Local getLocalByIndex(int index) {
+    return _locals.getEntity(index);
+  }
+
   @override
   JumpTarget getJumpTargetForBreak(ir.BreakStatement node) {
     _ensureJumpMap(node.target);
@@ -328,6 +447,10 @@
 }
 
 class JJumpTarget extends JumpTarget {
+  /// Tag used for identifying serialized [JJumpTarget] objects in a
+  /// debugging data stream.
+  static const String tag = 'jump-target';
+
   final MemberEntity memberContext;
   final int nestingLevel;
   List<LabelDefinition> _labels;
@@ -337,6 +460,47 @@
   JJumpTarget(this.memberContext, this.nestingLevel,
       {this.isSwitch: false, this.isSwitchCase: false});
 
+  /// Deserializes a [JJumpTarget] object from [source].
+  factory JJumpTarget.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberEntity memberContext = source.readMember();
+    int nestingLevel = source.readInt();
+    bool isSwitch = source.readBool();
+    bool isSwitchCase = source.readBool();
+    JJumpTarget target = new JJumpTarget(memberContext, nestingLevel,
+        isSwitch: isSwitch, isSwitchCase: isSwitchCase);
+    int labelCount = source.readInt();
+    for (int i = 0; i < labelCount; i++) {
+      String labelName = source.readString();
+      bool isBreakTarget = source.readBool();
+      bool isContinueTarget = source.readBool();
+      target.addLabel(labelName,
+          isBreakTarget: isBreakTarget, isContinueTarget: isContinueTarget);
+    }
+    source.end(tag);
+    return target;
+  }
+
+  /// Serializes this [JJumpTarget] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeMember(memberContext);
+    sink.writeInt(nestingLevel);
+    sink.writeBool(isSwitch);
+    sink.writeBool(isSwitchCase);
+    if (_labels != null) {
+      sink.writeInt(_labels.length);
+      for (LabelDefinition definition in _labels) {
+        sink.writeString(definition.name);
+        sink.writeBool(definition.isBreakTarget);
+        sink.writeBool(definition.isContinueTarget);
+      }
+    } else {
+      sink.writeInt(0);
+    }
+    sink.end(tag);
+  }
+
   bool isBreakTarget = false;
   bool isContinueTarget = false;
 
diff --git a/pkg/compiler/lib/src/kernel/env.dart b/pkg/compiler/lib/src/kernel/env.dart
index a343e9f..e82ef91 100644
--- a/pkg/compiler/lib/src/kernel/env.dart
+++ b/pkg/compiler/lib/src/kernel/env.dart
@@ -399,7 +399,7 @@
       }
       if (!includeStatic && member.isStatic) return;
       if (member.isNoSuchMethodForwarder) {
-        // TODO(sigmund): remove once #33665 is fixed.
+        // TODO(sigmund): remove once #33732 is fixed.
         if (!includeNoSuchMethodForwarders ||
             member.name.isPrivate &&
                 member.name.libraryName != member.enclosingLibrary.reference) {
diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
index 5de83bd..a437151 100644
--- a/pkg/compiler/lib/src/native/behavior.dart
+++ b/pkg/compiler/lib/src/native/behavior.dart
@@ -9,6 +9,7 @@
 import '../elements/types.dart';
 import '../js/js.dart' as js;
 import '../js_backend/native_data.dart' show NativeBasicData;
+import '../serialization/serialization.dart';
 import '../universe/side_effects.dart' show SideEffects;
 import 'js.dart';
 
@@ -124,6 +125,10 @@
  * `null` may be returned.
  */
 class NativeBehavior {
+  /// Tag used for identifying serialized [NativeBehavior] objects in a
+  /// debugging data stream.
+  static const String tag = 'native-behavior';
+
   /// [DartType]s or [SpecialType]s returned or yielded by the native
   /// element.
   final List typesReturned = [];
@@ -156,6 +161,88 @@
 
   NativeBehavior.internal(this.sideEffects);
 
+  /// Deserializes a [NativeBehavior] object from [source].
+  factory NativeBehavior.readFromDataSource(DataSource source) {
+    source.begin(tag);
+
+    List readTypes() {
+      List types = [];
+      types.addAll(source.readDartTypes());
+      int specialCount = source.readInt();
+      for (int i = 0; i < specialCount; i++) {
+        String name = source.readString();
+        types.add(SpecialType.fromName(name));
+      }
+      return types;
+    }
+
+    List typesReturned = readTypes();
+    List typesInstantiated = readTypes();
+    String codeTemplateText = source.readStringOrNull();
+    SideEffects sideEffects = new SideEffects.readFromDataSource(source);
+    int throwBehavior = source.readInt();
+    bool isAllocation = source.readBool();
+    bool useGvn = source.readBool();
+    source.end(tag);
+
+    NativeBehavior behavior = new NativeBehavior.internal(sideEffects);
+    behavior.typesReturned.addAll(typesReturned);
+    behavior.typesInstantiated.addAll(typesInstantiated);
+    if (codeTemplateText != null) {
+      behavior.codeTemplateText = codeTemplateText;
+      behavior.codeTemplate = js.js.parseForeignJS(codeTemplateText);
+    }
+    switch (throwBehavior) {
+      case 0:
+        behavior.throwBehavior = NativeThrowBehavior.NEVER;
+        break;
+      case 1:
+        behavior.throwBehavior =
+            NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS;
+        break;
+      case 2:
+        behavior.throwBehavior = NativeThrowBehavior.MAY;
+        break;
+      case 3:
+        behavior.throwBehavior = NativeThrowBehavior.MUST;
+        break;
+    }
+    behavior.isAllocation = isAllocation;
+    behavior.useGvn = useGvn;
+    return behavior;
+  }
+
+  /// Serializes this [NativeBehavior] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+
+    void writeTypes(List types) {
+      List<DartType> dartTypes = [];
+      List<SpecialType> specialTypes = [];
+      for (var type in types) {
+        if (type is DartType) {
+          dartTypes.add(type);
+        } else {
+          specialTypes.add(type);
+        }
+      }
+      sink.writeDartTypes(dartTypes);
+      sink.writeInt(specialTypes.length);
+      for (SpecialType type in specialTypes) {
+        sink.writeString(type.name);
+      }
+    }
+
+    writeTypes(typesReturned);
+    writeTypes(typesInstantiated);
+    sink.writeStringOrNull(codeTemplateText);
+    sideEffects.writeToDataSink(sink);
+    sink.writeInt(throwBehavior._bits);
+    sink.writeBool(isAllocation);
+    sink.writeBool(useGvn);
+    sink.end(tag);
+  }
+
   String toString() {
     return 'NativeBehavior('
         'returns: ${typesReturned}'
diff --git a/pkg/compiler/lib/src/ordered_typeset.dart b/pkg/compiler/lib/src/ordered_typeset.dart
index 4b587b6..57ed4cb 100644
--- a/pkg/compiler/lib/src/ordered_typeset.dart
+++ b/pkg/compiler/lib/src/ordered_typeset.dart
@@ -11,6 +11,7 @@
 import 'diagnostics/diagnostic_listener.dart' show DiagnosticReporter;
 import 'elements/entities.dart';
 import 'elements/types.dart';
+import 'serialization/serialization.dart';
 
 /**
  * An ordered set of the supertypes of a class. The supertypes of a class are
@@ -30,12 +31,80 @@
  *     C: [C, B, A, Object]
  */
 class OrderedTypeSet {
+  /// Tag used for identifying serialized [OrderedTypeSet] objects in a
+  /// debugging data stream.
+  static const String tag = 'ordered-type-set';
+
   final List<Link<InterfaceType>> _levels;
   final Link<InterfaceType> types;
   final Link<InterfaceType> _supertypes;
 
   OrderedTypeSet.internal(this._levels, this.types, this._supertypes);
 
+  /// Deserializes a [OrderedTypeSet] object from [source].
+  factory OrderedTypeSet.readFromDataSource(DataSource source) {
+    // TODO(johnniwinther): Make the deserialized type sets share their
+    // internal links like the original type sets do?
+    source.begin(tag);
+    int typesCount = source.readInt();
+    LinkBuilder<InterfaceType> typeLinkBuilder =
+        new LinkBuilder<InterfaceType>();
+    List<Link<InterfaceType>> links = [];
+    for (int i = 0; i < typesCount; i++) {
+      links.add(typeLinkBuilder.addLast(source.readDartType()));
+    }
+    Link<InterfaceType> types =
+        typeLinkBuilder.toLink(const Link<InterfaceType>());
+    links.add(const Link<InterfaceType>());
+
+    int supertypesCount = source.readInt();
+    LinkBuilder<InterfaceType> supertypeLinkBuilder =
+        new LinkBuilder<InterfaceType>();
+    for (int i = 0; i < supertypesCount; i++) {
+      supertypeLinkBuilder.addLast(source.readDartType());
+    }
+    Link<InterfaceType> supertypes =
+        supertypeLinkBuilder.toLink(const Link<InterfaceType>());
+
+    int levelCount = source.readInt();
+    List<Link<InterfaceType>> levels =
+        new List<Link<InterfaceType>>(levelCount);
+    for (int i = 0; i < levelCount; i++) {
+      levels[i] = links[source.readInt()];
+    }
+    source.end(tag);
+    return new OrderedTypeSet.internal(levels, types, supertypes);
+  }
+
+  /// Serializes this [OrderedTypeSet] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    List<InterfaceType> typeList = types.toList();
+    sink.writeInt(typeList.length);
+    for (InterfaceType type in typeList) {
+      sink.writeDartType(type);
+    }
+    List<InterfaceType> supertypeList = _supertypes.toList();
+    sink.writeInt(supertypeList.length);
+    for (InterfaceType supertype in supertypeList) {
+      sink.writeDartType(supertype);
+    }
+    List<int> levelList = [];
+    Link<InterfaceType> link = types;
+    while (link != null) {
+      int index = _levels.indexOf(link);
+      if (index != -1) {
+        levelList.add(index);
+      }
+      link = link.tail;
+    }
+    sink.writeInt(levelList.length);
+    for (int level in levelList) {
+      sink.writeInt(level);
+    }
+    sink.end(tag);
+  }
+
   factory OrderedTypeSet.singleton(InterfaceType type) {
     Link<InterfaceType> types =
         new LinkEntry<InterfaceType>(type, const Link<InterfaceType>());
diff --git a/pkg/compiler/lib/src/serialization/abstract_sink.dart b/pkg/compiler/lib/src/serialization/abstract_sink.dart
new file mode 100644
index 0000000..b63b9a2
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/abstract_sink.dart
@@ -0,0 +1,332 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Base implementation of [DataSink] using [DataSinkMixin] to implement
+/// convenience methods.
+abstract class AbstractDataSink extends DataSinkMixin implements DataSink {
+  /// If `true`, serialization of every data kind is preceded by a [DataKind]
+  /// value.
+  ///
+  /// This is used for debugging data inconsistencies between serialization
+  /// and deserialization.
+  final bool useDataKinds;
+
+  /// Visitor used for serializing [DartType]s.
+  DartTypeWriter _dartTypeWriter;
+
+  /// Stack of tags used when [useDataKinds] is `true` to help debugging section
+  /// inconsistencies between serialization and deserialization.
+  List<String> _tags;
+
+  /// Map of [_MemberData] object for serialized kernel member nodes.
+  Map<ir.Member, _MemberData> _memberData = {};
+
+  AbstractDataSink({this.useDataKinds: false}) {
+    _dartTypeWriter = new DartTypeWriter(this);
+  }
+
+  void begin(String tag) {
+    if (useDataKinds) {
+      _tags ??= <String>[];
+      _tags.add(tag);
+      _begin(tag);
+    }
+  }
+
+  void end(Object tag) {
+    if (useDataKinds) {
+      _end(tag);
+
+      String existingTag = _tags.removeLast();
+      assert(existingTag == tag,
+          "Unexpected tag end. Expected $existingTag, found $tag.");
+    }
+  }
+
+  @override
+  void writeSourceSpan(SourceSpan value) {
+    _writeDataKind(DataKind.sourceSpan);
+    _writeUri(value.uri);
+    _writeInt(value.begin);
+    _writeInt(value.end);
+  }
+
+  @override
+  void writeDartType(DartType value, {bool allowNull: false}) {
+    _writeDataKind(DataKind.dartType);
+    _writeDartType(value, [], allowNull: allowNull);
+  }
+
+  void _writeDartType(
+      DartType value, List<FunctionTypeVariable> functionTypeVariables,
+      {bool allowNull: false}) {
+    if (value == null) {
+      if (!allowNull) {
+        throw new UnsupportedError("Missing DartType is not allowed.");
+      }
+      writeEnum(DartTypeKind.none);
+    } else {
+      _dartTypeWriter.visit(value, functionTypeVariables);
+    }
+  }
+
+  @override
+  void writeMemberNode(ir.Member value) {
+    _writeDataKind(DataKind.memberNode);
+    _writeMemberNode(value);
+  }
+
+  void _writeMemberNode(ir.Member value) {
+    ir.Class cls = value.enclosingClass;
+    if (cls != null) {
+      _writeEnum(MemberContextKind.cls);
+      _writeClassNode(cls);
+      _writeString(_computeMemberName(value));
+    } else {
+      _writeEnum(MemberContextKind.library);
+      _writeLibraryNode(value.enclosingLibrary);
+      _writeString(_computeMemberName(value));
+    }
+  }
+
+  @override
+  void writeClassNode(ir.Class value) {
+    _writeDataKind(DataKind.classNode);
+    _writeClassNode(value);
+  }
+
+  void _writeClassNode(ir.Class value) {
+    _writeLibraryNode(value.enclosingLibrary);
+    _writeString(value.name);
+  }
+
+  @override
+  void writeLibraryNode(ir.Library value) {
+    _writeDataKind(DataKind.libraryNode);
+    _writeLibraryNode(value);
+  }
+
+  void _writeLibraryNode(ir.Library value) {
+    _writeUri(value.importUri);
+  }
+
+  @override
+  void writeEnum(dynamic value) {
+    _writeDataKind(DataKind.enumValue);
+    _writeEnum(value);
+  }
+
+  @override
+  void writeBool(bool value) {
+    assert(value != null);
+    _writeDataKind(DataKind.bool);
+    _writeInt(value ? 1 : 0);
+  }
+
+  @override
+  void writeUri(Uri value) {
+    assert(value != null);
+    _writeDataKind(DataKind.uri);
+    _writeUri(value);
+  }
+
+  @override
+  void writeString(String value) {
+    assert(value != null);
+    _writeDataKind(DataKind.string);
+    _writeString(value);
+  }
+
+  @override
+  void writeInt(int value) {
+    assert(value != null);
+    assert(value >= 0 && value >> 30 == 0);
+    _writeDataKind(DataKind.int);
+    _writeInt(value);
+  }
+
+  void writeTreeNode(ir.TreeNode value) {
+    _writeDataKind(DataKind.treeNode);
+    _writeTreeNode(value);
+  }
+
+  void _writeTreeNode(ir.TreeNode value) {
+    if (value is ir.Class) {
+      _writeEnum(_TreeNodeKind.cls);
+      _writeClassNode(value);
+    } else if (value is ir.Member) {
+      _writeEnum(_TreeNodeKind.member);
+      _writeMemberNode(value);
+    } else if (value is ir.VariableDeclaration &&
+        value.parent is ir.FunctionDeclaration) {
+      _writeEnum(_TreeNodeKind.functionDeclarationVariable);
+      _writeTreeNode(value.parent);
+    } else if (value is ir.FunctionNode) {
+      _writeEnum(_TreeNodeKind.functionNode);
+      _writeFunctionNode(value);
+    } else if (value is ir.TypeParameter) {
+      _writeEnum(_TreeNodeKind.typeParameter);
+      _writeTypeParameter(value);
+    } else {
+      _writeEnum(_TreeNodeKind.node);
+      ir.TreeNode member = value;
+      while (member is! ir.Member) {
+        if (member == null) {
+          throw new UnsupportedError("No enclosing member of TreeNode "
+              "$value (${value.runtimeType})");
+        }
+        member = member.parent;
+      }
+      _writeMemberNode(member);
+      _MemberData memberData = _memberData[member] ??= new _MemberData(member);
+      int index = memberData.getIndexByTreeNode(value);
+      assert(index != null, "No index found for ${value.runtimeType}.");
+      _writeInt(index);
+    }
+  }
+
+  void _writeFunctionNode(ir.FunctionNode value) {
+    ir.TreeNode parent = value.parent;
+    if (parent is ir.Procedure) {
+      _writeEnum(_FunctionNodeKind.procedure);
+      _writeMemberNode(parent);
+    } else if (parent is ir.Constructor) {
+      _writeEnum(_FunctionNodeKind.constructor);
+      _writeMemberNode(parent);
+    } else if (parent is ir.FunctionExpression) {
+      _writeEnum(_FunctionNodeKind.functionExpression);
+      _writeTreeNode(parent);
+    } else if (parent is ir.FunctionDeclaration) {
+      _writeEnum(_FunctionNodeKind.functionDeclaration);
+      _writeTreeNode(parent);
+    } else {
+      throw new UnsupportedError(
+          "Unsupported FunctionNode parent ${parent.runtimeType}");
+    }
+  }
+
+  @override
+  void writeTypeParameterNode(ir.TypeParameter value) {
+    _writeDataKind(DataKind.typeParameterNode);
+    _writeTypeParameter(value);
+  }
+
+  void _writeTypeParameter(ir.TypeParameter value) {
+    ir.TreeNode parent = value.parent;
+    if (parent is ir.Class) {
+      _writeEnum(_TypeParameterKind.cls);
+      _writeClassNode(parent);
+      _writeInt(parent.typeParameters.indexOf(value));
+    } else if (parent is ir.FunctionNode) {
+      _writeEnum(_TypeParameterKind.functionNode);
+      _writeFunctionNode(parent);
+      _writeInt(parent.typeParameters.indexOf(value));
+    } else {
+      throw new UnsupportedError(
+          "Unsupported TypeParameter parent ${parent.runtimeType}");
+    }
+  }
+
+  void _writeDataKind(DataKind kind) {
+    if (useDataKinds) _writeEnum(kind);
+  }
+
+  void writeLibrary(IndexedLibrary value) {
+    writeInt(value.libraryIndex);
+  }
+
+  void writeClass(IndexedClass value) {
+    writeInt(value.classIndex);
+  }
+
+  void writeTypedef(IndexedTypedef value) {
+    writeInt(value.typedefIndex);
+  }
+
+  void writeMember(IndexedMember value) {
+    writeInt(value.memberIndex);
+  }
+
+  void writeLocal(Local local) {
+    if (local is JLocal) {
+      writeEnum(LocalKind.jLocal);
+      writeMember(local.memberContext);
+      writeInt(local.localIndex);
+    } else if (local is ThisLocal) {
+      writeEnum(LocalKind.thisLocal);
+      writeClass(local.enclosingClass);
+    } else if (local is BoxLocal) {
+      writeEnum(LocalKind.boxLocal);
+      writeClass(local.container);
+    } else if (local is AnonymousClosureLocal) {
+      writeEnum(LocalKind.anonymousClosureLocal);
+      writeClass(local.closureClass);
+    } else if (local is TypeVariableLocal) {
+      writeEnum(LocalKind.typeVariableLocal);
+      writeDartType(local.typeVariable);
+    } else {
+      throw new UnsupportedError("Unsupported local ${local.runtimeType}");
+    }
+  }
+
+  @override
+  void writeConstant(ConstantValue value) {
+    _writeDataKind(DataKind.constant);
+    _writeConstant(value);
+  }
+
+  void _writeConstant(ConstantValue value) {
+    _writeEnum(value.kind);
+    switch (value.kind) {
+      case ConstantValueKind.BOOL:
+        BoolConstantValue constant = value;
+        writeBool(constant.boolValue);
+        break;
+      case ConstantValueKind.INT:
+        IntConstantValue constant = value;
+        writeString(constant.intValue.toString());
+        break;
+      case ConstantValueKind.DOUBLE:
+        DoubleConstantValue constant = value;
+        ByteData data = new ByteData(8);
+        data.setFloat64(0, constant.doubleValue);
+        writeInt(data.getUint16(0));
+        writeInt(data.getUint16(2));
+        writeInt(data.getUint16(4));
+        writeInt(data.getUint16(6));
+        break;
+      case ConstantValueKind.STRING:
+        StringConstantValue constant = value;
+        writeString(constant.stringValue);
+        break;
+      case ConstantValueKind.NULL:
+        break;
+      default:
+        // TODO(johnniwinther): Support remaining constant values.
+        throw new UnsupportedError(
+            "Unexpected constant value kind ${value.kind}.");
+    }
+  }
+
+  /// Actual serialization of a section begin tag, implemented by subclasses.
+  void _begin(String tag);
+
+  /// Actual serialization of a section end tag, implemented by subclasses.
+  void _end(String tag);
+
+  /// Actual serialization of a URI value, implemented by subclasses.
+  void _writeUri(Uri value);
+
+  /// Actual serialization of a String value, implemented by subclasses.
+  void _writeString(String value);
+
+  /// Actual serialization of a non-negative integer value, implemented by
+  /// subclasses.
+  void _writeInt(int value);
+
+  /// Actual serialization of an enum value, implemented by subclasses.
+  void _writeEnum(dynamic value);
+}
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
new file mode 100644
index 0000000..f258917
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -0,0 +1,418 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Base implementation of [DataSource] using [DataSourceMixin] to implement
+/// convenience methods.
+abstract class AbstractDataSource extends DataSourceMixin
+    implements DataSource {
+  final bool useDataKinds;
+  ComponentLookup _componentLookup;
+  EntityLookup _entityLookup;
+  LocalLookup _localLookup;
+
+  AbstractDataSource({this.useDataKinds: false});
+
+  void begin(String tag) {
+    if (useDataKinds) _begin(tag);
+  }
+
+  void end(String tag) {
+    if (useDataKinds) _end(tag);
+  }
+
+  void registerComponentLookup(ComponentLookup componentLookup) {
+    assert(_componentLookup == null);
+    _componentLookup = componentLookup;
+  }
+
+  ComponentLookup get componentLookup {
+    assert(_componentLookup != null);
+    return _componentLookup;
+  }
+
+  void registerEntityLookup(EntityLookup entityLookup) {
+    assert(_entityLookup == null);
+    _entityLookup = entityLookup;
+  }
+
+  EntityLookup get entityLookup {
+    assert(_entityLookup != null);
+    return _entityLookup;
+  }
+
+  void registerLocalLookup(LocalLookup localLookup) {
+    assert(_localLookup == null);
+    _localLookup = localLookup;
+  }
+
+  LocalLookup get localLookup {
+    assert(_localLookup != null);
+    return _localLookup;
+  }
+
+  IndexedLibrary readLibrary() {
+    return getIndexedLibrary(readInt());
+  }
+
+  IndexedClass readClass() {
+    return getIndexedClass(readInt());
+  }
+
+  IndexedTypedef readTypedef() {
+    return getIndexedTypedef(readInt());
+  }
+
+  IndexedMember readMember() {
+    return getIndexedMember(readInt());
+  }
+
+  IndexedLibrary getIndexedLibrary(int libraryIndex) =>
+      entityLookup.getLibraryByIndex(libraryIndex);
+
+  IndexedClass getIndexedClass(int classIndex) =>
+      entityLookup.getClassByIndex(classIndex);
+
+  IndexedTypedef getIndexedTypedef(int typedefIndex) =>
+      entityLookup.getTypedefByIndex(typedefIndex);
+
+  IndexedMember getIndexedMember(int memberIndex) =>
+      entityLookup.getMemberByIndex(memberIndex);
+
+  IndexedTypeVariable getIndexedTypeVariable(int typeVariableIndex) =>
+      entityLookup.getTypeVariableByIndex(typeVariableIndex);
+
+  List<DartType> _readDartTypes(
+      List<FunctionTypeVariable> functionTypeVariables) {
+    int count = readInt();
+    List<DartType> types = new List<DartType>(count);
+    for (int index = 0; index < count; index++) {
+      types[index] = _readDartType(functionTypeVariables);
+    }
+    return types;
+  }
+
+  @override
+  SourceSpan readSourceSpan() {
+    _checkDataKind(DataKind.sourceSpan);
+    Uri uri = _readUri();
+    int begin = _readInt();
+    int end = _readInt();
+    return new SourceSpan(uri, begin, end);
+  }
+
+  @override
+  DartType readDartType({bool allowNull: false}) {
+    _checkDataKind(DataKind.dartType);
+    DartType type = _readDartType([]);
+    assert(type != null || allowNull);
+    return type;
+  }
+
+  DartType _readDartType(List<FunctionTypeVariable> functionTypeVariables) {
+    DartTypeKind kind = readEnum(DartTypeKind.values);
+    switch (kind) {
+      case DartTypeKind.none:
+        return null;
+      case DartTypeKind.voidType:
+        return const VoidType();
+      case DartTypeKind.typeVariable:
+        return new TypeVariableType(getIndexedTypeVariable(readInt()));
+      case DartTypeKind.functionTypeVariable:
+        int index = readInt();
+        assert(0 <= index && index < functionTypeVariables.length);
+        return functionTypeVariables[index];
+      case DartTypeKind.functionType:
+        int typeVariableCount = readInt();
+        List<FunctionTypeVariable> typeVariables =
+            new List<FunctionTypeVariable>.generate(typeVariableCount,
+                (int index) => new FunctionTypeVariable(index));
+        functionTypeVariables =
+            new List<FunctionTypeVariable>.from(functionTypeVariables)
+              ..addAll(typeVariables);
+        for (int index = 0; index < typeVariableCount; index++) {
+          typeVariables[index].bound = _readDartType(functionTypeVariables);
+        }
+        DartType returnType = _readDartType(functionTypeVariables);
+        List<DartType> parameterTypes = _readDartTypes(functionTypeVariables);
+        List<DartType> optionalParameterTypes =
+            _readDartTypes(functionTypeVariables);
+        List<DartType> namedParameterTypes =
+            _readDartTypes(functionTypeVariables);
+        List<String> namedParameters =
+            new List<String>(namedParameterTypes.length);
+        for (int i = 0; i < namedParameters.length; i++) {
+          namedParameters[i] = readString();
+        }
+        return new FunctionType(
+            returnType,
+            parameterTypes,
+            optionalParameterTypes,
+            namedParameters,
+            namedParameterTypes,
+            typeVariables);
+
+      case DartTypeKind.interfaceType:
+        IndexedClass cls = getIndexedClass(readInt());
+        List<DartType> typeArguments = _readDartTypes(functionTypeVariables);
+        return new InterfaceType(cls, typeArguments);
+      case DartTypeKind.typedef:
+        IndexedTypedef typedef = getIndexedTypedef(readInt());
+        List<DartType> typeArguments = _readDartTypes(functionTypeVariables);
+        DartType unaliased = _readDartType(functionTypeVariables);
+        return new TypedefType(typedef, typeArguments, unaliased);
+      case DartTypeKind.dynamicType:
+        return const DynamicType();
+      case DartTypeKind.futureOr:
+        DartType typeArgument = _readDartType(functionTypeVariables);
+        return new FutureOrType(typeArgument);
+    }
+    throw new UnsupportedError("Unexpected DartTypeKind $kind");
+  }
+
+  _MemberData _readMemberData() {
+    MemberContextKind kind = _readEnum(MemberContextKind.values);
+    switch (kind) {
+      case MemberContextKind.cls:
+        _ClassData cls = _readClassData();
+        String name = _readString();
+        return cls.lookupMember(name);
+      case MemberContextKind.library:
+        _LibraryData library = _readLibraryData();
+        String name = _readString();
+        return library.lookupMember(name);
+    }
+    throw new UnsupportedError("Unsupported _MemberKind $kind");
+  }
+
+  @override
+  ir.Member readMemberNode() {
+    _checkDataKind(DataKind.memberNode);
+    return _readMemberData().node;
+  }
+
+  _ClassData _readClassData() {
+    _LibraryData library = _readLibraryData();
+    String name = _readString();
+    return library.lookupClass(name);
+  }
+
+  @override
+  ir.Class readClassNode() {
+    _checkDataKind(DataKind.classNode);
+    return _readClassData().node;
+  }
+
+  _LibraryData _readLibraryData() {
+    Uri canonicalUri = _readUri();
+    return componentLookup.getLibraryDataByUri(canonicalUri);
+  }
+
+  @override
+  ir.Library readLibraryNode() {
+    _checkDataKind(DataKind.libraryNode);
+    return _readLibraryData().node;
+  }
+
+  @override
+  E readEnum<E>(List<E> values) {
+    _checkDataKind(DataKind.enumValue);
+    return _readEnum(values);
+  }
+
+  @override
+  Uri readUri() {
+    _checkDataKind(DataKind.uri);
+    return _readUri();
+  }
+
+  @override
+  bool readBool() {
+    _checkDataKind(DataKind.bool);
+    int value = _readInt();
+    assert(value == 0 || value == 1);
+    return value == 1;
+  }
+
+  @override
+  String readString() {
+    _checkDataKind(DataKind.string);
+    return _readString();
+  }
+
+  @override
+  int readInt() {
+    _checkDataKind(DataKind.int);
+    return _readInt();
+  }
+
+  @override
+  ir.TreeNode readTreeNode() {
+    _checkDataKind(DataKind.treeNode);
+    return _readTreeNode();
+  }
+
+  @override
+  ConstantValue readConstant() {
+    _checkDataKind(DataKind.constant);
+    return _readConstant();
+  }
+
+  ConstantValue _readConstant() {
+    ConstantValueKind kind = _readEnum(ConstantValueKind.values);
+    ConstantValue constant;
+    switch (kind) {
+      case ConstantValueKind.BOOL:
+        bool value = readBool();
+        constant = new BoolConstantValue(value);
+        break;
+      case ConstantValueKind.INT:
+        BigInt value = BigInt.parse(readString());
+        constant = new IntConstantValue(value);
+        break;
+      case ConstantValueKind.DOUBLE:
+        ByteData data = new ByteData(8);
+        data.setUint16(0, readInt());
+        data.setUint16(2, readInt());
+        data.setUint16(4, readInt());
+        data.setUint16(6, readInt());
+        double value = data.getFloat64(0);
+        constant = new DoubleConstantValue(value);
+        break;
+      case ConstantValueKind.STRING:
+        String value = readString();
+        constant = new StringConstantValue(value);
+        break;
+      case ConstantValueKind.NULL:
+        constant = const NullConstantValue();
+        break;
+      default:
+        // TODO(johnniwinther): Support remaining constant values.
+        throw new UnsupportedError("Unexpected constant value kind ${kind}.");
+    }
+    return constant;
+  }
+
+  ir.TreeNode _readTreeNode() {
+    _TreeNodeKind kind = _readEnum(_TreeNodeKind.values);
+    switch (kind) {
+      case _TreeNodeKind.cls:
+        return _readClassData().node;
+      case _TreeNodeKind.member:
+        return _readMemberData().node;
+      case _TreeNodeKind.functionDeclarationVariable:
+        ir.FunctionDeclaration functionDeclaration = _readTreeNode();
+        return functionDeclaration.variable;
+      case _TreeNodeKind.functionNode:
+        return _readFunctionNode();
+      case _TreeNodeKind.typeParameter:
+        return _readTypeParameter();
+      case _TreeNodeKind.node:
+        _MemberData data = _readMemberData();
+        int index = _readInt();
+        ir.TreeNode treeNode = data.getTreeNodeByIndex(index);
+        assert(treeNode != null,
+            "No TreeNode found for index $index in ${data.node}.$_errorContext");
+        return treeNode;
+    }
+    throw new UnsupportedError("Unexpected _TreeNodeKind $kind");
+  }
+
+  ir.FunctionNode _readFunctionNode() {
+    _FunctionNodeKind kind = _readEnum(_FunctionNodeKind.values);
+    switch (kind) {
+      case _FunctionNodeKind.procedure:
+        ir.Procedure procedure = _readMemberData().node;
+        return procedure.function;
+      case _FunctionNodeKind.constructor:
+        ir.Constructor constructor = _readMemberData().node;
+        return constructor.function;
+      case _FunctionNodeKind.functionExpression:
+        ir.FunctionExpression functionExpression = _readTreeNode();
+        return functionExpression.function;
+      case _FunctionNodeKind.functionDeclaration:
+        ir.FunctionDeclaration functionDeclaration = _readTreeNode();
+        return functionDeclaration.function;
+    }
+    throw new UnsupportedError("Unexpected _FunctionNodeKind $kind");
+  }
+
+  @override
+  ir.TypeParameter readTypeParameterNode() {
+    _checkDataKind(DataKind.typeParameterNode);
+    return _readTypeParameter();
+  }
+
+  ir.TypeParameter _readTypeParameter() {
+    _TypeParameterKind kind = _readEnum(_TypeParameterKind.values);
+    switch (kind) {
+      case _TypeParameterKind.cls:
+        ir.Class cls = _readClassData().node;
+        return cls.typeParameters[_readInt()];
+      case _TypeParameterKind.functionNode:
+        ir.FunctionNode functionNode = _readFunctionNode();
+        return functionNode.typeParameters[_readInt()];
+    }
+    throw new UnsupportedError("Unexpected _TypeParameterKind kind $kind");
+  }
+
+  void _checkDataKind(DataKind expectedKind) {
+    if (!useDataKinds) return;
+    DataKind actualKind = _readEnum(DataKind.values);
+    assert(
+        actualKind == expectedKind,
+        "Invalid data kind. "
+        "Expected $expectedKind, found $actualKind.$_errorContext");
+  }
+
+  @override
+  Local readLocal() {
+    LocalKind kind = readEnum(LocalKind.values);
+    switch (kind) {
+      case LocalKind.jLocal:
+        MemberEntity memberContext = readMember();
+        int localIndex = readInt();
+        return localLookup.getLocalByIndex(memberContext, localIndex);
+      case LocalKind.thisLocal:
+        ClassEntity cls = readClass();
+        return new ThisLocal(cls);
+      case LocalKind.boxLocal:
+        ClassEntity cls = readClass();
+        return new BoxLocal(cls);
+      case LocalKind.anonymousClosureLocal:
+        ClassEntity cls = readClass();
+        return new AnonymousClosureLocal(cls);
+      case LocalKind.typeVariableLocal:
+        TypeVariableType typeVariable = readDartType();
+        return new TypeVariableLocal(typeVariable);
+    }
+    throw new UnsupportedError("Unexpected local kind $kind");
+  }
+
+  /// Actual deserialization of a section begin tag, implemented by subclasses.
+  void _begin(String tag);
+
+  /// Actual deserialization of a section end tag, implemented by subclasses.
+  void _end(String tag);
+
+  /// Actual deserialization of a string value, implemented by subclasses.
+  String _readString();
+
+  /// Actual deserialization of a non-negative integer value, implemented by
+  /// subclasses.
+  int _readInt();
+
+  /// Actual deserialization of a URI value, implemented by subclasses.
+  Uri _readUri();
+
+  /// Actual deserialization of an enum value in [values], implemented by
+  /// subclasses.
+  E _readEnum<E>(List<E> values);
+
+  /// Returns a string representation of the current state of the data source
+  /// useful for debugging in consistencies between serialization and
+  /// deserialization.
+  String get _errorContext;
+}
diff --git a/pkg/compiler/lib/src/serialization/binary_sink.dart b/pkg/compiler/lib/src/serialization/binary_sink.dart
new file mode 100644
index 0000000..e7c2114
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/binary_sink.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// [DataSink] that writes data as a sequence of bytes.
+///
+/// This data sink works together with [BinarySource].
+class BinarySink extends AbstractDataSink {
+  final Sink<List<int>> sink;
+  BufferedSink _bufferedSink;
+
+  BinarySink(this.sink, {bool useDataKinds: false})
+      : _bufferedSink = new BufferedSink(sink),
+        super(useDataKinds: useDataKinds);
+
+  void _begin(String tag) {
+    // TODO(johnniwinther): Support tags in binary serialization?
+  }
+  void _end(String tag) {
+    // TODO(johnniwinther): Support tags in binary serialization?
+  }
+
+  @override
+  void _writeUri(Uri value) {
+    _writeString(value.toString());
+  }
+
+  @override
+  void _writeString(String value) {
+    List<int> bytes = utf8.encode(value);
+    _writeInt(bytes.length);
+    _bufferedSink.addBytes(bytes);
+  }
+
+  @override
+  void _writeInt(int value) {
+    assert(value >= 0 && value >> 30 == 0);
+    if (value < 0x80) {
+      _bufferedSink.addByte(value);
+    } else if (value < 0x4000) {
+      _bufferedSink.addByte2((value >> 8) | 0x80, value & 0xFF);
+    } else {
+      _bufferedSink.addByte4((value >> 24) | 0xC0, (value >> 16) & 0xFF,
+          (value >> 8) & 0xFF, value & 0xFF);
+    }
+  }
+
+  @override
+  void _writeEnum(dynamic value) {
+    _writeInt(value.index);
+  }
+
+  void close() {
+    _bufferedSink.flushAndDestroy();
+    _bufferedSink = null;
+    sink.close();
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/binary_source.dart b/pkg/compiler/lib/src/serialization/binary_source.dart
new file mode 100644
index 0000000..0836209
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/binary_source.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// [DataSource] that reads data from a sequence of bytes.
+///
+/// This data source works together with [BinarySink].
+class BinarySourceImpl extends AbstractDataSource {
+  int _byteOffset = 0;
+  final List<int> _bytes;
+
+  BinarySourceImpl(this._bytes, {bool useDataKinds: false})
+      : super(useDataKinds: useDataKinds);
+
+  void _begin(String tag) {}
+  void _end(String tag) {}
+
+  int _readByte() => _bytes[_byteOffset++];
+
+  @override
+  String _readString() {
+    int length = _readInt();
+    List<int> bytes = new Uint8List(length);
+    bytes.setRange(0, bytes.length, _bytes, _byteOffset);
+    _byteOffset += bytes.length;
+    return utf8.decode(bytes);
+  }
+
+  @override
+  int _readInt() {
+    var byte = _readByte();
+    if (byte & 0x80 == 0) {
+      // 0xxxxxxx
+      return byte;
+    } else if (byte & 0x40 == 0) {
+      // 10xxxxxx
+      return ((byte & 0x3F) << 8) | _readByte();
+    } else {
+      // 11xxxxxx
+      return ((byte & 0x3F) << 24) |
+          (_readByte() << 16) |
+          (_readByte() << 8) |
+          _readByte();
+    }
+  }
+
+  @override
+  Uri _readUri() {
+    String text = _readString();
+    return Uri.parse(text);
+  }
+
+  @override
+  E _readEnum<E>(List<E> values) {
+    int index = _readInt();
+    assert(
+        0 <= index && index < values.length,
+        "Invalid data kind index. "
+        "Expected one of $values, found index $index.");
+    return values[index];
+  }
+
+  @override
+  String get _errorContext => ' Offset $_byteOffset in ${_bytes.length}.';
+}
diff --git a/pkg/compiler/lib/src/serialization/helpers.dart b/pkg/compiler/lib/src/serialization/helpers.dart
new file mode 100644
index 0000000..14446a1
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/helpers.dart
@@ -0,0 +1,176 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Enum values used for identifying different kinds of serialized data.
+///
+/// This is used to for debugging data inconsistencies between serialization
+/// and deserialization.
+enum DataKind {
+  bool,
+  int,
+  string,
+  enumValue,
+  uri,
+  libraryNode,
+  classNode,
+  memberNode,
+  treeNode,
+  typeParameterNode,
+  dartType,
+  sourceSpan,
+  constant,
+}
+
+/// Enum used for identifying the enclosing entity of a member in serialization.
+enum MemberContextKind { library, cls }
+
+/// Enum used for identifying [Local] subclasses in serialization.
+enum LocalKind {
+  jLocal,
+  thisLocal,
+  boxLocal,
+  anonymousClosureLocal,
+  typeVariableLocal,
+}
+
+/// Enum used for identifying [ir.TreeNode] subclasses in serialization.
+enum _TreeNodeKind {
+  cls,
+  member,
+  node,
+  functionNode,
+  typeParameter,
+  functionDeclarationVariable
+}
+
+/// Enum used for identifying [ir.FunctionNode] context in serialization.
+enum _FunctionNodeKind {
+  procedure,
+  constructor,
+  functionExpression,
+  functionDeclaration,
+}
+
+/// Enum used for identifying [ir.TypeParameter] context in serialization.
+enum _TypeParameterKind {
+  cls,
+  functionNode,
+}
+
+/// Class used for encoding tags in [ObjectSink] and [ObjectSource].
+class Tag {
+  final String value;
+
+  Tag(this.value);
+
+  int get hashCode => value.hashCode * 13;
+
+  bool operator ==(other) {
+    if (identical(this, other)) return true;
+    if (other is! Tag) return false;
+    return value == other.value;
+  }
+
+  String toString() => 'Tag($value)';
+}
+
+/// Enum used for identifying [DartType] subclasses in serialization.
+enum DartTypeKind {
+  none,
+  voidType,
+  typeVariable,
+  functionTypeVariable,
+  functionType,
+  interfaceType,
+  typedef,
+  dynamicType,
+  futureOr
+}
+
+/// Visitor that serializes [DartType] object together with [AbstractDataSink].
+class DartTypeWriter
+    implements DartTypeVisitor<void, List<FunctionTypeVariable>> {
+  final AbstractDataSink _sink;
+
+  DartTypeWriter(this._sink);
+
+  void visit(covariant DartType type,
+          List<FunctionTypeVariable> functionTypeVariables) =>
+      type.accept(this, functionTypeVariables);
+
+  void visitTypes(
+      List<DartType> types, List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeInt(types.length);
+    for (DartType type in types) {
+      _sink._writeDartType(type, functionTypeVariables);
+    }
+  }
+
+  void visitVoidType(covariant VoidType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.voidType);
+  }
+
+  void visitTypeVariableType(covariant TypeVariableType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.typeVariable);
+    IndexedTypeVariable typeVariable = type.element;
+    _sink.writeInt(typeVariable.typeVariableIndex);
+  }
+
+  void visitFunctionTypeVariable(covariant FunctionTypeVariable type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.functionTypeVariable);
+    int index = functionTypeVariables.indexOf(type);
+    assert(index != -1);
+    _sink.writeInt(index);
+  }
+
+  void visitFunctionType(covariant FunctionType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.functionType);
+    functionTypeVariables =
+        new List<FunctionTypeVariable>.from(functionTypeVariables)
+          ..addAll(type.typeVariables);
+    _sink.writeInt(type.typeVariables.length);
+    for (FunctionTypeVariable variable in type.typeVariables) {
+      _sink._writeDartType(variable.bound, functionTypeVariables);
+    }
+    _sink._writeDartType(type.returnType, functionTypeVariables);
+    visitTypes(type.parameterTypes, functionTypeVariables);
+    visitTypes(type.optionalParameterTypes, functionTypeVariables);
+    visitTypes(type.namedParameterTypes, functionTypeVariables);
+    for (String namedParameter in type.namedParameters) {
+      _sink.writeString(namedParameter);
+    }
+  }
+
+  void visitInterfaceType(covariant InterfaceType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.interfaceType);
+    _sink.writeClass(type.element);
+    visitTypes(type.typeArguments, functionTypeVariables);
+  }
+
+  void visitTypedefType(covariant TypedefType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.typedef);
+    _sink.writeTypedef(type.element);
+    visitTypes(type.typeArguments, functionTypeVariables);
+    _sink._writeDartType(type.unaliased, functionTypeVariables);
+  }
+
+  void visitDynamicType(covariant DynamicType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.dynamicType);
+  }
+
+  void visitFutureOrType(covariant FutureOrType type,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    _sink.writeEnum(DartTypeKind.futureOr);
+    _sink._writeDartType(type.typeArgument, functionTypeVariables);
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/member_data.dart b/pkg/compiler/lib/src/serialization/member_data.dart
new file mode 100644
index 0000000..08fa9a3
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/member_data.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Helper for looking up object library data from an [ir.Component] node.
+class ComponentLookup {
+  final ir.Component _component;
+
+  /// Cache of [_LibraryData] for libraries in [_component].
+  Map<Uri, _LibraryData> _libraryMap;
+
+  ComponentLookup(this._component);
+
+  /// Returns the [_LibraryData] object for the library with the [canonicalUri].
+  _LibraryData getLibraryDataByUri(Uri canonicalUri) {
+    if (_libraryMap == null) {
+      _libraryMap = {};
+      for (ir.Library library in _component.libraries) {
+        _libraryMap[library.importUri] = new _LibraryData(library);
+      }
+    }
+    return _libraryMap[canonicalUri];
+  }
+}
+
+/// Returns a name uniquely identifying a member within its enclosing library
+/// or class.
+String _computeMemberName(ir.Member member) {
+  if (member.name.isPrivate &&
+      member.name.libraryName != member.enclosingLibrary.reference) {
+    // TODO(33732): Handle noSuchMethod forwarders for private members from
+    // other libraries.
+    return null;
+  }
+  String name = member.name.name;
+  if (member is ir.Procedure && member.kind == ir.ProcedureKind.Setter) {
+    name += "=";
+  }
+  return name;
+}
+
+/// Helper for looking up classes and members from an [ir.Library] node.
+class _LibraryData {
+  /// The [ir.Library] that defines the library.
+  final ir.Library node;
+
+  /// Cache of [_ClassData] for classes in this library.
+  Map<String, _ClassData> _classes;
+
+  /// Cache of [_MemberData] for members in this library.
+  Map<String, _MemberData> _members;
+
+  _LibraryData(this.node);
+
+  /// Returns the [_ClassData] for the class [name] in this library.
+  _ClassData lookupClass(String name) {
+    if (_classes == null) {
+      _classes = {};
+      for (ir.Class cls in node.classes) {
+        assert(!_classes.containsKey(cls.name),
+            "Duplicate class '${cls.name}' in $_classes trying to add $cls.");
+        _classes[cls.name] = new _ClassData(cls);
+      }
+    }
+    return _classes[name];
+  }
+
+  /// Returns the [_MemberData] for the member uniquely identified by [name] in
+  /// this library.
+  _MemberData lookupMember(String name) {
+    if (_members == null) {
+      _members = {};
+      for (ir.Member member in node.members) {
+        String name = _computeMemberName(member);
+        if (name == null) continue;
+        assert(!_members.containsKey(name),
+            "Duplicate member '$name' in $_members trying to add $member.");
+        _members[name] = new _MemberData(member);
+      }
+    }
+    return _members[name];
+  }
+
+  String toString() => '_LibraryData($node(${identityHashCode(node)}))';
+}
+
+/// Helper for looking up members from an [ir.Class] node.
+class _ClassData {
+  /// The [ir.Class] that defines the class.
+  final ir.Class node;
+
+  /// Cache of [_MemberData] for members in this class.
+  Map<String, _MemberData> _members;
+
+  _ClassData(this.node);
+
+  /// Returns the [_MemberData] for the member uniquely identified by [name] in
+  /// this class.
+  _MemberData lookupMember(String name) {
+    if (_members == null) {
+      _members = {};
+      for (ir.Member member in node.members) {
+        String name = _computeMemberName(member);
+        if (name == null) continue;
+        assert(!_members.containsKey(name),
+            "Duplicate member '$name' in $_members trying to add $member.");
+        _members[name] = new _MemberData(member);
+      }
+    }
+    return _members[name];
+  }
+
+  String toString() => '_ClassData($node(${identityHashCode(node)}))';
+}
+
+/// Helper for looking up child [ir.TreeNode]s of a [ir.Member] node.
+class _MemberData {
+  /// The [ir.Member] that defines the member.
+  final ir.Member node;
+
+  /// Cached index to [ir.TreeNode] map used for deserialization of
+  /// [ir.TreeNode]s.
+  Map<int, ir.TreeNode> _indexToNodeMap;
+
+  /// Cached [ir.TreeNode] to index map used for serialization of
+  /// [ir.TreeNode]s.
+  Map<ir.TreeNode, int> _nodeToIndexMap;
+
+  _MemberData(this.node);
+
+  void _ensureMaps() {
+    if (_indexToNodeMap == null) {
+      _indexToNodeMap = {};
+      _nodeToIndexMap = {};
+      node.accept(
+          new _TreeNodeIndexerVisitor(_indexToNodeMap, _nodeToIndexMap));
+    }
+  }
+
+  /// Returns the [ir.TreeNode] corresponding to [index] in this member.
+  ir.TreeNode getTreeNodeByIndex(int index) {
+    _ensureMaps();
+    ir.TreeNode treeNode = _indexToNodeMap[index];
+    assert(treeNode != null, "No TreeNode found for index $index in $node.");
+    return treeNode;
+  }
+
+  /// Returns the index corresponding to [ir.TreeNode] in this member.
+  int getIndexByTreeNode(ir.TreeNode node) {
+    _ensureMaps();
+    int index = _nodeToIndexMap[node];
+    assert(index != null, "No index found for ${node.runtimeType}.");
+    return index;
+  }
+
+  String toString() => '_MemberData($node(${identityHashCode(node)}))';
+}
diff --git a/pkg/compiler/lib/src/serialization/mixins.dart b/pkg/compiler/lib/src/serialization/mixins.dart
new file mode 100644
index 0000000..e5b009f
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/mixins.dart
@@ -0,0 +1,569 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Mixin that implements all convenience methods of [DataSource].
+abstract class DataSourceMixin implements DataSource {
+  @override
+  E readValueOrNull<E>(E f()) {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return f();
+    }
+    return null;
+  }
+
+  @override
+  List<E> readList<E>(E f(), {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      list[i] = f();
+    }
+    return list;
+  }
+
+  @override
+  int readIntOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readInt();
+    }
+    return null;
+  }
+
+  @override
+  String readStringOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readString();
+    }
+    return null;
+  }
+
+  @override
+  List<String> readStrings({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<String> list = new List<String>(count);
+    for (int i = 0; i < count; i++) {
+      list[i] = readString();
+    }
+    return list;
+  }
+
+  @override
+  List<DartType> readDartTypes({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<DartType> list = new List<DartType>(count);
+    for (int i = 0; i < count; i++) {
+      list[i] = readDartType();
+    }
+    return list;
+  }
+
+  @override
+  List<ir.TypeParameter> readTypeParameterNodes({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<ir.TypeParameter> list = new List<ir.TypeParameter>(count);
+    for (int i = 0; i < count; i++) {
+      list[i] = readTypeParameterNode();
+    }
+    return list;
+  }
+
+  @override
+  List<E> readMembers<E extends MemberEntity>({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      MemberEntity member = readMember();
+      list[i] = member;
+    }
+    return list;
+  }
+
+  @override
+  List<E> readMemberNodes<E extends ir.Member>({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      ir.Member value = readMemberNode();
+      list[i] = value;
+    }
+    return list;
+  }
+
+  @override
+  List<E> readClasses<E extends ClassEntity>({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      ClassEntity cls = readClass();
+      list[i] = cls;
+    }
+    return list;
+  }
+
+  @override
+  Map<K, V> readLibraryMap<K extends LibraryEntity, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      LibraryEntity library = readLibrary();
+      V value = f();
+      map[library] = value;
+    }
+    return map;
+  }
+
+  @override
+  Map<K, V> readClassMap<K extends ClassEntity, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      ClassEntity cls = readClass();
+      V value = f();
+      map[cls] = value;
+    }
+    return map;
+  }
+
+  @override
+  Map<K, V> readMemberMap<K extends MemberEntity, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      MemberEntity member = readMember();
+      V value = f();
+      map[member] = value;
+    }
+    return map;
+  }
+
+  @override
+  Map<K, V> readTreeNodeMap<K extends ir.TreeNode, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      ir.TreeNode node = readTreeNode();
+      V value = f();
+      map[node] = value;
+    }
+    return map;
+  }
+
+  @override
+  List<E> readLocals<E extends Local>({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      Local local = readLocal();
+      list[i] = local;
+    }
+    return list;
+  }
+
+  @override
+  Map<K, V> readLocalMap<K extends Local, V>(V f(), {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      Local local = readLocal();
+      V value = f();
+      map[local] = value;
+    }
+    return map;
+  }
+
+  @override
+  List<E> readTreeNodes<E extends ir.TreeNode>({bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    List<E> list = new List<E>(count);
+    for (int i = 0; i < count; i++) {
+      ir.TreeNode node = readTreeNode();
+      list[i] = node;
+    }
+    return list;
+  }
+
+  @override
+  Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<String, V> map = {};
+    for (int i = 0; i < count; i++) {
+      String key = readString();
+      V value = f();
+      map[key] = value;
+    }
+    return map;
+  }
+
+  @override
+  IndexedClass readClassOrNull() {
+    bool hasClass = readBool();
+    if (hasClass) {
+      return readClass();
+    }
+    return null;
+  }
+
+  @override
+  ir.TreeNode readTreeNodeOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readTreeNode();
+    }
+    return null;
+  }
+
+  @override
+  IndexedMember readMemberOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readMember();
+    }
+    return null;
+  }
+
+  @override
+  Local readLocalOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readLocal();
+    }
+    return null;
+  }
+
+  @override
+  Map<K, V> readConstantMap<K extends ConstantValue, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      ConstantValue key = readConstant();
+      V value = f();
+      map[key] = value;
+    }
+    return map;
+  }
+
+  @override
+  IndexedLibrary readLibraryOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readLibrary();
+    }
+    return null;
+  }
+}
+
+/// Mixin that implements all convenience methods of [DataSink].
+abstract class DataSinkMixin implements DataSink {
+  @override
+  void writeIntOrNull(int value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeInt(value);
+    }
+  }
+
+  @override
+  void writeStringOrNull(String value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeString(value);
+    }
+  }
+
+  @override
+  void writeClassOrNull(IndexedClass value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeClass(value);
+    }
+  }
+
+  @override
+  void writeLocalOrNull(Local value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeLocal(value);
+    }
+  }
+
+  @override
+  void writeClasses(Iterable<ClassEntity> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (IndexedClass value in values) {
+        writeClass(value);
+      }
+    }
+  }
+
+  @override
+  void writeTreeNodes(Iterable<ir.TreeNode> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (ir.TreeNode value in values) {
+        writeTreeNode(value);
+      }
+    }
+  }
+
+  @override
+  void writeStrings(Iterable<String> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (String value in values) {
+        writeString(value);
+      }
+    }
+  }
+
+  @override
+  void writeMemberNodes(Iterable<ir.Member> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (ir.Member value in values) {
+        writeMemberNode(value);
+      }
+    }
+  }
+
+  @override
+  void writeDartTypes(Iterable<DartType> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (DartType value in values) {
+        writeDartType(value);
+      }
+    }
+  }
+
+  @override
+  void writeLibraryMap<V>(Map<LibraryEntity, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((LibraryEntity library, V value) {
+        writeLibrary(library);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeClassMap<V>(Map<ClassEntity, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((ClassEntity cls, V value) {
+        writeClass(cls);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeMemberMap<V>(Map<MemberEntity, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((MemberEntity member, V value) {
+        writeMember(member);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeStringMap<V>(Map<String, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((String key, V value) {
+        writeString(key);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeLocals(Iterable<Local> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (Local value in values) {
+        writeLocal(value);
+      }
+    }
+  }
+
+  @override
+  void writeLocalMap<V>(Map<Local, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((Local key, V value) {
+        writeLocal(key);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeTreeNodeMap<V>(Map<ir.TreeNode, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((ir.TreeNode key, V value) {
+        writeTreeNode(key);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeList<E>(Iterable<E> values, void f(E value),
+      {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      values.forEach(f);
+    }
+  }
+
+  @override
+  void writeTreeNodeOrNull(ir.TreeNode value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeTreeNode(value);
+    }
+  }
+
+  @override
+  void writeValueOrNull<E>(E value, void f(E value)) {
+    writeBool(value != null);
+    if (value != null) {
+      f(value);
+    }
+  }
+
+  @override
+  void writeMemberOrNull(IndexedMember value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeMember(value);
+    }
+  }
+
+  @override
+  void writeMembers(Iterable<MemberEntity> values, {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (IndexedMember value in values) {
+        writeMember(value);
+      }
+    }
+  }
+
+  @override
+  void writeTypeParameterNodes(Iterable<ir.TypeParameter> values,
+      {bool allowNull: false}) {
+    if (values == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(values.length);
+      for (ir.TypeParameter value in values) {
+        writeTypeParameterNode(value);
+      }
+    }
+  }
+
+  @override
+  void writeConstantMap<V>(Map<ConstantValue, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((ConstantValue key, V value) {
+        writeConstant(key);
+        f(value);
+      });
+    }
+  }
+
+  @override
+  void writeLibraryOrNull(IndexedLibrary value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeLibrary(value);
+    }
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/node_indexer.dart b/pkg/compiler/lib/src/serialization/node_indexer.dart
new file mode 100644
index 0000000..c9370f1
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/node_indexer.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// Visitor that ascribes an index to all [ir.TreeNode]s that potentially
+/// needed for serialization and deserialization.
+class _TreeNodeIndexerVisitor extends ir.Visitor<void> {
+  int _currentIndex = 0;
+  final Map<int, ir.TreeNode> _indexToNodeMap;
+  final Map<ir.TreeNode, int> _nodeToIndexMap;
+
+  _TreeNodeIndexerVisitor(this._indexToNodeMap, this._nodeToIndexMap);
+
+  void registerNode(ir.TreeNode node) {
+    _indexToNodeMap[_currentIndex] = node;
+    _nodeToIndexMap[node] = _currentIndex;
+    _currentIndex++;
+  }
+
+  @override
+  void defaultTreeNode(ir.TreeNode node) {
+    node.visitChildren(this);
+  }
+
+  @override
+  void visitFunctionExpression(ir.FunctionExpression node) {
+    registerNode(node);
+    super.visitFunctionExpression(node);
+  }
+
+  @override
+  void visitFunctionDeclaration(ir.FunctionDeclaration node) {
+    registerNode(node);
+    super.visitFunctionDeclaration(node);
+  }
+
+  @override
+  void visitBlock(ir.Block node) {
+    registerNode(node);
+    super.visitBlock(node);
+  }
+
+  @override
+  void visitVariableDeclaration(ir.VariableDeclaration node) {
+    if (node.parent is! ir.FunctionDeclaration) {
+      registerNode(node);
+    }
+    super.visitVariableDeclaration(node);
+  }
+
+  @override
+  void visitSwitchStatement(ir.SwitchStatement node) {
+    registerNode(node);
+    super.visitSwitchStatement(node);
+  }
+
+  @override
+  void visitForStatement(ir.ForStatement node) {
+    registerNode(node);
+    super.visitForStatement(node);
+  }
+
+  @override
+  void visitForInStatement(ir.ForInStatement node) {
+    registerNode(node);
+    super.visitForInStatement(node);
+  }
+
+  @override
+  void visitWhileStatement(ir.WhileStatement node) {
+    registerNode(node);
+    super.visitWhileStatement(node);
+  }
+
+  @override
+  void visitDoStatement(ir.DoStatement node) {
+    registerNode(node);
+    super.visitDoStatement(node);
+  }
+
+  @override
+  void visitBreakStatement(ir.BreakStatement node) {
+    registerNode(node);
+    super.visitBreakStatement(node);
+  }
+
+  @override
+  void visitListLiteral(ir.ListLiteral node) {
+    registerNode(node);
+    super.visitListLiteral(node);
+  }
+
+  @override
+  void visitMapLiteral(ir.MapLiteral node) {
+    registerNode(node);
+    super.visitMapLiteral(node);
+  }
+
+  @override
+  void visitPropertyGet(ir.PropertyGet node) {
+    registerNode(node);
+    super.visitPropertyGet(node);
+  }
+
+  @override
+  void visitPropertySet(ir.PropertySet node) {
+    registerNode(node);
+    super.visitPropertySet(node);
+  }
+
+  @override
+  void visitMethodInvocation(ir.MethodInvocation node) {
+    registerNode(node);
+    super.visitMethodInvocation(node);
+  }
+
+  @override
+  void visitDirectPropertyGet(ir.DirectPropertyGet node) {
+    registerNode(node);
+    super.visitDirectPropertyGet(node);
+  }
+
+  @override
+  void visitDirectPropertySet(ir.DirectPropertySet node) {
+    registerNode(node);
+    super.visitDirectPropertySet(node);
+  }
+
+  @override
+  void visitDirectMethodInvocation(ir.DirectMethodInvocation node) {
+    registerNode(node);
+    super.visitDirectMethodInvocation(node);
+  }
+
+  @override
+  void visitStaticInvocation(ir.StaticInvocation node) {
+    registerNode(node);
+    super.visitStaticInvocation(node);
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/object_sink.dart b/pkg/compiler/lib/src/serialization/object_sink.dart
new file mode 100644
index 0000000..195db2a
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/object_sink.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// [DataSink] that writes to a list of objects, useful for debugging
+/// inconsistencies between serialization and deserialization.
+///
+/// This data sink works together with [ObjectSource].
+class ObjectSink extends AbstractDataSink {
+  List<dynamic> _data;
+
+  ObjectSink(this._data, {bool useDataKinds})
+      : super(useDataKinds: useDataKinds);
+
+  void _begin(String tag) {
+    _data.add(new Tag('begin:$tag'));
+  }
+
+  void _end(String tag) {
+    _data.add(new Tag('end:$tag'));
+  }
+
+  @override
+  void _writeEnum(dynamic value) {
+    assert(value != null);
+    _data.add(value);
+  }
+
+  @override
+  void _writeInt(int value) {
+    assert(value != null);
+    _data.add(value);
+  }
+
+  @override
+  void _writeString(String value) {
+    assert(value != null);
+    _data.add(value);
+  }
+
+  @override
+  void _writeUri(Uri value) {
+    assert(value != null);
+    _data.add(value);
+  }
+
+  @override
+  void close() {
+    _data = null;
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/object_source.dart b/pkg/compiler/lib/src/serialization/object_source.dart
new file mode 100644
index 0000000..cae965b
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/object_source.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, 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.
+
+part of 'serialization.dart';
+
+/// [DataSource] that read from a list of objects, useful for debugging
+/// inconsistencies between serialization and deserialization.
+///
+/// This data source works together with [ObjectSink].
+class ObjectSource extends AbstractDataSource {
+  int _index = 0;
+  final List<dynamic> _data;
+
+  ObjectSource(this._data, {bool useDataKinds})
+      : super(useDataKinds: useDataKinds);
+
+  T _read<T>() {
+    dynamic value = _data[_index++];
+    assert(value is T, "Expected $T value, found $value.$_errorContext");
+    return value;
+  }
+
+  void _begin(String tag) {
+    Tag expectedTag = new Tag('begin:$tag');
+    Tag actualTag = _read();
+    assert(
+        expectedTag == actualTag,
+        "Unexpected begin tag. "
+        "Expected $expectedTag, found $actualTag.$_errorContext");
+  }
+
+  void _end(String tag) {
+    Tag expectedTag = new Tag('end:$tag');
+    Tag actualTag = _read();
+    assert(
+        expectedTag == actualTag,
+        "Unexpected end tag. "
+        "Expected $expectedTag, found $actualTag.$_errorContext");
+  }
+
+  @override
+  String _readString() => _read();
+
+  @override
+  E _readEnum<E>(List<E> values) => _read();
+
+  @override
+  Uri _readUri() => _read();
+
+  @override
+  int _readInt() => _read();
+
+  @override
+  String get _errorContext {
+    StringBuffer sb = new StringBuffer();
+    for (int i = _index - 50; i < _index + 10; i++) {
+      if (i >= 0 && i < _data.length) {
+        if (i == _index - 1) {
+          sb.write('\n> ');
+        } else {
+          sb.write('\n  ');
+        }
+        sb.write(i);
+        sb.write(' ');
+        sb.write(_data[i]);
+      }
+    }
+    return sb.toString();
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
new file mode 100644
index 0000000..a5b738a
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -0,0 +1,560 @@
+// Copyright (c) 2018, 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.
+
+import 'dart:convert';
+import 'dart:typed_data';
+import 'package:kernel/ast.dart' as ir;
+import 'package:kernel/binary/ast_to_binary.dart';
+import '../closure.dart';
+import '../constants/values.dart';
+import '../diagnostics/source_span.dart';
+import '../elements/entities.dart';
+import '../elements/indexed.dart';
+import '../elements/types.dart';
+import '../js_model/closure.dart';
+import '../js_model/locals.dart';
+
+part 'abstract_sink.dart';
+part 'abstract_source.dart';
+part 'binary_sink.dart';
+part 'binary_source.dart';
+part 'helpers.dart';
+part 'member_data.dart';
+part 'mixins.dart';
+part 'node_indexer.dart';
+part 'object_sink.dart';
+part 'object_source.dart';
+
+/// Interface for serialization.
+abstract class DataSink {
+  /// Flushes any pending data and closes this data sink.
+  ///
+  /// The data sink can no longer be written to after closing.
+  void close();
+
+  /// Registers that the section [tag] starts.
+  ///
+  /// This is used for debugging to verify that sections are correctly aligned
+  /// between serialization and deserialization.
+  void begin(String tag);
+
+  /// Registers that the section [tag] ends.
+  ///
+  /// This is used for debugging to verify that sections are correctly aligned
+  /// between serialization and deserialization.
+  void end(String tag);
+
+  /// Writes the potentially `null` [value] to this data sink. If [value] is
+  /// non-null [f] is called to write the non-null value to the data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readValueOrNull].
+  void writeValueOrNull<E>(E value, void f(E value));
+
+  /// Writes the [values] to this data sink calling [f] to write each value to
+  /// the data sink. If [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readList].
+  void writeList<E>(Iterable<E> values, void f(E value),
+      {bool allowNull: false});
+
+  /// Writes the boolean [value] to this data sink.
+  void writeBool(bool value);
+
+  /// Writes the non-negative integer [value] to this data sink.
+  void writeInt(int value);
+
+  /// Writes the potentially `null` non-negative [value] to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readIntOrNull].
+  void writeIntOrNull(int value);
+
+  /// Writes the string [value] to this data sink.
+  void writeString(String value);
+
+  /// Writes the potentially `null` string [value] to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readStringOrNull].
+  void writeStringOrNull(String value);
+
+  /// Writes the string [values] to this data sink. If [allowNull] is `true`,
+  /// [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readStrings].
+  void writeStrings(Iterable<String> values, {bool allowNull: false});
+
+  /// Writes the [map] from string to [V] values to this data sink, calling [f]
+  /// to write each value to the data sink. If [allowNull] is `true`, [map] is
+  /// allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readStringMap].
+  void writeStringMap<V>(Map<String, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes the enum value [value] to this data sink.
+  // TODO(johnniwinther): Change the signature to
+  // `void writeEnum<E extends Enum<E>>(E value);` when an interface for enums
+  // is added to the language.
+  void writeEnum(dynamic value);
+
+  /// Writes the URI [value] to this data sink.
+  void writeUri(Uri value);
+
+  /// Writes a reference to the kernel library node [value] to this data sink.
+  void writeLibraryNode(ir.Library value);
+
+  /// Writes a reference to the kernel class node [value] to this data sink.
+  void writeClassNode(ir.Class value);
+
+  /// Writes a reference to the kernel member node [value] to this data sink.
+  void writeMemberNode(ir.Member value);
+
+  /// Writes references to the kernel member node [values] to this data sink.
+  /// If [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readMemberNodes].
+  void writeMemberNodes(Iterable<ir.Member> values, {bool allowNull: false});
+
+  /// Writes a reference to the kernel tree node [value] to this data sink.
+  void writeTreeNode(ir.TreeNode value);
+
+  /// Writes a reference to the potentially `null` kernel tree node [value]
+  /// to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readTreeNodeOrNull].
+  void writeTreeNodeOrNull(ir.TreeNode value);
+
+  /// Writes references to the kernel tree node [values] to this data sink.
+  /// If [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readTreeNodes].
+  void writeTreeNodes(Iterable<ir.TreeNode> values, {bool allowNull: false});
+
+  /// Writes the [map] from references to kernel tree nodes to [V] values to
+  /// this data sink, calling [f] to write each value to the data sink. If
+  /// [allowNull] is `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readTreeNodeMap].
+  void writeTreeNodeMap<V>(Map<ir.TreeNode, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes a reference to the kernel type parameter node [value] to this data
+  /// sink.
+  void writeTypeParameterNode(ir.TypeParameter value);
+
+  /// Writes references to the kernel type parameter node [values] to this data
+  /// sink.
+  /// If [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readTypeParameterNodes].
+  void writeTypeParameterNodes(Iterable<ir.TypeParameter> values,
+      {bool allowNull: false});
+
+  /// Writes the type [value] to this data sink. If [allowNull] is `true`,
+  /// [value] is allowed to be `null`.
+  void writeDartType(DartType value, {bool allowNull: false});
+
+  /// Writes the type [values] to this data sink. If [allowNull] is `true`,
+  /// [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readDartTypes].
+  void writeDartTypes(Iterable<DartType> values, {bool allowNull: false});
+
+  /// Writes the source span [value] to this data sink.
+  void writeSourceSpan(SourceSpan value);
+
+  /// Writes a reference to the indexed library [value] to this data sink.
+  void writeLibrary(IndexedLibrary value);
+
+  /// Writes a reference to the potentially `null` indexed library [value]
+  /// to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readLibraryOrNull].
+  void writeLibraryOrNull(IndexedLibrary value);
+
+  /// Writes the [map] from references to indexed libraries to [V] values to
+  /// this data sink, calling [f] to write each value to the data sink. If
+  /// [allowNull] is `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readLibraryMap].
+  void writeLibraryMap<V>(Map<LibraryEntity, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes a reference to the indexed class [value] to this data sink.
+  void writeClass(IndexedClass value);
+
+  /// Writes a reference to the potentially `null` indexed class [value]
+  /// to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readClassOrNull].
+  void writeClassOrNull(IndexedClass value);
+
+  /// Writes references to the indexed class [values] to this data sink. If
+  /// [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readClasses].
+  void writeClasses(Iterable<ClassEntity> values, {bool allowNull: false});
+
+  /// Writes the [map] from references to indexed classes to [V] values to this
+  /// data sink, calling [f] to write each value to the data sink. If
+  /// [allowNull] is `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readClassMap].
+  void writeClassMap<V>(Map<ClassEntity, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes a reference to the indexed typedef [value] to this data sink.
+  void writeTypedef(IndexedTypedef value);
+
+  /// Writes a reference to the indexed member [value] to this data sink.
+  void writeMember(IndexedMember value);
+
+  /// Writes a reference to the potentially `null` indexed member [value]
+  /// to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readMemberOrNull].
+  void writeMemberOrNull(IndexedMember value);
+
+  /// Writes references to the indexed member [values] to this data sink. If
+  /// [allowNull] is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readMembers].
+  void writeMembers(Iterable<MemberEntity> values, {bool allowNull: false});
+
+  /// Writes the [map] from references to indexed members to [V] values to this
+  /// data sink, calling [f] to write each value to the data sink. If
+  /// [allowNull] is `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readMemberMap].
+  void writeMemberMap<V>(Map<MemberEntity, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes a reference to the local [value] to this data sink.
+  void writeLocal(Local local);
+
+  /// Writes a reference to the potentially `null` local [value]
+  /// to this data sink.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readLocalOrNull].
+  void writeLocalOrNull(Local local);
+
+  /// Writes references to the local [values] to this data sink. If [allowNull]
+  /// is `true`, [values] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readLocals].
+  void writeLocals(Iterable<Local> locals, {bool allowNull: false});
+
+  /// Writes the [map] from references to locals to [V] values to this data
+  /// sink, calling [f] to write each value to the data sink. If [allowNull] is
+  /// `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readLocalMap].
+  void writeLocalMap<V>(Map<Local, V> map, void f(V value),
+      {bool allowNull: false});
+
+  /// Writes the constant [value] to this data sink.
+  void writeConstant(ConstantValue value);
+
+  /// Writes the [map] from constant values to [V] values to this data sink,
+  /// calling [f] to write each value to the data sink. If [allowNull] is
+  /// `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readConstantMap].
+  void writeConstantMap<V>(Map<ConstantValue, V> map, void f(V value),
+      {bool allowNull: false});
+}
+
+/// Interface for deserialization.
+abstract class DataSource {
+  /// Registers that the section [tag] starts.
+  ///
+  /// This is used for debugging to verify that sections are correctly aligned
+  /// between serialization and deserialization.
+  void begin(String tag);
+
+  /// Registers that the section [tag] ends.
+  ///
+  /// This is used for debugging to verify that sections are correctly aligned
+  /// between serialization and deserialization.
+  void end(String tag);
+
+  /// Registers a [ComponentLookup] object with this data source to support
+  /// deserialization of references to kernel nodes.
+  void registerComponentLookup(ComponentLookup componentLookup);
+
+  /// Registers an [EntityLookup] object with this data source to support
+  /// deserialization of references to indexed entities.
+  void registerEntityLookup(EntityLookup entityLookup);
+
+  /// Registers a [LocalLookup] object with this data source to support
+  /// deserialization of references to locals.
+  void registerLocalLookup(LocalLookup localLookup);
+
+  /// Reads a potentially `null` [E] value from this data source, calling [f] to
+  /// read the non-null value from the data source.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeValueOrNull].
+  E readValueOrNull<E>(E f());
+
+  /// Reads a list of [E] values from this data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeList].
+  List<E> readList<E>(E f(), {bool emptyAsNull: false});
+
+  /// Reads a boolean value from this data source.
+  bool readBool();
+
+  /// Reads a non-negative integer value from this data source.
+  int readInt();
+
+  /// Reads a potentially `null` non-negative integer value from this data
+  /// source.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeIntOrNull].
+  int readIntOrNull();
+
+  /// Reads a string value from this data source.
+  String readString();
+
+  /// Reads a potentially `null` string value from this data source.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeStringOrNull].
+  String readStringOrNull();
+
+  /// Reads a list of string values from this data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeStrings].
+  List<String> readStrings({bool emptyAsNull: false});
+
+  /// Reads a map from string values to [V] values from this data source,
+  /// calling [f] to read each value from the data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeStringMap].
+  Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false});
+
+  /// Reads an enum value from the list of enum [values] from this data source.
+  ///
+  /// The [values] argument is intended to be the static `.values` field on
+  /// enum classes, for instance:
+  ///
+  ///    enum Foo { bar, baz }
+  ///    ...
+  ///    Foo foo = source.readEnum(Foo.values);
+  ///
+  E readEnum<E>(List<E> values);
+
+  /// Reads a URI value from this data source.
+  Uri readUri();
+
+  /// Reads a reference to a kernel library node from this data source.
+  ir.Library readLibraryNode();
+
+  /// Reads a reference to a kernel class node from this data source.
+  ir.Class readClassNode();
+
+  /// Reads a reference to a kernel member node from this data source.
+  ir.Member readMemberNode();
+
+  /// Reads a list of references to kernel member nodes from this data source.
+  /// If [emptyAsNull] is `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeMemberNodes].
+  List<ir.Member> readMemberNodes<E extends ir.Member>(
+      {bool emptyAsNull: false});
+
+  /// Reads a reference to a kernel tree node from this data source.
+  ir.TreeNode readTreeNode();
+
+  /// Reads a reference to a potentially `null` kernel tree node from this data
+  /// source.
+  ir.TreeNode readTreeNodeOrNull();
+
+  /// Reads a list of references to kernel tree nodes from this data source.
+  /// If [emptyAsNull] is `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeTreeNodes].
+  List<E> readTreeNodes<E extends ir.TreeNode>({bool emptyAsNull: false});
+
+  /// Reads a map from kernel tree nodes to [V] values from this data source,
+  /// calling [f] to read each value from the data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeTreeNodeMap].
+  Map<K, V> readTreeNodeMap<K extends ir.TreeNode, V>(V f(),
+      {bool emptyAsNull: false});
+
+  /// Reads a reference to a kernel type parameter node from this data source.
+  ir.TypeParameter readTypeParameterNode();
+
+  /// Reads a list of references to kernel type parameter nodes from this data
+  /// source. If [emptyAsNull] is `true`, `null` is returned instead of an empty
+  /// list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeTypeParameterNodes].
+  List<ir.TypeParameter> readTypeParameterNodes({bool emptyAsNull: false});
+
+  /// Reads a type from this data source. If [allowNull], the returned type is
+  /// allowed to be `null`.
+  DartType readDartType({bool allowNull: false});
+
+  /// Reads a list of types from this data source. If [emptyAsNull] is `true`,
+  /// `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeDartTypes].
+  List<DartType> readDartTypes({bool emptyAsNull: false});
+
+  /// Reads a source span from this data source.
+  SourceSpan readSourceSpan();
+
+  /// Reads a reference to an indexed library from this data source.
+  IndexedLibrary readLibrary();
+
+  /// Reads a reference to a potentially `null` indexed library from this data
+  /// source.
+  IndexedLibrary readLibraryOrNull();
+  Map<K, V> readLibraryMap<K extends LibraryEntity, V>(V f(),
+      {bool emptyAsNull: false});
+
+  /// Reads a reference to an indexed class from this data source.
+  IndexedClass readClass();
+
+  /// Reads a reference to a potentially `null` indexed class from this data
+  /// source.
+  IndexedClass readClassOrNull();
+
+  /// Reads a list of references to indexed classes from this data source.
+  /// If [emptyAsNull] is `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeClasses].
+  List<E> readClasses<E extends ClassEntity>({bool emptyAsNull: false});
+
+  /// Reads a map from indexed classes to [V] values from this data source,
+  /// calling [f] to read each value from the data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeClassMap].
+  Map<K, V> readClassMap<K extends ClassEntity, V>(V f(),
+      {bool emptyAsNull: false});
+
+  /// Reads a reference to an indexed typedef from this data source.
+  IndexedTypedef readTypedef();
+
+  /// Reads a reference to an indexed member from this data source.
+  IndexedMember readMember();
+
+  /// Reads a reference to a potentially `null` indexed member from this data
+  /// source.
+  IndexedMember readMemberOrNull();
+
+  /// Reads a list of references to indexed members from this data source.
+  /// If [emptyAsNull] is `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeMembers].
+  List<E> readMembers<E extends MemberEntity>({bool emptyAsNull: false});
+
+  /// Reads a map from indexed members to [V] values from this data source,
+  /// calling [f] to read each value from the data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeMemberMap].
+  Map<K, V> readMemberMap<K extends MemberEntity, V>(V f(),
+      {bool emptyAsNull: false});
+
+  /// Reads a reference to a local from this data source.
+  Local readLocal();
+
+  /// Reads a reference to a potentially `null` local from this data source.
+  Local readLocalOrNull();
+
+  /// Reads a list of references to locals from this data source. If
+  /// [emptyAsNull] is `true`, `null` is returned instead of an empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeLocals].
+  List<E> readLocals<E extends Local>({bool emptyAsNull: false});
+
+  /// Reads a map from locals to [V] values from this data source, calling [f]
+  /// to read each value from the data source. If [emptyAsNull] is `true`,
+  /// `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeLocalMap].
+  Map<K, V> readLocalMap<K extends Local, V>(V f(), {bool emptyAsNull: false});
+
+  /// Reads a constant value from this data source.
+  ConstantValue readConstant();
+
+  /// Reads a map from constant values to [V] values from this data source,
+  /// calling [f] to read each value from the data source. If [emptyAsNull] is
+  /// `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeConstantMap].
+  Map<K, V> readConstantMap<K extends ConstantValue, V>(V f(),
+      {bool emptyAsNull: false});
+}
+
+/// Interface used for looking up entities by index during deserialization.
+abstract class EntityLookup {
+  /// Returns the indexed library corresponding to [index].
+  IndexedLibrary getLibraryByIndex(int index);
+
+  /// Returns the indexed class corresponding to [index].
+  IndexedClass getClassByIndex(int index);
+
+  /// Returns the indexed typedef corresponding to [index].
+  IndexedTypedef getTypedefByIndex(int index);
+
+  /// Returns the indexed member corresponding to [index].
+  IndexedMember getMemberByIndex(int index);
+
+  /// Returns the indexed type variable corresponding to [index].
+  IndexedTypeVariable getTypeVariableByIndex(int index);
+}
+
+/// Interface used for looking up locals by index during deserialization.
+abstract class LocalLookup {
+  Local getLocalByIndex(MemberEntity memberContext, int index);
+}
diff --git a/pkg/compiler/lib/src/types/abstract_value_domain.dart b/pkg/compiler/lib/src/types/abstract_value_domain.dart
index 37ff14d..d6cd5d0 100644
--- a/pkg/compiler/lib/src/types/abstract_value_domain.dart
+++ b/pkg/compiler/lib/src/types/abstract_value_domain.dart
@@ -6,6 +6,7 @@
 
 import '../constants/values.dart' show ConstantValue, PrimitiveConstantValue;
 import '../elements/entities.dart';
+import '../serialization/serialization.dart';
 import '../universe/selector.dart';
 import '../universe/world_builder.dart';
 import '../world.dart';
@@ -456,4 +457,10 @@
 
   /// Returns compact a textual representation for [value] used for debugging.
   String getCompactText(AbstractValue value);
+
+  /// Deserializes an [AbstractValue] for this domain from [source].
+  AbstractValue readAbstractValueFromDataSource(DataSource source);
+
+  /// Serializes this [value] for this domain to [sink].
+  void writeAbstractValueToDataSink(DataSink sink, AbstractValue value);
 }
diff --git a/pkg/compiler/lib/src/types/types.dart b/pkg/compiler/lib/src/types/types.dart
index 77d194c..03ed29e5 100644
--- a/pkg/compiler/lib/src/types/types.dart
+++ b/pkg/compiler/lib/src/types/types.dart
@@ -12,9 +12,11 @@
 import '../elements/entities.dart';
 import '../js_backend/inferred_data.dart';
 import '../inferrer/type_graph_inferrer.dart' show TypeGraphInferrer;
+import '../serialization/serialization.dart';
 import '../universe/selector.dart' show Selector;
 import '../world.dart' show JClosedWorld;
 import 'abstract_value_domain.dart';
+import '../inferrer/inferrer_engine.dart';
 
 /// Results about a single element (e.g. a method, parameter, or field)
 /// produced by the global type-inference algorithm.
@@ -27,6 +29,14 @@
 /// guarantees) and the `subclass of Object or null` type mask for the type
 /// based queries (the runtime value could be anything).
 abstract class GlobalTypeInferenceMemberResult {
+  /// Deserializes a [GlobalTypeInferenceMemberResult] object from [source].
+  factory GlobalTypeInferenceMemberResult.readFromDataSource(
+          DataSource source, AbstractValueDomain abstractValueDomain) =
+      GlobalTypeInferenceMemberResultImpl.readFromDataSource;
+
+  /// Serializes this [GlobalTypeInferenceMemberResult] to [sink].
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain);
+
   /// The inferred type when this result belongs to a field, null otherwise.
   AbstractValue get type;
 
@@ -67,6 +77,18 @@
 /// Internal data used during type-inference to store intermediate results about
 /// a single element.
 abstract class GlobalTypeInferenceElementData {
+  /// Deserializes a [GlobalTypeInferenceElementData] object from [source].
+  factory GlobalTypeInferenceElementData.readFromDataSource(
+          DataSource source, AbstractValueDomain abstractValueDomain) =
+      KernelGlobalTypeInferenceElementData.readFromDataSource;
+
+  /// Serializes this [GlobalTypeInferenceElementData] to [sink].
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain);
+
+  /// Compresses the inner representation by removing [AbstractValue] mappings
+  /// to `null`.
+  void compress();
+
   // TODO(johnniwinther): Remove this. Maybe split by access/invoke.
   AbstractValue typeOfSend(ir.TreeNode node);
   AbstractValue typeOfGetter(ir.TreeNode node);
@@ -98,6 +120,20 @@
 /// return was inferred to be a "guaranteed type", that means, it is a type that
 /// we can prove to be correct for all executions of the program.
 abstract class GlobalTypeInferenceResults {
+  /// Deserializes a [GlobalTypeInferenceResults] object from [source].
+  factory GlobalTypeInferenceResults.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld, InferredData inferredData) {
+    bool isTrivial = source.readBool();
+    if (isTrivial) {
+      return new TrivialGlobalTypeInferenceResults(closedWorld);
+    }
+    return new GlobalTypeInferenceResultsImpl.readFromDataSource(
+        source, closedWorld, inferredData);
+  }
+
+  /// Serializes this [GlobalTypeInferenceResults] to [sink].
+  void writeToDataSink(DataSink sink);
+
   JClosedWorld get closedWorld;
 
   InferredData get inferredData;
@@ -154,6 +190,10 @@
 }
 
 class GlobalTypeInferenceResultsImpl implements GlobalTypeInferenceResults {
+  /// Tag used for identifying serialized [GlobalTypeInferenceResults] objects
+  /// in a debugging data stream.
+  static const String tag = 'global-type-inference-results';
+
   final JClosedWorld closedWorld;
   final InferredData inferredData;
   final GlobalTypeInferenceMemberResult _deadFieldResult;
@@ -162,7 +202,7 @@
 
   final Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults;
   final Map<Local, AbstractValue> parameterResults;
-  final Set<ir.Node> checkedForGrowableLists;
+  final Set<ir.TreeNode> checkedForGrowableLists;
   final Set<Selector> returnsListElementTypeSet;
 
   GlobalTypeInferenceResultsImpl(
@@ -178,6 +218,46 @@
             closedWorld.abstractValueDomain),
         _trivialParameterResult = closedWorld.abstractValueDomain.dynamicType;
 
+  factory GlobalTypeInferenceResultsImpl.readFromDataSource(
+      DataSource source, JClosedWorld closedWorld, InferredData inferredData) {
+    source.begin(tag);
+    Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults =
+        source.readMemberMap(() =>
+            new GlobalTypeInferenceMemberResult.readFromDataSource(
+                source, closedWorld.abstractValueDomain));
+    Map<Local, AbstractValue> parameterResults = source.readLocalMap(() =>
+        closedWorld.abstractValueDomain
+            .readAbstractValueFromDataSource(source));
+    Set<ir.TreeNode> checkedForGrowableLists = source.readTreeNodes().toSet();
+    Set<Selector> returnsListElementTypeSet =
+        source.readList(() => new Selector.readFromDataSource(source)).toSet();
+    source.end(tag);
+    return new GlobalTypeInferenceResultsImpl(
+        closedWorld,
+        inferredData,
+        memberResults,
+        parameterResults,
+        checkedForGrowableLists,
+        returnsListElementTypeSet);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(false); // Is _not_ trivial.
+    sink.begin(tag);
+    sink.writeMemberMap(
+        memberResults,
+        (GlobalTypeInferenceMemberResult result) =>
+            result.writeToDataSink(sink, closedWorld.abstractValueDomain));
+    sink.writeLocalMap(
+        parameterResults,
+        (AbstractValue value) => closedWorld.abstractValueDomain
+            .writeAbstractValueToDataSink(sink, value));
+    sink.writeTreeNodes(checkedForGrowableLists);
+    sink.writeList(returnsListElementTypeSet,
+        (Selector selector) => selector.writeToDataSink(sink));
+    sink.end(tag);
+  }
+
   @override
   GlobalTypeInferenceMemberResult resultOfMember(MemberEntity member) {
     assert(
@@ -285,6 +365,10 @@
 
 class GlobalTypeInferenceMemberResultImpl
     implements GlobalTypeInferenceMemberResult {
+  /// Tag used for identifying serialized [GlobalTypeInferenceMemberResult]
+  /// objects in a debugging data stream.
+  static const String tag = 'global-type-inference-mebmer-result';
+
   final GlobalTypeInferenceElementData _data;
   final Map<ir.TreeNode, AbstractValue> _allocatedLists;
   final AbstractValue returnType;
@@ -296,6 +380,43 @@
       this._data, this._allocatedLists, this.returnType, this.type,
       {this.throwsAlways, this.isCalledOnce});
 
+  factory GlobalTypeInferenceMemberResultImpl.readFromDataSource(
+      DataSource source, AbstractValueDomain abstractValueDomain) {
+    source.begin(tag);
+    GlobalTypeInferenceElementData data = source.readValueOrNull(() {
+      return new GlobalTypeInferenceElementData.readFromDataSource(
+          source, abstractValueDomain);
+    });
+    Map<ir.TreeNode, AbstractValue> allocatedLists = source.readTreeNodeMap(
+        () => abstractValueDomain.readAbstractValueFromDataSource(source));
+    AbstractValue returnType =
+        abstractValueDomain.readAbstractValueFromDataSource(source);
+    AbstractValue type =
+        abstractValueDomain.readAbstractValueFromDataSource(source);
+    bool throwsAlways = source.readBool();
+    bool isCalledOnce = source.readBool();
+    source.end(tag);
+    return new GlobalTypeInferenceMemberResultImpl(
+        data, allocatedLists, returnType, type,
+        throwsAlways: throwsAlways, isCalledOnce: isCalledOnce);
+  }
+
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain) {
+    sink.begin(tag);
+    sink.writeValueOrNull(_data, (GlobalTypeInferenceElementData data) {
+      data.writeToDataSink(sink, abstractValueDomain);
+    });
+    sink.writeTreeNodeMap(
+        _allocatedLists,
+        (AbstractValue value) =>
+            abstractValueDomain.writeAbstractValueToDataSink(sink, value));
+    abstractValueDomain.writeAbstractValueToDataSink(sink, returnType);
+    abstractValueDomain.writeAbstractValueToDataSink(sink, type);
+    sink.writeBool(throwsAlways);
+    sink.writeBool(isCalledOnce);
+    sink.end(tag);
+  }
+
   AbstractValue typeOfSend(ir.Node node) => _data?.typeOfSend(node);
   AbstractValue typeOfGetter(ir.Node node) => _data?.typeOfGetter(node);
   AbstractValue typeOfIterator(ir.Node node) => _data?.typeOfIterator(node);
@@ -320,6 +441,10 @@
             closedWorld.abstractValueDomain.dynamicType),
         _trivialParameterResult = closedWorld.abstractValueDomain.dynamicType;
 
+  void writeToDataSink(DataSink sink) {
+    sink.writeBool(true); // Is trivial.
+  }
+
   @override
   bool isFixedArrayCheckedForGrowable(ir.Node node) => false;
 
@@ -377,6 +502,11 @@
 
   @override
   bool get isCalledOnce => false;
+
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain) {
+    throw new UnsupportedError(
+        "TrivialGlobalTypeInferenceMemberResult.writeToDataSink");
+  }
 }
 
 class DeadFieldGlobalTypeInferenceResult
@@ -420,6 +550,11 @@
 
   @override
   bool get isCalledOnce => false;
+
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain) {
+    throw new UnsupportedError(
+        "DeadFieldGlobalTypeInferenceResult.writeToDataSink");
+  }
 }
 
 class DeadMethodGlobalTypeInferenceResult
@@ -463,4 +598,9 @@
 
   @override
   bool get isCalledOnce => false;
+
+  void writeToDataSink(DataSink sink, AbstractValueDomain abstractValueDomain) {
+    throw new UnsupportedError(
+        "DeadFieldGlobalTypeInferenceResult.writeToDataSink");
+  }
 }
diff --git a/pkg/compiler/lib/src/universe/call_structure.dart b/pkg/compiler/lib/src/universe/call_structure.dart
index 0ca8c4d..c574ac8 100644
--- a/pkg/compiler/lib/src/universe/call_structure.dart
+++ b/pkg/compiler/lib/src/universe/call_structure.dart
@@ -6,6 +6,7 @@
 
 import '../common/names.dart' show Names;
 import '../elements/entities.dart' show ParameterStructure;
+import '../serialization/serialization.dart';
 import '../util/util.dart';
 import 'selector.dart' show Selector;
 
@@ -14,6 +15,10 @@
 // TODO(johnniwinther): Should isGetter/isSetter be part of the call structure
 // instead of the selector?
 class CallStructure {
+  /// Tag used for identifying serialized [CallStructure] objects in a debugging
+  /// data stream.
+  static const String tag = 'call-structure';
+
   static const CallStructure NO_ARGS = const CallStructure.unnamed(0);
   static const CallStructure ONE_ARG = const CallStructure.unnamed(1);
   static const CallStructure TWO_ARGS = const CallStructure.unnamed(2);
@@ -43,6 +48,25 @@
         argumentCount, namedArguments, typeArgumentCount);
   }
 
+  /// Deserializes a [CallStructure] object from [source].
+  factory CallStructure.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    int argumentCount = source.readInt();
+    List<String> namedArguments = source.readStrings();
+    int typeArgumentCount = source.readInt();
+    source.end(tag);
+    return new CallStructure(argumentCount, namedArguments, typeArgumentCount);
+  }
+
+  /// Serializes this [CallStructure] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(argumentCount);
+    sink.writeStrings(namedArguments);
+    sink.writeInt(typeArgumentCount);
+    sink.end(tag);
+  }
+
   CallStructure withTypeArgumentCount(int typeArgumentCount) =>
       new CallStructure(argumentCount, namedArguments, typeArgumentCount);
 
diff --git a/pkg/compiler/lib/src/universe/class_hierarchy.dart b/pkg/compiler/lib/src/universe/class_hierarchy.dart
index c351f1e..7032550 100644
--- a/pkg/compiler/lib/src/universe/class_hierarchy.dart
+++ b/pkg/compiler/lib/src/universe/class_hierarchy.dart
@@ -6,11 +6,20 @@
 import '../common_elements.dart';
 import '../elements/entities.dart';
 import '../elements/types.dart' show InterfaceType;
+import '../serialization/serialization.dart';
 import 'class_set.dart';
 
 // TODO(johnniwinther): Move more methods from `JClosedWorld` to
 // `ClassHierarchy`.
 abstract class ClassHierarchy {
+  /// Deserializes a [ClassHierarchy] object from [source].
+  factory ClassHierarchy.readFromDataSource(
+          DataSource source, CommonElements commonElements) =
+      ClassHierarchyImpl.readFromDataSource;
+
+  /// Serializes this [ClassHierarchy] to [sink].
+  void writeToDataSink(DataSink sink);
+
   /// Returns `true` if [cls] is either directly or indirectly instantiated.
   bool isInstantiated(ClassEntity cls);
 
@@ -141,6 +150,10 @@
 }
 
 class ClassHierarchyImpl implements ClassHierarchy {
+  /// Tag used for identifying serialized [ClassHierarchy] objects in a debugging
+  /// data stream.
+  static const String tag = 'class-hierarchy';
+
   final CommonElements _commonElements;
   final Map<ClassEntity, ClassHierarchyNode> _classHierarchyNodes;
   final Map<ClassEntity, ClassSet> _classSets;
@@ -148,6 +161,44 @@
   ClassHierarchyImpl(
       this._commonElements, this._classHierarchyNodes, this._classSets);
 
+  factory ClassHierarchyImpl.readFromDataSource(
+      DataSource source, CommonElements commonElements) {
+    source.begin(tag);
+    Map<ClassEntity, ClassHierarchyNode> classHierarchyNodes =
+        new ClassHierarchyNodesMap();
+    int classCount = source.readInt();
+    for (int i = 0; i < classCount; i++) {
+      ClassHierarchyNode node = new ClassHierarchyNode.readFromDataSource(
+          source, classHierarchyNodes);
+      classHierarchyNodes[node.cls] = node;
+    }
+    Map<ClassEntity, ClassSet> classSets = {};
+    for (int i = 0; i < classCount; i++) {
+      ClassSet classSet =
+          new ClassSet.readFromDataSource(source, classHierarchyNodes);
+      classSets[classSet.cls] = classSet;
+    }
+
+    source.end(tag);
+    return new ClassHierarchyImpl(
+        commonElements, classHierarchyNodes, classSets);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(_classSets.length);
+    ClassHierarchyNode node =
+        getClassHierarchyNode(_commonElements.objectClass);
+    node.forEachSubclass((ClassEntity cls) {
+      getClassHierarchyNode(cls).writeToDataSink(sink);
+    }, ClassHierarchyNode.ALL);
+    ClassSet set = getClassSet(_commonElements.objectClass);
+    set.forEachSubclass((ClassEntity cls) {
+      getClassSet(cls).writeToDataSink(sink);
+    }, ClassHierarchyNode.ALL);
+    sink.end(tag);
+  }
+
   @override
   bool isInstantiated(ClassEntity cls) {
     ClassHierarchyNode node = _classHierarchyNodes[cls];
diff --git a/pkg/compiler/lib/src/universe/class_set.dart b/pkg/compiler/lib/src/universe/class_set.dart
index 04a9733..6431ab6 100644
--- a/pkg/compiler/lib/src/universe/class_set.dart
+++ b/pkg/compiler/lib/src/universe/class_set.dart
@@ -10,6 +10,7 @@
 
 import '../elements/entities.dart' show ClassEntity;
 import '../elements/indexed.dart' show IndexedClass;
+import '../serialization/serialization.dart';
 import '../util/enumset.dart' show EnumSet;
 
 /// Enum for the different kinds of instantiation of a class.
@@ -46,6 +47,10 @@
 ///       E
 ///
 class ClassHierarchyNode {
+  /// Tag used for identifying serialized [ClassHierarchyNode] objects in a
+  /// debugging data stream.
+  static const String tag = 'class-hierarchy-node';
+
   /// Enum set for selecting instantiated classes in
   /// [ClassHierarchyNode.subclassesByMask],
   /// [ClassHierarchyNode.subclassesByMask] and [ClassSet.subtypesByMask].
@@ -209,6 +214,38 @@
     }
   }
 
+  /// Deserializes a [ClassHierarchyNode] object from [source].
+  factory ClassHierarchyNode.readFromDataSource(
+      DataSource source, Map<ClassEntity, ClassHierarchyNode> nodeMap) {
+    source.begin(tag);
+    IndexedClass cls = source.readClass();
+    ClassHierarchyNode parentNode;
+    IndexedClass superclass = source.readClassOrNull();
+    if (superclass != null) {
+      parentNode = nodeMap[superclass];
+      assert(parentNode != null,
+          "No ClassHierarchyNode for superclass $superclass.");
+    }
+    int maskValue = source.readInt();
+    int hierarchyDepth = source.readInt();
+    int instantiatedSubclassCount = source.readInt();
+    source.end(tag);
+    return new ClassHierarchyNode(parentNode, cls, hierarchyDepth)
+      .._instantiatedSubclassCount = instantiatedSubclassCount
+      .._mask.value = maskValue;
+  }
+
+  /// Serializes this [ClassHierarchyNode] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeClass(cls);
+    sink.writeClassOrNull(parentNode?.cls);
+    sink.writeInt(_mask.value);
+    sink.writeInt(hierarchyDepth);
+    sink.writeInt(_instantiatedSubclassCount);
+    sink.end(tag);
+  }
+
   /// Adds [subclass] as a direct subclass of [cls].
   void addDirectSubclass(ClassHierarchyNode subclass) {
     assert(!_directSubclasses.contains(subclass));
@@ -463,6 +500,10 @@
 ///      B          E
 ///
 class ClassSet {
+  /// Tag used for identifying serialized [ClassSet] objects in a debugging
+  /// data stream.
+  static const String tag = 'class-set';
+
   final ClassHierarchyNode node;
   ClassEntity _leastUpperInstantiatedSubtype;
 
@@ -502,6 +543,36 @@
 
   ClassSet(this.node);
 
+  /// Deserializes a [ClassSet] object from [source].
+  factory ClassSet.readFromDataSource(
+      DataSource source, Map<ClassEntity, ClassHierarchyNode> nodeMap) {
+    source.begin(tag);
+    ClassHierarchyNode node = nodeMap[source.readClass()];
+    List<ClassHierarchyNode> subtypes = source.readList(() {
+      return nodeMap[source.readClass()];
+    }, emptyAsNull: true);
+    List<ClassHierarchyNode> mixinApplications = source.readList(() {
+      return nodeMap[source.readClass()];
+    }, emptyAsNull: true);
+    source.end(tag);
+    return new ClassSet(node)
+      .._subtypes = subtypes
+      .._mixinApplications = mixinApplications;
+  }
+
+  /// Serializes this [ClassSet] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeClass(node.cls);
+    sink.writeList(_subtypes, (ClassHierarchyNode node) {
+      sink.writeClass(node.cls);
+    }, allowNull: true);
+    sink.writeList(_mixinApplications, (ClassHierarchyNode node) {
+      sink.writeClass(node.cls);
+    }, allowNull: true);
+    sink.end(tag);
+  }
+
   ClassEntity get cls => node.cls;
 
   /// Returns `true` if `other.cls` is a subclass of [cls].
diff --git a/pkg/compiler/lib/src/universe/selector.dart b/pkg/compiler/lib/src/universe/selector.dart
index 34aaf06..acc7100 100644
--- a/pkg/compiler/lib/src/universe/selector.dart
+++ b/pkg/compiler/lib/src/universe/selector.dart
@@ -10,6 +10,7 @@
 import '../elements/entity_utils.dart' as utils;
 import '../elements/names.dart';
 import '../elements/operators.dart';
+import '../serialization/serialization.dart';
 import '../util/util.dart' show Hashing;
 import 'call_structure.dart' show CallStructure;
 
@@ -40,6 +41,10 @@
 }
 
 class Selector {
+  /// Tag used for identifying serialized [Selector] objects in a debugging
+  /// data stream.
+  static const String tag = 'selector';
+
   final SelectorKind kind;
   final Name memberName;
   final CallStructure callStructure;
@@ -192,6 +197,30 @@
       Names.genericInstantiation,
       new CallStructure(0, null, typeArguments));
 
+  /// Deserializes a [Selector] object from [source].
+  factory Selector.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    SelectorKind kind = source.readEnum(SelectorKind.values);
+    bool isSetter = source.readBool();
+    LibraryEntity library = source.readLibraryOrNull();
+    String text = source.readString();
+    CallStructure callStructure = new CallStructure.readFromDataSource(source);
+    source.end(tag);
+    return new Selector(
+        kind, new Name(text, library, isSetter: isSetter), callStructure);
+  }
+
+  /// Serializes this [Selector] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeEnum(kind);
+    sink.writeBool(memberName.isSetter);
+    sink.writeLibraryOrNull(memberName.library);
+    sink.writeString(memberName.text);
+    callStructure.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   bool get isGetter => kind == SelectorKind.GETTER;
   bool get isSetter => kind == SelectorKind.SETTER;
   bool get isCall => kind == SelectorKind.CALL;
diff --git a/pkg/compiler/lib/src/universe/side_effects.dart b/pkg/compiler/lib/src/universe/side_effects.dart
index 81d513f..aac589a 100644
--- a/pkg/compiler/lib/src/universe/side_effects.dart
+++ b/pkg/compiler/lib/src/universe/side_effects.dart
@@ -5,8 +5,13 @@
 library universe.side_effects;
 
 import '../elements/entities.dart';
+import '../serialization/serialization.dart';
 
 class SideEffects {
+  /// Tag used for identifying serialized [SideEffects] objects in a debugging
+  /// data stream.
+  static const String tag = 'side-effects';
+
   // Changes flags.
   static const int FLAG_CHANGES_INDEX = 0;
   static const int FLAG_CHANGES_INSTANCE_PROPERTY = FLAG_CHANGES_INDEX + 1;
@@ -37,6 +42,21 @@
 
   SideEffects.fromFlags(this._flags);
 
+  /// Deserializes a [SideEffects] object from [source].
+  factory SideEffects.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    int flags = source.readInt();
+    source.end(tag);
+    return new SideEffects.fromFlags(flags);
+  }
+
+  /// Serializes this [SideEffects] to [sink].
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(_flags);
+    sink.end(tag);
+  }
+
   bool operator ==(other) => _flags == other._flags;
 
   int get hashCode => throw new UnsupportedError('SideEffects.hashCode');
diff --git a/pkg/compiler/lib/src/util/enumset.dart b/pkg/compiler/lib/src/util/enumset.dart
index 31fef3c..97df1a4 100644
--- a/pkg/compiler/lib/src/util/enumset.dart
+++ b/pkg/compiler/lib/src/util/enumset.dart
@@ -32,6 +32,10 @@
   /// The bit mask of the shifted indices for the enum values in this set.
   int get value;
 
+  /// Sets the enum values in this set through a bit mask of the shifted enum
+  /// value indices.
+  void set value(int mask);
+
   /// Adds [enumValue] to this set.
   void add(E enumValue);
 
@@ -128,6 +132,11 @@
   int get value => _value;
 
   @override
+  void set value(int mask) {
+    _value = mask;
+  }
+
+  @override
   void add(E enumValue) {
     _value |= 1 << (enumValue as dynamic).index;
   }
@@ -173,6 +182,10 @@
     return new _ConstEnumSet(value);
   }
 
+  void set value(int mask) {
+    throw new UnsupportedError('EnumSet.value=');
+  }
+
   @override
   void add(E enumValue) {
     throw new UnsupportedError('EnumSet.add');
diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart
index e174ebc..cb83743 100644
--- a/pkg/compiler/lib/src/world.dart
+++ b/pkg/compiler/lib/src/world.dart
@@ -32,6 +32,7 @@
 import 'js_model/locals.dart';
 import 'ordered_typeset.dart';
 import 'options.dart';
+import 'js_emitter/sorter.dart';
 import 'types/abstract_value_domain.dart';
 import 'universe/class_hierarchy.dart';
 import 'universe/class_set.dart';
@@ -88,6 +89,9 @@
 
   OutputUnitData get outputUnitData;
 
+  /// The [Sorter] used for sorting elements in the generated code.
+  Sorter get sorter;
+
   /// Returns `true` if [cls] is implemented by an instantiated class.
   bool isImplemented(ClassEntity cls);
 
@@ -258,7 +262,7 @@
   final JCommonElements commonElements;
 
   // TODO(johnniwinther): Can this be derived from [ClassSet]s?
-  final Set<ClassEntity> _implementedClasses;
+  final Set<ClassEntity> implementedClasses;
 
   final Iterable<MemberEntity> liveInstanceMembers;
 
@@ -280,15 +284,14 @@
       this.interceptorData,
       this.backendUsage,
       this.noSuchMethodData,
-      Set<ClassEntity> implementedClasses,
+      this.implementedClasses,
       this.liveNativeClasses,
       this.liveInstanceMembers,
       this.assignedInstanceMembers,
       this.processedMembers,
       this.mixinUses,
       this.typesImplementedBySubclasses,
-      this.classHierarchy)
-      : this._implementedClasses = implementedClasses;
+      this.classHierarchy);
 
   OrderedTypeSet getOrderedTypeSet(covariant ClassEntity cls);
 
@@ -304,7 +307,7 @@
 
   /// Returns `true` if [cls] is implemented by an instantiated class.
   bool isImplemented(ClassEntity cls) {
-    return _implementedClasses.contains(cls);
+    return implementedClasses.contains(cls);
   }
 
   @override
diff --git a/tests/compiler/dart2js/analyses/dart2js_allowed.json b/tests/compiler/dart2js/analyses/dart2js_allowed.json
index 1277128..c8f89bc6 100644
--- a/tests/compiler/dart2js/analyses/dart2js_allowed.json
+++ b/tests/compiler/dart2js/analyses/dart2js_allowed.json
@@ -217,6 +217,12 @@
     "Dynamic access of 'isString'.": 1,
     "Dynamic access of 'stringValue'.": 1
   },
+  "pkg/compiler/lib/src/serialization/binary_sink.dart": {
+    "Dynamic access of 'index'.": 1
+  },
+  "pkg/compiler/lib/src/serialization/helpers.dart": {
+    "Dynamic access of 'value'.": 1
+  },
   "pkg/compiler/lib/src/universe/use.dart": {
     "Dynamic access of 'selector'.": 1,
     "Dynamic access of 'receiverConstraint'.": 1,
@@ -298,6 +304,12 @@
     "Dynamic access of 'usedBy'.": 2,
     "Dynamic access of 'inputs'.": 1
   },
+  "pkg/compiler/lib/src/inferrer/inferrer_engine.dart": {
+    "Dynamic access of 'isVoid'.": 1,
+    "Dynamic access of 'isDynamic'.": 1,
+    "Dynamic access of 'isInterfaceType'.": 1,
+    "Dynamic access of 'element'.": 1
+  },
   "pkg/compiler/lib/src/universe/function_set.dart": {
     "Dynamic access of 'selector'.": 1,
     "Dynamic access of 'receiver'.": 1
@@ -365,12 +377,6 @@
     "Dynamic invocation of '[]'.": 9,
     "Dynamic invocation of 'toStatement'.": 3
   },
-  "pkg/compiler/lib/src/inferrer/inferrer_engine.dart": {
-    "Dynamic access of 'isVoid'.": 1,
-    "Dynamic access of 'isDynamic'.": 1,
-    "Dynamic access of 'isInterfaceType'.": 1,
-    "Dynamic access of 'element'.": 1
-  },
   "pkg/compiler/lib/src/inferrer/type_graph_nodes.dart": {
     "Dynamic invocation of 'add'.": 1,
     "Dynamic invocation of 'remove'.": 1,
@@ -383,6 +389,16 @@
     "Dynamic access of 'startPosition'.": 1,
     "Dynamic access of 'innerPosition'.": 1
   },
+  "pkg/compiler/lib/src/inferrer/locals_handler.dart": {
+    "Dynamic access of 'isEmpty'.": 1,
+    "Dynamic access of 'positional'.": 2,
+    "Dynamic access of 'length'.": 2,
+    "Dynamic access of 'named'.": 2,
+    "Dynamic invocation of '[]'.": 2
+  },
+  "pkg/compiler/lib/src/inferrer/type_graph_dump.dart": {
+    "Dynamic invocation of 'contains'.": 2
+  },
   "pkg/compiler/lib/src/ssa/variable_allocator.dart": {
     "Dynamic access of 'checkedInput'.": 1,
     "Dynamic access of 'usedBy'.": 1,
@@ -412,16 +428,6 @@
   "pkg/compiler/lib/src/ssa/value_set.dart": {
     "Dynamic invocation of 'add'.": 2
   },
-  "pkg/compiler/lib/src/inferrer/locals_handler.dart": {
-    "Dynamic access of 'isEmpty'.": 1,
-    "Dynamic access of 'positional'.": 2,
-    "Dynamic access of 'length'.": 2,
-    "Dynamic access of 'named'.": 2,
-    "Dynamic invocation of '[]'.": 2
-  },
-  "pkg/compiler/lib/src/inferrer/type_graph_dump.dart": {
-    "Dynamic invocation of 'contains'.": 2
-  },
   "pkg/compiler/lib/src/js_emitter/full_emitter/emitter.dart": {
     "Dynamic access of 'scheme'.": 1,
     "Dynamic invocation of 'finalizeTokens'.": 1
diff --git a/tests/compiler/dart2js/model/in_memory_split_test.dart b/tests/compiler/dart2js/model/in_memory_split_test.dart
index 8c1a080..ab13b90 100644
--- a/tests/compiler/dart2js/model/in_memory_split_test.dart
+++ b/tests/compiler/dart2js/model/in_memory_split_test.dart
@@ -2,41 +2,95 @@
 // 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.
 
+import 'package:args/args.dart';
 import 'package:async_helper/async_helper.dart';
 import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/filenames.dart';
 import 'package:compiler/src/js_backend/inferred_data.dart';
+import 'package:compiler/src/js_model/js_strategy.dart';
+import 'package:compiler/src/serialization/serialization.dart';
 import 'package:compiler/src/types/types.dart';
-import 'package:compiler/src/world.dart';
 import 'package:expect/expect.dart';
+import 'package:front_end/src/fasta/kernel/utils.dart' as ir
+    show serializeComponent;
+import 'package:front_end/src/fasta/kernel/utils.dart';
+import 'package:kernel/ast.dart' as ir;
+import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
 import '../helpers/memory_compiler.dart';
 
-String code = '''
-main() {}
-''';
-main() {
+main(List<String> args) {
+  ArgParser argParser = new ArgParser(allowTrailingOptions: true);
+  argParser.addFlag('debug', abbr: 'd', defaultsTo: false);
+  argParser.addFlag('object', abbr: 'o', defaultsTo: false);
+  argParser.addFlag('kinds', abbr: 'k', defaultsTo: false);
+  ArgResults argResults = argParser.parse(args);
+
+  bool useObjectSink = argResults['object'] || argResults['debug'];
+  bool useDataKinds = argResults['kinds'] || argResults['debug'];
+
   asyncTest(() async {
+    Uri entryPoint;
+    if (argResults.rest.isEmpty) {
+      entryPoint = Uri.base.resolve('samples-dev/swarm/swarm.dart');
+    } else {
+      entryPoint = Uri.base.resolve(nativeToUriPath(argResults.rest.last));
+    }
+
     CompilationResult result = await runCompiler(
-        memorySourceFiles: {'main.dart': code},
+        entryPoint: entryPoint,
         beforeRun: (Compiler compiler) {
           compiler.stopAfterTypeInference = true;
         });
     Expect.isTrue(result.isSuccess);
     Compiler compiler = result.compiler;
-    GlobalTypeInferenceResults globalInferenceResults =
-        cloneInferenceResults(compiler.globalInference.resultsForTesting);
+    GlobalTypeInferenceResults globalInferenceResults = cloneInferenceResults(
+        compiler, compiler.globalInference.resultsForTesting,
+        useObjectSink: useObjectSink, useDataKinds: useDataKinds);
     compiler.generateJavaScriptCode(globalInferenceResults);
   });
 }
 
 GlobalTypeInferenceResults cloneInferenceResults(
-    GlobalTypeInferenceResultsImpl result) {
-  JClosedWorld closedWorld = result.closedWorld;
-  InferredData inferredData = result.inferredData;
-  return new GlobalTypeInferenceResultsImpl(
-      closedWorld,
-      inferredData,
-      result.memberResults,
-      result.parameterResults,
-      result.checkedForGrowableLists,
-      result.returnsListElementTypeSet);
+    Compiler compiler, GlobalTypeInferenceResults results,
+    {bool useObjectSink: false, bool useDataKinds}) {
+  JsClosedWorld closedWorld = results.closedWorld;
+  InferredData inferredData = results.inferredData;
+  ir.Component component = closedWorld.elementMap.programEnv.mainComponent;
+  List<int> irData = ir.serializeComponent(component);
+
+  Function() getData;
+
+  DataSink sink;
+  if (useObjectSink) {
+    List data = [];
+    sink = new ObjectSink(data, useDataKinds: useDataKinds);
+    getData = () => data;
+  } else {
+    ByteSink byteSink = new ByteSink();
+    sink = new BinarySink(byteSink, useDataKinds: useDataKinds);
+    getData = () => byteSink.builder.takeBytes();
+  }
+  closedWorld.writeToDataSink(sink);
+  inferredData.writeToDataSink(sink);
+  results.writeToDataSink(sink);
+  sink.close();
+  var worldData = getData();
+  print('data size: ${worldData.length}');
+
+  ir.Component newComponent = new ir.Component();
+  new BinaryBuilder(irData).readComponent(newComponent);
+  DataSource source = useObjectSink
+      ? new ObjectSource(worldData, useDataKinds: useDataKinds)
+      : new BinarySourceImpl(worldData, useDataKinds: useDataKinds);
+  closedWorld = new JsClosedWorld.readFromDataSource(
+      compiler.options,
+      compiler.reporter,
+      compiler.environment,
+      compiler.abstractValueStrategy,
+      newComponent,
+      source);
+  inferredData = new InferredData.readFromDataSource(source, closedWorld);
+  results = new GlobalTypeInferenceResults.readFromDataSource(
+      source, closedWorld, inferredData);
+  return results;
 }