Support in-memory CodegenResult serialization/deserialization

The serialization/deserialization is performed in test-mode.

Change-Id: I90f31a6a88dbdf2c38beb9b5409ce3c52557e227
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102740
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/backend_strategy.dart b/pkg/compiler/lib/src/backend_strategy.dart
index 7c14557..444937c 100644
--- a/pkg/compiler/lib/src/backend_strategy.dart
+++ b/pkg/compiler/lib/src/backend_strategy.dart
@@ -38,7 +38,7 @@
       SelectorConstraintsStrategy selectorConstraintsStrategy);
 
   /// Creates the [WorkItemBuilder] used by the codegen enqueuer.
-  WorkItemBuilder createCodegenWorkItemBuilder();
+  WorkItemBuilder createCodegenWorkItemBuilder(JClosedWorld closedWorld);
 
   /// Creates the [SsaBuilder] used for the element model.
   SsaBuilder createSsaBuilder(
diff --git a/pkg/compiler/lib/src/common/codegen.dart b/pkg/compiler/lib/src/common/codegen.dart
index eaaa3f1..852152d 100644
--- a/pkg/compiler/lib/src/common/codegen.dart
+++ b/pkg/compiler/lib/src/common/codegen.dart
@@ -8,22 +8,35 @@
 
 import '../common_elements.dart';
 import '../constants/values.dart';
+import '../deferred_load.dart';
 import '../elements/entities.dart';
 import '../elements/types.dart' show DartType, InterfaceType;
+import '../inferrer/abstract_value_domain.dart';
+import '../io/source_information.dart';
 import '../js/js.dart' as js;
-import '../js_backend/namer.dart' show Namer;
+import '../js_backend/namer.dart';
 import '../js_emitter/code_emitter_task.dart' show Emitter;
 import '../native/behavior.dart';
+import '../serialization/serialization.dart';
 import '../universe/feature.dart';
+import '../universe/selector.dart';
 import '../universe/use.dart' show ConstantUse, DynamicUse, StaticUse, TypeUse;
 import '../universe/world_impact.dart'
     show WorldImpact, WorldImpactBuilderImpl, WorldImpactVisitor;
 import '../util/enumset.dart';
 import '../util/util.dart';
+import '../world.dart';
 
 class CodegenImpact extends WorldImpact {
   const CodegenImpact();
 
+  factory CodegenImpact.readFromDataSource(DataSource source) =
+      _CodegenImpact.readFromDataSource;
+
+  void writeToDataSink(DataSink sink) {
+    throw new UnsupportedError('CodegenImpact.writeToDataSink');
+  }
+
   Iterable<Pair<DartType, DartType>> get typeVariableBoundsSubtypeChecks {
     return const <Pair<DartType, DartType>>[];
   }
@@ -47,8 +60,10 @@
 }
 
 class _CodegenImpact extends WorldImpactBuilderImpl implements CodegenImpact {
-  Setlet<Pair<DartType, DartType>> _typeVariableBoundsSubtypeChecks;
-  Setlet<String> _constSymbols;
+  static const String tag = 'codegen-impact';
+
+  Set<Pair<DartType, DartType>> _typeVariableBoundsSubtypeChecks;
+  Set<String> _constSymbols;
   List<Set<ClassEntity>> _specializedGetInterceptors;
   bool _usesInterceptor = false;
   EnumSet<AsyncMarker> _asyncMarkers;
@@ -58,6 +73,108 @@
 
   _CodegenImpact();
 
+  _CodegenImpact.internal(
+      Set<DynamicUse> dynamicUses,
+      Set<StaticUse> staticUses,
+      Set<TypeUse> typeUses,
+      Set<ConstantUse> constantUses,
+      this._typeVariableBoundsSubtypeChecks,
+      this._constSymbols,
+      this._specializedGetInterceptors,
+      this._usesInterceptor,
+      this._asyncMarkers,
+      this._genericInstantiations,
+      this._nativeBehaviors,
+      this._nativeMethods)
+      : super.internal(dynamicUses, staticUses, typeUses, constantUses);
+
+  factory _CodegenImpact.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Set<DynamicUse> dynamicUses = source
+        .readList(() => DynamicUse.readFromDataSource(source),
+            emptyAsNull: true)
+        ?.toSet();
+    Set<StaticUse> staticUses = source
+        .readList(() => StaticUse.readFromDataSource(source), emptyAsNull: true)
+        ?.toSet();
+    Set<TypeUse> typeUses = source
+        .readList(() => TypeUse.readFromDataSource(source), emptyAsNull: true)
+        ?.toSet();
+    Set<ConstantUse> constantUses = source
+        .readList(() => ConstantUse.readFromDataSource(source),
+            emptyAsNull: true)
+        ?.toSet();
+    Set<Pair<DartType, DartType>> typeVariableBoundsSubtypeChecks =
+        source.readList(() {
+      return new Pair(source.readDartType(), source.readDartType());
+    }, emptyAsNull: true)?.toSet();
+    Set<String> constSymbols = source.readStrings(emptyAsNull: true)?.toSet();
+    List<Set<ClassEntity>> specializedGetInterceptors = source.readList(() {
+      return source.readClasses().toSet();
+    }, emptyAsNull: true);
+    bool usesInterceptor = source.readBool();
+    int asyncMarkersValue = source.readIntOrNull();
+    EnumSet<AsyncMarker> asyncMarkers = asyncMarkersValue != null
+        ? new EnumSet.fromValue(asyncMarkersValue)
+        : null;
+    Set<GenericInstantiation> genericInstantiations = source
+        .readList(() => GenericInstantiation.readFromDataSource(source),
+            emptyAsNull: true)
+        ?.toSet();
+    List<NativeBehavior> nativeBehaviors = source.readList(
+        () => NativeBehavior.readFromDataSource(source),
+        emptyAsNull: true);
+    Set<FunctionEntity> nativeMethods =
+        source.readMembers<FunctionEntity>(emptyAsNull: true)?.toSet();
+    source.end(tag);
+    return new _CodegenImpact.internal(
+        dynamicUses,
+        staticUses,
+        typeUses,
+        constantUses,
+        typeVariableBoundsSubtypeChecks,
+        constSymbols,
+        specializedGetInterceptors,
+        usesInterceptor,
+        asyncMarkers,
+        genericInstantiations,
+        nativeBehaviors,
+        nativeMethods);
+  }
+
+  @override
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeList(dynamicUses, (DynamicUse use) => use.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeList(staticUses, (StaticUse use) => use.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeList(typeUses, (TypeUse use) => use.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeList(constantUses, (ConstantUse use) => use.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeList<Pair<DartType, DartType>>(_typeVariableBoundsSubtypeChecks,
+        (pair) {
+      sink.writeDartType(pair.a);
+      sink.writeDartType(pair.b);
+    }, allowNull: true);
+    sink.writeStrings(_constSymbols, allowNull: true);
+    sink.writeList(_specializedGetInterceptors, sink.writeClasses,
+        allowNull: true);
+    sink.writeBool(_usesInterceptor);
+    sink.writeIntOrNull(_asyncMarkers?.value);
+    sink.writeList(
+        _genericInstantiations,
+        (GenericInstantiation instantiation) =>
+            instantiation.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeList(_nativeBehaviors,
+        (NativeBehavior behavior) => behavior.writeToDataSink(sink),
+        allowNull: true);
+    sink.writeMembers(_nativeMethods, allowNull: true);
+    sink.end(tag);
+  }
+
   @override
   void apply(WorldImpactVisitor visitor) {
     staticUses.forEach(visitor.visitStaticUse);
@@ -67,7 +184,7 @@
 
   void registerTypeVariableBoundsSubtypeCheck(
       DartType subtype, DartType supertype) {
-    _typeVariableBoundsSubtypeChecks ??= new Setlet<Pair<DartType, DartType>>();
+    _typeVariableBoundsSubtypeChecks ??= {};
     _typeVariableBoundsSubtypeChecks
         .add(new Pair<DartType, DartType>(subtype, supertype));
   }
@@ -80,7 +197,7 @@
   }
 
   void registerConstSymbol(String name) {
-    _constSymbols ??= new Setlet<String>();
+    _constSymbols ??= {};
     _constSymbols.add(name);
   }
 
@@ -121,7 +238,7 @@
   }
 
   void registerGenericInstantiation(GenericInstantiation instantiation) {
-    _genericInstantiations ??= new Set<GenericInstantiation>();
+    _genericInstantiations ??= {};
     _genericInstantiations.add(instantiation);
   }
 
@@ -149,6 +266,33 @@
   Iterable<FunctionEntity> get nativeMethods {
     return _nativeMethods ?? const [];
   }
+
+  @override
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.write('CodegenImpact:');
+    WorldImpact.printOn(sb, this);
+
+    void add(String title, Iterable iterable) {
+      if (iterable.isNotEmpty) {
+        sb.write('\n $title:');
+        iterable.forEach((e) => sb.write('\n  $e'));
+      }
+    }
+
+    add('typeVariableBoundsSubtypeChecks', typeVariableBoundsSubtypeChecks);
+    add('constSymbols', constSymbols);
+    add('specializedGetInterceptors', specializedGetInterceptors);
+    if (usesInterceptor) {
+      sb.write('\n usesInterceptor: true');
+    }
+    add('asyncMarkers', asyncMarkers);
+    add('genericInstantiations', genericInstantiations);
+    add('nativeBehaviors', nativeBehaviors);
+    add('nativeMethods', nativeMethods);
+
+    return sb.toString();
+  }
 }
 
 // TODO(johnniwinther): Split this class into interface and implementation.
@@ -245,6 +389,8 @@
 }
 
 class CodegenResult {
+  static const String tag = 'codegen-result';
+
   final js.Fun code;
   final CodegenImpact impact;
   final Iterable<ModularName> modularNames;
@@ -252,6 +398,35 @@
 
   CodegenResult(
       this.code, this.impact, this.modularNames, this.modularExpressions);
+
+  /// Reads a [CodegenResult] object from [source].
+  ///
+  /// The [ModularName] and [ModularExpression] nodes read during
+  /// deserialization are collected in [modularNames] and [modularExpressions]
+  /// to avoid the need for visiting the [code] node post deserialization.
+  factory CodegenResult.readFromDataSource(
+      DataSource source,
+      List<ModularName> modularNames,
+      List<ModularExpression> modularExpressions) {
+    source.begin(tag);
+    js.Fun code = source.readJsNodeOrNull();
+    CodegenImpact impact = CodegenImpact.readFromDataSource(source);
+    source.end(tag);
+    return new CodegenResult(code, impact, modularNames, modularExpressions);
+  }
+
+  /// Writes the [CodegenResult] object to [sink].
+  ///
+  /// The [modularNames] and [modularExpressions] fields are not directly
+  /// serializes because these are embedded in the [code] node and collected
+  /// through this during deserialization.
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeJsNodeOrNull(code);
+    impact.writeToDataSink(sink);
+    sink.end(tag);
+  }
+
   void applyModularState(Namer namer, Emitter emitter) {
     for (ModularName name in modularNames) {
       switch (name.kind) {
@@ -386,6 +561,8 @@
 }
 
 class ModularName extends js.Name implements js.AstContainer {
+  static const String tag = 'modular-name';
+
   final ModularNameKind kind;
   js.Name _value;
   final Object data;
@@ -393,6 +570,111 @@
 
   ModularName(this.kind, {this.data, this.set});
 
+  factory ModularName.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ModularNameKind kind = source.readEnum(ModularNameKind.values);
+    Object data;
+    Set<ClassEntity> set;
+    switch (kind) {
+      case ModularNameKind.rtiField:
+        break;
+      case ModularNameKind.globalPropertyNameForType:
+      case ModularNameKind.runtimeTypeName:
+        bool dataIsClassEntity = source.readBool();
+        if (dataIsClassEntity) {
+          data = source.readClass();
+        } else {
+          data = source.readTypedef();
+        }
+        break;
+      case ModularNameKind.className:
+      case ModularNameKind.operatorIs:
+      case ModularNameKind.substitution:
+      case ModularNameKind.globalPropertyNameForClass:
+        data = source.readClass();
+        break;
+      case ModularNameKind.aliasedSuperMember:
+      case ModularNameKind.staticClosure:
+      case ModularNameKind.methodProperty:
+      case ModularNameKind.instanceField:
+      case ModularNameKind.instanceMethod:
+      case ModularNameKind.lazyInitializer:
+      case ModularNameKind.globalPropertyNameForMember:
+        data = source.readMember();
+        break;
+      case ModularNameKind.operatorIsType:
+        data = source.readDartType();
+        break;
+      case ModularNameKind.invocation:
+        data = Selector.readFromDataSource(source);
+        break;
+      case ModularNameKind.nameForGetInterceptor:
+        set = source.readClasses().toSet();
+        break;
+      case ModularNameKind.nameForGetOneShotInterceptor:
+        data = Selector.readFromDataSource(source);
+        set = source.readClasses().toSet();
+        break;
+      case ModularNameKind.asName:
+        data = source.readString();
+        break;
+    }
+    source.end(tag);
+    return new ModularName(kind, data: data, set: set);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeEnum(kind);
+    switch (kind) {
+      case ModularNameKind.rtiField:
+        break;
+      case ModularNameKind.globalPropertyNameForType:
+      case ModularNameKind.runtimeTypeName:
+        sink.writeBool(data is ClassEntity);
+        if (data is ClassEntity) {
+          sink.writeClass(data);
+        } else {
+          sink.writeTypedef(data);
+        }
+        break;
+      case ModularNameKind.className:
+      case ModularNameKind.operatorIs:
+      case ModularNameKind.substitution:
+      case ModularNameKind.globalPropertyNameForClass:
+        sink.writeClass(data);
+        break;
+      case ModularNameKind.aliasedSuperMember:
+      case ModularNameKind.staticClosure:
+      case ModularNameKind.methodProperty:
+      case ModularNameKind.instanceField:
+      case ModularNameKind.instanceMethod:
+      case ModularNameKind.lazyInitializer:
+      case ModularNameKind.globalPropertyNameForMember:
+        sink.writeMember(data);
+        break;
+      case ModularNameKind.operatorIsType:
+        sink.writeDartType(data);
+        break;
+      case ModularNameKind.invocation:
+        Selector selector = data;
+        selector.writeToDataSink(sink);
+        break;
+      case ModularNameKind.nameForGetInterceptor:
+        sink.writeClasses(set);
+        break;
+      case ModularNameKind.nameForGetOneShotInterceptor:
+        Selector selector = data;
+        selector.writeToDataSink(sink);
+        sink.writeClasses(set);
+        break;
+      case ModularNameKind.asName:
+        sink.writeString(data);
+        break;
+    }
+    sink.end(tag);
+  }
+
   js.Name get value {
     assert(_value != null);
     return _value;
@@ -448,7 +730,7 @@
   }
 
   @override
-  String toString() => 'ModularName(kind=$kind,data=$data,value=$value)';
+  String toString() => 'ModularName(kind=$kind,data=$data,value=${value?.key})';
 }
 
 enum ModularExpressionKind {
@@ -462,12 +744,78 @@
 
 class ModularExpression extends js.DeferredExpression
     implements js.AstContainer {
+  static const String tag = 'modular-expression';
+
   final ModularExpressionKind kind;
   final Object data;
   js.Expression _value;
 
   ModularExpression(this.kind, this.data);
 
+  factory ModularExpression.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ModularExpressionKind kind = source.readEnum(ModularExpressionKind.values);
+    Object data;
+    switch (kind) {
+      case ModularExpressionKind.globalObjectForLibrary:
+        data = source.readLibrary();
+        break;
+      case ModularExpressionKind.globalObjectForClass:
+        data = source.readClass();
+        break;
+      case ModularExpressionKind.globalObjectForType:
+        bool dataIsClassEntity = source.readBool();
+        if (dataIsClassEntity) {
+          data = source.readClass();
+        } else {
+          data = source.readTypedef();
+        }
+        break;
+      case ModularExpressionKind.globalObjectForMember:
+        data = source.readMember();
+        break;
+      case ModularExpressionKind.constant:
+        data = source.readConstant();
+        break;
+      case ModularExpressionKind.embeddedGlobalAccess:
+        data = source.readString();
+        break;
+    }
+    source.end(tag);
+    return new ModularExpression(kind, data);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeEnum(kind);
+    switch (kind) {
+      case ModularExpressionKind.globalObjectForLibrary:
+        sink.writeLibrary(data);
+        break;
+      case ModularExpressionKind.globalObjectForClass:
+        sink.writeClass(data);
+        break;
+      case ModularExpressionKind.globalObjectForType:
+        sink.writeBool(data is ClassEntity);
+        if (data is ClassEntity) {
+          sink.writeClass(data);
+        } else {
+          sink.writeTypedef(data);
+        }
+        break;
+      case ModularExpressionKind.globalObjectForMember:
+        sink.writeMember(data);
+        break;
+      case ModularExpressionKind.constant:
+        sink.writeConstant(data);
+        break;
+      case ModularExpressionKind.embeddedGlobalAccess:
+        sink.writeString(data);
+        break;
+    }
+    sink.end(tag);
+  }
+
   @override
   js.Expression get value {
     assert(_value != null);
@@ -514,3 +862,1208 @@
     return sb.toString();
   }
 }
+
+enum JsNodeKind {
+  comment,
+  await,
+  regExpLiteral,
+  property,
+  objectInitializer,
+  arrayHole,
+  arrayInitializer,
+  parentheses,
+  modularName,
+  asyncName,
+  stringBackedName,
+  stringConcatenation,
+  literalNull,
+  literalNumber,
+  literalString,
+  literalStringFromName,
+  literalBool,
+  modularExpression,
+  function,
+  namedFunction,
+  access,
+  parameter,
+  variableDeclaration,
+  thisExpression,
+  variableUse,
+  postfix,
+  prefix,
+  binary,
+  callExpression,
+  newExpression,
+  conditional,
+  variableInitialization,
+  assignment,
+  variableDeclarationList,
+  literalExpression,
+  dartYield,
+  literalStatement,
+  labeledStatement,
+  functionDeclaration,
+  switchDefault,
+  switchCase,
+  switchStatement,
+  catchClause,
+  tryStatement,
+  throwStatement,
+  returnStatement,
+  breakStatement,
+  continueStatement,
+  doStatement,
+  whileStatement,
+  forInStatement,
+  forStatement,
+  ifStatement,
+  emptyStatement,
+  expressionStatement,
+  block,
+  program,
+}
+
+/// Tags used for debugging serialization/deserialization boundary mismatches.
+class JsNodeTags {
+  static const String tag = 'js-node';
+  static const String comment = 'js-comment';
+  static const String await = 'js-await';
+  static const String regExpLiteral = 'js-regExpLiteral';
+  static const String property = 'js-property';
+  static const String objectInitializer = 'js-objectInitializer';
+  static const String arrayHole = 'js-arrayHole';
+  static const String arrayInitializer = 'js-arrayInitializer';
+  static const String parentheses = 'js-parentheses';
+  static const String modularName = 'js-modularName';
+  static const String asyncName = 'js-asyncName';
+  static const String stringBackedName = 'js-stringBackedName';
+  static const String stringConcatenation = 'js-stringConcatenation';
+  static const String literalNull = 'js-literalNull';
+  static const String literalNumber = 'js-literalNumber';
+  static const String literalString = 'js-literalString';
+  static const String literalStringFromName = 'js-literalStringFromName';
+  static const String literalBool = 'js-literalBool';
+  static const String modularExpression = 'js-modularExpression';
+  static const String function = 'js-function';
+  static const String namedFunction = 'js-namedFunction';
+  static const String access = 'js-access';
+  static const String parameter = 'js-parameter';
+  static const String variableDeclaration = 'js-variableDeclaration';
+  static const String thisExpression = 'js-thisExpression';
+  static const String variableUse = 'js-variableUse';
+  static const String postfix = 'js-postfix';
+  static const String prefix = 'js-prefix';
+  static const String binary = 'js-binary';
+  static const String callExpression = 'js-callExpression';
+  static const String newExpression = 'js-newExpression';
+  static const String conditional = 'js-conditional';
+  static const String variableInitialization = 'js-variableInitialization';
+  static const String assignment = 'js-assignment';
+  static const String variableDeclarationList = 'js-variableDeclarationList';
+  static const String literalExpression = 'js-literalExpression';
+  static const String dartYield = 'js-dartYield';
+  static const String literalStatement = 'js-literalStatement';
+  static const String labeledStatement = 'js-labeledStatement';
+  static const String functionDeclaration = 'js-functionDeclaration';
+  static const String switchDefault = 'js-switchDefault';
+  static const String switchCase = 'js-switchCase';
+  static const String switchStatement = 'js-switchStatement';
+  static const String catchClause = 'js-catchClause';
+  static const String tryStatement = 'js-tryStatement';
+  static const String throwStatement = 'js-throwStatement';
+  static const String returnStatement = 'js-returnStatement';
+  static const String breakStatement = 'js-breakStatement';
+  static const String continueStatement = 'js-continueStatement';
+  static const String doStatement = 'js-doStatement';
+  static const String whileStatement = 'js-whileStatement';
+  static const String forInStatement = 'js-forInStatement';
+  static const String forStatement = 'js-forStatement';
+  static const String ifStatement = 'js-ifStatement';
+  static const String emptyStatement = 'js-emptyStatement';
+  static const String expressionStatement = 'js-expressionStatement';
+  static const String block = 'js-block';
+  static const String program = 'js-program';
+}
+
+/// Visitor that serializes a [js.Node] into a [DataSink].
+class JsNodeSerializer implements js.NodeVisitor<void> {
+  final DataSink sink;
+
+  JsNodeSerializer._(this.sink);
+
+  static void writeToDataSink(DataSink sink, js.Node node) {
+    sink.begin(JsNodeTags.tag);
+    JsNodeSerializer serializer = new JsNodeSerializer._(sink);
+    serializer.visit(node);
+    sink.end(JsNodeTags.tag);
+  }
+
+  void visit(js.Node node, {bool allowNull: false}) {
+    if (allowNull) {
+      sink.writeBool(node != null);
+      if (node != null) {
+        node.accept(this);
+      }
+    } else {
+      node.accept(this);
+    }
+  }
+
+  void visitList(Iterable<js.Node> nodes) {
+    sink.writeList(nodes, visit);
+  }
+
+  void _writeInfo(js.Node node) {
+    SourceInformation.writeToDataSink(sink, node.sourceInformation);
+  }
+
+  @override
+  void visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedDeclaration');
+  }
+
+  @override
+  void visitInterpolatedStatement(js.InterpolatedStatement node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedStatement');
+  }
+
+  @override
+  void visitInterpolatedSelector(js.InterpolatedSelector node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedDeclaration');
+  }
+
+  @override
+  void visitInterpolatedParameter(js.InterpolatedParameter node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedParameter');
+  }
+
+  @override
+  void visitInterpolatedLiteral(js.InterpolatedLiteral node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedLiteral');
+  }
+
+  @override
+  void visitInterpolatedExpression(js.InterpolatedExpression node) {
+    throw new UnsupportedError('JsNodeSerializer.visitInterpolatedExpression');
+  }
+
+  @override
+  void visitComment(js.Comment node) {
+    sink.writeEnum(JsNodeKind.comment);
+    sink.begin(JsNodeTags.comment);
+    sink.writeString(node.comment);
+    sink.end(JsNodeTags.comment);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitAwait(js.Await node) {
+    sink.writeEnum(JsNodeKind.await);
+    sink.begin(JsNodeTags.await);
+    visit(node.expression);
+    sink.end(JsNodeTags.await);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitRegExpLiteral(js.RegExpLiteral node) {
+    sink.writeEnum(JsNodeKind.regExpLiteral);
+    sink.begin(JsNodeTags.regExpLiteral);
+    sink.writeString(node.pattern);
+    sink.end(JsNodeTags.regExpLiteral);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitProperty(js.Property node) {
+    sink.writeEnum(JsNodeKind.property);
+    sink.begin(JsNodeTags.property);
+    visit(node.name);
+    visit(node.value);
+    sink.end(JsNodeTags.property);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitObjectInitializer(js.ObjectInitializer node) {
+    sink.writeEnum(JsNodeKind.objectInitializer);
+    sink.begin(JsNodeTags.objectInitializer);
+    visitList(node.properties);
+    sink.writeBool(node.isOneLiner);
+    sink.end(JsNodeTags.objectInitializer);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitArrayHole(js.ArrayHole node) {
+    sink.writeEnum(JsNodeKind.arrayHole);
+    sink.begin(JsNodeTags.arrayHole);
+    sink.end(JsNodeTags.arrayHole);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitArrayInitializer(js.ArrayInitializer node) {
+    sink.writeEnum(JsNodeKind.arrayInitializer);
+    sink.begin(JsNodeTags.arrayInitializer);
+    visitList(node.elements);
+    sink.end(JsNodeTags.arrayInitializer);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitParentheses(js.Parentheses node) {
+    sink.writeEnum(JsNodeKind.parentheses);
+    sink.begin(JsNodeTags.parentheses);
+    visit(node.enclosed);
+    sink.end(JsNodeTags.parentheses);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitName(js.Name node) {
+    if (node is ModularName) {
+      sink.writeEnum(JsNodeKind.modularName);
+      sink.begin(JsNodeTags.modularName);
+      node.writeToDataSink(sink);
+      sink.end(JsNodeTags.modularName);
+      _writeInfo(node);
+    } else if (node is AsyncName) {
+      sink.writeEnum(JsNodeKind.asyncName);
+      sink.begin(JsNodeTags.asyncName);
+      visit(node.prefix);
+      visit(node.base);
+      sink.end(JsNodeTags.asyncName);
+      _writeInfo(node);
+    } else if (node is StringBackedName) {
+      sink.writeEnum(JsNodeKind.stringBackedName);
+      sink.begin(JsNodeTags.stringBackedName);
+      sink.writeString(node.name);
+      sink.end(JsNodeTags.stringBackedName);
+      _writeInfo(node);
+    } else {
+      throw new UnsupportedError(
+          'Unexpected deferred expression: ${node.runtimeType}.');
+    }
+  }
+
+  @override
+  void visitStringConcatenation(js.StringConcatenation node) {
+    sink.writeEnum(JsNodeKind.stringConcatenation);
+    sink.begin(JsNodeTags.stringConcatenation);
+    visitList(node.parts);
+    sink.end(JsNodeTags.stringConcatenation);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralNull(js.LiteralNull node) {
+    sink.writeEnum(JsNodeKind.literalNull);
+    sink.begin(JsNodeTags.literalNull);
+    sink.end(JsNodeTags.literalNull);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralNumber(js.LiteralNumber node) {
+    sink.writeEnum(JsNodeKind.literalNumber);
+    sink.begin(JsNodeTags.literalNumber);
+    sink.writeString(node.value);
+    sink.end(JsNodeTags.literalNumber);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralString(js.LiteralString node) {
+    if (node is js.LiteralStringFromName) {
+      sink.writeEnum(JsNodeKind.literalStringFromName);
+      sink.begin(JsNodeTags.literalStringFromName);
+      visit(node.name);
+      sink.end(JsNodeTags.literalStringFromName);
+    } else {
+      sink.writeEnum(JsNodeKind.literalString);
+      sink.begin(JsNodeTags.literalString);
+      sink.writeString(node.value);
+      sink.end(JsNodeTags.literalString);
+    }
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralBool(js.LiteralBool node) {
+    sink.writeEnum(JsNodeKind.literalBool);
+    sink.begin(JsNodeTags.literalBool);
+    sink.writeBool(node.value);
+    sink.end(JsNodeTags.literalBool);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitDeferredString(js.DeferredString node) {
+    throw new UnsupportedError('JsNodeSerializer.visitDeferredString');
+  }
+
+  @override
+  void visitDeferredNumber(js.DeferredNumber node) {
+    throw new UnsupportedError('JsNodeSerializer.visitDeferredNumber');
+  }
+
+  @override
+  void visitDeferredExpression(js.DeferredExpression node) {
+    if (node is ModularExpression) {
+      sink.writeEnum(JsNodeKind.modularExpression);
+      sink.begin(JsNodeTags.modularExpression);
+      node.writeToDataSink(sink);
+      sink.end(JsNodeTags.modularExpression);
+      _writeInfo(node);
+    } else {
+      throw new UnsupportedError(
+          'Unexpected deferred expression: ${node.runtimeType}.');
+    }
+  }
+
+  @override
+  void visitFun(js.Fun node) {
+    sink.writeEnum(JsNodeKind.function);
+    sink.begin(JsNodeTags.function);
+    visitList(node.params);
+    visit(node.body);
+    sink.writeEnum(node.asyncModifier);
+    sink.end(JsNodeTags.function);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitNamedFunction(js.NamedFunction node) {
+    sink.writeEnum(JsNodeKind.namedFunction);
+    sink.begin(JsNodeTags.namedFunction);
+    visit(node.name);
+    visit(node.function);
+    sink.end(JsNodeTags.namedFunction);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitAccess(js.PropertyAccess node) {
+    sink.writeEnum(JsNodeKind.access);
+    sink.begin(JsNodeTags.access);
+    visit(node.receiver);
+    visit(node.selector);
+    sink.end(JsNodeTags.access);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitParameter(js.Parameter node) {
+    sink.writeEnum(JsNodeKind.parameter);
+    sink.begin(JsNodeTags.parameter);
+    sink.writeString(node.name);
+    sink.end(JsNodeTags.parameter);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitVariableDeclaration(js.VariableDeclaration node) {
+    sink.writeEnum(JsNodeKind.variableDeclaration);
+    sink.begin(JsNodeTags.variableDeclaration);
+    sink.writeString(node.name);
+    sink.writeBool(node.allowRename);
+    sink.end(JsNodeTags.variableDeclaration);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitThis(js.This node) {
+    sink.writeEnum(JsNodeKind.thisExpression);
+    sink.begin(JsNodeTags.thisExpression);
+    sink.end(JsNodeTags.thisExpression);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitVariableUse(js.VariableUse node) {
+    sink.writeEnum(JsNodeKind.variableUse);
+    sink.begin(JsNodeTags.variableUse);
+    sink.writeString(node.name);
+    sink.end(JsNodeTags.variableUse);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitPostfix(js.Postfix node) {
+    sink.writeEnum(JsNodeKind.postfix);
+    sink.begin(JsNodeTags.postfix);
+    sink.writeString(node.op);
+    visit(node.argument);
+    sink.end(JsNodeTags.postfix);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitPrefix(js.Prefix node) {
+    sink.writeEnum(JsNodeKind.prefix);
+    sink.begin(JsNodeTags.prefix);
+    sink.writeString(node.op);
+    visit(node.argument);
+    sink.end(JsNodeTags.prefix);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitBinary(js.Binary node) {
+    sink.writeEnum(JsNodeKind.binary);
+    sink.begin(JsNodeTags.binary);
+    sink.writeString(node.op);
+    visit(node.left);
+    visit(node.right);
+    sink.end(JsNodeTags.binary);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitCall(js.Call node) {
+    sink.writeEnum(JsNodeKind.callExpression);
+    sink.begin(JsNodeTags.callExpression);
+    visit(node.target);
+    visitList(node.arguments);
+    sink.end(JsNodeTags.callExpression);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitNew(js.New node) {
+    sink.writeEnum(JsNodeKind.newExpression);
+    sink.begin(JsNodeTags.newExpression);
+    visit(node.target);
+    visitList(node.arguments);
+    sink.end(JsNodeTags.newExpression);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitConditional(js.Conditional node) {
+    sink.writeEnum(JsNodeKind.conditional);
+    sink.begin(JsNodeTags.conditional);
+    visit(node.condition);
+    visit(node.then);
+    visit(node.otherwise);
+    sink.end(JsNodeTags.conditional);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitVariableInitialization(js.VariableInitialization node) {
+    sink.writeEnum(JsNodeKind.variableInitialization);
+    sink.begin(JsNodeTags.variableInitialization);
+    visit(node.declaration);
+    visit(node.value, allowNull: true);
+    sink.end(JsNodeTags.variableInitialization);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitAssignment(js.Assignment node) {
+    sink.writeEnum(JsNodeKind.assignment);
+    sink.begin(JsNodeTags.assignment);
+    visit(node.leftHandSide);
+    sink.writeStringOrNull(node.op);
+    visit(node.value);
+    sink.end(JsNodeTags.assignment);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitVariableDeclarationList(js.VariableDeclarationList node) {
+    sink.writeEnum(JsNodeKind.variableDeclarationList);
+    sink.begin(JsNodeTags.variableDeclarationList);
+    visitList(node.declarations);
+    sink.writeBool(node.indentSplits);
+    sink.end(JsNodeTags.variableDeclarationList);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralExpression(js.LiteralExpression node) {
+    sink.writeEnum(JsNodeKind.literalExpression);
+    sink.begin(JsNodeTags.literalExpression);
+    sink.writeString(node.template);
+    visitList(node.inputs);
+    sink.end(JsNodeTags.literalExpression);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitDartYield(js.DartYield node) {
+    sink.writeEnum(JsNodeKind.dartYield);
+    sink.begin(JsNodeTags.dartYield);
+    visit(node.expression);
+    sink.writeBool(node.hasStar);
+    sink.end(JsNodeTags.dartYield);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLiteralStatement(js.LiteralStatement node) {
+    sink.writeEnum(JsNodeKind.literalStatement);
+    sink.begin(JsNodeTags.literalStatement);
+    sink.writeString(node.code);
+    sink.end(JsNodeTags.literalStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitLabeledStatement(js.LabeledStatement node) {
+    sink.writeEnum(JsNodeKind.labeledStatement);
+    sink.begin(JsNodeTags.labeledStatement);
+    sink.writeString(node.label);
+    visit(node.body);
+    sink.end(JsNodeTags.labeledStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitFunctionDeclaration(js.FunctionDeclaration node) {
+    sink.writeEnum(JsNodeKind.functionDeclaration);
+    sink.begin(JsNodeTags.functionDeclaration);
+    visit(node.name);
+    visit(node.function);
+    sink.end(JsNodeTags.functionDeclaration);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitDefault(js.Default node) {
+    sink.writeEnum(JsNodeKind.switchDefault);
+    sink.begin(JsNodeTags.switchDefault);
+    visit(node.body);
+    sink.end(JsNodeTags.switchDefault);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitCase(js.Case node) {
+    sink.writeEnum(JsNodeKind.switchCase);
+    sink.begin(JsNodeTags.switchCase);
+    visit(node.expression);
+    visit(node.body);
+    sink.end(JsNodeTags.switchCase);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitSwitch(js.Switch node) {
+    sink.writeEnum(JsNodeKind.switchStatement);
+    sink.begin(JsNodeTags.switchStatement);
+    visit(node.key);
+    visitList(node.cases);
+    sink.end(JsNodeTags.switchStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitCatch(js.Catch node) {
+    sink.writeEnum(JsNodeKind.catchClause);
+    sink.begin(JsNodeTags.catchClause);
+    visit(node.declaration);
+    visit(node.body);
+    sink.end(JsNodeTags.catchClause);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitTry(js.Try node) {
+    sink.writeEnum(JsNodeKind.tryStatement);
+    sink.begin(JsNodeTags.tryStatement);
+    visit(node.body);
+    visit(node.catchPart, allowNull: true);
+    visit(node.finallyPart, allowNull: true);
+    sink.end(JsNodeTags.tryStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitThrow(js.Throw node) {
+    sink.writeEnum(JsNodeKind.throwStatement);
+    sink.begin(JsNodeTags.throwStatement);
+    visit(node.expression);
+    sink.end(JsNodeTags.throwStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitReturn(js.Return node) {
+    sink.writeEnum(JsNodeKind.returnStatement);
+    sink.begin(JsNodeTags.returnStatement);
+    visit(node.value, allowNull: true);
+    sink.end(JsNodeTags.returnStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitBreak(js.Break node) {
+    sink.writeEnum(JsNodeKind.breakStatement);
+    sink.begin(JsNodeTags.breakStatement);
+    sink.writeStringOrNull(node.targetLabel);
+    sink.end(JsNodeTags.breakStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitContinue(js.Continue node) {
+    sink.writeEnum(JsNodeKind.continueStatement);
+    sink.begin(JsNodeTags.continueStatement);
+    sink.writeStringOrNull(node.targetLabel);
+    sink.end(JsNodeTags.continueStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitDo(js.Do node) {
+    sink.writeEnum(JsNodeKind.doStatement);
+    sink.begin(JsNodeTags.doStatement);
+    visit(node.body);
+    visit(node.condition);
+    sink.end(JsNodeTags.doStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitWhile(js.While node) {
+    sink.writeEnum(JsNodeKind.whileStatement);
+    sink.begin(JsNodeTags.whileStatement);
+    visit(node.condition);
+    visit(node.body);
+    sink.end(JsNodeTags.whileStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitForIn(js.ForIn node) {
+    sink.writeEnum(JsNodeKind.forInStatement);
+    sink.begin(JsNodeTags.forInStatement);
+    visit(node.leftHandSide);
+    visit(node.object);
+    visit(node.body);
+    sink.end(JsNodeTags.forInStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitFor(js.For node) {
+    sink.writeEnum(JsNodeKind.forStatement);
+    sink.begin(JsNodeTags.forStatement);
+    visit(node.init, allowNull: true);
+    visit(node.condition, allowNull: true);
+    visit(node.update, allowNull: true);
+    visit(node.body);
+    sink.end(JsNodeTags.forStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitIf(js.If node) {
+    sink.writeEnum(JsNodeKind.ifStatement);
+    sink.begin(JsNodeTags.ifStatement);
+    visit(node.condition);
+    visit(node.then);
+    visit(node.otherwise);
+    sink.end(JsNodeTags.ifStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitEmptyStatement(js.EmptyStatement node) {
+    sink.writeEnum(JsNodeKind.emptyStatement);
+    sink.begin(JsNodeTags.emptyStatement);
+    sink.end(JsNodeTags.emptyStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitExpressionStatement(js.ExpressionStatement node) {
+    sink.writeEnum(JsNodeKind.expressionStatement);
+    sink.begin(JsNodeTags.expressionStatement);
+    visit(node.expression);
+    sink.end(JsNodeTags.expressionStatement);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitBlock(js.Block node) {
+    sink.writeEnum(JsNodeKind.block);
+    sink.begin(JsNodeTags.block);
+    visitList(node.statements);
+    sink.end(JsNodeTags.block);
+    _writeInfo(node);
+  }
+
+  @override
+  void visitProgram(js.Program node) {
+    sink.writeEnum(JsNodeKind.program);
+    sink.begin(JsNodeTags.program);
+    visitList(node.body);
+    sink.end(JsNodeTags.program);
+    _writeInfo(node);
+  }
+}
+
+/// Helper class that deserializes a [js.Node] from [DataSource].
+///
+/// Deserialized [ModularName]s and [ModularExpression]s are collected in the
+/// [modularNames] and [modularExpressions] lists.
+class JsNodeDeserializer {
+  final DataSource source;
+  final List<ModularName> modularNames;
+  final List<ModularExpression> modularExpressions;
+
+  JsNodeDeserializer._(this.source, this.modularNames, this.modularExpressions);
+
+  static js.Node readFromDataSource(
+      DataSource source,
+      List<ModularName> modularNames,
+      List<ModularExpression> modularExpressions) {
+    source.begin(JsNodeTags.tag);
+    JsNodeDeserializer deserializer =
+        new JsNodeDeserializer._(source, modularNames, modularExpressions);
+    js.Node node = deserializer.read();
+    source.end(JsNodeTags.tag);
+    return node;
+  }
+
+  T read<T extends js.Node>({bool allowNull: false}) {
+    if (allowNull) {
+      bool hasValue = source.readBool();
+      if (!hasValue) return null;
+    }
+    JsNodeKind kind = source.readEnum(JsNodeKind.values);
+    js.Node node;
+    switch (kind) {
+      case JsNodeKind.comment:
+        source.begin(JsNodeTags.comment);
+        node = new js.Comment(source.readString());
+        source.end(JsNodeTags.comment);
+        break;
+      case JsNodeKind.await:
+        source.begin(JsNodeTags.await);
+        node = new js.Await(read());
+        source.end(JsNodeTags.await);
+        break;
+      case JsNodeKind.regExpLiteral:
+        source.begin(JsNodeTags.regExpLiteral);
+        node = new js.RegExpLiteral(source.readString());
+        source.end(JsNodeTags.regExpLiteral);
+        break;
+      case JsNodeKind.property:
+        source.begin(JsNodeTags.property);
+        js.Expression name = read();
+        js.Expression value = read();
+        node = new js.Property(name, value);
+        source.end(JsNodeTags.property);
+        break;
+      case JsNodeKind.objectInitializer:
+        source.begin(JsNodeTags.objectInitializer);
+        List<js.Property> properties = readList();
+        bool isOneLiner = source.readBool();
+        node = new js.ObjectInitializer(properties, isOneLiner: isOneLiner);
+        source.end(JsNodeTags.objectInitializer);
+        break;
+      case JsNodeKind.arrayHole:
+        source.begin(JsNodeTags.arrayHole);
+        node = new js.ArrayHole();
+        source.end(JsNodeTags.arrayHole);
+        break;
+      case JsNodeKind.arrayInitializer:
+        source.begin(JsNodeTags.arrayInitializer);
+        List<js.Expression> elements = readList();
+        node = new js.ArrayInitializer(elements);
+        source.end(JsNodeTags.arrayInitializer);
+        break;
+      case JsNodeKind.parentheses:
+        source.begin(JsNodeTags.parentheses);
+        node = new js.Parentheses(read());
+        source.end(JsNodeTags.parentheses);
+        break;
+      case JsNodeKind.modularName:
+        source.begin(JsNodeTags.modularName);
+        ModularName modularName = ModularName.readFromDataSource(source);
+        modularNames.add(modularName);
+        node = modularName;
+        source.end(JsNodeTags.modularName);
+        break;
+      case JsNodeKind.asyncName:
+        source.begin(JsNodeTags.asyncName);
+        js.Name prefix = read();
+        js.Name base = read();
+        node = new AsyncName(prefix, base);
+        source.end(JsNodeTags.asyncName);
+        break;
+      case JsNodeKind.stringBackedName:
+        source.begin(JsNodeTags.stringBackedName);
+        node = new StringBackedName(source.readString());
+        source.end(JsNodeTags.stringBackedName);
+        break;
+      case JsNodeKind.stringConcatenation:
+        source.begin(JsNodeTags.stringConcatenation);
+        List<js.Literal> parts = readList();
+        node = new js.StringConcatenation(parts);
+        source.end(JsNodeTags.stringConcatenation);
+        break;
+      case JsNodeKind.literalNull:
+        source.begin(JsNodeTags.literalNull);
+        node = new js.LiteralNull();
+        source.end(JsNodeTags.literalNull);
+        break;
+      case JsNodeKind.literalNumber:
+        source.begin(JsNodeTags.literalNumber);
+        node = new js.LiteralNumber(source.readString());
+        source.end(JsNodeTags.literalNumber);
+        break;
+      case JsNodeKind.literalString:
+        source.begin(JsNodeTags.literalString);
+        node = new js.LiteralString(source.readString());
+        source.end(JsNodeTags.literalString);
+        break;
+      case JsNodeKind.literalStringFromName:
+        source.begin(JsNodeTags.literalStringFromName);
+        js.Name name = read();
+        node = new js.LiteralStringFromName(name);
+        source.end(JsNodeTags.literalStringFromName);
+        break;
+      case JsNodeKind.literalBool:
+        source.begin(JsNodeTags.literalBool);
+        node = new js.LiteralBool(source.readBool());
+        source.end(JsNodeTags.literalBool);
+        break;
+      case JsNodeKind.modularExpression:
+        source.begin(JsNodeTags.modularExpression);
+        ModularExpression modularExpression =
+            ModularExpression.readFromDataSource(source);
+        modularExpressions.add(modularExpression);
+        node = modularExpression;
+        source.end(JsNodeTags.modularExpression);
+        break;
+      case JsNodeKind.function:
+        source.begin(JsNodeTags.function);
+        List<js.Parameter> params = readList();
+        js.Block body = read();
+        js.AsyncModifier asyncModifier =
+            source.readEnum(js.AsyncModifier.values);
+        node = new js.Fun(params, body, asyncModifier: asyncModifier);
+        source.end(JsNodeTags.function);
+        break;
+      case JsNodeKind.namedFunction:
+        source.begin(JsNodeTags.namedFunction);
+        js.Declaration name = read();
+        js.Fun function = read();
+        node = new js.NamedFunction(name, function);
+        source.end(JsNodeTags.namedFunction);
+        break;
+      case JsNodeKind.access:
+        source.begin(JsNodeTags.access);
+        js.Expression receiver = read();
+        js.Expression selector = read();
+        node = new js.PropertyAccess(receiver, selector);
+        source.end(JsNodeTags.access);
+        break;
+      case JsNodeKind.parameter:
+        source.begin(JsNodeTags.parameter);
+        node = new js.Parameter(source.readString());
+        source.end(JsNodeTags.parameter);
+        break;
+      case JsNodeKind.variableDeclaration:
+        source.begin(JsNodeTags.variableDeclaration);
+        String name = source.readString();
+        bool allowRename = source.readBool();
+        node = new js.VariableDeclaration(name, allowRename: allowRename);
+        source.end(JsNodeTags.variableDeclaration);
+        break;
+      case JsNodeKind.thisExpression:
+        source.begin(JsNodeTags.thisExpression);
+        node = new js.This();
+        source.end(JsNodeTags.thisExpression);
+        break;
+      case JsNodeKind.variableUse:
+        source.begin(JsNodeTags.variableUse);
+        node = new js.VariableUse(source.readString());
+        source.end(JsNodeTags.variableUse);
+        break;
+      case JsNodeKind.postfix:
+        source.begin(JsNodeTags.postfix);
+        String op = source.readString();
+        js.Expression argument = read();
+        node = new js.Postfix(op, argument);
+        source.end(JsNodeTags.postfix);
+        break;
+      case JsNodeKind.prefix:
+        source.begin(JsNodeTags.prefix);
+        String op = source.readString();
+        js.Expression argument = read();
+        node = new js.Prefix(op, argument);
+        source.end(JsNodeTags.prefix);
+        break;
+      case JsNodeKind.binary:
+        source.begin(JsNodeTags.binary);
+        String op = source.readString();
+        js.Expression left = read();
+        js.Expression right = read();
+        node = new js.Binary(op, left, right);
+        source.end(JsNodeTags.binary);
+        break;
+      case JsNodeKind.callExpression:
+        source.begin(JsNodeTags.callExpression);
+        js.Expression target = read();
+        List<js.Expression> arguments = readList();
+        node = new js.Call(target, arguments);
+        source.end(JsNodeTags.callExpression);
+        break;
+      case JsNodeKind.newExpression:
+        source.begin(JsNodeTags.newExpression);
+        js.Expression cls = read();
+        List<js.Expression> arguments = readList();
+        node = new js.New(cls, arguments);
+        source.end(JsNodeTags.newExpression);
+        break;
+      case JsNodeKind.conditional:
+        source.begin(JsNodeTags.conditional);
+        js.Expression condition = read();
+        js.Expression then = read();
+        js.Expression otherwise = read();
+        node = new js.Conditional(condition, then, otherwise);
+        source.end(JsNodeTags.conditional);
+        break;
+      case JsNodeKind.variableInitialization:
+        source.begin(JsNodeTags.variableInitialization);
+        js.Declaration declaration = read();
+        js.Expression value = source.readValueOrNull(read);
+        node = new js.VariableInitialization(declaration, value);
+        source.end(JsNodeTags.variableInitialization);
+        break;
+      case JsNodeKind.assignment:
+        source.begin(JsNodeTags.assignment);
+        js.Expression leftHandSide = read();
+        String op = source.readStringOrNull();
+        js.Expression value = read();
+        node = new js.Assignment.compound(leftHandSide, op, value);
+        source.end(JsNodeTags.assignment);
+        break;
+      case JsNodeKind.variableDeclarationList:
+        source.begin(JsNodeTags.variableDeclarationList);
+        List<js.VariableInitialization> declarations = readList();
+        bool indentSplits = source.readBool();
+        node = new js.VariableDeclarationList(declarations,
+            indentSplits: indentSplits);
+        source.end(JsNodeTags.variableDeclarationList);
+        break;
+      case JsNodeKind.literalExpression:
+        source.begin(JsNodeTags.literalExpression);
+        String template = source.readString();
+        List<js.Expression> inputs = readList();
+        node = new js.LiteralExpression.withData(template, inputs);
+        source.end(JsNodeTags.literalExpression);
+        break;
+      case JsNodeKind.dartYield:
+        source.begin(JsNodeTags.dartYield);
+        js.Expression expression = read();
+        bool hasStar = source.readBool();
+        node = new js.DartYield(expression, hasStar);
+        source.end(JsNodeTags.dartYield);
+        break;
+      case JsNodeKind.literalStatement:
+        source.begin(JsNodeTags.literalStatement);
+        node = new js.LiteralStatement(source.readString());
+        source.end(JsNodeTags.literalStatement);
+        break;
+      case JsNodeKind.labeledStatement:
+        source.begin(JsNodeTags.labeledStatement);
+        String label = source.readString();
+        js.Statement body = read();
+        node = new js.LabeledStatement(label, body);
+        source.end(JsNodeTags.labeledStatement);
+        break;
+      case JsNodeKind.functionDeclaration:
+        source.begin(JsNodeTags.functionDeclaration);
+        js.Declaration name = read();
+        js.Fun function = read();
+        node = new js.FunctionDeclaration(name, function);
+        source.end(JsNodeTags.functionDeclaration);
+        break;
+      case JsNodeKind.switchDefault:
+        source.begin(JsNodeTags.switchDefault);
+        js.Block body = read();
+        node = new js.Default(body);
+        source.end(JsNodeTags.switchDefault);
+        break;
+      case JsNodeKind.switchCase:
+        source.begin(JsNodeTags.switchCase);
+        js.Expression expression = read();
+        js.Block body = read();
+        node = new js.Case(expression, body);
+        source.end(JsNodeTags.switchCase);
+        break;
+      case JsNodeKind.switchStatement:
+        source.begin(JsNodeTags.switchStatement);
+        js.Expression key = read();
+        List<js.SwitchClause> cases = readList();
+        node = new js.Switch(key, cases);
+        source.end(JsNodeTags.switchStatement);
+        break;
+      case JsNodeKind.catchClause:
+        source.begin(JsNodeTags.catchClause);
+        js.Declaration declaration = read();
+        js.Block body = read();
+        node = new js.Catch(declaration, body);
+        source.end(JsNodeTags.catchClause);
+        break;
+      case JsNodeKind.tryStatement:
+        source.begin(JsNodeTags.tryStatement);
+        js.Block body = read();
+        js.Catch catchPart = source.readValueOrNull(read);
+        js.Block finallyPart = source.readValueOrNull(read);
+        node = new js.Try(body, catchPart, finallyPart);
+        source.end(JsNodeTags.tryStatement);
+        break;
+      case JsNodeKind.throwStatement:
+        source.begin(JsNodeTags.throwStatement);
+        js.Expression expression = read();
+        node = new js.Throw(expression);
+        source.end(JsNodeTags.throwStatement);
+        break;
+      case JsNodeKind.returnStatement:
+        source.begin(JsNodeTags.returnStatement);
+        js.Expression value = source.readValueOrNull(read);
+        node = new js.Return(value);
+        source.end(JsNodeTags.returnStatement);
+        break;
+      case JsNodeKind.breakStatement:
+        source.begin(JsNodeTags.breakStatement);
+        String targetLabel = source.readStringOrNull();
+        node = new js.Break(targetLabel);
+        source.end(JsNodeTags.breakStatement);
+        break;
+      case JsNodeKind.continueStatement:
+        source.begin(JsNodeTags.continueStatement);
+        String targetLabel = source.readStringOrNull();
+        node = new js.Continue(targetLabel);
+        source.end(JsNodeTags.continueStatement);
+        break;
+      case JsNodeKind.doStatement:
+        source.begin(JsNodeTags.doStatement);
+        js.Statement body = read();
+        js.Expression condition = read();
+        node = new js.Do(body, condition);
+        source.end(JsNodeTags.doStatement);
+        break;
+      case JsNodeKind.whileStatement:
+        source.begin(JsNodeTags.whileStatement);
+        js.Expression condition = read();
+        js.Statement body = read();
+        node = new js.While(condition, body);
+        source.end(JsNodeTags.whileStatement);
+        break;
+      case JsNodeKind.forInStatement:
+        source.begin(JsNodeTags.forInStatement);
+        js.Expression leftHandSide = read();
+        js.Expression object = read();
+        js.Statement body = read();
+        node = new js.ForIn(leftHandSide, object, body);
+        source.end(JsNodeTags.forInStatement);
+        break;
+      case JsNodeKind.forStatement:
+        source.begin(JsNodeTags.forStatement);
+        js.Expression init = read(allowNull: true);
+        js.Expression condition = read(allowNull: true);
+        js.Expression update = read(allowNull: true);
+        js.Statement body = read();
+        node = new js.For(init, condition, update, body);
+        source.end(JsNodeTags.forStatement);
+        break;
+      case JsNodeKind.ifStatement:
+        source.begin(JsNodeTags.ifStatement);
+        js.Expression condition = read();
+        js.Statement then = read();
+        js.Statement otherwise = read();
+        node = new js.If(condition, then, otherwise);
+        source.end(JsNodeTags.ifStatement);
+        break;
+      case JsNodeKind.emptyStatement:
+        source.begin(JsNodeTags.emptyStatement);
+        node = new js.EmptyStatement();
+        source.end(JsNodeTags.emptyStatement);
+        break;
+      case JsNodeKind.expressionStatement:
+        source.begin(JsNodeTags.expressionStatement);
+        node = new js.ExpressionStatement(read());
+        source.end(JsNodeTags.expressionStatement);
+        break;
+      case JsNodeKind.block:
+        source.begin(JsNodeTags.block);
+        List<js.Statement> statements = readList();
+        node = new js.Block(statements);
+        source.end(JsNodeTags.block);
+        break;
+      case JsNodeKind.program:
+        source.begin(JsNodeTags.program);
+        List<js.Statement> body = readList();
+        node = new js.Program(body);
+        source.end(JsNodeTags.program);
+        break;
+    }
+    SourceInformation sourceInformation =
+        SourceInformation.readFromDataSource(source);
+    if (sourceInformation != null) {
+      node = node.withSourceInformation(sourceInformation);
+    }
+    return node;
+  }
+
+  List<T> readList<T extends js.Node>({bool emptyAsNull: false}) {
+    return source.readList(read, emptyAsNull: emptyAsNull);
+  }
+}
+
+class CodegenReaderImpl implements CodegenReader {
+  final JClosedWorld closedWorld;
+  final List<ModularName> modularNames;
+  final List<ModularExpression> modularExpressions;
+
+  CodegenReaderImpl(
+      this.closedWorld, this.modularNames, this.modularExpressions);
+
+  @override
+  AbstractValue readAbstractValue(DataSource source) {
+    return closedWorld.abstractValueDomain
+        .readAbstractValueFromDataSource(source);
+  }
+
+  @override
+  js.Node readJsNode(DataSource source) {
+    return JsNodeDeserializer.readFromDataSource(
+        source, modularNames, modularExpressions);
+  }
+
+  @override
+  OutputUnit readOutputUnitReference(DataSource source) {
+    return closedWorld.outputUnitData.outputUnits[source.readInt()];
+  }
+}
+
+class CodegenWriterImpl implements CodegenWriter {
+  final JClosedWorld closedWorld;
+
+  CodegenWriterImpl(this.closedWorld);
+
+  @override
+  void writeAbstractValue(DataSink sink, AbstractValue value) {
+    closedWorld.abstractValueDomain.writeAbstractValueToDataSink(sink, value);
+  }
+
+  @override
+  void writeJsNode(DataSink sink, js.Node node) {
+    JsNodeSerializer.writeToDataSink(sink, node);
+  }
+
+  @override
+  void writeOutputUnitReference(DataSink sink, OutputUnit value) {
+    sink.writeInt(closedWorld.outputUnitData.outputUnits.indexOf(value));
+  }
+}
diff --git a/pkg/compiler/lib/src/constants/values.dart b/pkg/compiler/lib/src/constants/values.dart
index ffb7acf..c10956f 100644
--- a/pkg/compiler/lib/src/constants/values.dart
+++ b/pkg/compiler/lib/src/constants/values.dart
@@ -9,6 +9,8 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../deferred_load.dart' show OutputUnit;
+import '../inferrer/abstract_value_domain.dart';
+import '../js/js.dart' as js;
 import '../util/util.dart';
 
 enum ConstantValueKind {
@@ -24,7 +26,8 @@
   CONSTRUCTED,
   TYPE,
   INTERCEPTOR,
-  SYNTHETIC,
+  JS_NAME,
+  ABSTRACT_VALUE,
   INSTANTIATION,
   DEFERRED_GLOBAL,
   NON_CONSTANT,
@@ -47,7 +50,9 @@
   R visitType(covariant TypeConstantValue constant, covariant A arg);
   R visitInterceptor(
       covariant InterceptorConstantValue constant, covariant A arg);
-  R visitSynthetic(covariant SyntheticConstantValue constant, covariant A arg);
+  R visitAbstractValue(
+      covariant AbstractValueConstantValue constant, covariant A arg);
+  R visitJsName(covariant JsNameConstantValue constant, covariant A arg);
   R visitDeferredGlobal(
       covariant DeferredGlobalConstantValue constant, covariant A arg);
   R visitNonConstant(covariant NonConstantValue constant, covariant A arg);
@@ -808,42 +813,81 @@
   }
 }
 
-class SyntheticConstantValue extends ConstantValue {
-  final payload;
-  final valueKind;
+class JsNameConstantValue extends ConstantValue {
+  final js.LiteralString name;
 
-  SyntheticConstantValue(this.valueKind, this.payload);
+  JsNameConstantValue(this.name);
 
   @override
   bool get isDummy => true;
 
   @override
   bool operator ==(other) {
-    return other is SyntheticConstantValue && payload == other.payload;
+    return other is JsNameConstantValue && name == other.name;
   }
 
   @override
-  get hashCode => payload.hashCode * 17 + valueKind.hashCode;
+  get hashCode => name.hashCode * 17;
 
   @override
   List<ConstantValue> getDependencies() => const <ConstantValue>[];
 
   @override
   accept(ConstantValueVisitor visitor, arg) {
-    return visitor.visitSynthetic(this, arg);
+    return visitor.visitJsName(this, arg);
   }
 
   @override
   DartType getType(CommonElements types) => types.dynamicType;
 
   @override
-  ConstantValueKind get kind => ConstantValueKind.SYNTHETIC;
+  ConstantValueKind get kind => ConstantValueKind.JS_NAME;
 
   @override
-  String toDartText() => 'synthetic($valueKind, $payload)';
+  String toDartText() => 'js_name(${name})';
 
   @override
-  String toStructuredText() => 'SyntheticConstant($valueKind, $payload)';
+  String toStructuredText() => 'JsNameConstant(${name})';
+}
+
+/// An abstract value as a constant value. This is only used during code
+/// generation.
+class AbstractValueConstantValue extends ConstantValue {
+  final AbstractValue abstractValue;
+
+  AbstractValueConstantValue(this.abstractValue);
+
+  @override
+  bool get isDummy => true;
+
+  @override
+  bool operator ==(other) {
+    return other is AbstractValueConstantValue &&
+        abstractValue == other.abstractValue;
+  }
+
+  @override
+  get hashCode => abstractValue.hashCode * 17;
+
+  @override
+  List<ConstantValue> getDependencies() => const <ConstantValue>[];
+
+  @override
+  accept(ConstantValueVisitor visitor, arg) {
+    return visitor.visitAbstractValue(this, arg);
+  }
+
+  @override
+  DartType getType(CommonElements types) => types.dynamicType;
+
+  @override
+  ConstantValueKind get kind => ConstantValueKind.ABSTRACT_VALUE;
+
+  @override
+  String toDartText() => 'abstract_value($abstractValue)';
+
+  @override
+  String toStructuredText() => 'AbstractValueConstant($abstractValue)';
 }
 
 class ConstructedConstantValue extends ObjectConstantValue {
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index dd23b37..5c32df6 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -1235,7 +1235,7 @@
   final Map<MemberEntity, OutputUnit> _memberToUnit;
   final Map<Local, OutputUnit> _localFunctionToUnit;
   final Map<ConstantValue, OutputUnit> _constantToUnit;
-  final Iterable<OutputUnit> outputUnits;
+  final List<OutputUnit> outputUnits;
   final Map<ImportEntity, String> _importDeferName;
 
   /// A mapping from the name of a defer import to all the output units it
diff --git a/pkg/compiler/lib/src/inferrer/typemasks/constants.dart b/pkg/compiler/lib/src/inferrer/typemasks/constants.dart
index 9b5232f..a453382 100644
--- a/pkg/compiler/lib/src/inferrer/typemasks/constants.dart
+++ b/pkg/compiler/lib/src/inferrer/typemasks/constants.dart
@@ -4,10 +4,8 @@
 
 library types.constants;
 
-import '../../common.dart';
 import '../../constants/constant_system.dart' as constant_system;
 import '../../constants/values.dart';
-import '../../js_backend/js_backend.dart' show SyntheticConstantKind;
 import '../../world.dart' show JClosedWorld;
 import 'masks.dart';
 
@@ -50,21 +48,14 @@
   }
 
   @override
-  TypeMask visitSynthetic(
-      SyntheticConstantValue constant, JClosedWorld closedWorld) {
-    switch (constant.valueKind) {
-      case SyntheticConstantKind.DUMMY_INTERCEPTOR:
-        return constant.payload;
-      case SyntheticConstantKind.EMPTY_VALUE:
-        return constant.payload;
-      case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
-        return closedWorld.abstractValueDomain.intType;
-      case SyntheticConstantKind.NAME:
-        return closedWorld.abstractValueDomain.stringType;
-      default:
-        throw failedAt(CURRENT_ELEMENT_SPANNABLE,
-            "Unexpected DummyConstantKind: ${constant.toStructuredText()}.");
-    }
+  TypeMask visitAbstractValue(
+      AbstractValueConstantValue constant, JClosedWorld closedWorld) {
+    return constant.abstractValue;
+  }
+
+  @override
+  TypeMask visitJsName(JsNameConstantValue constant, JClosedWorld closedWorld) {
+    return closedWorld.abstractValueDomain.stringType;
   }
 
   @override
diff --git a/pkg/compiler/lib/src/io/position_information.dart b/pkg/compiler/lib/src/io/position_information.dart
index 63851e9..a92ab7e 100644
--- a/pkg/compiler/lib/src/io/position_information.dart
+++ b/pkg/compiler/lib/src/io/position_information.dart
@@ -11,12 +11,15 @@
 import '../js/js.dart' as js;
 import '../js/js_debug.dart';
 import '../js/js_source_mapping.dart';
+import '../serialization/serialization.dart';
 import 'code_output.dart' show BufferedCodeOutput;
 import 'source_information.dart';
 
 /// [SourceInformation] that consists of an offset position into the source
 /// code.
 class PositionSourceInformation extends SourceInformation {
+  static const String tag = 'source-information';
+
   @override
   final SourceLocation startPosition;
 
@@ -29,6 +32,28 @@
   PositionSourceInformation(
       this.startPosition, this.innerPosition, this.inliningContext);
 
+  factory PositionSourceInformation.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    SourceLocation startPosition = SourceLocation.readFromDataSource(source);
+    SourceLocation innerPosition = SourceLocation.readFromDataSource(source);
+    List<FrameContext> inliningContext = source.readList(
+        () => FrameContext.readFromDataSource(source),
+        emptyAsNull: true);
+    source.end(tag);
+    return new PositionSourceInformation(
+        startPosition, innerPosition, inliningContext);
+  }
+
+  void writeToDataSinkInternal(DataSink sink) {
+    sink.begin(tag);
+    SourceLocation.writeToDataSink(sink, startPosition);
+    SourceLocation.writeToDataSink(sink, innerPosition);
+    sink.writeList(inliningContext,
+        (FrameContext context) => context.writeToDataSink(sink),
+        allowNull: true);
+    sink.end(tag);
+  }
+
   @override
   List<SourceLocation> get sourceLocations {
     List<SourceLocation> list = <SourceLocation>[];
diff --git a/pkg/compiler/lib/src/io/source_information.dart b/pkg/compiler/lib/src/io/source_information.dart
index 64ca507..bc8f6e7 100644
--- a/pkg/compiler/lib/src/io/source_information.dart
+++ b/pkg/compiler/lib/src/io/source_information.dart
@@ -8,14 +8,41 @@
 import '../common.dart';
 import '../elements/entities.dart';
 import '../js/js.dart' show JavaScriptNodeSourceInformation;
+import '../serialization/serialization.dart';
 import '../universe/call_structure.dart';
 import 'source_file.dart';
+import 'position_information.dart';
 
 /// Interface for passing source information, for instance for use in source
 /// maps, through the backend.
 abstract class SourceInformation extends JavaScriptNodeSourceInformation {
   const SourceInformation();
 
+  static SourceInformation readFromDataSource(DataSource source) {
+    int hasSourceInformation = source.readInt();
+    if (hasSourceInformation == 0) {
+      return null;
+    } else if (hasSourceInformation == 1) {
+      return const SourceMappedMarker();
+    } else {
+      assert(hasSourceInformation == 2);
+      return PositionSourceInformation.readFromDataSource(source);
+    }
+  }
+
+  static void writeToDataSink(
+      DataSink sink, SourceInformation sourceInformation) {
+    if (sourceInformation == null) {
+      sink.writeInt(0);
+    } else if (sourceInformation is SourceMappedMarker) {
+      sink.writeInt(1);
+    } else {
+      sink.writeInt(2);
+      PositionSourceInformation positionSourceInformation = sourceInformation;
+      positionSourceInformation.writeToDataSinkInternal(sink);
+    }
+  }
+
   SourceSpan get sourceSpan;
 
   /// The source location associated with the start of the JS node.
@@ -45,6 +72,8 @@
 /// precise data about inlining that can then be used by defobuscation tools
 /// when reconstructing a source stack from a production stack trace.
 class FrameContext {
+  static const String tag = 'frame-context';
+
   /// Location of the call that was inlined.
   final SourceInformation callInformation;
 
@@ -53,6 +82,22 @@
 
   FrameContext(this.callInformation, this.inlinedMethodName);
 
+  factory FrameContext.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    SourceInformation callInformation =
+        SourceInformation.readFromDataSource(source);
+    String inlinedMethodName = source.readString();
+    source.end(tag);
+    return new FrameContext(callInformation, inlinedMethodName);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    SourceInformation.writeToDataSink(sink, callInformation);
+    sink.writeString(inlinedMethodName);
+    sink.end(tag);
+  }
+
   @override
   String toString() => "(FrameContext: $callInformation, $inlinedMethodName)";
 }
@@ -206,6 +251,8 @@
 
 /// A location in a source file.
 abstract class SourceLocation {
+  static const String tag = 'source-location';
+
   const SourceLocation();
 
   /// The absolute URI of the source file of this source location.
@@ -223,8 +270,42 @@
   /// The name associated with this source location, if any.
   String get sourceName;
 
-  /// `true` if the offset within the length of the source file.
-  bool get isValid;
+  static SourceLocation readFromDataSource(DataSource source) {
+    int hasSourceLocation = source.readInt();
+    if (hasSourceLocation == 0) {
+      return null;
+    } else if (hasSourceLocation == 1) {
+      return const NoSourceLocationMarker();
+    } else {
+      assert(hasSourceLocation == 2);
+      source.begin(tag);
+      Uri sourceUri = source.readUri();
+      int offset = source.readInt();
+      int line = source.readInt();
+      int column = source.readInt();
+      String sourceName = source.readString();
+      source.end(tag);
+      return new DirectSourceLocation(
+          sourceUri, offset, line, column, sourceName);
+    }
+  }
+
+  static void writeToDataSink(DataSink sink, SourceLocation sourceLocation) {
+    if (sourceLocation == null) {
+      sink.writeInt(0);
+    } else if (sourceLocation is NoSourceLocationMarker) {
+      sink.writeInt(1);
+    } else {
+      sink.writeInt(2);
+      sink.begin(tag);
+      sink.writeUri(sourceLocation.sourceUri);
+      sink.writeInt(sourceLocation.offset);
+      sink.writeInt(sourceLocation.line);
+      sink.writeInt(sourceLocation.column);
+      sink.writeString(sourceLocation.sourceName);
+      sink.end(tag);
+    }
+  }
 
   @override
   int get hashCode {
@@ -248,6 +329,26 @@
   String toString() => '${sourceUri}:[${line},${column}]';
 }
 
+class DirectSourceLocation extends SourceLocation {
+  @override
+  final Uri sourceUri;
+
+  @override
+  final int offset;
+
+  @override
+  final int line;
+
+  @override
+  final int column;
+
+  @override
+  final String sourceName;
+
+  DirectSourceLocation(
+      this.sourceUri, this.offset, this.line, this.column, this.sourceName);
+}
+
 /// A location in a source file.
 abstract class AbstractSourceLocation extends SourceLocation {
   final SourceFile _sourceFile;
@@ -255,7 +356,7 @@
 
   AbstractSourceLocation(this._sourceFile) {
     assert(
-        isValid,
+        offset < _sourceFile.length,
         failedAt(
             new SourceSpan(sourceUri, 0, 0),
             "Invalid source location in ${sourceUri}: "
@@ -283,9 +384,6 @@
   String get sourceName;
 
   @override
-  bool get isValid => offset < _sourceFile.length;
-
-  @override
   String get shortText => '${sourceUri.pathSegments.last}:[$line,$column]';
 
   @override
@@ -359,9 +457,6 @@
   Uri get sourceUri => null;
 
   @override
-  bool get isValid => true;
-
-  @override
   String get sourceName => null;
 
   @override
diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart
index 0b7b8b3..79a1ada 100644
--- a/pkg/compiler/lib/src/js_backend/backend.dart
+++ b/pkg/compiler/lib/src/js_backend/backend.dart
@@ -6,7 +6,7 @@
 
 import '../common.dart';
 import '../common/backend_api.dart' show ImpactTransformer;
-import '../common/codegen.dart' show CodegenResult;
+import '../common/codegen.dart';
 import '../common/names.dart' show Uris;
 import '../common/tasks.dart' show CompilerTask;
 import '../common/work.dart';
@@ -24,6 +24,7 @@
 import '../js_emitter/js_emitter.dart' show CodeEmitterTask;
 import '../kernel/dart2js_target.dart';
 import '../native/enqueue.dart';
+import '../serialization/serialization.dart';
 import '../ssa/ssa.dart' show SsaFunctionCompiler;
 import '../tracer.dart';
 import '../universe/class_hierarchy.dart'
@@ -282,13 +283,6 @@
   }
 }
 
-enum SyntheticConstantKind {
-  DUMMY_INTERCEPTOR,
-  EMPTY_VALUE,
-  TYPEVARIABLE_REFERENCE, // Reference to a type in reflection data.
-  NAME
-}
-
 class JavaScriptBackend {
   static const String JS = 'JS';
   static const String JS_BUILTIN = 'JS_BUILTIN';
@@ -556,7 +550,7 @@
             closedWorld.nativeData,
             closedWorld,
             compiler.abstractValueStrategy.createSelectorStrategy()),
-        compiler.backendStrategy.createCodegenWorkItemBuilder(),
+        compiler.backendStrategy.createCodegenWorkItemBuilder(closedWorld),
         new CodegenEnqueuerListener(
             elementEnvironment,
             commonElements,
@@ -569,9 +563,26 @@
 
   Map<MemberEntity, WorldImpact> codegenImpactsForTesting;
 
-  WorldImpact generateCode(WorkItem work) {
+  WorldImpact generateCode(WorkItem work, JClosedWorld closedWorld,
+      EntityLookup entityLookup, ComponentLookup componentLookup) {
     MemberEntity member = work.element;
     CodegenResult result = functionCompiler.compile(member);
+    if (compiler.options.testMode) {
+      bool useDataKinds = true;
+      List<Object> data = [];
+      DataSink sink = new ObjectSink(data, useDataKinds: useDataKinds);
+      sink.registerCodegenWriter(new CodegenWriterImpl(closedWorld));
+      result.writeToDataSink(sink);
+      DataSource source = new ObjectSource(data, useDataKinds: useDataKinds);
+      List<ModularName> modularNames = [];
+      List<ModularExpression> modularExpression = [];
+      source.registerCodegenReader(
+          new CodegenReaderImpl(closedWorld, modularNames, modularExpression));
+      source.registerEntityLookup(entityLookup);
+      source.registerComponentLookup(componentLookup);
+      result = CodegenResult.readFromDataSource(
+          source, modularNames, modularExpression);
+    }
     if (result.code != null) {
       generatedCode[member] = result.code;
     }
@@ -583,6 +594,7 @@
         _codegenImpactTransformer.transformCodegenImpact(result.impact);
     compiler.dumpInfoTask.registerImpact(member, worldImpact);
     result.applyModularState(_namer, emitterTask.emitter);
+    //print('$member:$result');
     return worldImpact;
   }
 
diff --git a/pkg/compiler/lib/src/js_backend/constant_emitter.dart b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
index 780b1bc..96bc202 100644
--- a/pkg/compiler/lib/src/js_backend/constant_emitter.dart
+++ b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
@@ -15,7 +15,6 @@
 import '../js_emitter/code_emitter_task.dart';
 import '../options.dart';
 import 'field_analysis.dart' show JFieldAnalysis;
-import 'js_backend.dart';
 import 'runtime_types.dart';
 
 typedef jsAst.Expression _ConstantReferenceGenerator(ConstantValue constant);
@@ -145,18 +144,14 @@
   }
 
   @override
-  jsAst.Expression visitSynthetic(SyntheticConstantValue constant, [_]) {
-    switch (constant.valueKind) {
-      case SyntheticConstantKind.DUMMY_INTERCEPTOR:
-      case SyntheticConstantKind.EMPTY_VALUE:
-        return new jsAst.LiteralNumber('0');
-      case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
-      case SyntheticConstantKind.NAME:
-        return constant.payload;
-      default:
-        throw failedAt(NO_LOCATION_SPANNABLE,
-            "Unexpected DummyConstantKind ${constant.kind}");
-    }
+  jsAst.Expression visitAbstractValue(AbstractValueConstantValue constant,
+      [_]) {
+    return new jsAst.LiteralNumber('0');
+  }
+
+  @override
+  jsAst.Expression visitJsName(JsNameConstantValue constant, [_]) {
+    return constant.name;
   }
 
   @override
diff --git a/pkg/compiler/lib/src/js_backend/namer.dart b/pkg/compiler/lib/src/js_backend/namer.dart
index b76662e..ee53198 100644
--- a/pkg/compiler/lib/src/js_backend/namer.dart
+++ b/pkg/compiler/lib/src/js_backend/namer.dart
@@ -34,7 +34,6 @@
 import '../universe/selector.dart' show Selector, SelectorKind;
 import '../util/util.dart';
 import '../world.dart' show JClosedWorld;
-import 'backend.dart';
 import 'native_data.dart';
 
 part 'field_naming_mixin.dart';
@@ -1885,21 +1884,13 @@
   }
 
   @override
-  void visitSynthetic(SyntheticConstantValue constant, [_]) {
-    switch (constant.valueKind) {
-      case SyntheticConstantKind.DUMMY_INTERCEPTOR:
-        add('dummy_receiver');
-        break;
-      case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
-        // Omit. These are opaque deferred indexes with nothing helpful to add.
-        break;
-      case SyntheticConstantKind.NAME:
-        add('name');
-        break;
-      default:
-        failedAt(
-            CURRENT_ELEMENT_SPANNABLE, "Unexpected SyntheticConstantValue");
-    }
+  void visitAbstractValue(AbstractValueConstantValue constant, [_]) {
+    add('dummy_receiver');
+  }
+
+  @override
+  void visitJsName(JsNameConstantValue constant, [_]) {
+    add('name');
   }
 
   @override
@@ -2021,19 +2012,19 @@
   }
 
   @override
-  int visitSynthetic(SyntheticConstantValue constant, [_]) {
-    switch (constant.valueKind) {
-      case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
-        // These contain a deferred opaque index into metadata. There is nothing
-        // we can access that is stable between compiles.  Luckily, since they
-        // resolve to integer indexes, they're always part of a larger constant.
-        return 0;
-      default:
-        throw failedAt(
-            NO_LOCATION_SPANNABLE,
-            'SyntheticConstantValue should never be named and '
-            'never be subconstant');
-    }
+  int visitAbstractValue(AbstractValueConstantValue constant, [_]) {
+    throw failedAt(
+        NO_LOCATION_SPANNABLE,
+        'AbstractValueConstantValue should never be named and '
+        'never be subconstant');
+  }
+
+  @override
+  int visitJsName(JsNameConstantValue constant, [_]) {
+    throw failedAt(
+        NO_LOCATION_SPANNABLE,
+        'JsNameConstantValue should never be named and '
+        'never be subconstant');
   }
 
   @override
@@ -2433,7 +2424,7 @@
   /// Returns the name for the async body of the method with the [original]
   /// name.
   jsAst.Name deriveAsyncBodyName(jsAst.Name original) {
-    return new _AsyncName(_literalAsyncPrefix, original);
+    return new AsyncName(_literalAsyncPrefix, original);
   }
 
   /// Returns the label name for [label] used as a break target.
diff --git a/pkg/compiler/lib/src/js_backend/namer_names.dart b/pkg/compiler/lib/src/js_backend/namer_names.dart
index 0604258..f74a30a 100644
--- a/pkg/compiler/lib/src/js_backend/namer_names.dart
+++ b/pkg/compiler/lib/src/js_backend/namer_names.dart
@@ -118,11 +118,11 @@
   SetterName(jsAst.Name prefix, jsAst.Name base) : super(prefix, base);
 }
 
-class _AsyncName extends _PrefixedName {
+class AsyncName extends _PrefixedName {
   @override
   int get _kind => _NamerNameKinds.Async.index;
 
-  _AsyncName(jsAst.Name prefix, jsAst.Name base) : super(prefix, base);
+  AsyncName(jsAst.Name prefix, jsAst.Name base) : super(prefix, base);
 
   @override
   bool get allowRename => true;
diff --git a/pkg/compiler/lib/src/js_emitter/constant_ordering.dart b/pkg/compiler/lib/src/js_emitter/constant_ordering.dart
index 9306361..fb1a792 100644
--- a/pkg/compiler/lib/src/js_emitter/constant_ordering.dart
+++ b/pkg/compiler/lib/src/js_emitter/constant_ordering.dart
@@ -8,7 +8,6 @@
 import '../elements/entities.dart'
     show ClassEntity, FieldEntity, MemberEntity, TypedefEntity;
 import '../elements/types.dart';
-import '../js_backend/js_backend.dart' show SyntheticConstantKind;
 import 'sorter.dart' show Sorter;
 
 /// A canonical but arbitrary ordering of constants. The ordering is 'stable'
@@ -169,32 +168,16 @@
   }
 
   @override
-  int visitSynthetic(SyntheticConstantValue a, SyntheticConstantValue b) {
-    // [SyntheticConstantValue]s have abstract fields that are set only by
-    // convention.  Lucky for us, they do not occur as top level constant, only
-    // as elements of a few constants.  If this becomes a source of instability,
-    // we will need to add a total ordering on JavaScript ASTs including
-    // deferred elements.
-    SyntheticConstantKind aKind = a.valueKind;
-    SyntheticConstantKind bKind = b.valueKind;
-    int r = aKind.index - bKind.index;
-    if (r != 0) return r;
-    switch (aKind) {
-      case SyntheticConstantKind.DUMMY_INTERCEPTOR:
-      case SyntheticConstantKind.EMPTY_VALUE:
-        // Never emitted.
-        return 0;
+  int visitAbstractValue(
+      AbstractValueConstantValue a, AbstractValueConstantValue b) {
+    // Never emitted.
+    return 0;
+  }
 
-      case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
-        // An opaque deferred JS AST reference to a type in reflection data.
-        return 0;
-      case SyntheticConstantKind.NAME:
-        // An opaque deferred JS AST reference to a name.
-        return 0;
-      default:
-        // Should not happen.
-        throw 'unexpected SyntheticConstantKind $aKind';
-    }
+  @override
+  int visitJsName(JsNameConstantValue a, JsNameConstantValue b) {
+    // An opaque deferred JS AST reference to a name.
+    return 0;
   }
 
   @override
@@ -229,10 +212,11 @@
   static const int CONSTRUCTED = 10;
   static const int TYPE = 11;
   static const int INTERCEPTOR = 12;
-  static const int SYNTHETIC = 13;
-  static const int DEFERRED_GLOBAL = 14;
-  static const int NONCONSTANT = 15;
-  static const int INSTANTIATION = 16;
+  static const int ABSTRACT_VALUE = 13;
+  static const int JS_NAME = 14;
+  static const int DEFERRED_GLOBAL = 15;
+  static const int NONCONSTANT = 16;
+  static const int INSTANTIATION = 17;
 
   static int kind(ConstantValue constant) =>
       constant.accept(const _KindVisitor(), null);
@@ -264,7 +248,9 @@
   @override
   int visitInterceptor(InterceptorConstantValue a, _) => INTERCEPTOR;
   @override
-  int visitSynthetic(SyntheticConstantValue a, _) => SYNTHETIC;
+  int visitAbstractValue(AbstractValueConstantValue a, _) => ABSTRACT_VALUE;
+  @override
+  int visitJsName(JsNameConstantValue a, _) => JS_NAME;
   @override
   int visitDeferredGlobal(DeferredGlobalConstantValue a, _) => DEFERRED_GLOBAL;
   @override
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 47f5b36..5356e84 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -2584,3 +2584,35 @@
     return typeVariable;
   }
 }
+
+/// [EntityLookup] implementation for an fully built [JsKernelToElementMap].
+class ClosedEntityLookup implements EntityLookup {
+  final JsKernelToElementMap _elementMap;
+
+  ClosedEntityLookup(this._elementMap);
+
+  @override
+  IndexedTypeVariable getTypeVariableByIndex(int index) {
+    return _elementMap.typeVariables.getEntity(index);
+  }
+
+  @override
+  IndexedMember getMemberByIndex(int index) {
+    return _elementMap.members.getEntity(index);
+  }
+
+  @override
+  IndexedTypedef getTypedefByIndex(int index) {
+    return _elementMap.typedefs.getEntity(index);
+  }
+
+  @override
+  IndexedClass getClassByIndex(int index) {
+    return _elementMap.classes.getEntity(index);
+  }
+
+  @override
+  IndexedLibrary getLibraryByIndex(int index) {
+    return _elementMap.libraries.getEntity(index);
+  }
+}
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index 6eafe83..ffe37e3 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -31,6 +31,7 @@
 import '../kernel/kernel_strategy.dart';
 import '../native/behavior.dart';
 import '../options.dart';
+import '../serialization/serialization.dart';
 import '../ssa/builder_kernel.dart';
 import '../ssa/nodes.dart';
 import '../ssa/ssa.dart';
@@ -110,8 +111,17 @@
   }
 
   @override
-  WorkItemBuilder createCodegenWorkItemBuilder() {
-    return new KernelCodegenWorkItemBuilder(_compiler.backend);
+  WorkItemBuilder createCodegenWorkItemBuilder(JClosedWorld closedWorld) {
+    assert(_elementMap != null,
+        "JsBackendStrategy.elementMap has not been created yet.");
+    return new KernelCodegenWorkItemBuilder(
+        _compiler.backend,
+        closedWorld,
+        new ClosedEntityLookup(_elementMap),
+        // TODO(johnniwinther): Avoid the need for a [ComponentLookup]. This
+        // is caused by some type masks holding a kernel node for using in
+        // tracing.
+        new ComponentLookup(_elementMap.programEnv.mainComponent));
   }
 
   @override
@@ -137,26 +147,36 @@
 
 class KernelCodegenWorkItemBuilder implements WorkItemBuilder {
   final JavaScriptBackend _backend;
+  final JClosedWorld _closedWorld;
+  final EntityLookup _entityLookup;
+  final ComponentLookup _componentLookup;
 
-  KernelCodegenWorkItemBuilder(this._backend);
+  KernelCodegenWorkItemBuilder(this._backend, this._closedWorld,
+      this._entityLookup, this._componentLookup);
 
   @override
   WorkItem createWorkItem(MemberEntity entity) {
     if (entity.isAbstract) return null;
-    return new KernelCodegenWorkItem(_backend, entity);
+    return new KernelCodegenWorkItem(
+        _backend, _closedWorld, _entityLookup, _componentLookup, entity);
   }
 }
 
 class KernelCodegenWorkItem extends WorkItem {
   final JavaScriptBackend _backend;
+  final JClosedWorld _closedWorld;
+  final EntityLookup _entityLookup;
+  final ComponentLookup _componentLookup;
   @override
   final MemberEntity element;
 
-  KernelCodegenWorkItem(this._backend, this.element);
+  KernelCodegenWorkItem(this._backend, this._closedWorld, this._entityLookup,
+      this._componentLookup, this.element);
 
   @override
   WorldImpact run() {
-    return _backend.generateCode(this);
+    return _backend.generateCode(
+        this, _closedWorld, _entityLookup, _componentLookup);
   }
 }
 
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index 1a9544f..b3a59d2 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -846,7 +846,10 @@
   @override
   ConstantValue visitString(StringConstantValue constant, _) => constant;
   @override
-  ConstantValue visitSynthetic(SyntheticConstantValue constant, _) => constant;
+  ConstantValue visitAbstractValue(AbstractValueConstantValue constant, _) =>
+      constant;
+  @override
+  ConstantValue visitJsName(JsNameConstantValue constant, _) => constant;
   @override
   ConstantValue visitNonConstant(NonConstantValue constant, _) => constant;
 
diff --git a/pkg/compiler/lib/src/serialization/abstract_sink.dart b/pkg/compiler/lib/src/serialization/abstract_sink.dart
index dfe4e9b..ac9d487 100644
--- a/pkg/compiler/lib/src/serialization/abstract_sink.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_sink.dart
@@ -34,6 +34,8 @@
 
   Map<Type, IndexedSink> _generalCaches = {};
 
+  CodegenWriter _codegenWriter;
+
   AbstractDataSink({this.useDataKinds: false}) {
     _dartTypeWriter = new DartTypeWriter(this);
     _dartTypeNodeWriter = new DartTypeNodeWriter(this);
@@ -439,12 +441,23 @@
         break;
       case ConstantValueKind.NON_CONSTANT:
         break;
-      case ConstantValueKind.DEFERRED_GLOBAL:
       case ConstantValueKind.INTERCEPTOR:
-      case ConstantValueKind.SYNTHETIC:
-        // These are only created in the SSA graph builder.
-        throw new UnsupportedError(
-            "Unsupported constant value kind ${value.kind}.");
+        InterceptorConstantValue constant = value;
+        writeClass(constant.cls);
+        break;
+      case ConstantValueKind.DEFERRED_GLOBAL:
+        DeferredGlobalConstantValue constant = value;
+        writeConstant(constant.referenced);
+        writeOutputUnitReference(constant.unit);
+        break;
+      case ConstantValueKind.ABSTRACT_VALUE:
+        AbstractValueConstantValue constant = value;
+        writeAbstractValue(constant.abstractValue);
+        break;
+      case ConstantValueKind.JS_NAME:
+        JsNameConstantValue constant = value;
+        writeJsNode(constant.name);
+        break;
     }
   }
 
@@ -474,6 +487,36 @@
     _writeBool(value.isDeferred);
   }
 
+  @override
+  void registerCodegenWriter(CodegenWriter writer) {
+    assert(writer != null);
+    assert(_codegenWriter == null);
+    _codegenWriter = writer;
+  }
+
+  @override
+  void writeOutputUnitReference(OutputUnit value) {
+    assert(
+        _codegenWriter != null,
+        "Can not serialize an OutputUnit reference "
+        "without a registered codegen writer.");
+    _codegenWriter.writeOutputUnitReference(this, value);
+  }
+
+  @override
+  void writeAbstractValue(AbstractValue value) {
+    assert(_codegenWriter != null,
+        "Can not serialize an AbstractValue without a registered codegen writer.");
+    _codegenWriter.writeAbstractValue(this, value);
+  }
+
+  @override
+  void writeJsNode(js.Node value) {
+    assert(_codegenWriter != null,
+        "Can not serialize a JS ndoe without a registered codegen writer.");
+    _codegenWriter.writeJsNode(this, value);
+  }
+
   /// Actual serialization of a section begin tag, implemented by subclasses.
   void _begin(String tag);
 
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
index f4160ce..87cb765 100644
--- a/pkg/compiler/lib/src/serialization/abstract_source.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -12,6 +12,7 @@
   ComponentLookup _componentLookup;
   EntityLookup _entityLookup;
   LocalLookup _localLookup;
+  CodegenReader _codegenReader;
 
   IndexedSource<String> _stringIndex;
   IndexedSource<Uri> _uriIndex;
@@ -71,6 +72,13 @@
   }
 
   @override
+  void registerCodegenReader(CodegenReader reader) {
+    assert(reader != null);
+    assert(_codegenReader == null);
+    _codegenReader = reader;
+  }
+
+  @override
   E readCached<E>(E f()) {
     IndexedSource source = _generalCaches[E] ??= new IndexedSource<E>(this);
     return source.read(f);
@@ -495,11 +503,19 @@
         return new InstantiationConstantValue(typeArguments, function);
       case ConstantValueKind.NON_CONSTANT:
         return new NonConstantValue();
-      case ConstantValueKind.DEFERRED_GLOBAL:
       case ConstantValueKind.INTERCEPTOR:
-      case ConstantValueKind.SYNTHETIC:
-        // These are only created in the SSA graph builder.
-        throw new UnsupportedError("Unsupported constant value kind ${kind}.");
+        ClassEntity cls = readClass();
+        return new InterceptorConstantValue(cls);
+      case ConstantValueKind.DEFERRED_GLOBAL:
+        ConstantValue constant = readConstant();
+        OutputUnit unit = readOutputUnitReference();
+        return new DeferredGlobalConstantValue(constant, unit);
+      case ConstantValueKind.ABSTRACT_VALUE:
+        AbstractValue abstractValue = readAbstractValue();
+        return new AbstractValueConstantValue(abstractValue);
+      case ConstantValueKind.JS_NAME:
+        js.LiteralString name = readJsNode();
+        return new JsNameConstantValue(name);
     }
     throw new UnsupportedError("Unexpexted constant value kind ${kind}.");
   }
@@ -626,6 +642,31 @@
     return new ImportEntity(isDeferred, name, uri, enclosingLibraryUri);
   }
 
+  @override
+  OutputUnit readOutputUnitReference() {
+    assert(
+        _codegenReader != null,
+        "Can not deserialize an OutputUnit reference "
+        "without a registered codegen reader.");
+    return _codegenReader.readOutputUnitReference(this);
+  }
+
+  @override
+  AbstractValue readAbstractValue() {
+    assert(
+        _codegenReader != null,
+        "Can not deserialize an AbstractValue "
+        "without a registered codegen reader.");
+    return _codegenReader.readAbstractValue(this);
+  }
+
+  @override
+  js.Node readJsNode() {
+    assert(_codegenReader != null,
+        "Can not deserialize a JS node without a registered codegen reader.");
+    return _codegenReader.readJsNode(this);
+  }
+
   /// Actual deserialization of a section begin tag, implemented by subclasses.
   void _begin(String tag);
 
diff --git a/pkg/compiler/lib/src/serialization/mixins.dart b/pkg/compiler/lib/src/serialization/mixins.dart
index 695d112..2de3e8a 100644
--- a/pkg/compiler/lib/src/serialization/mixins.dart
+++ b/pkg/compiler/lib/src/serialization/mixins.dart
@@ -314,6 +314,15 @@
   }
 
   @override
+  ImportEntity readImportOrNull() {
+    bool hasClass = readBool();
+    if (hasClass) {
+      return readImport();
+    }
+    return null;
+  }
+
+  @override
   List<ImportEntity> readImports({bool emptyAsNull: false}) {
     int count = readInt();
     if (count == 0 && emptyAsNull) return null;
@@ -366,6 +375,15 @@
   ir.LibraryDependency readLibraryDependencyNodeOrNull() {
     return readValueOrNull(readLibraryDependencyNode);
   }
+
+  @override
+  js.Node readJsNodeOrNull() {
+    bool hasValue = readBool();
+    if (hasValue) {
+      return readJsNode();
+    }
+    return null;
+  }
 }
 
 /// Mixin that implements all convenience methods of [DataSink].
@@ -693,6 +711,14 @@
   }
 
   @override
+  void writeImportOrNull(ImportEntity value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeImport(value);
+    }
+  }
+
+  @override
   void writeImports(Iterable<ImportEntity> values, {bool allowNull: false}) {
     if (values == null) {
       assert(allowNull);
@@ -751,4 +777,12 @@
   void writeLibraryDependencyNodeOrNull(ir.LibraryDependency value) {
     writeValueOrNull(value, writeLibraryDependencyNode);
   }
+
+  @override
+  void writeJsNodeOrNull(js.Node value) {
+    writeBool(value != null);
+    if (value != null) {
+      writeJsNode(value);
+    }
+  }
 }
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index 8623f87..d4f574c 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -11,12 +11,15 @@
 import '../closure.dart';
 import '../constants/constant_system.dart' as constant_system;
 import '../constants/values.dart';
+import '../deferred_load.dart';
 import '../diagnostics/source_span.dart';
 import '../elements/entities.dart';
 import '../elements/indexed.dart';
 import '../elements/types.dart';
+import '../inferrer/abstract_value_domain.dart';
 import '../ir/constants.dart';
 import '../ir/static_type_base.dart';
+import '../js/js.dart' as js;
 import '../js_model/closure.dart';
 import '../js_model/locals.dart';
 
@@ -357,6 +360,9 @@
   /// Writes the import [value] to this data sink.
   void writeImport(ImportEntity value);
 
+  /// Writes the potentially `null` import [value] to this data sink.
+  void writeImportOrNull(ImportEntity value);
+
   /// Writes import [values] to this data sink. If [allowNull] is `true`,
   /// [values] is allowed to be `null`.
   ///
@@ -372,6 +378,30 @@
   /// [DataSource.readImportMap].
   void writeImportMap<V>(Map<ImportEntity, V> map, void f(V value),
       {bool allowNull: false});
+
+  /// Writes an abstract [value] to this data sink.
+  ///
+  /// This feature is only available a [CodegenWriter] has been registered.
+  void writeAbstractValue(AbstractValue value);
+
+  /// Writes a reference to the output unit [value] to this data sink.
+  ///
+  /// This feature is only available a [CodegenWriter] has been registered.
+  void writeOutputUnitReference(OutputUnit value);
+
+  /// Writes a js node [value] to this data sink.
+  ///
+  /// This feature is only available a [CodegenWriter] has been registered.
+  void writeJsNode(js.Node value);
+
+  /// Writes a potentially `null` js node [value] to this data sink.
+  ///
+  /// This feature is only available a [CodegenWriter] has been registered.
+  void writeJsNodeOrNull(js.Node value);
+
+  /// Register a [CodegenWriter] with this data sink to support serialization
+  /// of codegen only data.
+  void registerCodegenWriter(CodegenWriter writer);
 }
 
 /// Interface for deserialization.
@@ -400,6 +430,10 @@
   /// deserialization of references to locals.
   void registerLocalLookup(LocalLookup localLookup);
 
+  /// Registers a [CodegenReader] with this data source to support
+  /// deserialization of codegen only data.
+  void registerCodegenReader(CodegenReader read);
+
   /// Reads a reference to an [E] value from this data source. If the value has
   /// not yet been deserialized, [f] is called to deserialize the value itself.
   E readCached<E>(E f());
@@ -680,6 +714,9 @@
   /// Reads a import from this data source.
   ImportEntity readImport();
 
+  /// Reads a potentially `null` import from this data source.
+  ImportEntity readImportOrNull();
+
   /// Reads a list of imports from this data source. If [emptyAsNull] is
   /// `true`, `null` is returned instead of an empty list.
   ///
@@ -694,6 +731,26 @@
   /// This is a convenience method to be used together with
   /// [DataSink.writeImportMap].
   Map<ImportEntity, V> readImportMap<V>(V f(), {bool emptyAsNull: false});
+
+  /// Reads an [AbstractValue] from this data source.
+  ///
+  /// This feature is only available a [CodegenReader] has been registered.
+  AbstractValue readAbstractValue();
+
+  /// Reads a reference to an [OutputUnit] from this data source.
+  ///
+  /// This feature is only available a [CodegenReader] has been registered.
+  OutputUnit readOutputUnitReference();
+
+  /// Reads a [js.Node] value from this data source.
+  ///
+  /// This feature is only available a [CodegenReader] has been registered.
+  js.Node readJsNode();
+
+  /// Reads a potentially `null` [js.Node] value from this data source.
+  ///
+  /// This feature is only available a [CodegenReader] has been registered.
+  js.Node readJsNodeOrNull();
 }
 
 /// Interface used for looking up entities by index during deserialization.
@@ -718,3 +775,17 @@
 abstract class LocalLookup {
   Local getLocalByIndex(MemberEntity memberContext, int index);
 }
+
+/// Interface used for reading codegen only data during deserialization.
+abstract class CodegenReader {
+  AbstractValue readAbstractValue(DataSource source);
+  OutputUnit readOutputUnitReference(DataSource source);
+  js.Node readJsNode(DataSource source);
+}
+
+/// Interface used for writing codegen only data during serialization.
+abstract class CodegenWriter {
+  void writeAbstractValue(DataSink sink, AbstractValue value);
+  void writeOutputUnitReference(DataSink sink, OutputUnit value);
+  void writeJsNode(DataSink sink, js.Node node);
+}
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index e39180d..67cc980 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -4947,16 +4947,16 @@
     var arguments = <HInstruction>[];
     node.expression.accept(this);
     arguments.add(pop());
-    // TODO(johnniwinther): Use the static type of the expression.
+    StaticType expressionType = _getStaticType(node.expression);
     bool typeArgumentsNeeded = _rtiNeed.instantiationNeedsTypeArguments(
-        null, node.typeArguments.length);
+        expressionType.type, node.typeArguments.length);
     List<DartType> typeArguments = node.typeArguments
         .map((type) => typeArgumentsNeeded
             ? _elementMap.getDartType(type)
             : _commonElements.dynamicType)
         .toList();
     registry.registerGenericInstantiation(
-        new GenericInstantiation(null, typeArguments));
+        new GenericInstantiation(expressionType.type, typeArguments));
     // TODO(johnniwinther): Can we avoid creating the instantiation object?
     for (DartType type in typeArguments) {
       HInstruction instruction =
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 502ab8e..47d0de0 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -67,13 +67,13 @@
     if (needsAsyncRewrite) {
       return finish(element.asyncMarker.isAsync
           ? (element.asyncMarker.isYielding
-              ? const js.AsyncModifier.asyncStar()
-              : const js.AsyncModifier.async())
+              ? js.AsyncModifier.asyncStar
+              : js.AsyncModifier.async)
           : (element.asyncMarker.isYielding
-              ? const js.AsyncModifier.syncStar()
-              : const js.AsyncModifier.sync()));
+              ? js.AsyncModifier.syncStar
+              : js.AsyncModifier.sync));
     } else {
-      return finish(const js.AsyncModifier.sync());
+      return finish(js.AsyncModifier.sync);
     }
   }
 
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index 7639921..bc388f2 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -5,8 +5,7 @@
 import '../constants/values.dart';
 import '../elements/entities.dart';
 import '../inferrer/abstract_value_domain.dart';
-import '../js_backend/js_backend.dart'
-    show SuperMemberData, SyntheticConstantKind;
+import '../js_backend/js_backend.dart' show SuperMemberData;
 import '../js_backend/interceptor_data.dart';
 import '../options.dart';
 import '../universe/selector.dart' show Selector;
@@ -191,9 +190,8 @@
       if (_interceptorData.isInterceptedSelector(selector) &&
           !_interceptorData.isInterceptedMixinSelector(
               selector, mask, _closedWorld)) {
-        ConstantValue constant = new SyntheticConstantValue(
-            SyntheticConstantKind.DUMMY_INTERCEPTOR,
-            receiverArgument.instructionType);
+        ConstantValue constant =
+            new AbstractValueConstantValue(receiverArgument.instructionType);
         HConstant dummy = graph.addConstant(constant, _closedWorld);
         receiverArgument.usedBy.remove(node);
         node.inputs[1] = dummy;
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 8852a01..a893874 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -15,7 +15,6 @@
 import '../inferrer/abstract_value_domain.dart';
 import '../io/source_information.dart';
 import '../js/js.dart' as js;
-import '../js_backend/backend.dart' show SyntheticConstantKind;
 import '../native/behavior.dart';
 import '../universe/selector.dart' show Selector;
 import '../universe/side_effects.dart' show SideEffects;
@@ -313,9 +312,7 @@
 
   HConstant addConstantStringFromName(js.Name name, JClosedWorld closedWorld) {
     return addConstant(
-        new SyntheticConstantValue(
-            SyntheticConstantKind.NAME, js.quoteName(name)),
-        closedWorld);
+        new JsNameConstantValue(js.quoteName(name)), closedWorld);
   }
 
   HConstant addConstantBool(bool value, JClosedWorld closedWorld) {
@@ -330,7 +327,7 @@
     // A constant with an empty type used as the HInstruction of an expression
     // in an unreachable context.
     return addConstant(
-        new SyntheticConstantValue(SyntheticConstantKind.EMPTY_VALUE,
+        new AbstractValueConstantValue(
             closedWorld.abstractValueDomain.emptyType),
         closedWorld);
   }
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index b605866..91a06794f 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -15,7 +15,7 @@
 import '../inferrer/types.dart';
 import '../js_backend/field_analysis.dart'
     show FieldAnalysisData, JFieldAnalysis;
-import '../js_backend/backend.dart' show CodegenInputs, SyntheticConstantKind;
+import '../js_backend/backend.dart' show CodegenInputs;
 import '../js_backend/native_data.dart' show NativeData;
 import '../js_backend/runtime_types.dart';
 import '../native/behavior.dart';
@@ -1955,8 +1955,8 @@
   HInstruction get zapInstruction {
     if (zapInstructionCache == null) {
       // A constant with no type does not pollute types at phi nodes.
-      ConstantValue constant = new SyntheticConstantValue(
-          SyntheticConstantKind.EMPTY_VALUE, _abstractValueDomain.emptyType);
+      ConstantValue constant =
+          new AbstractValueConstantValue(_abstractValueDomain.emptyType);
       zapInstructionCache = analyzer.graph.addConstant(constant, closedWorld);
     }
     return zapInstructionCache;
diff --git a/pkg/compiler/lib/src/universe/feature.dart b/pkg/compiler/lib/src/universe/feature.dart
index f6386be..92ded95 100644
--- a/pkg/compiler/lib/src/universe/feature.dart
+++ b/pkg/compiler/lib/src/universe/feature.dart
@@ -10,6 +10,7 @@
 
 import '../elements/types.dart';
 import '../ir/runtime_type_analysis.dart';
+import '../serialization/serialization.dart';
 import '../util/util.dart';
 
 /// A language feature that may be seen in the program.
@@ -236,6 +237,8 @@
 /// A generic instantiation of an expression of type [functionType] with the
 /// given [typeArguments].
 class GenericInstantiation {
+  static const String tag = 'generic-instantiation';
+
   /// The static type of the instantiated expression.
   final DartType functionType;
 
@@ -244,6 +247,21 @@
 
   GenericInstantiation(this.functionType, this.typeArguments);
 
+  factory GenericInstantiation.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    DartType functionType = source.readDartType();
+    List<DartType> typeArguments = source.readDartTypes();
+    source.end(tag);
+    return new GenericInstantiation(functionType, typeArguments);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeDartType(functionType);
+    sink.writeDartTypes(typeArguments);
+    sink.end(tag);
+  }
+
   /// Short textual representation use for testing.
   String get shortText => '<${typeArguments.join(',')}>';
 
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index 8b4c082..d062fc5 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -20,6 +20,8 @@
 import '../constants/values.dart';
 import '../elements/types.dart';
 import '../elements/entities.dart';
+import '../inferrer/abstract_value_domain.dart';
+import '../serialization/serialization.dart';
 import '../js_model/closure.dart';
 import '../util/util.dart' show equalElements, Hashing;
 import 'call_structure.dart' show CallStructure;
@@ -36,6 +38,8 @@
 /// property and [receiverConstraint] defines the known constraint for the
 /// object on which the property is accessed.
 class DynamicUse {
+  static const String tag = 'dynamic-use';
+
   final Selector selector;
   final Object receiverConstraint;
   final List<DartType> _typeArguments;
@@ -49,6 +53,35 @@
         "${_typeArguments?.length ?? 0} were passed.");
   }
 
+  factory DynamicUse.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Selector selector = Selector.readFromDataSource(source);
+    bool hasConstraint = source.readBool();
+    Object receiverConstraint;
+    if (hasConstraint) {
+      receiverConstraint = source.readAbstractValue();
+    }
+    List<DartType> typeArguments = source.readDartTypes(emptyAsNull: true);
+    source.end(tag);
+    return new DynamicUse(selector, receiverConstraint, typeArguments);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    selector.writeToDataSink(sink);
+    sink.writeBool(receiverConstraint != null);
+    if (receiverConstraint != null) {
+      if (receiverConstraint is AbstractValue) {
+        sink.writeAbstractValue(receiverConstraint);
+      } else {
+        throw new UnsupportedError(
+            "Unsupported receiver constraint: ${receiverConstraint}");
+      }
+    }
+    sink.writeDartTypes(_typeArguments, allowNull: true);
+    sink.end(tag);
+  }
+
   /// Short textual representation use for testing.
   String get shortText {
     StringBuffer sb = new StringBuffer();
@@ -136,6 +169,8 @@
 // TODO(johnniwinther): Create backend-specific implementations with better
 // invariants.
 class StaticUse {
+  static const String tag = 'static-use';
+
   final Entity element;
   final StaticUseKind kind;
   @override
@@ -174,6 +209,39 @@
     return true;
   }
 
+  factory StaticUse.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    MemberEntity element = source.readMember();
+    StaticUseKind kind = source.readEnum(StaticUseKind.values);
+    InterfaceType type = source.readDartType(allowNull: true);
+    CallStructure callStructure =
+        source.readValueOrNull(() => CallStructure.readFromDataSource(source));
+    ImportEntity deferredImport = source.readImportOrNull();
+    ConstantValue constant = source.readConstantOrNull();
+    List<DartType> typeArguments = source.readDartTypes(emptyAsNull: true);
+    source.end(tag);
+    return new StaticUse.internal(element, kind,
+        type: type,
+        callStructure: callStructure,
+        deferredImport: deferredImport,
+        constant: constant,
+        typeArguments: typeArguments);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    assert(element is MemberEntity, "Unsupported entity: $element");
+    sink.writeMember(element);
+    sink.writeEnum(kind);
+    sink.writeDartType(type, allowNull: true);
+    sink.writeValueOrNull(
+        callStructure, (CallStructure c) => c.writeToDataSink(sink));
+    sink.writeImportOrNull(deferredImport);
+    sink.writeConstantOrNull(constant);
+    sink.writeDartTypes(typeArguments, allowNull: true);
+    sink.end(tag);
+  }
+
   /// Short textual representation use for testing.
   String get shortText {
     StringBuffer sb = new StringBuffer();
@@ -638,6 +706,8 @@
 
 /// Use of a [DartType].
 class TypeUse {
+  static const String tag = 'type-use';
+
   final DartType type;
   final TypeUseKind kind;
   @override
@@ -649,6 +719,23 @@
         this.kind = kind,
         this.hashCode = Hashing.objectsHash(type, kind, deferredImport);
 
+  factory TypeUse.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    DartType type = source.readDartType();
+    TypeUseKind kind = source.readEnum(TypeUseKind.values);
+    ImportEntity deferredImport = source.readImportOrNull();
+    source.end(tag);
+    return new TypeUse.internal(type, kind, deferredImport);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeDartType(type);
+    sink.writeEnum(kind);
+    sink.writeImportOrNull(deferredImport);
+    sink.end(tag);
+  }
+
   /// Short textual representation use for testing.
   String get shortText {
     StringBuffer sb = new StringBuffer();
@@ -784,10 +871,25 @@
 
 /// Use of a [ConstantValue].
 class ConstantUse {
+  static const String tag = 'constant-use';
+
   final ConstantValue value;
 
   ConstantUse._(this.value);
 
+  factory ConstantUse.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    ConstantValue value = source.readConstant();
+    source.end(tag);
+    return new ConstantUse._(value);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeConstant(value);
+    sink.end(tag);
+  }
+
   /// Short textual representation use for testing.
   String get shortText {
     return value.toDartText();
diff --git a/pkg/compiler/lib/src/universe/world_impact.dart b/pkg/compiler/lib/src/universe/world_impact.dart
index cc9a92c..f3c8e21 100644
--- a/pkg/compiler/lib/src/universe/world_impact.dart
+++ b/pkg/compiler/lib/src/universe/world_impact.dart
@@ -82,6 +82,11 @@
   Set<TypeUse> _typeUses;
   Set<ConstantUse> _constantUses;
 
+  WorldImpactBuilderImpl();
+
+  WorldImpactBuilderImpl.internal(
+      this._dynamicUses, this._staticUses, this._typeUses, this._constantUses);
+
   @override
   bool get isEmpty =>
       _dynamicUses == null &&
diff --git a/pkg/js_ast/lib/src/builder.dart b/pkg/js_ast/lib/src/builder.dart
index 635549b..bf9e94a 100644
--- a/pkg/js_ast/lib/src/builder.dart
+++ b/pkg/js_ast/lib/src/builder.dart
@@ -1029,15 +1029,15 @@
     AsyncModifier asyncModifier;
     if (acceptString('async')) {
       if (acceptString('*')) {
-        asyncModifier = const AsyncModifier.asyncStar();
+        asyncModifier = AsyncModifier.asyncStar;
       } else {
-        asyncModifier = const AsyncModifier.async();
+        asyncModifier = AsyncModifier.async;
       }
     } else if (acceptString('sync')) {
       if (!acceptString('*')) error("Only sync* is valid - sync is implied");
-      asyncModifier = const AsyncModifier.syncStar();
+      asyncModifier = AsyncModifier.syncStar;
     } else {
-      asyncModifier = const AsyncModifier.sync();
+      asyncModifier = AsyncModifier.sync;
     }
     expectCategory(LBRACE);
     Block block = parseBlock();
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index 28923cf..d2970d4 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -1448,7 +1448,7 @@
   final Block body;
   final AsyncModifier asyncModifier;
 
-  Fun(this.params, this.body, {this.asyncModifier: const AsyncModifier.sync()});
+  Fun(this.params, this.body, {this.asyncModifier: AsyncModifier.sync});
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitFun(this);
 
@@ -1471,27 +1471,26 @@
 }
 
 class AsyncModifier {
+  final int index;
   final bool isAsync;
   final bool isYielding;
   final String description;
 
-  const AsyncModifier.sync()
-      : isAsync = false,
-        isYielding = false,
-        description = "sync";
-  const AsyncModifier.async()
-      : isAsync = true,
-        isYielding = false,
-        description = "async";
-  const AsyncModifier.asyncStar()
-      : isAsync = true,
-        isYielding = true,
-        description = "async*";
-  const AsyncModifier.syncStar()
-      : isAsync = false,
-        isYielding = true,
-        description = "sync*";
-  toString() => description;
+  const AsyncModifier(this.index, this.description,
+      {this.isAsync, this.isYielding});
+
+  static const AsyncModifier sync =
+      const AsyncModifier(0, "sync", isAsync: false, isYielding: false);
+  static const AsyncModifier async =
+      const AsyncModifier(1, "async", isAsync: true, isYielding: false);
+  static const AsyncModifier asyncStar =
+      const AsyncModifier(2, "async*", isAsync: true, isYielding: true);
+  static const AsyncModifier syncStar =
+      const AsyncModifier(3, "sync*", isAsync: false, isYielding: true);
+
+  static const List<AsyncModifier> values = [sync, async, asyncStar, syncStar];
+
+  String toString() => description;
 }
 
 class PropertyAccess extends Expression {
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index d5eba7b..399ab56 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -620,17 +620,17 @@
     }
     out(")");
     switch (fun.asyncModifier) {
-      case const AsyncModifier.sync():
+      case AsyncModifier.sync:
         break;
-      case const AsyncModifier.async():
+      case AsyncModifier.async:
         out(' ', isWhitespace: true);
         out('async');
         break;
-      case const AsyncModifier.syncStar():
+      case AsyncModifier.syncStar:
         out(' ', isWhitespace: true);
         out('sync*');
         break;
-      case const AsyncModifier.asyncStar():
+      case AsyncModifier.asyncStar:
         out(' ', isWhitespace: true);
         out('async*');
         break;
diff --git a/tests/compiler/dart2js/end_to_end/exit_code_test.dart b/tests/compiler/dart2js/end_to_end/exit_code_test.dart
index 082edeb..e424fad 100644
--- a/tests/compiler/dart2js/end_to_end/exit_code_test.dart
+++ b/tests/compiler/dart2js/end_to_end/exit_code_test.dart
@@ -22,8 +22,10 @@
 import 'package:compiler/src/elements/entities.dart';
 import 'package:compiler/src/js_backend/js_backend.dart';
 import 'package:compiler/src/null_compiler_output.dart';
+import 'package:compiler/src/serialization/serialization.dart';
 import 'package:compiler/src/options.dart' show CompilerOptions;
 import 'package:compiler/src/universe/world_impact.dart';
+import 'package:compiler/src/world.dart';
 import 'diagnostic_reporter_helper.dart';
 
 class TestCompiler extends apiimpl.CompilerImpl {
@@ -108,9 +110,10 @@
             useNewSourceInfo: compiler.options.useNewSourceInfo);
 
   @override
-  WorldImpact generateCode(WorkItem work) {
+  WorldImpact generateCode(WorkItem work, JClosedWorld closedWorld,
+      EntityLookup entityLookup, ComponentLookup componentLookup) {
     compiler.test('Compiler.codegen');
-    return super.generateCode(work);
+    return super.generateCode(work, closedWorld, entityLookup, componentLookup);
   }
 }