[dart2js] Upstream deferred_loading_test_helper.

Change-Id: I24b06251902d142afd1a5c62a8137459a283348e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/206663
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
index 5b20db1..09e4bf0 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
@@ -4,7 +4,7 @@
 
 /*class: A:
  class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
 */
 /*member: A.:member_unit=1{libb}*/
 class A {}
@@ -25,7 +25,7 @@
 
 /*class: D:
  class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
 */
 /*member: D.:member_unit=1{libb}*/
 class D {}
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
index d32f920..9c29878 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
@@ -6,7 +6,7 @@
  a_pre_fragments=[
   p1: {units: [3{liba}], usedBy: [], needs: []},
   p2: {units: [1{libb}], usedBy: [], needs: []},
-  p3: {units: [2{libb, liba}], usedBy: [], needs: []}],
+  p3: {units: [2{liba, libb}], usedBy: [], needs: []}],
  b_finalized_fragments=[
   f1: [3{liba}],
   f2: [1{libb}]],
@@ -19,7 +19,7 @@
  a_pre_fragments=[
   p1: {units: [3{liba}], usedBy: [p3], needs: []},
   p2: {units: [1{libb}], usedBy: [p3], needs: []},
-  p3: {units: [2{libb, liba}], usedBy: [], needs: [p1, p2]}],
+  p3: {units: [2{liba, libb}], usedBy: [], needs: [p1, p2]}],
  b_finalized_fragments=[
   f1: [3{liba}],
   f2: [1{libb}]],
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
index 621ba83..7ef31f8 100644
--- a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
@@ -5,24 +5,10 @@
 // @dart = 2.7
 
 import 'dart:io' hide Link;
-import 'package:_fe_analyzer_shared/src/testing/features.dart';
 import 'package:async_helper/async_helper.dart';
-import 'package:compiler/src/closure.dart';
-import 'package:compiler/src/common.dart';
-import 'package:compiler/src/compiler.dart';
 import 'package:compiler/src/deferred_load.dart';
-import 'package:compiler/src/elements/entities.dart';
-import 'package:compiler/src/ir/util.dart';
-import 'package:compiler/src/js_model/element_map.dart';
-import 'package:compiler/src/js_model/js_world.dart';
-import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
-import 'package:compiler/src/kernel/kernel_strategy.dart';
-import 'package:expect/expect.dart';
-import '../equivalence/id_equivalence.dart';
 import '../equivalence/id_equivalence_helper.dart';
-import 'package:compiler/src/constants/values.dart';
-
-import 'package:kernel/ast.dart' as ir;
+import 'deferred_loading_test_helper.dart';
 
 ///  Add in options to pass to the compiler like
 /// `Flags.disableTypeInference` or `Flags.disableInlining`
@@ -44,342 +30,3 @@
             [twoDeferredFragmentConfig, threeDeferredFragmentConfig]);
   });
 }
