Support modular code generation

Change-Id: Id5511296eb0b6acf812bc464d71efa4019f211d7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103523
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 44c29be..d9a48b1 100644
--- a/pkg/compiler/lib/src/backend_strategy.dart
+++ b/pkg/compiler/lib/src/backend_strategy.dart
@@ -5,6 +5,7 @@
 library dart2js.backend_strategy;
 
 import 'common.dart';
+import 'common/codegen.dart';
 import 'common/tasks.dart';
 import 'deferred_load.dart' show OutputUnitData;
 import 'enqueue.dart';
@@ -14,6 +15,7 @@
 import 'js_backend/inferred_data.dart';
 import 'js_backend/interceptor_data.dart';
 import 'js_backend/native_data.dart';
+import 'serialization/serialization.dart';
 import 'ssa/ssa.dart';
 import 'universe/codegen_world_builder.dart';
 import 'universe/world_builder.dart';
@@ -40,7 +42,8 @@
       OneShotInterceptorData oneShotInterceptorData);
 
   /// Creates the [WorkItemBuilder] used by the codegen enqueuer.
-  WorkItemBuilder createCodegenWorkItemBuilder(JClosedWorld closedWorld);
+  WorkItemBuilder createCodegenWorkItemBuilder(
+      JClosedWorld closedWorld, CodegenResults codegenResults);
 
   /// Creates the [SsaBuilder] used for the element model.
   SsaBuilder createSsaBuilder(
@@ -55,4 +58,15 @@
   /// Creates the [TypesInferrer] used by this strategy.
   TypesInferrer createTypesInferrer(
       JClosedWorld closedWorld, InferredDataBuilder inferredDataBuilder);
+
+  /// Calls [f] for every member that needs to be serialized for modular code
+  /// generation and returns an [EntityWriter] for encoding these members in
+  /// the serialized data.
+  ///
+  /// The needed members include members computed on demand during non-modular
+  /// code generation, such as constructor bodies and and generator bodies.
+  EntityWriter forEachCodegenMember(void Function(MemberEntity member) f);
+
+  /// Prepare [source] to deserialize modular code generation data.
+  void prepareCodegenReader(DataSource source);
 }
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index 576cfc4..801a18f 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -84,6 +84,10 @@
   static const String dillDependencies = '--dill-dependencies';
   static const String readData = '--read-data';
   static const String writeData = '--write-data';
+  static const String readCodegen = '--read-codegen';
+  static const String writeCodegen = '--write-codegen';
+  static const String codegenShard = '--codegen-shard';
+  static const String codegenShards = '--codegen-shards';
   static const String cfeOnly = '--cfe-only';
   static const String debugGlobalInference = '--debug-global-inference';
 
diff --git a/pkg/compiler/lib/src/common/codegen.dart b/pkg/compiler/lib/src/common/codegen.dart
index 153c54b..928e70d 100644
--- a/pkg/compiler/lib/src/common/codegen.dart
+++ b/pkg/compiler/lib/src/common/codegen.dart
@@ -6,18 +6,22 @@
 
 import 'package:js_ast/src/precedence.dart' as js show PRIMARY;
 
+import '../common.dart';
 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 '../inferrer/types.dart';
 import '../io/source_information.dart';
 import '../js/js.dart' as js;
+import '../js_backend/backend.dart';
 import '../js_backend/namer.dart';
 import '../js_emitter/code_emitter_task.dart' show Emitter;
 import '../native/behavior.dart';
 import '../serialization/serialization.dart';
+import '../ssa/ssa.dart';
 import '../universe/feature.dart';
 import '../universe/selector.dart';
 import '../universe/use.dart' show ConstantUse, DynamicUse, StaticUse, TypeUse;
@@ -414,6 +418,58 @@
   }
 }
 
+/// Interface for reading the code generation results for all [MemberEntity]s.
+abstract class CodegenResults {
+  GlobalTypeInferenceResults get globalTypeInferenceResults;
+  CodegenInputs get codegenInputs;
+  CodegenResult getCodegenResults(MemberEntity member);
+}
+
+/// Code generation results computed on-demand.
+///
+/// This is used in the non-modular codegen enqueuer driving code generation.
+class OnDemandCodegenResults extends CodegenResults {
+  @override
+  final GlobalTypeInferenceResults globalTypeInferenceResults;
+  @override
+  final CodegenInputs codegenInputs;
+  final SsaFunctionCompiler _functionCompiler;
+
+  OnDemandCodegenResults(this.globalTypeInferenceResults, this.codegenInputs,
+      this._functionCompiler);
+
+  @override
+  CodegenResult getCodegenResults(MemberEntity member) {
+    return _functionCompiler.compile(member);
+  }
+}
+
+/// Deserialized code generation results.
+///
+/// This is used for modular code generation.
+class DeserializedCodegenResults extends CodegenResults {
+  @override
+  final GlobalTypeInferenceResults globalTypeInferenceResults;
+  @override
+  final CodegenInputs codegenInputs;
+
+  final Map<MemberEntity, CodegenResult> _map;
+
+  DeserializedCodegenResults(
+      this.globalTypeInferenceResults, this.codegenInputs, this._map);
+
+  @override
+  CodegenResult getCodegenResults(MemberEntity member) {
+    CodegenResult result = _map[member];
+    if (result == null) {
+      failedAt(member,
+          "No codegen results from $member (${identityHashCode(member)}).");
+    }
+    return result;
+  }
+}
+
+/// The code generation result for a single [MemberEntity].
 class CodegenResult {
   static const String tag = 'codegen-result';
 
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 2988fbb..85057ab 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -11,6 +11,7 @@
 
 import '../compiler_new.dart' as api;
 import 'backend_strategy.dart';
+import 'common/codegen.dart';
 import 'common/names.dart' show Selectors, Uris;
 import 'common/tasks.dart' show CompilerTask, GenericTask, Measurer;
 import 'common/work.dart' show WorkItem;
@@ -171,7 +172,8 @@
       enqueuer,
       dumpInfoTask = new DumpInfoTask(this),
       selfTask,
-      serializationTask = new SerializationTask(this, measurer),
+      serializationTask = new SerializationTask(
+          options, reporter, provider, outputProvider, measurer),
     ];
 
     tasks.addAll(backend.tasks);
@@ -227,13 +229,14 @@
     reporter.log('Compiling $uri (${options.buildId})');
 
     if (options.readDataUri != null) {
-      GlobalTypeInferenceResults results =
-          await serializationTask.deserialize();
+      GlobalTypeInferenceResults globalTypeInferenceResults =
+          await serializationTask.deserializeGlobalTypeInference(
+              environment, abstractValueStrategy);
       if (options.debugGlobalInference) {
-        performGlobalTypeInference(results.closedWorld);
+        performGlobalTypeInference(globalTypeInferenceResults.closedWorld);
         return;
       }
-      emitJavaScriptCode(results);
+      await generateJavaScriptCode(globalTypeInferenceResults);
     } else {
       KernelResult result = await kernelLoader.load(uri);
       reporter.log("Kernel load complete");
@@ -263,6 +266,31 @@
     }
   }
 
+  void generateJavaScriptCode(
+      GlobalTypeInferenceResults globalTypeInferenceResults) async {
+    JClosedWorld closedWorld = globalTypeInferenceResults.closedWorld;
+    backendStrategy.registerJClosedWorld(closedWorld);
+    if (options.showInternalProgress) reporter.log('Compiling...');
+    phase = PHASE_COMPILING;
+    CodegenInputs codegenInputs =
+        backend.onCodegenStart(globalTypeInferenceResults);
+
+    if (options.readCodegenUri != null) {
+      CodegenResults codegenResults =
+          await serializationTask.deserializeCodegen(
+              backendStrategy, globalTypeInferenceResults, codegenInputs);
+      runCodegenEnqueuer(codegenResults);
+    } else {
+      CodegenResults codegenResults = new OnDemandCodegenResults(
+          globalTypeInferenceResults, codegenInputs, backend.functionCompiler);
+      if (options.writeCodegenUri != null) {
+        serializationTask.serializeCodegen(backendStrategy, codegenResults);
+      } else {
+        runCodegenEnqueuer(codegenResults);
+      }
+    }
+  }
+
   /// Clear the internal compiler state to prevent memory leaks when invoking
   /// the compiler multiple times (e.g. in batch mode).
   // TODO(ahe): implement a better mechanism where we can store
@@ -355,15 +383,13 @@
         mainFunction, closedWorld, inferredDataBuilder);
   }
 
-  void emitJavaScriptCode(GlobalTypeInferenceResults globalInferenceResults) {
+  void runCodegenEnqueuer(CodegenResults codegenResults) {
+    GlobalTypeInferenceResults globalInferenceResults =
+        codegenResults.globalTypeInferenceResults;
     JClosedWorld closedWorld = globalInferenceResults.closedWorld;
-    backendStrategy.registerJClosedWorld(closedWorld);
-    if (options.showInternalProgress) reporter.log('Compiling...');
-    phase = PHASE_COMPILING;
-
-    CodegenInputs codegen = backend.onCodegenStart(globalInferenceResults);
+    CodegenInputs codegenInputs = codegenResults.codegenInputs;
     Enqueuer codegenEnqueuer = enqueuer.createCodegenEnqueuer(
-        closedWorld, globalInferenceResults, codegen);
+        closedWorld, globalInferenceResults, codegenInputs, codegenResults);
     _codegenWorldBuilder = codegenEnqueuer.worldBuilder;
 
     FunctionEntity mainFunction = closedWorld.elementEnvironment.mainFunction;
@@ -375,14 +401,14 @@
       codegenWorldForTesting = codegenWorld;
     }
     int programSize = backend.assembleProgram(closedWorld,
-        globalInferenceResults.inferredData, codegen, codegenWorld);
+        globalInferenceResults.inferredData, codegenInputs, codegenWorld);
 
     if (options.dumpInfo) {
       dumpInfoTask.reportSize(programSize);
       dumpInfoTask.dumpInfo(closedWorld, globalInferenceResults);
     }
 