-
-// For ease of testing and making our tests easier to read, we impose an
-// artificial constraint of requiring every deferred import use a different
-// named prefix per test. We enforce this constraint here by checking that no
-// prefix name responds to two different libraries.
-Map<String, Uri> importPrefixes = {};
-
-String importPrefixString(OutputUnit unit) {
-  StringBuffer sb = StringBuffer();
-  bool first = true;
-  for (ImportEntity import in unit.imports) {
-    if (!first) sb.write(', ');
-    sb.write('${import.name}');
-    first = false;
-    Expect.isTrue(import.isDeferred);
-
-    if (importPrefixes.containsKey(import.name)) {
-      var existing = importPrefixes[import.name];
-      var current = import.enclosingLibraryUri;
-      Expect.equals(
-          existing,
-          current,
-          '\n    Duplicate prefix \'${import.name}\' used in both:\n'
-          '     - $existing and\n'
-          '     - $current.\n'
-          '    We require using unique prefixes on these tests to make '
-          'the expectations more readable.');
-    }
-    importPrefixes[import.name] = import.enclosingLibraryUri;
-  }
-  return sb.toString();
-}
-
-/// Create a consistent string representation of [OutputUnit]s for both
-/// KImportEntities and ImportElements.
-String outputUnitString(OutputUnit unit) {
-  if (unit == null) return 'none';
-  String sb = importPrefixString(unit);
-  return '${unit.name}{$sb}';
-}
-
-Map<String, List<PreFragment>> buildPreFragmentMap(
-    Map<String, List<FinalizedFragment>> fragmentsToLoad,
-    List<PreFragment> preDeferredFragments) {
-  Map<FinalizedFragment, PreFragment> fragmentMap = {};
-  for (var preFragment in preDeferredFragments) {
-    fragmentMap[preFragment.finalizedFragment] = preFragment;
-  }
-  Map<String, List<PreFragment>> preFragmentMap = {};
-  fragmentsToLoad.forEach((loadId, fragments) {
-    List<PreFragment> preFragments = [];
-    for (var fragment in fragments) {
-      preFragments.add(fragmentMap[fragment]);
-    }
-    preFragmentMap[loadId] = preFragments.toList();
-  });
-  return preFragmentMap;
-}
-
-class Tags {
-  static const String cls = 'class_unit';
-  static const String member = 'member_unit';
-  static const String closure = 'closure_unit';
-  static const String constants = 'constants';
-  static const String type = 'type_unit';
-  // The below tags appear in a single block comment in the main file.
-  // To keep them appearing in sequential order we prefix characters.
-  static const String preFragments = 'a_pre_fragments';
-  static const String finalizedFragments = 'b_finalized_fragments';
-  static const String steps = 'c_steps';
-}
-
-class OutputUnitDataComputer extends DataComputer<Features> {
-  const OutputUnitDataComputer();
-
-  /// OutputData for [member] as a kernel based element.
-  ///
-  /// At this point the compiler has already been run, so it is holding the
-  /// relevant OutputUnits, we just need to extract that information from it. We
-  /// fill [actualMap] with the data computed about what the resulting OutputUnit
-  /// is.
-  @override
-  void computeMemberData(Compiler compiler, MemberEntity member,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose: false}) {
-    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
-    JsToElementMap elementMap = closedWorld.elementMap;
-    MemberDefinition definition = elementMap.getMemberDefinition(member);
-    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
-            closedWorld.outputUnitData, closedWorld.closureDataLookup)
-        .run(definition.node);
-  }
-
-  @override
-  void computeClassData(Compiler compiler, ClassEntity cls,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose: false}) {
-    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
-    JsToElementMap elementMap = closedWorld.elementMap;
-    ClassDefinition definition = elementMap.getClassDefinition(cls);
-    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
-            closedWorld.outputUnitData, closedWorld.closureDataLookup)
-        .computeForClass(definition.node);
-  }
-
-  @override
-  void computeLibraryData(Compiler compiler, LibraryEntity library,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
-    KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
-    ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
-    List<PreFragment> preDeferredFragments = compiler
-        .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
-    Map<String, List<FinalizedFragment>> fragmentsToLoad =
-        compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
-    Set<OutputUnit> omittedOutputUnits =
-        compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
-    PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
-            fragmentsToLoad, omittedOutputUnits)
-        .computeForLibrary(node);
-  }
-
-  @override
-  DataInterpreter<Features> get dataValidator =>
-      const FeaturesDataInterpreter();
-}
-
-class PreFragmentsIrComputer extends IrDataExtractor<Features> {
-  final List<PreFragment> _preDeferredFragments;
-  final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
-  final Set<OutputUnit> _omittedOutputUnits;
-
-  PreFragmentsIrComputer(
-      DiagnosticReporter reporter,
-      Map<Id, ActualData<Features>> actualMap,
-      this._preDeferredFragments,
-      this._fragmentsToLoad,
-      this._omittedOutputUnits)
-      : super(reporter, actualMap);
-
-  @override
-  Features computeLibraryValue(Id id, ir.Library library) {
-    var name = '${library.importUri.pathSegments.last}';
-    Features features = new Features();
-    if (!name.startsWith('main')) return features;
-
-    // First build a list of pre fragments and their dependencies.
-    int index = 1;
-    Map<FinalizedFragment, int> finalizedFragmentIndices = {};
-    Map<PreFragment, int> preFragmentIndices = {};
-    Map<int, PreFragment> reversePreFragmentIndices = {};
-    Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
-    for (var preFragment in _preDeferredFragments) {
-      if (!preFragmentIndices.containsKey(preFragment)) {
-        var finalizedFragment = preFragment.finalizedFragment;
-        preFragmentIndices[preFragment] = index;
-        finalizedFragmentIndices[finalizedFragment] = index;
-        reversePreFragmentIndices[index] = preFragment;
-        reverseFinalizedFragmentIndices[index] = finalizedFragment;
-        index++;
-      }
-    }
-
-    for (int i = 1; i < index; i++) {
-      var preFragment = reversePreFragmentIndices[i];
-      List<String> needs = [];
-      List<OutputUnit> supplied = [];
-      List<String> usedBy = [];
-      for (var dependent in preFragment.successors) {
-        if (preFragmentIndices.containsKey(dependent)) {
-          usedBy.add('p${preFragmentIndices[dependent]}');
-        }
-      }
-
-      for (var dependency in preFragment.predecessors) {
-        if (preFragmentIndices.containsKey(dependency)) {
-          needs.add('p${preFragmentIndices[dependency]}');
-        }
-      }
-
-      for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
-        supplied.add(emittedOutputUnit.outputUnit);
-      }
-
-      var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
-      features.addElement(Tags.preFragments,
-          'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
-    }
-
-    // Now dump finalized fragments and load ids.
-    for (int i = 1; i < index; i++) {
-      var finalizedFragment = reverseFinalizedFragmentIndices[i];
-      List<String> supplied = [];
-
-      for (var codeFragment in finalizedFragment.codeFragments) {
-        List<String> outputUnitStrings = [];
-        for (var outputUnit in codeFragment.outputUnits) {
-          if (!_omittedOutputUnits.contains(outputUnit)) {
-            outputUnitStrings.add(outputUnitString(outputUnit));
-          }
-        }
-        if (outputUnitStrings.isNotEmpty) {
-          supplied.add(outputUnitStrings.join('+'));
-        }
-      }
-
-      if (supplied.isNotEmpty) {
-        var suppliedString = '[${supplied.join(', ')}]';
-        features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
-      }
-    }
-
-    _fragmentsToLoad.forEach((loadId, finalizedFragments) {
-      List<String> finalizedFragmentNeeds = [];
-      for (var finalizedFragment in finalizedFragments) {
-        assert(finalizedFragmentIndices.containsKey(finalizedFragment));
-        finalizedFragmentNeeds
-            .add('f${finalizedFragmentIndices[finalizedFragment]}');
-      }
-      features.addElement(
-          Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
-    });
-
-    return features;
-  }
-}
-
-class OutputUnitIrComputer extends IrDataExtractor<Features> {
-  final JsToElementMap _elementMap;
-  final OutputUnitData _data;
-  final ClosureData _closureDataLookup;
-
-  Set<String> _constants = {};
-
-  OutputUnitIrComputer(
-      DiagnosticReporter reporter,
-      Map<Id, ActualData<Features>> actualMap,
-      this._elementMap,
-      this._data,
-      this._closureDataLookup)
-      : super(reporter, actualMap);
-
-  Features getMemberValue(
-      String tag, MemberEntity member, Set<String> constants) {
-    Features features = Features();
-    features.add(tag,
-        value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
-    for (var constant in constants) {
-      features.addElement(Tags.constants, constant);
-    }
-    return features;
-  }
-
-  @override
-  Features computeClassValue(Id id, ir.Class node) {
-    var cls = _elementMap.getClass(node);
-    Features features = Features();
-    features.add(Tags.cls,
-        value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
-    features.add(Tags.type,
-        value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
-    return features;
-  }
-
-  @override
-  Features computeMemberValue(Id id, ir.Member node) {
-    if (node is ir.Field && node.isConst) {
-      ir.Expression initializer = node.initializer;
-      ConstantValue constant = _elementMap.getConstantValue(node, initializer);
-      if (!constant.isPrimitive) {
-        SourceSpan span = computeSourceSpanFromTreeNode(initializer);
-        if (initializer is ir.ConstructorInvocation) {
-          // Adjust the source-span to match the AST-based location. The kernel FE
-          // skips the "const" keyword for the expression offset and any prefix in
-          // front of the constructor. The "-6" is an approximation assuming that
-          // there is just a single space after "const" and no prefix.
-          // TODO(sigmund): offsets should be fixed in the FE instead.
-          span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
-        }
-        _registerValue(
-            NodeId(span.begin, IdKind.node),
-            Features.fromMap({
-              Tags.member: outputUnitString(
-                  _data.outputUnitForConstantForTesting(constant))
-            }),
-            node,
-            span,
-            actualMap,
-            reporter);
-      }
-    }
-
-    Features features =
-        getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
-    _constants = {};
-    return features;
-  }
-
-  @override
-  visitConstantExpression(ir.ConstantExpression node) {
-    ConstantValue constant = _elementMap.getConstantValue(null, node);
-    if (!constant.isPrimitive) {
-      _constants.add('${constant.toStructuredText(_elementMap.types)}='
-          '${outputUnitString(_data.outputUnitForConstant(constant))}');
-    }
-    return super.visitConstantExpression(node);
-  }
-
-  @override
-  Features computeNodeValue(Id id, ir.TreeNode node) {
-    if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
-      ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
-      return getMemberValue(Tags.closure, info.callMethod, const {});
-    }
-    return null;
-  }
-}
-
-/// Set [actualMap] to hold a key of [id] with the computed data [value]
-/// corresponding to [object] at location [sourceSpan]. We also perform error
-/// checking to ensure that the same [id] isn't added twice.
-void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
-    Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
-  if (actualMap.containsKey(id)) {
-    ActualData<T> existingData = actualMap[id];
-    reportHere(reporter, sourceSpan,
-        "Duplicate id ${id}, value=$value, object=$object");
-    reportHere(
-        reporter,
-        sourceSpan,
-        "Duplicate id ${id}, value=${existingData.value}, "
-        "object=${existingData.object}");
-    Expect.fail("Duplicate id $id.");
-  }
-  if (value != null) {
-    actualMap[id] =
-        ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
-  }
-}
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
new file mode 100644
index 0000000..8028c05
--- /dev/null
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
@@ -0,0 +1,360 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.7
+
+import 'package:_fe_analyzer_shared/src/testing/features.dart';
+import 'package:compiler/src/closure.dart';
+import 'package:compiler/src/common.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/deferred_load.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/ir/util.dart';
+import 'package:compiler/src/js_model/element_map.dart';
+import 'package:compiler/src/js_model/js_world.dart';
+import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
+import 'package:compiler/src/kernel/kernel_strategy.dart';
+import 'package:expect/expect.dart';
+import '../equivalence/id_equivalence.dart';
+import '../equivalence/id_equivalence_helper.dart';
+import 'package:compiler/src/constants/values.dart';
+
+import 'package:kernel/ast.dart' as ir;
+
+// For ease of testing and making our tests easier to read, we impose an
+// artificial constraint of requiring every deferred import use a different
+// named prefix per test. We enforce this constraint here by checking that no
+// prefix name responds to two different libraries.
+Map<String, Uri> importPrefixes = {};
+
+String importPrefixString(OutputUnit unit) {
+  List<String> importNames = [];
+  for (ImportEntity import in unit.imports) {
+    importNames.add(import.name);
+    Expect.isTrue(import.isDeferred);
+
+    if (importPrefixes.containsKey(import.name)) {
+      var existing = importPrefixes[import.name];
+      var current = import.enclosingLibraryUri;
+      Expect.equals(
+          existing,
+          current,
+          '\n    Duplicate prefix \'${import.name}\' used in both:\n'
+          '     - $existing and\n'
+          '     - $current.\n'
+          '    We require using unique prefixes on these tests to make '
+          'the expectations more readable.');
+    }
+    importPrefixes[import.name] = import.enclosingLibraryUri;
+  }
+  importNames.sort();
+  return importNames.join(', ');
+}
+
+/// Create a consistent string representation of [OutputUnit]s for both
+/// KImportEntities and ImportElements.
+String outputUnitString(OutputUnit unit) {
+  if (unit == null) return 'none';
+  String sb = importPrefixString(unit);
+  return '${unit.name}{$sb}';
+}
+
+Map<String, List<PreFragment>> buildPreFragmentMap(
+    Map<String, List<FinalizedFragment>> fragmentsToLoad,
+    List<PreFragment> preDeferredFragments) {
+  Map<FinalizedFragment, PreFragment> fragmentMap = {};
+  for (var preFragment in preDeferredFragments) {
+    fragmentMap[preFragment.finalizedFragment] = preFragment;
+  }
+  Map<String, List<PreFragment>> preFragmentMap = {};
+  fragmentsToLoad.forEach((loadId, fragments) {
+    List<PreFragment> preFragments = [];
+    for (var fragment in fragments) {
+      preFragments.add(fragmentMap[fragment]);
+    }
+    preFragmentMap[loadId] = preFragments.toList();
+  });
+  return preFragmentMap;
+}
+
+class Tags {
+  static const String cls = 'class_unit';
+  static const String member = 'member_unit';
+  static const String closure = 'closure_unit';
+  static const String constants = 'constants';
+  static const String type = 'type_unit';
+  // The below tags appear in a single block comment in the main file.
+  // To keep them appearing in sequential order we prefix characters.
+  static const String preFragments = 'a_pre_fragments';
+  static const String finalizedFragments = 'b_finalized_fragments';
+  static const String steps = 'c_steps';
+}
+
+class OutputUnitDataComputer extends DataComputer<Features> {
+  const OutputUnitDataComputer();
+
+  /// OutputData for [member] as a kernel based element.
+  ///
+  /// At this point the compiler has already been run, so it is holding the
+  /// relevant OutputUnits, we just need to extract that information from it. We
+  /// fill [actualMap] with the data computed about what the resulting OutputUnit
+  /// is.
+  @override
+  void computeMemberData(Compiler compiler, MemberEntity member,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose: false}) {
+    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+    JsToElementMap elementMap = closedWorld.elementMap;
+    MemberDefinition definition = elementMap.getMemberDefinition(member);
+    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+            closedWorld.outputUnitData, closedWorld.closureDataLookup)
+        .run(definition.node);
+  }
+
+  @override
+  void computeClassData(Compiler compiler, ClassEntity cls,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose: false}) {
+    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+    JsToElementMap elementMap = closedWorld.elementMap;
+    ClassDefinition definition = elementMap.getClassDefinition(cls);
+    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+            closedWorld.outputUnitData, closedWorld.closureDataLookup)
+        .computeForClass(definition.node);
+  }
+
+  @override
+  void computeLibraryData(Compiler compiler, LibraryEntity library,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose}) {
+    KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
+    ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
+    List<PreFragment> preDeferredFragments = compiler
+        .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
+    Map<String, List<FinalizedFragment>> fragmentsToLoad =
+        compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
+    Set<OutputUnit> omittedOutputUnits =
+        compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
+    PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
+            fragmentsToLoad, omittedOutputUnits)
+        .computeForLibrary(node);
+  }
+
+  @override
+  DataInterpreter<Features> get dataValidator =>
+      const FeaturesDataInterpreter();
+}
+
+class PreFragmentsIrComputer extends IrDataExtractor<Features> {
+  final List<PreFragment> _preDeferredFragments;
+  final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
+  final Set<OutputUnit> _omittedOutputUnits;
+
+  PreFragmentsIrComputer(
+      DiagnosticReporter reporter,
+      Map<Id, ActualData<Features>> actualMap,
+      this._preDeferredFragments,
+      this._fragmentsToLoad,
+      this._omittedOutputUnits)
+      : super(reporter, actualMap);
+
+  @override
+  Features computeLibraryValue(Id id, ir.Library library) {
+    var name = '${library.importUri.pathSegments.last}';
+    Features features = new Features();
+    if (!name.startsWith('main')) return features;
+
+    // First build a list of pre fragments and their dependencies.
+    int index = 1;
+    Map<FinalizedFragment, int> finalizedFragmentIndices = {};
+    Map<PreFragment, int> preFragmentIndices = {};
+    Map<int, PreFragment> reversePreFragmentIndices = {};
+    Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
+    for (var preFragment in _preDeferredFragments) {
+      if (!preFragmentIndices.containsKey(preFragment)) {
+        var finalizedFragment = preFragment.finalizedFragment;
+        preFragmentIndices[preFragment] = index;
+        finalizedFragmentIndices[finalizedFragment] = index;
+        reversePreFragmentIndices[index] = preFragment;
+        reverseFinalizedFragmentIndices[index] = finalizedFragment;
+        index++;
+      }
+    }
+
+    for (int i = 1; i < index; i++) {
+      var preFragment = reversePreFragmentIndices[i];
+      List<String> needs = [];
+      List<OutputUnit> supplied = [];
+      List<String> usedBy = [];
+      for (var dependent in preFragment.successors) {
+        if (preFragmentIndices.containsKey(dependent)) {
+          usedBy.add('p${preFragmentIndices[dependent]}');
+        }
+      }
+
+      for (var dependency in preFragment.predecessors) {
+        if (preFragmentIndices.containsKey(dependency)) {
+          needs.add('p${preFragmentIndices[dependency]}');
+        }
+      }
+
+      for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
+        supplied.add(emittedOutputUnit.outputUnit);
+      }
+
+      var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
+      features.addElement(Tags.preFragments,
+          'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+    }
+
+    // Now dump finalized fragments and load ids.
+    for (int i = 1; i < index; i++) {
+      var finalizedFragment = reverseFinalizedFragmentIndices[i];
+      List<String> supplied = [];
+
+      for (var codeFragment in finalizedFragment.codeFragments) {
+        List<String> outputUnitStrings = [];
+        for (var outputUnit in codeFragment.outputUnits) {
+          if (!_omittedOutputUnits.contains(outputUnit)) {
+            outputUnitStrings.add(outputUnitString(outputUnit));
+          }
+        }
+        if (outputUnitStrings.isNotEmpty) {
+          supplied.add(outputUnitStrings.join('+'));
+        }
+      }
+
+      if (supplied.isNotEmpty) {
+        var suppliedString = '[${supplied.join(', ')}]';
+        features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
+      }
+    }
+
+    _fragmentsToLoad.forEach((loadId, finalizedFragments) {
+      List<String> finalizedFragmentNeeds = [];
+      for (var finalizedFragment in finalizedFragments) {
+        assert(finalizedFragmentIndices.containsKey(finalizedFragment));
+        finalizedFragmentNeeds
+            .add('f${finalizedFragmentIndices[finalizedFragment]}');
+      }
+      features.addElement(
+          Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
+    });
+
+    return features;
+  }
+}
+
+class OutputUnitIrComputer extends IrDataExtractor<Features> {
+  final JsToElementMap _elementMap;
+  final OutputUnitData _data;
+  final ClosureData _closureDataLookup;
+
+  Set<String> _constants = {};
+
+  OutputUnitIrComputer(
+      DiagnosticReporter reporter,
+      Map<Id, ActualData<Features>> actualMap,
+      this._elementMap,
+      this._data,
+      this._closureDataLookup)
+      : super(reporter, actualMap);
+
+  Features getMemberValue(
+      String tag, MemberEntity member, Set<String> constants) {
+    Features features = Features();
+    features.add(tag,
+        value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
+    for (var constant in constants) {
+      features.addElement(Tags.constants, constant);
+    }
+    return features;
+  }
+
+  @override
+  Features computeClassValue(Id id, ir.Class node) {
+    var cls = _elementMap.getClass(node);
+    Features features = Features();
+    features.add(Tags.cls,
+        value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
+    features.add(Tags.type,
+        value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
+    return features;
+  }
+
+  @override
+  Features computeMemberValue(Id id, ir.Member node) {
+    if (node is ir.Field && node.isConst) {
+      ir.Expression initializer = node.initializer;
+      ConstantValue constant = _elementMap.getConstantValue(node, initializer);
+      if (!constant.isPrimitive) {
+        SourceSpan span = computeSourceSpanFromTreeNode(initializer);
+        if (initializer is ir.ConstructorInvocation) {
+          // Adjust the source-span to match the AST-based location. The kernel FE
+          // skips the "const" keyword for the expression offset and any prefix in
+          // front of the constructor. The "-6" is an approximation assuming that
+          // there is just a single space after "const" and no prefix.
+          // TODO(sigmund): offsets should be fixed in the FE instead.
+          span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
+        }
+        _registerValue(
+            NodeId(span.begin, IdKind.node),
+            Features.fromMap({
+              Tags.member: outputUnitString(
+                  _data.outputUnitForConstantForTesting(constant))
+            }),
+            node,
+            span,
+            actualMap,
+            reporter);
+      }
+    }
+
+    Features features =
+        getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
+    _constants = {};
+    return features;
+  }
+
+  @override
+  visitConstantExpression(ir.ConstantExpression node) {
+    ConstantValue constant = _elementMap.getConstantValue(null, node);
+    if (!constant.isPrimitive) {
+      _constants.add('${constant.toStructuredText(_elementMap.types)}='
+          '${outputUnitString(_data.outputUnitForConstant(constant))}');
+    }
+    return super.visitConstantExpression(node);
+  }
+
+  @override
+  Features computeNodeValue(Id id, ir.TreeNode node) {
+    if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
+      ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
+      return getMemberValue(Tags.closure, info.callMethod, const {});
+    }
+    return null;
+  }
+}
+
+/// Set [actualMap] to hold a key of [id] with the computed data [value]
+/// corresponding to [object] at location [sourceSpan]. We also perform error
+/// checking to ensure that the same [id] isn't added twice.
+void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
+    Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
+  if (actualMap.containsKey(id)) {
+    ActualData<T> existingData = actualMap[id];
+    reportHere(reporter, sourceSpan,
+        "Duplicate id ${id}, value=$value, object=$object");
+    reportHere(
+        reporter,
+        sourceSpan,
+        "Duplicate id ${id}, value=${existingData.value}, "
+        "object=${existingData.object}");
+    Expect.fail("Duplicate id $id.");
+  }
+  if (value != null) {
+    actualMap[id] =
+        ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
+  }
+}