-    backend.onCodegenEnd(codegen);
+    backend.onCodegenEnd(codegenInputs);
 
     checkQueue(codegenEnqueuer);
   }
@@ -397,7 +423,8 @@
         GlobalTypeInferenceResults globalInferenceResults =
             performGlobalTypeInference(closedWorld);
         if (options.writeDataUri != null) {
-          serializationTask.serialize(globalInferenceResults);
+          serializationTask
+              .serializeGlobalTypeInference(globalInferenceResults);
           return;
         }
         if (options.testMode) {
@@ -415,7 +442,7 @@
               worldData);
         }
         if (stopAfterTypeInference) return;
-        emitJavaScriptCode(globalInferenceResults);
+        generateJavaScriptCode(globalInferenceResults);
       }
     });
   }
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index ebdc6a0..7348d20 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -110,6 +110,10 @@
   Uri sourceMapOut;
   Uri readDataUri;
   Uri writeDataUri;
+  Uri readCodegenUri;
+  Uri writeCodegenUri;
+  int codegenShard;
+  int codegenShards;
   List<String> bazelPaths;
   Uri packageConfig = null;
   Uri packageRoot = null;
@@ -129,7 +133,8 @@
   int optimizationLevel = null;
   Uri platformBinaries;
   Map<String, String> environment = new Map<String, String>();
-  CompilationStrategy compilationStrategy = CompilationStrategy.direct;
+  ReadStrategy readStrategy = ReadStrategy.fromDart;
+  WriteStrategy writeStrategy = WriteStrategy.toJs;
 
   void passThrough(String argument) => options.add(argument);
   void ignoreOption(String argument) {}
@@ -246,13 +251,12 @@
   }
 
   void setReadData(String argument) {
-    if (compilationStrategy == CompilationStrategy.toData) {
-      fail("Cannot read and write serialized simultaneously.");
-    }
     if (argument != Flags.readData) {
       readDataUri = nativeToUri(extractPath(argument, isDirectory: false));
     }
-    compilationStrategy = CompilationStrategy.fromData;
+    if (readStrategy != ReadStrategy.fromCodegen) {
+      readStrategy = ReadStrategy.fromData;
+    }
   }
 
   void setDillDependencies(String argument) {
@@ -263,17 +267,58 @@
   }
 
   void setCfeOnly(String argument) {
-    compilationStrategy = CompilationStrategy.toKernel;
+    if (writeStrategy == WriteStrategy.toData) {
+      fail("Cannot use ${Flags.cfeOnly} "
+          "and write serialized data simultaneously.");
+    }
+    if (writeStrategy == WriteStrategy.toCodegen) {
+      fail("Cannot use ${Flags.cfeOnly} "
+          "and write serialized codegen simultaneously.");
+    }
+    writeStrategy = WriteStrategy.toKernel;
+  }
+
+  void setReadCodegen(String argument) {
+    if (argument != Flags.readCodegen) {
+      readCodegenUri = nativeToUri(extractPath(argument, isDirectory: false));
+    }
+    readStrategy = ReadStrategy.fromCodegen;
   }
 
   void setWriteData(String argument) {
-    if (compilationStrategy == CompilationStrategy.fromData) {
-      fail("Cannot read and write serialized simultaneously.");
+    if (writeStrategy == WriteStrategy.toKernel) {
+      fail("Cannot use ${Flags.cfeOnly} "
+          "and write serialized data simultaneously.");
+    }
+    if (writeStrategy == WriteStrategy.toCodegen) {
+      fail("Cannot write serialized data and codegen simultaneously.");
     }
     if (argument != Flags.writeData) {
       writeDataUri = nativeToUri(extractPath(argument, isDirectory: false));
     }
-    compilationStrategy = CompilationStrategy.toData;
+    writeStrategy = WriteStrategy.toData;
+  }
+
+  void setWriteCodegen(String argument) {
+    if (writeStrategy == WriteStrategy.toKernel) {
+      fail("Cannot use ${Flags.cfeOnly} "
+          "and write serialized codegen simultaneously.");
+    }
+    if (writeStrategy == WriteStrategy.toData) {
+      fail("Cannot write serialized data and codegen data simultaneously.");
+    }
+    if (argument != Flags.writeCodegen) {
+      writeCodegenUri = nativeToUri(extractPath(argument, isDirectory: false));
+    }
+    writeStrategy = WriteStrategy.toCodegen;
+  }
+
+  void setCodegenShard(String argument) {
+    codegenShard = int.parse(extractParameter(argument));
+  }
+
+  void setCodegenShards(String argument) {
+    codegenShards = int.parse(extractParameter(argument));
   }
 
   void setDumpInfo(String argument) {
@@ -349,6 +394,12 @@
     new OptionHandler('${Flags.dillDependencies}=.+', setDillDependencies),
     new OptionHandler('${Flags.readData}|${Flags.readData}=.+', setReadData),
     new OptionHandler('${Flags.writeData}|${Flags.writeData}=.+', setWriteData),
+    new OptionHandler(
+        '${Flags.readCodegen}|${Flags.readCodegen}=.+', setReadCodegen),
+    new OptionHandler(
+        '${Flags.writeCodegen}|${Flags.writeCodegen}=.+', setWriteCodegen),
+    new OptionHandler('${Flags.codegenShard}=.+', setCodegenShard),
+    new OptionHandler('${Flags.codegenShards}=.+', setCodegenShards),
     new OptionHandler(Flags.cfeOnly, setCfeOnly),
     new OptionHandler(Flags.debugGlobalInference, passThrough),
     new OptionHandler('--out=.+|-o.*', setOutput, multipleArguments: true),
@@ -511,28 +562,86 @@
 
   String scriptName = arguments[0];
 
-  switch (compilationStrategy) {
-    case CompilationStrategy.direct:
+  switch (writeStrategy) {
+    case WriteStrategy.toJs:
       out ??= currentDirectory.resolve('out.js');
       break;
-    case CompilationStrategy.toKernel:
+    case WriteStrategy.toKernel:
       out ??= currentDirectory.resolve('out.dill');
       options.add(Flags.cfeOnly);
+      if (readStrategy == ReadStrategy.fromData) {
+        fail("Cannot use ${Flags.cfeOnly} "
+            "and read serialized data simultaneously.");
+      } else if (readStrategy == ReadStrategy.fromCodegen) {
+        fail("Cannot use ${Flags.cfeOnly} "
+            "and read serialized codegen simultaneously.");
+      }
       break;
-    case CompilationStrategy.toData:
+    case WriteStrategy.toData:
       out ??= currentDirectory.resolve('out.dill');
       writeDataUri ??= currentDirectory.resolve('$out.data');
       options.add('${Flags.writeData}=${writeDataUri}');
+      if (readStrategy == ReadStrategy.fromData) {
+        fail("Cannot read and write serialized data simultaneously.");
+      } else if (readStrategy == ReadStrategy.fromCodegen) {
+        fail("Cannot read serialized codegen and "
+            "write serialized data simultaneously.");
+      }
       break;
-    case CompilationStrategy.fromData:
-      out ??= currentDirectory.resolve('out.js');
+    case WriteStrategy.toCodegen:
+      // TODO(johnniwinther): Avoid the need for an [out] value in this case or
+      // use [out] to pass [writeCodegenUri].
+      out ??= currentDirectory.resolve('out');
+      writeCodegenUri ??= currentDirectory.resolve('$out.code');
+      options.add('${Flags.writeCodegen}=${writeCodegenUri}');
+      if (readStrategy == ReadStrategy.fromCodegen) {
+        fail("Cannot read and write serialized codegen simultaneously.");
+      }
+      if (readStrategy != ReadStrategy.fromData) {
+        fail("Can only write serialized codegen from serialized data.");
+      }
+      if (codegenShards == null) {
+        fail("Cannot write serialized codegen without setting "
+            "${Flags.codegenShards}.");
+      } else if (codegenShards <= 0) {
+        fail("${Flags.codegenShards} must be a positive integer.");
+      }
+      if (codegenShard == null) {
+        fail("Cannot write serialized codegen without setting "
+            "${Flags.codegenShard}.");
+      } else if (codegenShard < 0 || codegenShard >= codegenShards) {
+        fail("${Flags.codegenShard} must be between 0 and "
+            "${Flags.codegenShards}.");
+      }
+      options.add('${Flags.codegenShard}=$codegenShard');
+      options.add('${Flags.codegenShards}=$codegenShards');
+      break;
+  }
+  switch (readStrategy) {
+    case ReadStrategy.fromDart:
+      break;
+    case ReadStrategy.fromData:
       readDataUri ??= currentDirectory.resolve('$scriptName.data');
       options.add('${Flags.readData}=${readDataUri}');
       break;
+    case ReadStrategy.fromCodegen:
+      readDataUri ??= currentDirectory.resolve('$scriptName.data');
+      options.add('${Flags.readData}=${readDataUri}');
+      readCodegenUri ??= currentDirectory.resolve('$scriptName.code');
+      options.add('${Flags.readCodegen}=${readCodegenUri}');
+      if (codegenShards == null) {
+        fail("Cannot write serialized codegen without setting "
+            "${Flags.codegenShards}.");
+      } else if (codegenShards <= 0) {
+        fail("${Flags.codegenShards} must be a positive integer.");
+      }
+      options.add('${Flags.codegenShards}=$codegenShards');
   }
   options.add('--out=$out');
-  sourceMapOut = Uri.parse('$out.map');
-  options.add('--source-map=${sourceMapOut}');
+  if (writeStrategy == WriteStrategy.toJs) {
+    sourceMapOut = Uri.parse('$out.map');
+    options.add('--source-map=${sourceMapOut}');
+  }
 
   RandomAccessFileOutputProvider outputProvider =
       new RandomAccessFileOutputProvider(out, sourceMapOut,
@@ -544,86 +653,98 @@
     }
     writeString(
         Uri.parse('$out.deps'), getDepsOutput(inputProvider.getSourceUris()));
-    switch (compilationStrategy) {
-      case CompilationStrategy.direct:
-        int dartCharactersRead = inputProvider.dartCharactersRead;
-        int jsCharactersWritten =
-            outputProvider.totalCharactersWrittenJavaScript;
-        int jsCharactersPrimary = outputProvider.totalCharactersWrittenPrimary;
-        print('Compiled '
-            '${_formatCharacterCount(dartCharactersRead)} characters Dart to '
-            '${_formatCharacterCount(jsCharactersWritten)} characters '
-            'JavaScript in '
-            '${_formatDurationAsSeconds(wallclock.elapsed)} seconds');
 
-        diagnosticHandler
-            .info('${_formatCharacterCount(jsCharactersPrimary)} characters '
-                'JavaScript in '
-                '${relativize(currentDirectory, out, Platform.isWindows)}');
-        if (outputSpecified || diagnosticHandler.verbose) {
-          String input = uriPathToNative(scriptName);
-          String output = relativize(currentDirectory, out, Platform.isWindows);
-          print('Dart file ($input) compiled to JavaScript: $output');
-          if (diagnosticHandler.verbose) {
-            var files = outputProvider.allOutputFiles;
-            int jsCount = files.where((f) => f.endsWith('.js')).length;
-            print('Emitted file $jsCount JavaScript files.');
-          }
-        }
+    String input = uriPathToNative(scriptName);
+    int inputSize;
+    String processName;
+    String inputName;
+
+    int outputSize;
+    int primaryOutputSize;
+    String outputName;
+
+    String summary;
+    switch (readStrategy) {
+      case ReadStrategy.fromDart:
+        inputName = 'characters Dart';
+        inputSize = inputProvider.dartCharactersRead;
+        summary = 'Dart file $input ';
         break;
-      case CompilationStrategy.toKernel:
-        int dartCharactersRead = inputProvider.dartCharactersRead;
-        int dataBytesWritten = outputProvider.totalDataWritten;
-        print('Compiled '
-            '${_formatCharacterCount(dartCharactersRead)} characters Dart to '
-            '${_formatCharacterCount(dataBytesWritten)} kernel bytes in '
-            '${_formatDurationAsSeconds(wallclock.elapsed)} seconds');
-        String input = uriPathToNative(scriptName);
-        String dillOutput =
-            relativize(currentDirectory, out, Platform.isWindows);
-        print('Dart file ($input) compiled to ${dillOutput}.');
+      case ReadStrategy.fromData:
+        inputName = 'bytes data';
+        inputSize = inputProvider.dartCharactersRead;
+        String dataInput =
+            relativize(currentDirectory, readDataUri, Platform.isWindows);
+        summary = 'Data files $input and $dataInput ';
         break;
-      case CompilationStrategy.toData:
-        int dartCharactersRead = inputProvider.dartCharactersRead;
-        int dataBytesWritten = outputProvider.totalDataWritten;
-        print('Serialized '
-            '${_formatCharacterCount(dartCharactersRead)} characters Dart to '
-            '${_formatCharacterCount(dataBytesWritten)} bytes data in '
-            '${_formatDurationAsSeconds(wallclock.elapsed)} seconds');
-        String input = uriPathToNative(scriptName);
-        String dillOutput =
-            relativize(currentDirectory, out, Platform.isWindows);
+      case ReadStrategy.fromCodegen:
+        inputName = 'bytes data';
+        inputSize = inputProvider.dartCharactersRead;
+        String dataInput =
+            relativize(currentDirectory, readDataUri, Platform.isWindows);
+        String codeInput =
+            relativize(currentDirectory, readCodegenUri, Platform.isWindows);
+        summary = 'Data files $input, $dataInput and '
+            '${codeInput}[0-${codegenShards - 1}] ';
+        break;
+    }
+
+    switch (writeStrategy) {
+      case WriteStrategy.toJs:
+        processName = 'Compiled';
+        outputName = 'characters JavaScript';
+        outputSize = outputProvider.totalCharactersWrittenJavaScript;
+        primaryOutputSize = outputProvider.totalCharactersWrittenPrimary;
+        String output = relativize(currentDirectory, out, Platform.isWindows);
+        summary += 'compiled to JavaScript: ${output}';
+        break;
+      case WriteStrategy.toKernel:
+        processName = 'Compiled';
+        outputName = 'kernel bytes';
+        outputSize = outputProvider.totalDataWritten;
+        String output = relativize(currentDirectory, out, Platform.isWindows);
+        summary += 'compiled to dill: ${output}.';
+        break;
+      case WriteStrategy.toData:
+        processName = 'Serialized';
+        outputName = 'bytes data';
+        outputSize = outputProvider.totalDataWritten;
+        String output = relativize(currentDirectory, out, Platform.isWindows);
         String dataOutput =
             relativize(currentDirectory, writeDataUri, Platform.isWindows);
-        print('Dart file ($input) serialized to '
-            '${dillOutput} and ${dataOutput}.');
+        summary += 'serialized to dill and data: ${output} and ${dataOutput}.';
         break;
-      case CompilationStrategy.fromData:
-        int dataCharactersRead = inputProvider.dartCharactersRead;
-        int jsCharactersWritten =
-            outputProvider.totalCharactersWrittenJavaScript;
-        int jsCharactersPrimary = outputProvider.totalCharactersWrittenPrimary;
-        print('Compiled '
-            '${_formatCharacterCount(dataCharactersRead)} bytes data to '
-            '${_formatCharacterCount(jsCharactersWritten)} characters '
-            'JavaScript in '
-            '${_formatDurationAsSeconds(wallclock.elapsed)} seconds');
+      case WriteStrategy.toCodegen:
+        processName = 'Serialized';
+        outputName = 'bytes data';
+        outputSize = outputProvider.totalDataWritten;
+        String codeOutput =
+            relativize(currentDirectory, writeCodegenUri, Platform.isWindows);
+        summary += 'serialized to codegen data: '
+            '${codeOutput}${codegenShard}.';
+        break;
+    }
 
-        diagnosticHandler
-            .info('${_formatCharacterCount(jsCharactersPrimary)} characters '
-                'JavaScript in '
-                '${relativize(currentDirectory, out, Platform.isWindows)}');
-        if (outputSpecified || diagnosticHandler.verbose) {
-          String input = uriPathToNative(scriptName);
-          String output = relativize(currentDirectory, out, Platform.isWindows);
-          print('Dart file ($input) compiled to JavaScript: $output');
-          if (diagnosticHandler.verbose) {
-            var files = outputProvider.allOutputFiles;
-            int jsCount = files.where((f) => f.endsWith('.js')).length;
-            print('Emitted file $jsCount JavaScript files.');
-          }
+    print('$processName '
+        '${_formatCharacterCount(inputSize)} $inputName to '
+        '${_formatCharacterCount(outputSize)} $outputName in '
+        '${_formatDurationAsSeconds(wallclock.elapsed)} seconds');
+    if (primaryOutputSize != null) {
+      diagnosticHandler
+          .info('${_formatCharacterCount(primaryOutputSize)} $outputName '
+              'in ${relativize(currentDirectory, out, Platform.isWindows)}');
+    }
+    if (writeStrategy == WriteStrategy.toJs) {
+      if (outputSpecified || diagnosticHandler.verbose) {
+        print(summary);
+        if (diagnosticHandler.verbose) {
+          var files = outputProvider.allOutputFiles;
+          int jsCount = files.where((f) => f.endsWith('.js')).length;
+          print('Emitted file $jsCount JavaScript files.');
         }
-        break;
+      }
+    } else {
+      print(summary);
     }
 
     return result;
@@ -1043,4 +1164,5 @@
   });
 }
 
-enum CompilationStrategy { direct, toKernel, toData, fromData }
+enum ReadStrategy { fromDart, fromData, fromCodegen }
+enum WriteStrategy { toKernel, toData, toCodegen, toJs }
diff --git a/pkg/compiler/lib/src/elements/indexed.dart b/pkg/compiler/lib/src/elements/indexed.dart
index e83b8a0..6a6afc4 100644
--- a/pkg/compiler/lib/src/elements/indexed.dart
+++ b/pkg/compiler/lib/src/elements/indexed.dart
@@ -56,6 +56,8 @@
 
 /// Base implementation for an index based map of entities of type [E].
 abstract class EntityMapBase<E extends _Indexed> {
+  bool _closed = false;
+
   int _size = 0;
   List<E> _list = <E>[];
 
@@ -67,6 +69,14 @@
 
   /// Returns the number (null and non-null) entities in the map.
   int get length => _list.length;
+
+  /// Closes the entity map, prohibiting further registration.
+  ///
+  /// This is used to ensure that no new entities are added while serializing
+  /// modular code generation data.
+  void close() {
+    _closed = true;
+  }
 }
 
 /// Index based map of entities of type [E].
@@ -76,6 +86,8 @@
   /// The index of [entity] is set to match its index in the entity list in this
   /// map.
   E0 register<E0 extends E>(E0 entity) {
+    assert(
+        !_closed, "Trying to register $entity @ ${_list.length} when closed.");
     assert(entity != null);
     assert(entity._index == null);
     entity._index = _list.length;
@@ -134,6 +146,8 @@
   /// The index of [entity] is set to match its index in the entity and data
   /// lists in this map.
   E0 register<E0 extends E, D0 extends D>(E0 entity, D0 data) {
+    assert(
+        !_closed, "Trying to register $entity @ ${_list.length} when closed.");
     assert(entity != null);
     assert(entity._index == null);
     assert(
@@ -199,6 +213,8 @@
   /// environment lists in this map.
   E0 register<E0 extends E, D0 extends D, V0 extends V>(
       E0 entity, D0 data, V0 env) {
+    assert(
+        !_closed, "Trying to register $entity @ ${_list.length} when closed.");
     assert(entity != null);
     assert(entity._index == null);
     assert(
diff --git a/pkg/compiler/lib/src/enqueue.dart b/pkg/compiler/lib/src/enqueue.dart
index 5439e6e..81ca07d 100644
--- a/pkg/compiler/lib/src/enqueue.dart
+++ b/pkg/compiler/lib/src/enqueue.dart
@@ -6,6 +6,7 @@
 
 import 'dart:collection' show Queue;
 
+import 'common/codegen.dart';
 import 'common/tasks.dart' show CompilerTask;
 import 'common/work.dart' show WorkItem;
 import 'common.dart';
@@ -68,9 +69,10 @@
   Enqueuer createCodegenEnqueuer(
       JClosedWorld closedWorld,
       GlobalTypeInferenceResults globalInferenceResults,
-      CodegenInputs codegen) {
-    Enqueuer enqueuer = compiler.backend.createCodegenEnqueuer(
-        this, compiler, closedWorld, globalInferenceResults, codegen)
+      CodegenInputs codegenInputs,
+      CodegenResults codegenResults) {
+    Enqueuer enqueuer = compiler.backend.createCodegenEnqueuer(this, compiler,
+        closedWorld, globalInferenceResults, codegenInputs, codegenResults)
       ..onEmptyForTesting = compiler.onCodegenQueueEmptyForTesting;
     if (retainDataForTesting) {
       codegenEnqueuerForTesting = enqueuer;
diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart
index ea610ea..3ced9a0 100644
--- a/pkg/compiler/lib/src/js_backend/backend.dart
+++ b/pkg/compiler/lib/src/js_backend/backend.dart
@@ -535,7 +535,8 @@
       Compiler compiler,
       JClosedWorld closedWorld,
       GlobalTypeInferenceResults globalInferenceResults,
-      CodegenInputs codegen) {
+      CodegenInputs codegen,
+      CodegenResults codegenResults) {
     OneShotInterceptorData oneShotInterceptorData = new OneShotInterceptorData(
         closedWorld.interceptorData,
         closedWorld.commonElements,
@@ -555,7 +556,8 @@
             closedWorld,
             compiler.abstractValueStrategy.createSelectorStrategy(),
             oneShotInterceptorData),
-        compiler.backendStrategy.createCodegenWorkItemBuilder(closedWorld),
+        compiler.backendStrategy
+            .createCodegenWorkItemBuilder(closedWorld, codegenResults),
         new CodegenEnqueuerListener(
             elementEnvironment,
             commonElements,
@@ -568,10 +570,14 @@
 
   Map<MemberEntity, WorldImpact> codegenImpactsForTesting;
 
-  WorldImpact generateCode(WorkItem work, JClosedWorld closedWorld,
-      EntityLookup entityLookup, ComponentLookup componentLookup) {
+  WorldImpact generateCode(
+      WorkItem work,
+      JClosedWorld closedWorld,
+      CodegenResults codegenResults,
+      EntityLookup entityLookup,
+      ComponentLookup componentLookup) {
     MemberEntity member = work.element;
-    CodegenResult result = functionCompiler.compile(member);
+    CodegenResult result = codegenResults.getCodegenResults(member);
     if (compiler.options.testMode) {
       bool useDataKinds = true;
       List<Object> data = [];
@@ -617,9 +623,9 @@
 
   /// Generates the output and returns the total size of the generated code.
   int assembleProgram(JClosedWorld closedWorld, InferredData inferredData,
-      CodegenInputs codegen, CodegenWorld codegenWorld) {
+      CodegenInputs codegenInputs, CodegenWorld codegenWorld) {
     int programSize = emitterTask.assembleProgram(
-        _namer, closedWorld, inferredData, codegen, codegenWorld);
+        _namer, closedWorld, inferredData, codegenInputs, codegenWorld);
     closedWorld.noSuchMethodData.emitDiagnostic(reporter);
     return programSize;
   }
diff --git a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
index e5c066f..5393c9b 100644
--- a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
+++ b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
@@ -107,10 +107,10 @@
       Namer namer,
       JClosedWorld closedWorld,
       InferredData inferredData,
-      CodegenInputs codegen,
+      CodegenInputs codegenInputs,
       CodegenWorld codegenWorld) {
     return measure(() {
-      _finalizeRti(codegen, codegenWorld);
+      _finalizeRti(codegenInputs, codegenWorld);
       ProgramBuilder programBuilder = new ProgramBuilder(
           _compiler.options,
           _compiler.reporter,
@@ -124,7 +124,7 @@
           closedWorld.rtiNeed,
           closedWorld.interceptorData,
           typeTestRegistry.rtiChecks,
-          codegen.rtiEncoder,
+          codegenInputs.rtiEncoder,
           codegenWorld.oneShotInterceptorData,
           _backend.customElementsCodegenAnalysis,
           _backend.generatedCode,
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 5356e84..5c46220 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -441,6 +441,31 @@
     source.end(tag);
   }
 
+  /// Prepares the entity maps for codegen serialization and returns the member
+  /// index limit for early members.
+  ///
+  /// This method creates all late members, such as constructor bodies and
+  /// generator bodies, and closes the entity maps for further registration.
+  int prepareForCodegenSerialization() {
+    int length = members.length;
+    for (int memberIndex = 0; memberIndex < length; memberIndex++) {
+      MemberEntity member = members.getEntity(memberIndex);
+      if (member == null) continue;
+      if (member is JGenerativeConstructor) {
+        getConstructorBody(members.getData(member).definition.node);
+      }
+      if (member is IndexedFunction && member.asyncMarker != AsyncMarker.SYNC) {
+        getGeneratorBody(member);
+      }
+    }
+    libraries.close();
+    classes.close();
+    members.close();
+    typedefs.close();
+    typeVariables.close();
+    return length;
+  }
+
   /// Serializes this [JsToElementMap] to [sink].
   void writeToDataSink(DataSink sink) {
     sink.begin(tag);
@@ -1604,27 +1629,27 @@
   @override
   FunctionEntity getConstructorBody(ir.Constructor node) {
     ConstructorEntity constructor = getConstructor(node);
-    return _getConstructorBody(node, constructor);
+    return _getConstructorBody(constructor);
   }
 
-  FunctionEntity _getConstructorBody(
-      ir.Constructor node, covariant IndexedConstructor constructor) {
+  JConstructorBody _getConstructorBody(IndexedConstructor constructor) {
     JConstructorDataImpl data = members.getData(constructor);
     if (data.constructorBody == null) {
       /// The constructor calls the constructor body with all parameters.
       // TODO(johnniwinther): Remove parameters that are not used in the
       //  constructor body.
       ParameterStructure parameterStructure =
-          _getParameterStructureFromFunctionNode(node.function);
+          _getParameterStructureFromFunctionNode(data.node.function);
 
       JConstructorBody constructorBody =
           createConstructorBody(constructor, parameterStructure);
       members.register<IndexedFunction, FunctionData>(
           constructorBody,
           new ConstructorBodyDataImpl(
-              node,
-              node.function,
-              new SpecialMemberDefinition(node, MemberKind.constructorBody),
+              data.node,
+              data.node.function,
+              new SpecialMemberDefinition(
+                  data.node, MemberKind.constructorBody),
               data.staticTypes));
       IndexedClass cls = constructor.enclosingClass;
       JClassEnvImpl classEnv = classes.getEnv(cls);
@@ -2079,6 +2104,7 @@
         (_injectedClassMembers[function.enclosingClass] ??= <IndexedMember>[])
             .add(generatorBody);
       }
+      _generatorBodies[function] = generatorBody;
     }
     return generatorBody;
   }
@@ -2616,3 +2642,81 @@
     return _elementMap.libraries.getEntity(index);
   }
 }
+
+/// Enum used for serialization of 'late members', that is, members normally
+/// created on demand, like constructor bodies and generator bodies.
+enum LateMemberKind {
+  constructorBody,
+  generatorBody,
+}
+
+/// Entity reader that supports member entities normally created on-demand, like
+/// constructor bodies and generator bodies.
+///
+/// The support encoding corresponds to the encoding generated by the
+/// [ClosedEntityWriter].
+class ClosedEntityReader extends EntityReader {
+  final JsKernelToElementMap _elementMap;
+
+  ClosedEntityReader(this._elementMap);
+
+  @override
+  IndexedMember readMemberFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    int index = source.readInt();
+    if (index == 0) {
+      return _readLateMemberFromDataSource(source, entityLookup);
+    } else {
+      return entityLookup.getMemberByIndex(index - 1);
+    }
+  }
+
+  IndexedMember _readLateMemberFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    LateMemberKind kind = source.readEnum(LateMemberKind.values);
+    switch (kind) {
+      case LateMemberKind.constructorBody:
+        IndexedConstructor constructor = source.readMember();
+        return _elementMap._getConstructorBody(constructor);
+      case LateMemberKind.generatorBody:
+        IndexedFunction function = source.readMember();
+        return _elementMap.getGeneratorBody(function);
+    }
+    throw new UnsupportedError("Unexpected late member kind: $kind.");
+  }
+}
+
+/// Entity writer that supports member entities normally created on-demand, like
+/// constructor bodies and generator bodies.
+///
+/// The generated encoding corresponds to the encoding read by the
+/// [ClosedEntityReader].
+class ClosedEntityWriter extends EntityWriter {
+  final int _earlyMemberIndexLimit;
+
+  final JsKernelToElementMap _elementMap;
+
+  ClosedEntityWriter(this._elementMap, this._earlyMemberIndexLimit);
+
+  @override
+  void writeMemberToDataSink(DataSink sink, IndexedMember value) {
+    if (value.memberIndex >= _earlyMemberIndexLimit) {
+      sink.writeInt(0);
+      _writeLateMemberToDataSink(sink, value);
+    } else {
+      sink.writeInt(value.memberIndex + 1);
+    }
+  }
+
+  void _writeLateMemberToDataSink(DataSink sink, IndexedMember value) {
+    if (value is JConstructorBody) {
+      sink.writeEnum(LateMemberKind.constructorBody);
+      sink.writeMember(value.constructor);
+    } else if (value is JGeneratorBody) {
+      sink.writeEnum(LateMemberKind.generatorBody);
+      sink.writeMember(value.function);
+    } else {
+      throw new UnsupportedError("Unexpected late member $value.");
+    }
+  }
+}
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index a6126a1..d24290f 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -8,7 +8,7 @@
 
 import '../backend_strategy.dart';
 import '../common.dart';
-import '../common/codegen.dart' show CodegenRegistry;
+import '../common/codegen.dart' show CodegenRegistry, CodegenResults;
 import '../common/tasks.dart';
 import '../common/work.dart';
 import '../compiler.dart';
@@ -112,12 +112,14 @@
   }
 
   @override
-  WorkItemBuilder createCodegenWorkItemBuilder(JClosedWorld closedWorld) {
+  WorkItemBuilder createCodegenWorkItemBuilder(
+      JClosedWorld closedWorld, CodegenResults codegenResults) {
     assert(_elementMap != null,
         "JsBackendStrategy.elementMap has not been created yet.");
     return new KernelCodegenWorkItemBuilder(
         _compiler.backend,
         closedWorld,
+        codegenResults,
         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
@@ -145,40 +147,65 @@
       JClosedWorld closedWorld, InferredDataBuilder inferredDataBuilder) {
     return new TypeGraphInferrer(_compiler, closedWorld, inferredDataBuilder);
   }
+
+  @override
+  void prepareCodegenReader(DataSource source) {
+    source.registerEntityReader(new ClosedEntityReader(_elementMap));
+    source.registerEntityLookup(new ClosedEntityLookup(_elementMap));
+    source.registerComponentLookup(
+        new ComponentLookup(_elementMap.programEnv.mainComponent));
+  }
+
+  @override
+  EntityWriter forEachCodegenMember(void Function(MemberEntity member) f) {
+    int earlyMemberIndexLimit = _elementMap.prepareForCodegenSerialization();
+    ClosedEntityWriter entityWriter =
+        new ClosedEntityWriter(_elementMap, earlyMemberIndexLimit);
+    for (int memberIndex = 0;
+        memberIndex < _elementMap.members.length;
+        memberIndex++) {
+      MemberEntity member = _elementMap.members.getEntity(memberIndex);
+      if (member == null || member.isAbstract) continue;
+      f(member);
+    }
+    return entityWriter;
+  }
 }
 
 class KernelCodegenWorkItemBuilder implements WorkItemBuilder {
   final JavaScriptBackend _backend;
   final JClosedWorld _closedWorld;
+  final CodegenResults _codegenResults;
   final EntityLookup _entityLookup;
   final ComponentLookup _componentLookup;
 
   KernelCodegenWorkItemBuilder(this._backend, this._closedWorld,
-      this._entityLookup, this._componentLookup);
+      this._codegenResults, this._entityLookup, this._componentLookup);
 
   @override
   WorkItem createWorkItem(MemberEntity entity) {
     if (entity.isAbstract) return null;
-    return new KernelCodegenWorkItem(
-        _backend, _closedWorld, _entityLookup, _componentLookup, entity);
+    return new KernelCodegenWorkItem(_backend, _closedWorld, _codegenResults,
+        _entityLookup, _componentLookup, entity);
   }
 }
 
 class KernelCodegenWorkItem extends WorkItem {
   final JavaScriptBackend _backend;
   final JClosedWorld _closedWorld;
+  final CodegenResults _codegenResults;
   final EntityLookup _entityLookup;
   final ComponentLookup _componentLookup;
   @override
   final MemberEntity element;
 
-  KernelCodegenWorkItem(this._backend, this._closedWorld, this._entityLookup,
-      this._componentLookup, this.element);
+  KernelCodegenWorkItem(this._backend, this._closedWorld, this._codegenResults,
+      this._entityLookup, this._componentLookup, this.element);
 
   @override
   WorldImpact run() {
     return _backend.generateCode(
-        this, _closedWorld, _entityLookup, _componentLookup);
+        this, _closedWorld, _codegenResults, _entityLookup, _componentLookup);
   }
 }
 
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 2e4ac4b..acce37c 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -76,6 +76,16 @@
   /// If this is set, the compilation stops after type inference.
   Uri writeDataUri;
 
+  /// Location from which codegen data is read.
+  ///
+  /// If this is set, the compilation starts at codegen enqueueing.
+  Uri readCodegenUri;
+
+  /// Location to which codegen data is serialized.
+  ///
+  /// If this is set, the compilation stops after code generation.
+  Uri writeCodegenUri;
+
   /// Whether to run only the CFE and emit the generated kernel file in
   /// [outputUri].
   bool cfeOnly = false;
@@ -325,6 +335,13 @@
   /// If specified, a bundle of optimizations to enable (or disable).
   int optimizationLevel = null;
 
+  /// The shard to serialize when using [writeCodegenUri].
+  int codegenShard;
+
+  /// The number of shards to serialize when using [writeCodegenUri] or to
+  /// deserialize when using [readCodegenUri].
+  int codegenShards;
+
   // -------------------------------------------------
   // Options for deprecated features
   // -------------------------------------------------
@@ -407,6 +424,10 @@
           _extractUriListOption(options, '${Flags.dillDependencies}')
       ..readDataUri = _extractUriOption(options, '${Flags.readData}=')
       ..writeDataUri = _extractUriOption(options, '${Flags.writeData}=')
+      ..readCodegenUri = _extractUriOption(options, '${Flags.readCodegen}=')
+      ..writeCodegenUri = _extractUriOption(options, '${Flags.writeCodegen}=')
+      ..codegenShard = _extractIntOption(options, '${Flags.codegenShard}=')
+      ..codegenShards = _extractIntOption(options, '${Flags.codegenShards}=')
       ..cfeOnly = _hasOption(options, Flags.cfeOnly)
       ..debugGlobalInference = _hasOption(options, Flags.debugGlobalInference);
   }
@@ -533,10 +554,15 @@
 }
 
 Uri _extractUriOption(List<String> options, String prefix) {
-  var option = _extractStringOption(options, prefix, null);
+  String option = _extractStringOption(options, prefix, null);
   return (option == null) ? null : Uri.parse(option);
 }
 
+int _extractIntOption(List<String> options, String prefix) {
+  String option = _extractStringOption(options, prefix, null);
+  return (option == null) ? null : int.parse(option);
+}
+
 bool _hasOption(List<String> options, String option) {
   return options.indexOf(option) >= 0;
 }
diff --git a/pkg/compiler/lib/src/serialization/abstract_sink.dart b/pkg/compiler/lib/src/serialization/abstract_sink.dart
index 66909c3..2015a3a 100644
--- a/pkg/compiler/lib/src/serialization/abstract_sink.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_sink.dart
@@ -34,6 +34,7 @@
 
   Map<Type, IndexedSink> _generalCaches = {};
 
+  EntityWriter _entityWriter = const EntityWriter();
   CodegenWriter _codegenWriter;
 
   AbstractDataSink({this.useDataKinds: false}) {
@@ -307,22 +308,26 @@
 
   @override
   void writeLibrary(IndexedLibrary value) {
-    writeInt(value.libraryIndex);
+    _entityWriter.writeLibraryToDataSink(this, value);
   }
 
   @override
   void writeClass(IndexedClass value) {
-    writeInt(value.classIndex);
+    _entityWriter.writeClassToDataSink(this, value);
   }
 
   @override
   void writeTypedef(IndexedTypedef value) {
-    writeInt(value.typedefIndex);
+    _entityWriter.writeTypedefToDataSink(this, value);
   }
 
   @override
   void writeMember(IndexedMember value) {
-    writeInt(value.memberIndex);
+    _entityWriter.writeMemberToDataSink(this, value);
+  }
+
+  void writeTypeVariable(IndexedTypeVariable value) {
+    _entityWriter.writeTypeVariableToDataSink(this, value);
   }
 
   @override
@@ -490,6 +495,12 @@
   }
 
   @override
+  void registerEntityWriter(EntityWriter writer) {
+    assert(writer != null);
+    _entityWriter = writer;
+  }
+
+  @override
   void registerCodegenWriter(CodegenWriter writer) {
     assert(writer != null);
     assert(_codegenWriter == null);
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
index f10276c..a2fa583 100644
--- a/pkg/compiler/lib/src/serialization/abstract_source.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -9,6 +9,7 @@
 abstract class AbstractDataSource extends DataSourceMixin
     implements DataSource {
   final bool useDataKinds;
+  EntityReader _entityReader = const EntityReader();
   ComponentLookup _componentLookup;
   EntityLookup _entityLookup;
   LocalLookup _localLookup;
@@ -66,6 +67,12 @@
     _localLookup = localLookup;
   }
 
+  @override
+  void registerEntityReader(EntityReader reader) {
+    assert(reader != null);
+    _entityReader = reader;
+  }
+
   LocalLookup get localLookup {
     assert(_localLookup != null);
     return _localLookup;
@@ -79,6 +86,12 @@
   }
 
   @override
+  void deregisterCodegenReader(CodegenReader reader) {
+    assert(_codegenReader == reader);
+    _codegenReader = null;
+  }
+
+  @override
   E readCached<E>(E f()) {
     IndexedSource source = _generalCaches[E] ??= new IndexedSource<E>(this);
     return source.read(f);
@@ -86,38 +99,27 @@
 
   @override
   IndexedLibrary readLibrary() {
-    return getIndexedLibrary(readInt());
+    return _entityReader.readLibraryFromDataSource(this, entityLookup);
   }
 
   @override
   IndexedClass readClass() {
-    return getIndexedClass(readInt());
+    return _entityReader.readClassFromDataSource(this, entityLookup);
   }
 
   @override
   IndexedTypedef readTypedef() {
-    return getIndexedTypedef(readInt());
+    return _entityReader.readTypedefFromDataSource(this, entityLookup);
   }
 
   @override
   IndexedMember readMember() {
-    return getIndexedMember(readInt());
+    return _entityReader.readMemberFromDataSource(this, entityLookup);
   }
 
-  IndexedLibrary getIndexedLibrary(int libraryIndex) =>
-      entityLookup.getLibraryByIndex(libraryIndex);
-
-  IndexedClass getIndexedClass(int classIndex) =>
-      entityLookup.getClassByIndex(classIndex);
-
-  IndexedTypedef getIndexedTypedef(int typedefIndex) =>
-      entityLookup.getTypedefByIndex(typedefIndex);
-
-  IndexedMember getIndexedMember(int memberIndex) =>
-      entityLookup.getMemberByIndex(memberIndex);
-
-  IndexedTypeVariable getIndexedTypeVariable(int typeVariableIndex) =>
-      entityLookup.getTypeVariableByIndex(typeVariableIndex);
+  IndexedTypeVariable readTypeVariable() {
+    return _entityReader.readTypeVariableFromDataSource(this, entityLookup);
+  }
 
   List<DartType> _readDartTypes(
       List<FunctionTypeVariable> functionTypeVariables) {
@@ -164,7 +166,7 @@
       case DartTypeKind.voidType:
         return const VoidType();
       case DartTypeKind.typeVariable:
-        return new TypeVariableType(getIndexedTypeVariable(readInt()));
+        return new TypeVariableType(readTypeVariable());
       case DartTypeKind.functionTypeVariable:
         int index = readInt();
         assert(0 <= index && index < functionTypeVariables.length);
@@ -200,11 +202,11 @@
             typeVariables);
 
       case DartTypeKind.interfaceType:
-        IndexedClass cls = getIndexedClass(readInt());
+        IndexedClass cls = readClass();
         List<DartType> typeArguments = _readDartTypes(functionTypeVariables);
         return new InterfaceType(cls, typeArguments);
       case DartTypeKind.typedef:
-        IndexedTypedef typedef = getIndexedTypedef(readInt());
+        IndexedTypedef typedef = readTypedef();
         List<DartType> typeArguments = _readDartTypes(functionTypeVariables);
         DartType unaliased = _readDartType(functionTypeVariables);
         return new TypedefType(typedef, typeArguments, unaliased);
diff --git a/pkg/compiler/lib/src/serialization/helpers.dart b/pkg/compiler/lib/src/serialization/helpers.dart
index 40cc83d..c0c8c29 100644
--- a/pkg/compiler/lib/src/serialization/helpers.dart
+++ b/pkg/compiler/lib/src/serialization/helpers.dart
@@ -130,7 +130,7 @@
       List<FunctionTypeVariable> functionTypeVariables) {
     _sink.writeEnum(DartTypeKind.typeVariable);
     IndexedTypeVariable typeVariable = type.element;
-    _sink.writeInt(typeVariable.typeVariableIndex);
+    _sink.writeTypeVariable(typeVariable);
   }
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index d4f574c..92e6ad0 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -399,6 +399,10 @@
   /// This feature is only available a [CodegenWriter] has been registered.
   void writeJsNodeOrNull(js.Node value);
 
+  /// Register an [EntityWriter] with this data sink for non-default encoding
+  /// of entity references.
+  void registerEntityWriter(EntityWriter writer);
+
   /// Register a [CodegenWriter] with this data sink to support serialization
   /// of codegen only data.
   void registerCodegenWriter(CodegenWriter writer);
@@ -426,13 +430,21 @@
   /// deserialization of references to indexed entities.
   void registerEntityLookup(EntityLookup entityLookup);
 
+  /// Registers an [EntityReader] with this data source for non-default encoding
+  /// of entity references.
+  void registerEntityReader(EntityReader reader);
+
   /// Registers a [LocalLookup] object with this data source to support
   /// 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);
+  void registerCodegenReader(CodegenReader reader);
+
+  /// Unregisters the [CodegenReader] from this data source to remove support
+  /// for deserialization of codegen only data.
+  void deregisterCodegenReader(CodegenReader reader);
 
   /// 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.
@@ -771,6 +783,61 @@
   IndexedTypeVariable getTypeVariableByIndex(int index);
 }
 
+/// Decoding strategy for entity references.
+class EntityReader {
+  const EntityReader();
+
+  IndexedLibrary readLibraryFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    return entityLookup.getLibraryByIndex(source.readInt());
+  }
+
+  IndexedClass readClassFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    return entityLookup.getClassByIndex(source.readInt());
+  }
+
+  IndexedTypedef readTypedefFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    return entityLookup.getTypedefByIndex(source.readInt());
+  }
+
+  IndexedMember readMemberFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    return entityLookup.getMemberByIndex(source.readInt());
+  }
+
+  IndexedTypeVariable readTypeVariableFromDataSource(
+      DataSource source, EntityLookup entityLookup) {
+    return entityLookup.getTypeVariableByIndex(source.readInt());
+  }
+}
+
+/// Encoding strategy for entity references.
+class EntityWriter {
+  const EntityWriter();
+
+  void writeLibraryToDataSink(DataSink sink, IndexedLibrary value) {
+    sink.writeInt(value.libraryIndex);
+  }
+
+  void writeClassToDataSink(DataSink sink, IndexedClass value) {
+    sink.writeInt(value.classIndex);
+  }
+
+  void writeTypedefToDataSink(DataSink sink, IndexedTypedef value) {
+    sink.writeInt(value.typedefIndex);
+  }
+
+  void writeMemberToDataSink(DataSink sink, IndexedMember value) {
+    sink.writeInt(value.memberIndex);
+  }
+
+  void writeTypeVariableToDataSink(DataSink sink, IndexedTypeVariable value) {
+    sink.writeInt(value.typeVariableIndex);
+  }
+}
+
 /// Interface used for looking up locals by index during deserialization.
 abstract class LocalLookup {
   Local getLocalByIndex(MemberEntity memberContext, int index);
diff --git a/pkg/compiler/lib/src/serialization/task.dart b/pkg/compiler/lib/src/serialization/task.dart
index 56ab4bd..61771d5 100644
--- a/pkg/compiler/lib/src/serialization/task.dart
+++ b/pkg/compiler/lib/src/serialization/task.dart
@@ -7,16 +7,20 @@
 import 'package:kernel/binary/ast_from_binary.dart' as ir;
 import 'package:kernel/binary/ast_to_binary.dart' as ir;
 import '../../compiler_new.dart' as api;
+import '../backend_strategy.dart';
+import '../common/codegen.dart';
 import '../common/tasks.dart';
-import '../compiler.dart';
 import '../diagnostics/diagnostic_listener.dart';
+import '../elements/entities.dart';
 import '../environment.dart';
 import '../inferrer/abstract_value_domain.dart';
 import '../inferrer/types.dart';
+import '../js_backend/backend.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_model/js_world.dart';
 import '../options.dart';
 import '../util/sink_adapter.dart';
+import '../world.dart';
 import 'serialization.dart';
 
 void serializeGlobalTypeInferenceResults(
@@ -45,21 +49,26 @@
 }
 
 class SerializationTask extends CompilerTask {
-  final Compiler compiler;
+  final CompilerOptions _options;
+  final DiagnosticReporter _reporter;
+  final api.CompilerInput _provider;
+  final api.CompilerOutput _outputProvider;
 
-  SerializationTask(this.compiler, Measurer measurer) : super(measurer);
+  SerializationTask(this._options, this._reporter, this._provider,
+      this._outputProvider, Measurer measurer)
+      : super(measurer);
 
   @override
   String get name => 'Serialization';
 
-  void serialize(GlobalTypeInferenceResults results) {
+  void serializeGlobalTypeInference(GlobalTypeInferenceResults results) {
     measureSubtask('serialize dill', () {
       // TODO(sigmund): remove entirely: we will do this immediately as soon as
       // we get the component in the kernel/loader.dart task once we refactor
       // how we apply our modular kernel transformation for super mixin calls.
-      compiler.reporter.log('Writing dill to ${compiler.options.outputUri}');
+      _reporter.log('Writing dill to ${_options.outputUri}');
       api.BinaryOutputSink dillOutput =
-          compiler.outputProvider.createBinarySink(compiler.options.outputUri);
+          _outputProvider.createBinarySink(_options.outputUri);
       JsClosedWorld closedWorld = results.closedWorld;
       ir.Component component = closedWorld.elementMap.programEnv.mainComponent;
       BinaryOutputSinkAdapter irSink = new BinaryOutputSinkAdapter(dillOutput);
@@ -69,40 +78,99 @@
     });
 
     measureSubtask('serialize data', () {
-      compiler.reporter.log('Writing data to ${compiler.options.writeDataUri}');
-      api.BinaryOutputSink dataOutput = compiler.outputProvider
-          .createBinarySink(compiler.options.writeDataUri);
+      _reporter.log('Writing data to ${_options.writeDataUri}');
+      api.BinaryOutputSink dataOutput =
+          _outputProvider.createBinarySink(_options.writeDataUri);
       DataSink sink = new BinarySink(new BinaryOutputSinkAdapter(dataOutput));
       serializeGlobalTypeInferenceResults(results, sink);
     });
   }
 
-  Future<GlobalTypeInferenceResults> deserialize() async {
+  Future<GlobalTypeInferenceResults> deserializeGlobalTypeInference(
+      Environment environment,
+      AbstractValueStrategy abstractValueStrategy) async {
     ir.Component component =
         await measureIoSubtask('deserialize dill', () async {
-      compiler.reporter.log('Reading dill from ${compiler.options.entryPoint}');
-      api.Input<List<int>> dillInput = await compiler.provider.readFromUri(
-          compiler.options.entryPoint,
-          inputKind: api.InputKind.binary);
+      _reporter.log('Reading dill from ${_options.entryPoint}');
+      api.Input<List<int>> dillInput = await _provider
+          .readFromUri(_options.entryPoint, inputKind: api.InputKind.binary);
       ir.Component component = new ir.Component();
       new ir.BinaryBuilder(dillInput.data).readComponent(component);
       return component;
     });
 
     return await measureIoSubtask('deserialize data', () async {
-      compiler.reporter
-          .log('Reading data from ${compiler.options.readDataUri}');
-      api.Input<List<int>> dataInput = await compiler.provider.readFromUri(
-          compiler.options.readDataUri,
-          inputKind: api.InputKind.binary);
+      _reporter.log('Reading data from ${_options.readDataUri}');
+      api.Input<List<int>> dataInput = await _provider
+          .readFromUri(_options.readDataUri, inputKind: api.InputKind.binary);
       DataSource source = new BinarySourceImpl(dataInput.data);
-      return deserializeGlobalTypeInferenceResults(
-          compiler.options,
-          compiler.reporter,
-          compiler.environment,
-          compiler.abstractValueStrategy,
-          component,
-          source);
+      return deserializeGlobalTypeInferenceResults(_options, _reporter,
+          environment, abstractValueStrategy, component, source);
     });
   }
+
+  void serializeCodegen(
+      BackendStrategy backendStrategy, CodegenResults codegenResults) {
+    GlobalTypeInferenceResults globalTypeInferenceResults =
+        codegenResults.globalTypeInferenceResults;
+    JClosedWorld closedWorld = globalTypeInferenceResults.closedWorld;
+    int shard = _options.codegenShard;
+    int shards = _options.codegenShards;
+    Map<MemberEntity, CodegenResult> results = {};
+    int index = 0;
+    EntityWriter entityWriter =
+        backendStrategy.forEachCodegenMember((MemberEntity member) {
+      if (index % shards == shard) {
+        CodegenResult codegenResult = codegenResults.getCodegenResults(member);
+        results[member] = codegenResult;
+      }
+      index++;
+    });
+    measureSubtask('serialize codegen', () {
+      Uri uri = Uri.parse('${_options.writeCodegenUri}$shard');
+      api.BinaryOutputSink dataOutput = _outputProvider.createBinarySink(uri);
+      DataSink sink = new BinarySink(new BinaryOutputSinkAdapter(dataOutput));
+      _reporter.log('Writing data to ${uri}');
+      sink.registerEntityWriter(entityWriter);
+      sink.registerCodegenWriter(new CodegenWriterImpl(closedWorld));
+      sink.writeMemberMap(
+          results, (CodegenResult result) => result.writeToDataSink(sink));
+      sink.close();
+    });
+  }
+
+  Future<CodegenResults> deserializeCodegen(
+      BackendStrategy backendStrategy,
+      GlobalTypeInferenceResults globalTypeInferenceResults,
+      CodegenInputs codegenInputs) async {
+    int shards = _options.codegenShards;
+    JClosedWorld closedWorld = globalTypeInferenceResults.closedWorld;
+    Map<MemberEntity, CodegenResult> results = {};
+    for (int shard = 0; shard < shards; shard++) {
+      Uri uri = Uri.parse('${_options.readCodegenUri}$shard');
+      await measureIoSubtask('deserialize codegen', () async {
+        _reporter.log('Reading data from ${uri}');
+        api.Input<List<int>> dataInput =
+            await _provider.readFromUri(uri, inputKind: api.InputKind.binary);
+        DataSource source = new BinarySourceImpl(dataInput.data);
+        backendStrategy.prepareCodegenReader(source);
+        Map<MemberEntity, CodegenResult> codegenResults =
+            source.readMemberMap(() {
+          List<ModularName> modularNames = [];
+          List<ModularExpression> modularExpressions = [];
+          CodegenReader reader = new CodegenReaderImpl(
+              closedWorld, modularNames, modularExpressions);
+          source.registerCodegenReader(reader);
+          CodegenResult result = CodegenResult.readFromDataSource(
+              source, modularNames, modularExpressions);
+          source.deregisterCodegenReader(reader);
+          return result;
+        });
+        _reporter.log('Read ${codegenResults.length} members from ${uri}');
+        results.addAll(codegenResults);
+      });
+    }
+    return new DeserializedCodegenResults(
+        globalTypeInferenceResults, codegenInputs, results);
+  }
 }
diff --git a/pkg/compiler/tool/modular_dart2js.dart b/pkg/compiler/tool/modular_dart2js.dart
new file mode 100644
index 0000000..e313a9d
--- /dev/null
+++ b/pkg/compiler/tool/modular_dart2js.dart
@@ -0,0 +1,168 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+import 'package:compiler/src/commandline_options.dart';
+
+main(List<String> args) async {
+  Stopwatch stopwatch = new Stopwatch();
+  String input;
+  String output = 'out.js';
+  List<String> arguments = [];
+  int start = 0;
+  int shards;
+  bool enableAssertions = false;
+  for (String arg in args) {
+    if (arg.startsWith('-')) {
+      if (arg.startsWith('--start=')) {
+        start = int.parse(arg.substring('--start='.length));
+      } else if (arg.startsWith('--shards=')) {
+        shards = int.parse(arg.substring('--shards='.length));
+      } else if (arg == '-ea' || arg == '--enable_asserts') {
+        enableAssertions = true;
+      } else if (arg.startsWith('-o')) {
+        output = arg.substring('-o'.length);
+      } else if (arg.startsWith('--out=')) {
+        output = arg.substring('--out='.length);
+      } else {
+        arguments.add(arg);
+      }
+    } else {
+      if (input != null) {
+        print("Multiple entrypoints provided: '${input}' and '${arg}'.");
+        exit(-1);
+      }
+      input = arg;
+    }
+  }
+
+  if (input == null) {
+    print("No entrypoint provided.");
+    exit(-1);
+  }
+
+  String outputPrefix = output;
+  if (output.endsWith('.js')) {
+    outputPrefix = output.substring(0, output.length - '.js'.length);
+  }
+
+  List<String> baseOptions = ['--packages=${Platform.packageConfig}'];
+  if (enableAssertions) {
+    baseOptions.add('--enable_asserts');
+  }
+  baseOptions.add('package:compiler/src/dart2js.dart');
+  baseOptions.addAll(arguments);
+
+  String cfeOutput = '${outputPrefix}0.dill';
+  String dillOutput = '${outputPrefix}.dill';
+  String dataOutput = '${outputPrefix}.dill.data';
+  String codeOutput = '${outputPrefix}.code';
+  shards ??= 2;
+
+  stopwatch.start();
+  if (start <= 0) {
+    await subProcess(
+        baseOptions, [input, Flags.cfeOnly, '--out=$cfeOutput'], '0:\t');
+  }
+  if (start <= 1) {
+    await subProcess(
+        baseOptions,
+        [cfeOutput, '--out=$dillOutput', '${Flags.writeData}=${dataOutput}'],
+        '1:\t');
+  }
+  if (shards <= 1) {
+    await subProcess(
+        baseOptions,
+        [dillOutput, '${Flags.readData}=${dataOutput}', '--out=${output}'],
+        '3:\t');
+  } else {
+    if (start <= 2) {
+      List<List<String>> additionalArguments = [];
+      List<String> outputPrefixes = [];
+      for (int shard = 0; shard < shards; shard++) {
+        additionalArguments.add([
+          dillOutput,
+          '${Flags.readData}=${dataOutput}',
+          '${Flags.codegenShard}=$shard',
+          '${Flags.codegenShards}=$shards',
+          '${Flags.writeCodegen}=${codeOutput}'
+        ]);
+        outputPrefixes.add('2:${shard + 1}/$shards\t');
+      }
+
+      Stopwatch subwatch = new Stopwatch();
+      subwatch.start();
+      await Future.wait(new List<Future>.generate(shards, (int shard) {
+        return subProcess(
+            baseOptions, additionalArguments[shard], outputPrefixes[shard]);
+      }));
+      subwatch.stop();
+      print('2:\tTotal time: ${_formatMs(subwatch.elapsedMilliseconds)}');
+    }
+    if (start <= 3) {
+      await subProcess(
+          baseOptions,
+          [
+            dillOutput,
+            '${Flags.readData}=${dataOutput}',
+            '${Flags.readCodegen}=${codeOutput}',
+            '${Flags.codegenShards}=$shards',
+            '--out=${output}'
+          ],
+          '3:\t');
+    }
+  }
+  stopwatch.stop();
+  print('Total time: ${_formatMs(stopwatch.elapsedMilliseconds)}');
+}
+
+Future subProcess(List<String> baseOptions, List<String> additionalOptions,
+    String outputPrefix) async {
+  List<String> options = []..addAll(baseOptions)..addAll(additionalOptions);
+  print(
+      '${outputPrefix}Command: ${Platform.resolvedExecutable} ${options.join(' ')}');
+  Process process = await Process.start(Platform.resolvedExecutable, options,
+      runInShell: true);
+  _Prefixer stdoutPrefixer = new _Prefixer(outputPrefix, stdout);
+  _Prefixer stderrOutputter = new _Prefixer(outputPrefix, stderr);
+  process.stdout.transform(utf8.decoder).listen(stdoutPrefixer);
+  process.stderr.transform(utf8.decoder).listen(stderrOutputter);
+
+  int exitCode = await process.exitCode;
+  if (exitCode != 0) {
+    exit(exitCode);
+  }
+}
+
+class _Prefixer {
+  final String _prefix;
+  final Stdout _stdout;
+  bool _atNewLine = true;
+
+  _Prefixer(this._prefix, this._stdout);
+
+  void call(String text) {
+    int index = 0;
+    while (index < text.length) {
+      if (_atNewLine) {
+        _stdout.write(_prefix);
+        _atNewLine = false;
+      }
+      int pos = text.indexOf('\n', index);
+      if (pos != -1) {
+        _stdout.write(text.substring(index, pos + 1));
+        _atNewLine = true;
+        index = pos + 1;
+      } else {
+        _stdout.write(text.substring(index));
+        index = text.length;
+      }
+    }
+  }
+}
+
+String _formatMs(int ms) {
+  return (ms / 1000).toStringAsFixed(3) + 's';
+}
diff --git a/tests/compiler/dart2js/end_to_end/command_line_test.dart b/tests/compiler/dart2js/end_to_end/command_line_test.dart
index 88fb3e8..076b918 100644
--- a/tests/compiler/dart2js/end_to_end/command_line_test.dart
+++ b/tests/compiler/dart2js/end_to_end/command_line_test.dart
@@ -10,6 +10,7 @@
 import 'package:expect/expect.dart';
 
 import 'package:compiler/compiler_new.dart' as api;
+import 'package:compiler/src/commandline_options.dart';
 import 'package:compiler/src/dart2js.dart' as entry;
 import 'package:compiler/src/options.dart' show CompilerOptions;
 
@@ -17,11 +18,191 @@
   entry.enableWriteString = false;
   asyncTest(() async {
     await test([], exitCode: 1);
-    await test(['foo.dart']);
+    await test(['foo.dart'], out: 'out.js');
+    await test(['foo.dart', '-ofoo.js'], out: 'foo.js');
+    await test(['foo.dart', '--out=foo.js'], out: 'foo.js');
+
+    await test([Flags.cfeOnly], exitCode: 1);
+    await test([Flags.cfeOnly, 'foo.dart'], out: 'out.dill');
+    await test([Flags.cfeOnly, 'foo.dart', '--out=out.dill'], out: 'out.dill');
+    await test([Flags.cfeOnly, 'foo.dart', Flags.readData], exitCode: 1);
+    await test(['foo.dart', Flags.readData, Flags.cfeOnly], exitCode: 1);
+    await test([Flags.cfeOnly, 'foo.dart', Flags.readCodegen], exitCode: 1);
+    await test(['foo.dart', Flags.readCodegen, Flags.cfeOnly], exitCode: 1);
+    await test([Flags.cfeOnly, 'foo.dart', Flags.writeData], exitCode: 1);
+    await test(['foo.dart', Flags.writeData, Flags.cfeOnly], exitCode: 1);
+    await test([Flags.cfeOnly, 'foo.dart', Flags.writeCodegen], exitCode: 1);
+    await test(['foo.dart', Flags.writeCodegen, Flags.cfeOnly], exitCode: 1);
+
+    await test([Flags.writeData, 'foo.dart'],
+        out: 'out.dill', writeData: 'out.dill.data');
+    await test(['${Flags.writeData}=foo.data', 'foo.dart', '--out=foo.dill'],
+        out: 'foo.dill', writeData: 'foo.data');
+    await test([Flags.readData, Flags.writeData, 'foo.dart'], exitCode: 1);
+    await test([Flags.writeData, Flags.readData, 'foo.dart'], exitCode: 1);
+    await test([Flags.readCodegen, Flags.writeData, 'foo.dart'], exitCode: 1);
+    await test([Flags.writeData, Flags.readCodegen, 'foo.dart'], exitCode: 1);
+
+    await test([Flags.readData, 'foo.dill'],
+        out: 'out.js', readData: 'foo.dill.data');
+    await test([Flags.readData, 'foo.dill', '--out=foo.js'],
+        out: 'foo.js', readData: 'foo.dill.data');
+    await test(['${Flags.readData}=out.data', 'foo.dill'],
+        out: 'out.js', readData: 'out.data');
+    await test(['${Flags.readData}=out.data', 'foo.dill', '--out=foo.js'],
+        out: 'foo.js', readData: 'out.data');
+
+    await test([
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShard}=0',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShard}=0',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out',
+        readData: 'foo.dill.data',
+        writeCodegen: 'out.code',
+        codegenShard: 0,
+        codegenShards: 2);
+    await test([
+      Flags.writeCodegen,
+      Flags.readData,
+      'foo.dill',
+      '${Flags.codegenShard}=1',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out',
+        readData: 'foo.dill.data',
+        writeCodegen: 'out.code',
+        codegenShard: 1,
+        codegenShards: 2);
+    await test([
+      '${Flags.readData}=foo.data',
+      '${Flags.writeCodegen}=foo.code',
+      'foo.dill',
+      '${Flags.codegenShard}=0',
+      '${Flags.codegenShards}=3'
+    ],
+        out: 'out',
+        readData: 'foo.data',
+        writeCodegen: 'foo.code',
+        codegenShard: 0,
+        codegenShards: 3);
+    await test([
+      '${Flags.readData}=foo.data',
+      '${Flags.writeCodegen}',
+      'foo.dill',
+      '--out=foo.js',
+      '${Flags.codegenShard}=0',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'foo.js',
+        readData: 'foo.data',
+        writeCodegen: 'foo.js.code',
+        codegenShard: 0,
+        codegenShards: 2);
+    await test([Flags.writeCodegen, 'foo.dill', Flags.readCodegen],
+        exitCode: 1);
+    await test([Flags.readCodegen, Flags.writeCodegen, 'foo.dill'],
+        exitCode: 1);
+    await test(
+        [Flags.readData, Flags.writeCodegen, 'foo.dill', Flags.readCodegen],
+        exitCode: 1);
+    await test(
+        [Flags.readCodegen, Flags.readData, Flags.writeCodegen, 'foo.dill'],
+        exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShard}=0'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=0'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShard}=-1',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.writeCodegen,
+      'foo.dill',
+      '${Flags.codegenShard}=2',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+
+    await test([Flags.readCodegen, 'foo.dill', '${Flags.codegenShards}=2'],
+        out: 'out.js',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      '${Flags.readCodegen}=foo.code',
+      'foo.dill',
+      '${Flags.codegenShards}=3'
+    ],
+        out: 'out.js',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.code',
+        codegenShards: 3);
+
+    await test([
+      Flags.readData,
+      Flags.readCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      '${Flags.readData}=foo.data',
+      '${Flags.readCodegen}=foo.code',
+      'foo.dill',
+      '${Flags.codegenShards}=3',
+      '-v'
+    ],
+        out: 'out.js',
+        readData: 'foo.data',
+        readCodegen: 'foo.code',
+        codegenShards: 3);
   });
 }
 
-Future test(List<String> arguments, {int exitCode}) async {
+Future test(List<String> arguments,
+    {int exitCode,
+    String out,
+    String readData,
+    String writeData,
+    String readCodegen,
+    String writeCodegen,
+    int codegenShard,
+    int codegenShards}) async {
   print('--------------------------------------------------------------------');
   print('dart2js ${arguments.join(' ')}');
   print('--------------------------------------------------------------------');
@@ -47,8 +228,23 @@
   Expect.equals(exitCode, actualExitCode, "Unexpected exit code");
   if (actualExitCode == null) {
     Expect.isNotNull(options, "Missing options object");
+    Expect.equals(toUri(out), options.outputUri, "Unexpected output uri.");
+    Expect.equals(
+        toUri(readData), options.readDataUri, "Unexpected readData uri");
+    Expect.equals(
+        toUri(writeData), options.writeDataUri, "Unexpected writeData uri");
+    Expect.equals(toUri(readCodegen), options.readCodegenUri,
+        "Unexpected readCodegen uri");
+    Expect.equals(toUri(writeCodegen), options.writeCodegenUri,
+        "Unexpected writeCodegen uri");
+    Expect.equals(
+        codegenShard, options.codegenShard, "Unexpected codegenShard uri");
+    Expect.equals(
+        codegenShards, options.codegenShards, "Unexpected codegenShards uri");
   }
 
   entry.compileFunc = oldCompileFunc;
   entry.exitFunc = oldExitFunc;
 }
+
+Uri toUri(String path) => path != null ? Uri.base.resolve(path) : null;
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 e424fad..3397753 100644
--- a/tests/compiler/dart2js/end_to_end/exit_code_test.dart
+++ b/tests/compiler/dart2js/end_to_end/exit_code_test.dart
@@ -11,6 +11,7 @@
 
 import 'package:compiler/compiler_new.dart' as api;
 import 'package:compiler/src/commandline_options.dart';
+import 'package:compiler/src/common/codegen.dart';
 import 'package:compiler/src/common/work.dart';
 import 'package:compiler/src/compiler.dart';
 import 'package:compiler/src/dart2js.dart' as entry;
@@ -110,10 +111,15 @@
             useNewSourceInfo: compiler.options.useNewSourceInfo);
 
   @override
-  WorldImpact generateCode(WorkItem work, JClosedWorld closedWorld,
-      EntityLookup entityLookup, ComponentLookup componentLookup) {
+  WorldImpact generateCode(
+      WorkItem work,
+      JClosedWorld closedWorld,
+      CodegenResults codegenResults,
+      EntityLookup entityLookup,
+      ComponentLookup componentLookup) {
     compiler.test('Compiler.codegen');
-    return super.generateCode(work, closedWorld, entityLookup, componentLookup);
+    return super.generateCode(
+        work, closedWorld, codegenResults, entityLookup, componentLookup);
   }
 }
 
diff --git a/tests/compiler/dart2js/serialization/serialization_test_helper.dart b/tests/compiler/dart2js/serialization/serialization_test_helper.dart
index 43ee996..9f48d01 100644
--- a/tests/compiler/dart2js/serialization/serialization_test_helper.dart
+++ b/tests/compiler/dart2js/serialization/serialization_test_helper.dart
@@ -60,7 +60,7 @@
 
   Map<OutputType, Map<String, String>> output = collector1.clear();
 
-  compiler.emitJavaScriptCode(newGlobalInferenceResults);
+  compiler.generateJavaScriptCode(newGlobalInferenceResults);
   Map<OutputType, Map<String, String>> newOutput = collector2.clear();
 
   Expect.setEquals(output.keys, newOutput.keys, "Output type mismatch.");