[dart2js] Add flag to specify maximum number of deferred code fragments.
Change-Id: Ifd85eb01c0be9de9ebbb157df616ef8765a4d5b5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/158366
Commit-Queue: Joshua Litt <joshualitt@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index 1c7d0ed..c93d471 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -117,6 +117,7 @@
static const String soundNullSafety = '--sound-null-safety';
static const String noSoundNullSafety = '--no-sound-null-safety';
+ static const String mergeFragmentsThreshold = '--merge-fragments-threshold';
/// Flag for a combination of flags for 'production' mode.
static const String benchmarkingProduction = '--benchmarking-production';
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index 11b8c5d..5a69e20 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -559,6 +559,7 @@
new OptionHandler(Flags.experimentUnreachableMethodsThrow, passThrough),
new OptionHandler(Flags.experimentCallInstrumentation, passThrough),
new OptionHandler(Flags.experimentNewRti, ignoreOption),
+ new OptionHandler('${Flags.mergeFragmentsThreshold}=.+', passThrough),
// The following three options must come last.
new OptionHandler('-D.+=.*', addInEnvironment),
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index 364fe9a..829e2c5 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -84,6 +84,13 @@
Set<ImportEntity> get importsForTesting => _imports;
+ void merge(OutputUnit that) {
+ assert(this != that);
+ // We don't currently support merging code into the main output unit.
+ assert(!isMainOutput);
+ this._imports.addAll(that._imports);
+ }
+
@override
String toString() => "OutputUnit($name, $_imports)";
}
diff --git a/pkg/compiler/lib/src/js/size_estimator.dart b/pkg/compiler/lib/src/js/size_estimator.dart
index 60d0354..347c146 100644
--- a/pkg/compiler/lib/src/js/size_estimator.dart
+++ b/pkg/compiler/lib/src/js/size_estimator.dart
@@ -12,6 +12,13 @@
import '../js_backend/type_reference.dart';
import '../js_emitter/metadata_collector.dart';
+/// Estimates the size of the Javascript AST represented by the provided [Node].
+int estimateSize(Node node) {
+ var estimator = SizeEstimator();
+ estimator.visit(node);
+ return estimator.charCount;
+}
+
/// [SizeEstimator] is a [NodeVisitor] designed to produce a consistent size
/// estimate for a given JavaScript AST. [SizeEstimator] trades accuracy for
/// stability and performance. In addition, [SizeEstimator] assumes we will emit
@@ -55,12 +62,20 @@
} else if (node is StringReference) {
// Worst case we have to inline the string so size of string + 2 bytes for
// quotes.
- return "'${node.constant}'";
+ return "'${node.constant.toDartString()}'";
} else {
throw UnsupportedError('$node type is not supported');
}
}
+ String literalStringToString(LiteralString node) {
+ if (node.isFinalized) {
+ return node.value;
+ } else {
+ return sizeEstimate(node);
+ }
+ }
+
/// Always emit a newline, even under `enableMinification`.
void forceLine() {
out('\n'); // '\n'
@@ -757,7 +772,7 @@
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
Node selector = access.selector;
if (selector is LiteralString) {
- String fieldWithQuotes = selector.value;
+ String fieldWithQuotes = literalStringToString(selector);
if (isValidJavaScriptId(fieldWithQuotes)) {
if (access.receiver is LiteralNumber) {
// We can eliminate the space in some cases, but for simplicity we
@@ -846,11 +861,7 @@
@override
void visitLiteralString(LiteralString node) {
- if (node.isFinalized) {
- out(node.value); // '${node.value}'
- } else {
- out(sizeEstimate(node));
- }
+ out(literalStringToString(node));
}
@override
@@ -943,7 +954,7 @@
void visitProperty(Property node) {
Node name = node.name;
if (name is LiteralString) {
- String text = name.value;
+ String text = literalStringToString(name);
if (isValidJavaScriptId(text)) {
// '${text.substring(1, text.length - 1)}
out('${text.substring(1, text.length - 1)}');
@@ -1034,9 +1045,3 @@
visit(node.expression);
}
}
-
-int EstimateSize(Node node) {
- var estimator = SizeEstimator();
- estimator.visit(node);
- return estimator.charCount;
-}
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 b412bd7..fb72825 100644
--- a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
+++ b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
@@ -20,6 +20,7 @@
import '../world.dart' show JClosedWorld;
import 'program_builder/program_builder.dart';
import 'startup_emitter/emitter.dart' as startup_js_emitter;
+import 'startup_emitter/fragment_merger.dart' as fragment_merger;
import 'metadata_collector.dart' show MetadataCollector;
import 'model.dart';
@@ -206,6 +207,8 @@
abstract class Emitter implements ModularEmitter {
Program get programForTesting;
+ List<fragment_merger.PreFragment> get preDeferredFragmentsForTesting;
+
/// Uses the [programBuilder] to generate a model of the program, emits
/// the program, and returns the size of the generated output.
int emitProgram(ProgramBuilder programBuilder, CodegenWorld codegenWorld);
diff --git a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
index 6fac2f9..db10350 100644
--- a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
+++ b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
@@ -115,8 +115,7 @@
}
/// A map used to canonicalize the entries of metadata.
- Map<OutputUnit, Map<String, BoundMetadataEntry>> _metadataMap =
- <OutputUnit, Map<String, BoundMetadataEntry>>{};
+ Map<OutputUnit, Map<String, List<BoundMetadataEntry>>> _metadataMap = {};
/// A map with a token for a lists of JS expressions, one token for each
/// output unit. Once finalized, the entries represent types including
@@ -129,8 +128,35 @@
}
/// A map used to canonicalize the entries of types.
- Map<OutputUnit, Map<DartType, BoundMetadataEntry>> _typesMap =
- <OutputUnit, Map<DartType, BoundMetadataEntry>>{};
+ Map<OutputUnit, Map<DartType, List<BoundMetadataEntry>>> _typesMap = {};
+
+ void mergeOutputUnitMetadata(OutputUnit target, OutputUnit source) {
+ assert(target != source);
+
+ // Merge _metadataMap
+ var sourceMetadataMap = _metadataMap[source];
+ if (sourceMetadataMap != null) {
+ var targetMetadataMap =
+ _metadataMap[target] ??= Map<String, List<BoundMetadataEntry>>();
+ _metadataMap.remove(source);
+ sourceMetadataMap.forEach((str, entries) {
+ var targetMetadataMapList = targetMetadataMap[str] ??= [];
+ targetMetadataMapList.addAll(entries);
+ });
+ }
+
+ // Merge _typesMap
+ var sourceTypesMap = _typesMap[source];
+ if (sourceTypesMap != null) {
+ var targetTypesMap =
+ _typesMap[target] ??= Map<DartType, List<BoundMetadataEntry>>();
+ _typesMap.remove(source);
+ sourceTypesMap.forEach((type, entries) {
+ var targetTypesMapList = targetTypesMap[type] ??= [];
+ targetTypesMapList.addAll(entries);
+ });
+ }
+ }
MetadataCollector(this._options, this.reporter, this._emitter,
this._rtiRecipeEncoder, this._elementEnvironment);
@@ -166,10 +192,9 @@
String printed = jsAst.prettyPrint(node,
enableMinification: _options.enableMinification,
renamerForNames: nameToKey);
- _metadataMap[outputUnit] ??= new Map<String, BoundMetadataEntry>();
- return _metadataMap[outputUnit].putIfAbsent(printed, () {
- return new BoundMetadataEntry(node);
- });
+ final submap = _metadataMap[outputUnit] ??= {};
+ final entries = submap[printed] ??= [BoundMetadataEntry(node)];
+ return entries.single;
}
jsAst.Expression _computeTypeRepresentationNewRti(DartType type) {
@@ -178,10 +203,20 @@
}
jsAst.Expression addTypeInOutputUnit(DartType type, OutputUnit outputUnit) {
- _typesMap[outputUnit] ??= new Map<DartType, BoundMetadataEntry>();
- return _typesMap[outputUnit].putIfAbsent(type, () {
- return new BoundMetadataEntry(_computeTypeRepresentationNewRti(type));
- });
+ _typesMap[outputUnit] ??= Map<DartType, List<BoundMetadataEntry>>();
+ BoundMetadataEntry metadataEntry;
+
+ // See comment for _addGlobalMetadata.
+ if (_typesMap[outputUnit].containsKey(type)) {
+ metadataEntry = _typesMap[outputUnit][type].single;
+ } else {
+ _typesMap[outputUnit].putIfAbsent(type, () {
+ metadataEntry =
+ BoundMetadataEntry(_computeTypeRepresentationNewRti(type));
+ return [metadataEntry];
+ });
+ }
+ return metadataEntry;
}
@override
@@ -194,9 +229,13 @@
.forEach(counter.countTokens);
}
- jsAst.ArrayInitializer finalizeMap(Map<dynamic, BoundMetadataEntry> map) {
- bool isUsed(BoundMetadataEntry entry) => entry.isUsed;
- List<BoundMetadataEntry> entries = map.values.where(isUsed).toList();
+ jsAst.ArrayInitializer finalizeMap(
+ Map<dynamic, List<BoundMetadataEntry>> map) {
+ List<BoundMetadataEntry> entries = [
+ for (var entriesList in map.values)
+ for (var entry in entriesList)
+ if (entry.isUsed) entry
+ ];
entries.sort();
// TODO(herhut): Bucket entries by index length and use a stable
@@ -222,9 +261,9 @@
});
_typesTokens.forEach((OutputUnit outputUnit, _MetadataList token) {
- Map typesMap = _typesMap[outputUnit];
+ Map<DartType, List<BoundMetadataEntry>> typesMap = _typesMap[outputUnit];
if (typesMap != null) {
- countTokensInTypes(typesMap.values);
+ typesMap.values.forEach(countTokensInTypes);
token.setExpression(finalizeMap(typesMap));
} else {
token.setExpression(new jsAst.ArrayInitializer([]));
diff --git a/pkg/compiler/lib/src/js_emitter/model.dart b/pkg/compiler/lib/src/js_emitter/model.dart
index 7479339..fb2ffc6 100644
--- a/pkg/compiler/lib/src/js_emitter/model.dart
+++ b/pkg/compiler/lib/src/js_emitter/model.dart
@@ -42,6 +42,10 @@
assert(outputContainsConstantList != null);
}
+ void mergeOutputUnitMetadata(OutputUnit target, OutputUnit source) {
+ _metadataCollector.mergeOutputUnitMetadata(target, source);
+ }
+
/// Accessor for the list of metadata entries for a given [OutputUnit].
///
/// There is one list for each output unit. The list belonging to the main
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
index b2b924b..acf7e3c 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
@@ -24,6 +24,7 @@
import '../model.dart';
import '../native_emitter.dart';
import '../program_builder/program_builder.dart' show ProgramBuilder;
+import 'fragment_merger.dart';
import 'model_emitter.dart';
abstract class ModularEmitterBase implements ModularEmitter {
@@ -156,6 +157,9 @@
@override
Program programForTesting;
+ @override
+ List<PreFragment> preDeferredFragmentsForTesting;
+
EmitterImpl(
CompilerOptions options,
this._reporter,
@@ -196,7 +200,12 @@
programForTesting = program;
}
return _task.measureSubtask('emit program', () {
- return _emitter.emitProgram(program, codegenWorld);
+ var size = _emitter.emitProgram(program, codegenWorld);
+ if (retainDataForTesting) {
+ preDeferredFragmentsForTesting =
+ _emitter.preDeferredFragmentsForTesting;
+ }
+ return size;
});
}
@@ -238,11 +247,9 @@
@override
int generatedSize(OutputUnit unit) {
- if (_emitter.omittedFragments.any((f) => f.outputUnit == unit)) {
+ if (_emitter.omittedOutputUnits.contains(unit)) {
return 0;
}
- Fragment key = _emitter.outputBuffers.keys
- .firstWhere((Fragment fragment) => fragment.outputUnit == unit);
- return _emitter.outputBuffers[key].length;
+ return _emitter.emittedOutputBuffers[unit].length;
}
}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 597f756..dd7bd3a 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -690,6 +690,35 @@
if (library != null) _dumpInfoTask.registerEntityAst(library, code);
}
+ PreFragment emitPreFragment(DeferredFragment fragment, bool estimateSize) {
+ var classPrototypes = emitPrototypes(fragment, includeClosures: false);
+ var closurePrototypes = emitPrototypes(fragment, includeClosures: true);
+ var inheritance = emitInheritance(fragment);
+ var methodAliases = emitInstanceMethodAliases(fragment);
+ var tearOffs = emitInstallTearOffs(fragment);
+ var constants = emitConstants(fragment);
+ var typeRules = emitTypeRules(fragment);
+ var variances = emitVariances(fragment);
+ var staticNonFinalFields = emitStaticNonFinalFields(fragment);
+ var lazyInitializers = emitLazilyInitializedStatics(fragment);
+ // TODO(floitsch): only call emitNativeSupport if we need native.
+ var nativeSupport = emitNativeSupport(fragment);
+ return PreFragment(
+ fragment,
+ classPrototypes,
+ closurePrototypes,
+ inheritance,
+ methodAliases,
+ tearOffs,
+ constants,
+ typeRules,
+ variances,
+ staticNonFinalFields,
+ lazyInitializers,
+ nativeSupport,
+ estimateSize);
+ }
+
js.Statement emitMainFragment(
Program program, DeferredLoadingState deferredLoadingState) {
MainFragment fragment = program.fragments.first;
@@ -699,8 +728,8 @@
String softDeferredId = "softDeferred${new Random().nextInt(0x7FFFFFFF)}";
- HolderCode holderCode =
- emitHolders(program.holders, fragment, initializeEmptyHolders: true);
+ HolderCode holderCode = emitHolders(program.holders, fragment.libraries,
+ initializeEmptyHolders: true);
js.Statement mainCode = js.js.statement(_mainBoilerplate, {
// TODO(29455): 'hunkHelpers' displaces other names, so don't minify it.
@@ -791,10 +820,10 @@
return new js.Block(holderInits);
}
- js.Expression emitDeferredFragment(DeferredFragment fragment,
- js.Expression deferredTypes, List<Holder> holders) {
+ js.Expression emitDeferredFragment(
+ FinalizedFragment fragment, List<Holder> holders) {
HolderCode holderCode =
- emitHolders(holders, fragment, initializeEmptyHolders: false);
+ emitHolders(holders, fragment.libraries, initializeEmptyHolders: false);
List<Holder> nonStaticStateHolders = holders
.where((Holder holder) => !holder.isStaticStateHolder)
@@ -821,19 +850,6 @@
}
}
- var classPrototypes = emitPrototypes(fragment, includeClosures: false);
- var closurePrototypes = emitPrototypes(fragment, includeClosures: true);
- var inheritance = emitInheritance(fragment);
- var methodAliases = emitInstanceMethodAliases(fragment);
- var tearOffs = emitInstallTearOffs(fragment);
- var constants = emitConstants(fragment);
- var typeRules = emitTypeRules(fragment);
- var variances = emitVariances(fragment);
- var staticNonFinalFields = emitStaticNonFinalFields(fragment);
- var lazyInitializers = emitLazilyInitializedStatics(fragment);
- // TODO(floitsch): only call emitNativeSupport if we need native.
- var nativeSupport = emitNativeSupport(fragment);
-
// TODO(sra): How do we tell if [deferredTypes] is empty? It is filled-in
// later via the program finalizers. So we should defer the decision on the
// emptiness of the fragment until the finalizers have run. For now we seem
@@ -843,16 +859,7 @@
// not emit any functions, then we probably did not use the signature types
// in the OutputUnit's types, leaving them unused and tree-shaken.
- if (holderCode.activeHolders.isEmpty &&
- isEmptyStatement(classPrototypes) &&
- isEmptyStatement(closurePrototypes) &&
- isEmptyStatement(inheritance) &&
- isEmptyStatement(methodAliases) &&
- isEmptyStatement(tearOffs) &&
- isEmptyStatement(constants) &&
- isEmptyStatement(staticNonFinalFields) &&
- isEmptyStatement(lazyInitializers) &&
- isEmptyStatement(nativeSupport)) {
+ if (holderCode.activeHolders.isEmpty && fragment.isEmpty) {
return null;
}
@@ -865,18 +872,18 @@
.map((holder) => js.js("#", holder.name))
.toList(growable: false)),
'updateHolders': new js.Block(updateHolderAssignments),
- 'prototypes': classPrototypes,
- 'closures': closurePrototypes,
- 'inheritance': inheritance,
- 'aliases': methodAliases,
- 'tearOffs': tearOffs,
- 'typeRules': typeRules,
- 'variances': variances,
- 'constants': constants,
- 'staticNonFinalFields': staticNonFinalFields,
- 'lazyStatics': lazyInitializers,
- 'types': deferredTypes,
- 'nativeSupport': nativeSupport,
+ 'prototypes': fragment.classPrototypes,
+ 'closures': fragment.closurePrototypes,
+ 'inheritance': fragment.inheritance,
+ 'aliases': fragment.methodAliases,
+ 'tearOffs': fragment.tearOffs,
+ 'typeRules': fragment.typeRules,
+ 'variances': fragment.variances,
+ 'constants': fragment.constants,
+ 'staticNonFinalFields': fragment.staticNonFinalFields,
+ 'lazyStatics': fragment.lazyInitializers,
+ 'types': fragment.deferredTypes,
+ 'nativeSupport': fragment.nativeSupport,
'typesOffset': _namer.typesOffsetName,
'sharedStrings': StringReferenceResource(),
'sharedTypeRtis': TypeReferenceResource(),
@@ -904,7 +911,7 @@
///
/// The emitted holders contain classes (only the constructors) and all
/// static functions.
- HolderCode emitHolders(List<Holder> holders, Fragment fragment,
+ HolderCode emitHolders(List<Holder> holders, List<Library> libraries,
{bool initializeEmptyHolders}) {
assert(initializeEmptyHolders != null);
// Skip the static-state holder in this function.
@@ -918,7 +925,7 @@
holderCode[holder] = <js.Property>[];
}
- for (Library library in fragment.libraries) {
+ for (Library library in libraries) {
for (StaticMethod method in library.statics) {
assert(!method.holder.isStaticStateHolder);
Map<js.Name, js.Expression> propertyMap = emitStaticMethod(method);
@@ -1488,13 +1495,6 @@
return js.js.statement('(function #(){#})();', [name, block]);
}
- bool isEmptyStatement(js.Statement statement) {
- if (statement is js.Block) {
- return statement.statements.isEmpty;
- }
- return statement is js.EmptyStatement;
- }
-
/// Emits the section that installs tear-off getters.
js.Statement emitInstallTearOffs(Fragment fragment,
{bool softDeferred = false}) {
@@ -1864,20 +1864,20 @@
// array of hashes indexed by part.
// [deferredLoadHashes] may have missing entries to indicate empty parts.
void finalizeDeferredLoadingData(
- Map<String, List<Fragment>> loadMap,
- Map<DeferredFragment, String> deferredLoadHashes,
+ Map<String, List<FinalizedFragment>> loadMap,
+ Map<FinalizedFragment, String> deferredLoadHashes,
DeferredLoadingState deferredLoadingState) {
if (loadMap.isEmpty) return;
- Map<Fragment, int> fragmentIndexes = {};
+ Map<FinalizedFragment, int> fragmentIndexes = {};
List<String> fragmentUris = [];
List<String> fragmentHashes = [];
List<js.Property> libraryPartsMapEntries = [];
- loadMap.forEach((String loadId, List<Fragment> fragmentList) {
+ loadMap.forEach((String loadId, List<FinalizedFragment> fragmentList) {
List<js.Expression> indexes = [];
- for (Fragment fragment in fragmentList) {
+ for (FinalizedFragment fragment in fragmentList) {
String fragmentHash = deferredLoadHashes[fragment];
if (fragmentHash == null) continue;
int index = fragmentIndexes[fragment];
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_merger.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_merger.dart
new file mode 100644
index 0000000..576f4ee
--- /dev/null
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_merger.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../deferred_load.dart' show OutputUnit;
+import '../../js/js.dart' as js;
+import '../../js/size_estimator.dart';
+import '../../options.dart';
+import '../model.dart';
+
+class PreFragment {
+ final List<DeferredFragment> fragments = [];
+ final List<js.Statement> classPrototypes = [];
+ final List<js.Statement> closurePrototypes = [];
+ final List<js.Statement> inheritance = [];
+ final List<js.Statement> methodAliases = [];
+ final List<js.Statement> tearOffs = [];
+ final List<js.Statement> constants = [];
+ final List<js.Statement> typeRules = [];
+ final List<js.Statement> variances = [];
+ final List<js.Statement> staticNonFinalFields = [];
+ final List<js.Statement> lazyInitializers = [];
+ final List<js.Statement> nativeSupport = [];
+ final Set<PreFragment> successors = {};
+ final Set<PreFragment> predecessors = {};
+ int size = 0;
+
+ PreFragment(
+ Fragment fragment,
+ js.Statement classPrototypes,
+ js.Statement closurePrototypes,
+ js.Statement inheritance,
+ js.Statement methodAliases,
+ js.Statement tearOffs,
+ js.Statement constants,
+ js.Statement typeRules,
+ js.Statement variances,
+ js.Statement staticNonFinalFields,
+ js.Statement lazyInitializers,
+ js.Statement nativeSupport,
+ bool estimateSize) {
+ this.fragments.add(fragment);
+ this.classPrototypes.add(classPrototypes);
+ this.closurePrototypes.add(closurePrototypes);
+ this.inheritance.add(inheritance);
+ this.methodAliases.add(methodAliases);
+ this.tearOffs.add(tearOffs);
+ this.constants.add(constants);
+ this.typeRules.add(typeRules);
+ this.variances.add(variances);
+ this.staticNonFinalFields.add(staticNonFinalFields);
+ this.lazyInitializers.add(lazyInitializers);
+ this.nativeSupport.add(nativeSupport);
+ if (estimateSize) {
+ var estimator = SizeEstimator();
+ estimator.visit(classPrototypes);
+ estimator.visit(closurePrototypes);
+ estimator.visit(inheritance);
+ estimator.visit(methodAliases);
+ estimator.visit(tearOffs);
+ estimator.visit(constants);
+ estimator.visit(typeRules);
+ estimator.visit(variances);
+ estimator.visit(staticNonFinalFields);
+ estimator.visit(lazyInitializers);
+ estimator.visit(nativeSupport);
+ size = estimator.charCount;
+ }
+ }
+
+ void mergeAfter(PreFragment that) {
+ assert(this != that);
+ assert(
+ (that.predecessors.length == 1 && that.predecessors.single == this) ||
+ (this.successors.length == 1 && this.successors.single == that));
+ this.fragments.addAll(that.fragments);
+ this.classPrototypes.addAll(that.classPrototypes);
+ this.closurePrototypes.addAll(that.closurePrototypes);
+ this.inheritance.addAll(that.inheritance);
+ this.methodAliases.addAll(that.methodAliases);
+ this.tearOffs.addAll(that.tearOffs);
+ this.constants.addAll(that.constants);
+ this.typeRules.addAll(that.typeRules);
+ this.variances.addAll(that.variances);
+ this.staticNonFinalFields.addAll(that.staticNonFinalFields);
+ this.lazyInitializers.addAll(that.lazyInitializers);
+ this.nativeSupport.addAll(that.nativeSupport);
+ this.successors.remove(that);
+ that.successors.forEach((fragment) {
+ fragment.predecessors.remove(that);
+ fragment.predecessors.add(this);
+ });
+ this.successors.addAll(that.successors);
+ that.predecessors.remove(this);
+ that.predecessors.forEach((fragment) {
+ fragment.successors.remove(that);
+ fragment.successors.add(this);
+ });
+ this.predecessors.addAll(that.predecessors);
+ this.size += that.size;
+ }
+
+ FinalizedFragment finalize(
+ Program program, Map<Fragment, FinalizedFragment> fragmentMap) {
+ FinalizedFragment finalizedFragment;
+ var seedFragment = fragments.first;
+
+ // If we only have a single fragment, then wen just finalize it by itself.
+ // Otherwise, we finalize an entire group of fragments into a single
+ // merged and finalized fragment.
+ if (fragments.length == 1) {
+ finalizedFragment = FinalizedFragment(
+ seedFragment.outputFileName,
+ seedFragment.outputUnit,
+ seedFragment.libraries,
+ classPrototypes.first,
+ closurePrototypes.first,
+ inheritance.first,
+ methodAliases.first,
+ tearOffs.first,
+ constants.first,
+ typeRules.first,
+ variances.first,
+ staticNonFinalFields.first,
+ lazyInitializers.first,
+ nativeSupport.first,
+ program.metadataTypesForOutputUnit(seedFragment.outputUnit));
+ fragmentMap[seedFragment] = finalizedFragment;
+ } else {
+ List<Library> libraries = [];
+ for (var fragment in fragments) {
+ if (seedFragment.outputUnit != fragment.outputUnit) {
+ program.mergeOutputUnitMetadata(
+ seedFragment.outputUnit, fragment.outputUnit);
+ seedFragment.outputUnit.merge(fragment.outputUnit);
+ }
+ libraries.addAll(fragment.libraries);
+ }
+ finalizedFragment = FinalizedFragment(
+ seedFragment.outputFileName,
+ seedFragment.outputUnit,
+ libraries,
+ js.Block(classPrototypes),
+ js.Block(closurePrototypes),
+ js.Block(inheritance),
+ js.Block(methodAliases),
+ js.Block(tearOffs),
+ js.Block(constants),
+ js.Block(typeRules),
+ js.Block(variances),
+ js.Block(staticNonFinalFields),
+ js.Block(lazyInitializers),
+ js.Block(nativeSupport),
+ program.metadataTypesForOutputUnit(seedFragment.outputUnit));
+ for (var fragment in fragments) {
+ fragmentMap[fragment] = finalizedFragment;
+ }
+ }
+ return finalizedFragment;
+ }
+
+ @override
+ String toString() {
+ // This is not an efficient operation and should only be used for debugging.
+ var successors =
+ this.successors.map((fragment) => fragment.debugName()).join(',');
+ var predecessors =
+ this.predecessors.map((fragment) => fragment.debugName()).join(',');
+ var name = debugName();
+ return 'PreFragment(fragments=[$name], successors=[$successors], '
+ 'predecessors=[$predecessors])';
+ }
+
+ String debugName() {
+ List<String> names = [];
+ this.fragments.forEach((fragment) => names.add(fragment.name));
+ return names.join(',');
+ }
+
+ static int compare(PreFragment l, PreFragment r) {
+ return l.size.compareTo(r.size);
+ }
+}
+
+class FinalizedFragment {
+ final String outputFileName;
+ final OutputUnit outputUnit;
+ final List<Library> libraries;
+ final js.Statement classPrototypes;
+ final js.Statement closurePrototypes;
+ final js.Statement inheritance;
+ final js.Statement methodAliases;
+ final js.Statement tearOffs;
+ final js.Statement constants;
+ final js.Statement typeRules;
+ final js.Statement variances;
+ final js.Statement staticNonFinalFields;
+ final js.Statement lazyInitializers;
+ final js.Statement nativeSupport;
+ final js.Expression deferredTypes;
+
+ FinalizedFragment(
+ this.outputFileName,
+ this.outputUnit,
+ this.libraries,
+ this.classPrototypes,
+ this.closurePrototypes,
+ this.inheritance,
+ this.methodAliases,
+ this.tearOffs,
+ this.constants,
+ this.typeRules,
+ this.variances,
+ this.staticNonFinalFields,
+ this.lazyInitializers,
+ this.nativeSupport,
+ this.deferredTypes);
+
+ bool isEmptyStatement(js.Statement statement) {
+ if (statement is js.Block) {
+ return statement.statements.isEmpty;
+ }
+ return statement is js.EmptyStatement;
+ }
+
+ bool get isEmpty {
+ // TODO(sra): How do we tell if [deferredTypes] is empty? It is filled-in
+ // later via the program finalizers. So we should defer the decision on the
+ // emptiness of the fragment until the finalizers have run. For now we seem
+ // to get away with the fact that type indexes are either (1) main unit or
+ // (2) local to the emitted unit, so there is no such thing as a type in a
+ // deferred unit that is referenced from another deferred unit. If we did
+ // not emit any functions, then we probably did not use the signature types
+ // in the OutputUnit's types, leaving them unused and tree-shaken.
+ // TODO(joshualitt): Currently, we ignore [typeRules] when determining
+ // emptiness because the type rules never seem to be empty.
+ return isEmptyStatement(classPrototypes) &&
+ isEmptyStatement(closurePrototypes) &&
+ isEmptyStatement(inheritance) &&
+ isEmptyStatement(methodAliases) &&
+ isEmptyStatement(tearOffs) &&
+ isEmptyStatement(constants) &&
+ isEmptyStatement(staticNonFinalFields) &&
+ isEmptyStatement(lazyInitializers) &&
+ isEmptyStatement(nativeSupport);
+ }
+}
+
+class FragmentMerger {
+ final CompilerOptions _options;
+
+ FragmentMerger(this._options);
+
+ // Converts a map of (loadId, List<fragments>) to a map of
+ // (loadId, List<FinalizedFragment>).
+ static Map<String, List<FinalizedFragment>> processLoadMap(
+ Map<String, List<Fragment>> programLoadMap,
+ Map<Fragment, FinalizedFragment> fragmentMap) {
+ Map<String, List<FinalizedFragment>> loadMap = {};
+ programLoadMap.forEach((loadId, fragments) {
+ Set<FinalizedFragment> unique = {};
+ List<FinalizedFragment> finalizedFragments = [];
+ loadMap[loadId] = finalizedFragments;
+ for (var fragment in fragments) {
+ var finalizedFragment = fragmentMap[fragment];
+ if (unique.add(finalizedFragment)) {
+ finalizedFragments.add(finalizedFragment);
+ }
+ }
+ });
+ return loadMap;
+ }
+
+ // Attaches predecessors to each PreFragment. We only care about
+ // direct predecessors.
+ static void attachDependencies(Map<String, List<Fragment>> programLoadMap,
+ Map<Fragment, PreFragment> fragmentMap) {
+ programLoadMap.forEach((loadId, fragments) {
+ for (int i = 0; i < fragments.length - 1; i++) {
+ var fragment = fragmentMap[fragments[i]];
+ var nextFragment = fragmentMap[fragments[i + 1]];
+ fragment.successors.add(nextFragment);
+ nextFragment.predecessors.add(fragment);
+ }
+ });
+ }
+
+ // Iterates through preDeferredFragments making as many merges as possible
+ // until either there are no more valid merges to make, or until there are
+ // only mergeFragmentsThreshold remaining.
+ List<PreFragment> mergeFragments(List<PreFragment> preDeferredFragments) {
+ Set<PreFragment> fragmentsBySize = {};
+
+ // We greedily look for a valid merge which results in the smallest
+ // possible increase in size. Currently, we only merge fragments in two
+ // cases:
+ // 1) We will merge two fragments A and B if B is A's single dependent.
+ // 2) We will merge two fragments C and D if C is D's single dependency.
+ bool mergeTwo() {
+ PreFragment aFragment = null;
+ PreFragment bFragment = null;
+ PreFragment cFragment = null;
+ PreFragment dFragment = null;
+ for (var fragment in fragmentsBySize) {
+ if (fragment.successors.length == 1 &&
+ (aFragment == null && bFragment == null ||
+ (fragment.size + fragment.successors.single.size <
+ aFragment.size + bFragment.size))) {
+ aFragment = fragment;
+ bFragment = fragment.successors.single;
+ }
+ if (fragment.predecessors.length == 1 &&
+ (cFragment == null && dFragment == null ||
+ (fragment.size + fragment.predecessors.single.size <
+ cFragment.size + dFragment.size))) {
+ cFragment = fragment.predecessors.single;
+ dFragment = fragment;
+ }
+ }
+ assert((aFragment != null &&
+ bFragment != null &&
+ aFragment != bFragment &&
+ aFragment.successors.single == bFragment) ||
+ (cFragment != null &&
+ dFragment != null &&
+ cFragment != dFragment &&
+ dFragment.predecessors.single == cFragment) ||
+ (aFragment == null &&
+ bFragment == null &&
+ cFragment == null &&
+ dFragment == null));
+ int mergeSentinel = 0x10000000000;
+ bool abCanMerge = aFragment != null && bFragment != null;
+ bool cdCanMerge = cFragment != null && dFragment != null;
+ int abMergeSize =
+ abCanMerge ? aFragment.size + bFragment.size : mergeSentinel;
+ int cdMergeSize =
+ cdCanMerge ? cFragment.size + dFragment.size : mergeSentinel;
+ bool abShouldMerge() => abCanMerge && abMergeSize <= cdMergeSize;
+ bool cdShouldMerge() => cdCanMerge && cdMergeSize <= abMergeSize;
+ void innerMerge(PreFragment a, PreFragment b) {
+ fragmentsBySize.remove(a);
+ fragmentsBySize.remove(b);
+ a.mergeAfter(b);
+ fragmentsBySize.add(a);
+ }
+
+ bool merged = abShouldMerge() || cdShouldMerge();
+ if (abShouldMerge()) {
+ innerMerge(aFragment, bFragment);
+ } else if (cdShouldMerge()) {
+ innerMerge(cFragment, dFragment);
+ } else {
+ assert(aFragment == null &&
+ bFragment == null &&
+ cFragment == null &&
+ dFragment == null);
+ }
+ return merged;
+ }
+
+ fragmentsBySize.addAll(preDeferredFragments);
+ var numFragments = preDeferredFragments.length;
+ while (numFragments-- > _options.mergeFragmentsThreshold) {
+ if (!mergeTwo()) {
+ // No further valid merges can be made.
+ break;
+ }
+ }
+ return fragmentsBySize.toList();
+ }
+}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
index f549f60..60d8baa 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
@@ -38,6 +38,7 @@
import '../../constants/values.dart'
show ConstantValue, FunctionConstantValue, NullConstantValue;
import '../../common_elements.dart' show CommonElements, JElementEnvironment;
+import '../../deferred_load.dart' show OutputUnit;
import '../../dump_info.dart';
import '../../elements/entities.dart';
import '../../elements/types.dart';
@@ -75,6 +76,7 @@
import '../js_emitter.dart' show buildTearOffCode, NativeGenerator;
import '../model.dart';
import '../native_emitter.dart';
+import 'fragment_merger.dart';
part 'fragment_emitter.dart';
@@ -94,9 +96,11 @@
final SourceInformationStrategy _sourceInformationStrategy;
// The full code that is written to each hunk part-file.
- final Map<Fragment, CodeOutput> outputBuffers = {};
+ final Map<OutputUnit, CodeOutput> emittedOutputBuffers = {};
- Set<Fragment> omittedFragments = Set();
+ final Set<OutputUnit> omittedOutputUnits = {};
+
+ List<PreFragment> preDeferredFragmentsForTesting;
/// For deferred loading we communicate the initializers via this global var.
static const String deferredInitializersGlobal =
@@ -183,6 +187,8 @@
[_namer.globalObjectForConstant(value), _namer.constantName(value)]);
}
+ bool get shouldMergeFragments => _options.mergeFragmentsThreshold != null;
+
int emitProgram(Program program, CodegenWorld codegenWorld) {
MainFragment mainFragment = program.fragments.first;
List<DeferredFragment> deferredFragments =
@@ -203,17 +209,44 @@
js.Statement mainCode =
fragmentEmitter.emitMainFragment(program, deferredLoadingState);
- Map<DeferredFragment, js.Expression> deferredFragmentsCode = {};
+ // In order to get size estimates, we partially emit deferred fragments.
+ List<PreFragment> preDeferredFragments = [];
+ Map<DeferredFragment, PreFragment> preFragmentMap = {};
+ _task.measureSubtask('emit prefragments', () {
+ for (var fragment in deferredFragments) {
+ var preFragment =
+ fragmentEmitter.emitPreFragment(fragment, shouldMergeFragments);
+ preFragmentMap[fragment] = preFragment;
+ preDeferredFragments.add(preFragment);
+ }
+ });
- for (DeferredFragment fragment in deferredFragments) {
- js.Expression types =
- program.metadataTypesForOutputUnit(fragment.outputUnit);
+ // Attach dependencies to each PreFragment.
+ FragmentMerger.attachDependencies(program.loadMap, preFragmentMap);
+
+ if (shouldMergeFragments) {
+ preDeferredFragments = _task.measureSubtask('merge fragments', () {
+ FragmentMerger fragmentMerger = FragmentMerger(_options);
+ return fragmentMerger.mergeFragments(preDeferredFragments);
+ });
+ }
+
+ // If necessary, we retain the merged PreFragments for testing.
+ if (retainDataForTesting) {
+ preDeferredFragmentsForTesting = preDeferredFragments;
+ }
+
+ Map<DeferredFragment, FinalizedFragment> fragmentMap = {};
+ Map<FinalizedFragment, js.Expression> deferredFragmentsCode = {};
+ for (var preDeferredFragment in preDeferredFragments) {
+ var finalizedFragment =
+ preDeferredFragment.finalize(program, fragmentMap);
js.Expression fragmentCode = fragmentEmitter.emitDeferredFragment(
- fragment, types, program.holders);
+ finalizedFragment, program.holders);
if (fragmentCode != null) {
- deferredFragmentsCode[fragment] = fragmentCode;
+ deferredFragmentsCode[finalizedFragment] = fragmentCode;
} else {
- omittedFragments.add(fragment);
+ omittedOutputUnits.add(finalizedFragment.outputUnit);
}
}
@@ -227,15 +260,17 @@
// deferred ASTs inside the parts) have any contents. We should wait until
// this point to decide if a part is empty.
- Map<DeferredFragment, String> hunkHashes =
+ Map<FinalizedFragment, String> hunkHashes =
_task.measureSubtask('write fragments', () {
return writeDeferredFragments(deferredFragmentsCode);
});
// Now that we have written the deferred hunks, we can create the deferred
// loading data.
+ Map<String, List<FinalizedFragment>> loadMap =
+ FragmentMerger.processLoadMap(program.loadMap, fragmentMap);
fragmentEmitter.finalizeDeferredLoadingData(
- program.loadMap, hunkHashes, deferredLoadingState);
+ loadMap, hunkHashes, deferredLoadingState);
_task.measureSubtask('write fragments', () {
writeMainFragment(mainFragment, mainCode,
@@ -254,7 +289,7 @@
}
// Return the total program size.
- return outputBuffers.values.fold(0, (a, b) => a + b.length);
+ return emittedOutputBuffers.values.fold(0, (a, b) => a + b.length);
}
/// Generates a simple header that provides the compiler's build id.
@@ -278,11 +313,11 @@
/// library code).
///
/// Updates the shared [outputBuffers] field with the output.
- Map<DeferredFragment, String> writeDeferredFragments(
- Map<DeferredFragment, js.Expression> fragmentsCode) {
- Map<DeferredFragment, String> hunkHashes = {};
+ Map<FinalizedFragment, String> writeDeferredFragments(
+ Map<FinalizedFragment, js.Expression> fragmentsCode) {
+ Map<FinalizedFragment, String> hunkHashes = {};
- fragmentsCode.forEach((DeferredFragment fragment, js.Expression code) {
+ fragmentsCode.forEach((FinalizedFragment fragment, js.Expression code) {
hunkHashes[fragment] = writeDeferredFragment(fragment, code);
});
@@ -313,7 +348,7 @@
CodeOutput mainOutput = StreamCodeOutput(
_outputProvider.createOutputSink('', 'js', OutputType.js),
codeOutputListeners);
- outputBuffers[fragment] = mainOutput;
+ emittedOutputBuffers[fragment.outputUnit] = mainOutput;
js.Program program = js.Program([
buildGeneratedBy(),
@@ -358,7 +393,7 @@
// Returns the deferred fragment's hash.
//
// Updates the shared [outputBuffers] field with the output.
- String writeDeferredFragment(DeferredFragment fragment, js.Expression code) {
+ String writeDeferredFragment(FinalizedFragment fragment, js.Expression code) {
List<CodeOutputListener> outputListeners = [];
Hasher hasher = new Hasher();
outputListeners.add(hasher);
@@ -378,7 +413,7 @@
hunkPrefix, deferredExtension, OutputType.jsPart),
outputListeners);
- outputBuffers[fragment] = output;
+ emittedOutputBuffers[fragment.outputUnit] = output;
// The [code] contains the function that must be invoked when the deferred
// hunk is loaded.
@@ -456,8 +491,7 @@
"needed for a given deferred library import.";
mapping.addAll(_closedWorld.outputUnitData.computeDeferredMap(
_options, _closedWorld.elementEnvironment,
- omittedUnits:
- omittedFragments.map((fragment) => fragment.outputUnit).toSet()));
+ omittedUnits: omittedOutputUnits));
_outputProvider.createOutputSink(
_options.deferredMapUri.path, '', OutputType.deferredMap)
..add(const JsonEncoder.withIndent(" ").convert(mapping))
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 47b0985..6e86321 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -161,6 +161,15 @@
/// libraries are subdivided.
Uri deferredMapUri;
+ /// The maximum number of deferred fragments to generate. If the number of
+ /// fragments exceeds this amount, then they may be merged.
+ /// Note: Currently, we only merge fragments in a single dependency chain. We
+ /// will not merge fragments with unrelated dependencies and thus we may
+ /// generate more fragments than the 'mergeFragmentsThreshold' under some
+ /// situations.
+ int mergeFragmentsThreshold = null; // default value, no max.
+ int _mergeFragmentsThreshold;
+
/// Whether to disable inlining during the backend optimizations.
// TODO(sigmund): negate, so all flags are positive
bool disableInlining = false;
@@ -524,7 +533,9 @@
..cfeOnly = _hasOption(options, Flags.cfeOnly)
..debugGlobalInference = _hasOption(options, Flags.debugGlobalInference)
.._soundNullSafety = _hasOption(options, Flags.soundNullSafety)
- .._noSoundNullSafety = _hasOption(options, Flags.noSoundNullSafety);
+ .._noSoundNullSafety = _hasOption(options, Flags.noSoundNullSafety)
+ .._mergeFragmentsThreshold =
+ _extractIntOption(options, '${Flags.mergeFragmentsThreshold}=');
}
void validate() {
@@ -628,6 +639,10 @@
if (_noNativeNullAssertions || nullSafetyMode != NullSafetyMode.sound) {
nativeNullAssertions = false;
}
+
+ if (_mergeFragmentsThreshold != null) {
+ mergeFragmentsThreshold = _mergeFragmentsThreshold;
+ }
}
/// Returns `true` if warnings and hints are shown for all packages.
diff --git a/pkg/compiler/test/deferred_loading/data/basic_deferred/main.dart b/pkg/compiler/test/deferred_loading/data/basic_deferred/main.dart
index ca84a9f..ab1d73d 100644
--- a/pkg/compiler/test/deferred_loading/data/basic_deferred/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/basic_deferred/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_class/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_class/main.dart
index bb45d8c..af43fe3 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_class/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_class/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_constant1/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_constant1/main.dart
index 5a1c77d..fa722a4 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_constant1/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_constant1/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib2}], usedBy: [], needs: []}],
+ steps=[lib2=(f1)]
+*/
+
// @dart = 2.7
import 'lib1.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_constant2/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_constant2/main.dart
index 71a715b..b4cc534 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_constant2/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_constant2/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'package:expect/expect.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_constant3/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_constant3/main.dart
index 5bc723e..0810049 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_constant3/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_constant3/main.dart
@@ -2,6 +2,15 @@
// 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.
+/*library:
+ output_units=[
+ f1: {units: [1{l1}], usedBy: [], needs: []},
+ f2: {units: [2{l2}], usedBy: [], needs: []}],
+ steps=[
+ l1=(f1),
+ l2=(f2)]
+*/
+
// @dart = 2.7
import 'shared.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_fail_and_retry/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_fail_and_retry/main.dart
index 167b145..ba1eed1 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_fail_and_retry/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_fail_and_retry/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
// Test that when a deferred import fails to load, it is possible to retry.
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_function/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_function/main.dart
index 3c99316..48dd14a 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_function/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_function/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
// Test that loading of a library (with top-level functions only) can
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_overlapping/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_overlapping/main.dart
index 782e177..1da36d1 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_overlapping/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_overlapping/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [1{lib1, lib2}], usedBy: [2, 3], needs: []},
+ f2: {units: [2{lib1}], usedBy: [], needs: [1]},
+ f3: {units: [3{lib2}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib2=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [1{lib1, lib2}, 2{lib1}], usedBy: [2], needs: []},
+ f2: {units: [3{lib2}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1),
+ lib2=(f1, f2)]
+*/
+
// @dart = 2.7
import 'lib1.dart' deferred as lib1;
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_typed_map/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_typed_map/main.dart
index 20bb44c..45bddff 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_typed_map/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_typed_map/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib1.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/deferred_typedef/main.dart b/pkg/compiler/test/deferred_loading/data/deferred_typedef/main.dart
index 9c56b84..c838c8f 100644
--- a/pkg/compiler/test/deferred_loading/data/deferred_typedef/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/deferred_typedef/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib1}], usedBy: [], needs: []}],
+ steps=[lib1=(f1)]
+*/
+
// @dart = 2.7
import 'lib1.dart' deferred as lib1;
diff --git a/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_constants/main.dart b/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_constants/main.dart
index e55be81..d65299a 100644
--- a/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_constants/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_constants/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [2{lib1, lib2}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{lib1}], usedBy: [], needs: [1]},
+ f3: {units: [3{lib2}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib2=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [2{lib1, lib2}, 3{lib2}], usedBy: [2], needs: []},
+ f2: {units: [1{lib1}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib2=(f1)]
+*/
+
// @dart = 2.7
// TODO(sigmund): remove this indirection and move the main code here. This is
diff --git a/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_global/main.dart b/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_global/main.dart
index 3a45b3e..59d567e 100644
--- a/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_global/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/dont_inline_deferred_global/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/follow_implicit_super_regression_test/main.dart b/pkg/compiler/test/deferred_loading/data/follow_implicit_super_regression_test/main.dart
index 74cd88d..0465c9f 100644
--- a/pkg/compiler/test/deferred_loading/data/follow_implicit_super_regression_test/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/follow_implicit_super_regression_test/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import "lib.dart" deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/future_or/main.dart b/pkg/compiler/test/deferred_loading/data/future_or/main.dart
index de44d54..77ca4a0 100644
--- a/pkg/compiler/test/deferred_loading/data/future_or/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/future_or/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib1}], usedBy: [], needs: []}],
+ steps=[lib1=(f1)]
+*/
+
// @dart = 2.7
import 'dart:async';
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation0/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation0/main.dart
index 5da17d9..9b83f62 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation0/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation0/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{b}], usedBy: [], needs: []}],
+ steps=[b=(f1)]
+*/
+
// @dart = 2.7
// Test instantiation used only in a deferred library.
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation1/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation1/main.dart
index ae2c5a7..963d421 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation1/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation1/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [2{b, c}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{b}], usedBy: [], needs: [1]},
+ f3: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1, f2),
+ c=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [2{b, c}, 1{b}], usedBy: [2], needs: []},
+ f2: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1),
+ c=(f1, f2)]
+*/
+
// @dart = 2.7
// Test instantiations with different type argument count used only in two
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation2/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation2/main.dart
index 3ff8f46..53cbbdb 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation2/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation2/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [1{b, c}], usedBy: [2, 3], needs: []},
+ f2: {units: [2{b}], usedBy: [], needs: [1]},
+ f3: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1, f2),
+ c=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [1{b, c}, 2{b}], usedBy: [2], needs: []},
+ f2: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1),
+ c=(f1, f2)]
+*/
+
// @dart = 2.7
// Test instantiations with the same type argument count used only in two
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation3/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation3/main.dart
index b0d3a8f..10e0815 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation3/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation3/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{b}], usedBy: [], needs: []}],
+ steps=[b=(f1)]
+*/
+
// @dart = 2.7
// Test instantiation used only in a deferred library.
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation4/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation4/main.dart
index 34bf22a..c6b601b 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation4/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation4/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [2{b, c}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{b}], usedBy: [], needs: [1]},
+ f3: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1, f2),
+ c=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [2{b, c}, 1{b}], usedBy: [2], needs: []},
+ f2: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1),
+ c=(f1, f2)]
+*/
+
// @dart = 2.7
// Test instantiations with different type argument count used only in two
diff --git a/pkg/compiler/test/deferred_loading/data/instantiation5/main.dart b/pkg/compiler/test/deferred_loading/data/instantiation5/main.dart
index 5612525..8dbfaf1 100644
--- a/pkg/compiler/test/deferred_loading/data/instantiation5/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/instantiation5/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [1{b, c}], usedBy: [2, 3], needs: []},
+ f2: {units: [2{b}], usedBy: [], needs: [1]},
+ f3: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1, f2),
+ c=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [1{b, c}, 2{b}], usedBy: [2], needs: []},
+ f2: {units: [3{c}], usedBy: [], needs: [1]}],
+ steps=[
+ b=(f1),
+ c=(f1, f2)]
+*/
+
// @dart = 2.7
// Test instantiations with the same type argument count used only in two
diff --git a/pkg/compiler/test/deferred_loading/data/inteface_type_variable/main.dart b/pkg/compiler/test/deferred_loading/data/inteface_type_variable/main.dart
index d4b10fb..491bcf6 100644
--- a/pkg/compiler/test/deferred_loading/data/inteface_type_variable/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/inteface_type_variable/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/data/lazy_types/main.dart b/pkg/compiler/test/deferred_loading/data/lazy_types/main.dart
index ca9bb68..441c28d 100644
--- a/pkg/compiler/test/deferred_loading/data/lazy_types/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/lazy_types/main.dart
@@ -2,6 +2,41 @@
// 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.
+/*spec.library:
+ output_units=[
+ f1: {units: [3{libA, libB, libC}], usedBy: [2, 4], needs: []},
+ f2: {units: [4{libA, libC}], usedBy: [3, 6], needs: [1, 4]},
+ f3: {units: [6{libA}], usedBy: [], needs: [2]},
+ f4: {units: [5{libB, libC}], usedBy: [5, 2], needs: [1]},
+ f5: {units: [1{libB}], usedBy: [], needs: [4]},
+ f6: {units: [2{libC}], usedBy: [], needs: [2]}],
+ steps=[
+ libA=(f1, f2, f3),
+ libB=(f1, f4, f5),
+ libC=(f1, f4, f2, f6)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [3{libA, libB, libC}, 5{libB, libC}, 4{libA, libC}, 6{libA}, 1{libB}], usedBy: [2], needs: []},
+ f2: {units: [2{libC}], usedBy: [], needs: [1]}],
+ steps=[
+ libA=(f1),
+ libB=(f1),
+ libC=(f1, f2)]
+*/
+
+/*three-frag.library:
+ output_units=[
+ f1: {units: [3{libA, libB, libC}, 5{libB, libC}, 4{libA, libC}, 6{libA}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{libB}], usedBy: [], needs: [1]},
+ f3: {units: [2{libC}], usedBy: [], needs: [1]}],
+ steps=[
+ libA=(f1),
+ libB=(f1, f2),
+ libC=(f1, f3)]
+*/
+
// @dart = 2.7
import 'liba.dart' deferred as libA;
diff --git a/pkg/compiler/test/deferred_loading/data/many_parts/libB.dart b/pkg/compiler/test/deferred_loading/data/many_parts/libB.dart
index 7f86fc1..85002e0 100644
--- a/pkg/compiler/test/deferred_loading/data/many_parts/libB.dart
+++ b/pkg/compiler/test/deferred_loading/data/many_parts/libB.dart
@@ -35,7 +35,8 @@
f_010_11(Set<String> u, int b) => v(u, '01011', b);
@pragma('dart2js:noInline')
-/*member: f_011_01:member_unit=8{b1, b3, b4}*/
+/*spec|two-frag.member: f_011_01:member_unit=8{b1, b3, b4}*/
+/*three-frag.member: f_011_01:member_unit=8{b1, b3, b4, b2, b5}*/
f_011_01(Set<String> u, int b) => v(u, '01101', b);
@pragma('dart2js:noInline')
@@ -51,7 +52,8 @@
f_100_11(Set<String> u, int b) => v(u, '10011', b);
@pragma('dart2js:noInline')
-/*member: f_101_01:member_unit=12{b1, b3, b5}*/
+/*spec|three-frag.member: f_101_01:member_unit=12{b1, b3, b5}*/
+/*two-frag.member: f_101_01:member_unit=12{b1, b3, b5, b4, b2}*/
f_101_01(Set<String> u, int b) => v(u, '10101', b);
@pragma('dart2js:noInline')
diff --git a/pkg/compiler/test/deferred_loading/data/many_parts/main.dart b/pkg/compiler/test/deferred_loading/data/many_parts/main.dart
index 7c057b4..0fc9253 100644
--- a/pkg/compiler/test/deferred_loading/data/many_parts/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/many_parts/main.dart
@@ -2,6 +2,72 @@
// 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.
+/*spec.library:
+ output_units=[
+ f10: {units: [7{b1, b2, b4}], usedBy: [11, 29], needs: [9, 8]},
+ f11: {units: [5{b1, b2, b3}], usedBy: [12, 21, 26], needs: [10, 8]},
+ f12: {units: [10{b1, b5}], usedBy: [13, 31], needs: [11, 21]},
+ f13: {units: [6{b1, b4}], usedBy: [14, 30], needs: [12, 22]},
+ f14: {units: [4{b1, b3}], usedBy: [15, 28], needs: [13, 23]},
+ f15: {units: [3{b1, b2}], usedBy: [16, 24], needs: [14, 23]},
+ f16: {units: [2{b1}], usedBy: [], needs: [15]},
+ f17: {units: [24{b2, b3, b4, b5}], usedBy: [3, 2], needs: [1]},
+ f18: {units: [23{b2, b4, b5}], usedBy: [19, 20], needs: [5, 25]},
+ f19: {units: [22{b2, b3, b5}], usedBy: [20, 6], needs: [18, 25]},
+ f1: {units: [1{b1, b2, b3, b4, b5}], usedBy: [2, 17], needs: []},
+ f20: {units: [20{b2, b3, b4}], usedBy: [9, 7, 6], needs: [19, 18]},
+ f21: {units: [21{b2, b5}], usedBy: [22, 12], needs: [11, 26]},
+ f22: {units: [19{b2, b4}], usedBy: [23, 13], needs: [21, 27]},
+ f23: {units: [18{b2, b3}], usedBy: [15, 14], needs: [22, 27]},
+ f24: {units: [17{b2}], usedBy: [], needs: [15]},
+ f25: {units: [28{b3, b4, b5}], usedBy: [19, 18], needs: [5, 4]},
+ f26: {units: [27{b3, b5}], usedBy: [27, 21], needs: [11, 29]},
+ f27: {units: [26{b3, b4}], usedBy: [23, 22], needs: [26, 29]},
+ f28: {units: [25{b3}], usedBy: [], needs: [14]},
+ f29: {units: [30{b4, b5}], usedBy: [27, 26], needs: [10, 9]},
+ f2: {units: [16{b1, b3, b4, b5}], usedBy: [3, 4], needs: [1, 17]},
+ f30: {units: [29{b4}], usedBy: [], needs: [13]},
+ f31: {units: [31{b5}], usedBy: [], needs: [12]},
+ f3: {units: [15{b1, b2, b4, b5}], usedBy: [4, 5], needs: [2, 17]},
+ f4: {units: [13{b1, b2, b3, b5}], usedBy: [5, 25], needs: [3, 2]},
+ f5: {units: [9{b1, b2, b3, b4}], usedBy: [6, 18, 25], needs: [4, 3]},
+ f6: {units: [14{b1, b4, b5}], usedBy: [7, 8], needs: [5, 20, 19]},
+ f7: {units: [12{b1, b3, b5}], usedBy: [8, 9], needs: [6, 20]},
+ f8: {units: [8{b1, b3, b4}], usedBy: [9, 11, 10], needs: [7, 6]},
+ f9: {units: [11{b1, b2, b5}], usedBy: [10, 29], needs: [8, 20, 7]}],
+ steps=[
+ b1=(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16),
+ b2=(f1, f17, f3, f4, f5, f18, f19, f20, f9, f10, f11, f21, f22, f23, f15, f24),
+ b3=(f1, f17, f2, f4, f5, f25, f19, f20, f7, f8, f11, f26, f27, f23, f14, f28),
+ b4=(f1, f17, f2, f3, f5, f25, f18, f20, f6, f8, f10, f29, f27, f22, f13, f30),
+ b5=(f1, f17, f2, f3, f4, f25, f18, f19, f6, f7, f9, f29, f26, f21, f12, f31)]
+*/
+
+/*three-frag.library:
+ output_units=[
+ f1: {units: [1{b1, b2, b3, b4, b5}, 24{b2, b3, b4, b5}, 16{b1, b3, b4, b5}, 15{b1, b2, b4, b5}, 13{b1, b2, b3, b5}, 9{b1, b2, b3, b4}, 28{b3, b4, b5}, 23{b2, b4, b5}, 22{b2, b3, b5}, 20{b2, b3, b4}, 14{b1, b4, b5}], usedBy: [2, 3], needs: []},
+ f2: {units: [12{b1, b3, b5}], usedBy: [3], needs: [1]},
+ f3: {units: [8{b1, b3, b4, b2, b5}, 11{b1, b2, b5}, 7{b1, b2, b4}, 5{b1, b2, b3}, 30{b4, b5}, 27{b3, b5}, 26{b3, b4}, 21{b2, b5}, 19{b2, b4}, 18{b2, b3}, 10{b1, b5}, 31{b5}, 6{b1, b4}, 29{b4}, 4{b1, b3}, 25{b3}, 3{b1, b2}, 2{b1}, 17{b2}], usedBy: [], needs: [2, 1]}],
+ steps=[
+ b1=(f1, f2, f3),
+ b2=(f1, f3),
+ b3=(f1, f2, f3),
+ b4=(f1, f3),
+ b5=(f1, f2, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [1{b1, b2, b3, b4, b5}, 24{b2, b3, b4, b5}, 16{b1, b3, b4, b5}, 15{b1, b2, b4, b5}, 13{b1, b2, b3, b5}, 9{b1, b2, b3, b4}, 28{b3, b4, b5}, 23{b2, b4, b5}, 22{b2, b3, b5}, 20{b2, b3, b4}, 14{b1, b4, b5}], usedBy: [2], needs: []},
+ f2: {units: [12{b1, b3, b5, b4, b2}, 8{b1, b3, b4}, 11{b1, b2, b5}, 7{b1, b2, b4}, 5{b1, b2, b3}, 30{b4, b5}, 27{b3, b5}, 26{b3, b4}, 21{b2, b5}, 19{b2, b4}, 18{b2, b3}, 10{b1, b5}, 31{b5}, 6{b1, b4}, 29{b4}, 4{b1, b3}, 25{b3}, 3{b1, b2}, 2{b1}, 17{b2}], usedBy: [], needs: [1]}],
+ steps=[
+ b1=(f1, f2),
+ b2=(f1, f2),
+ b3=(f1, f2),
+ b4=(f1, f2),
+ b5=(f1, f2)]
+*/
+
import 'lib1.dart';
import 'lib2.dart';
import 'lib3.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/marker.options b/pkg/compiler/test/deferred_loading/data/marker.options
index 1415216..95010cc 100644
--- a/pkg/compiler/test/deferred_loading/data/marker.options
+++ b/pkg/compiler/test/deferred_loading/data/marker.options
@@ -1 +1,3 @@
spec=pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+two-frag=pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+three-frag=pkg/compiler/test/deferred_loading/deferred_loading_test.dart
diff --git a/pkg/compiler/test/deferred_loading/data/regress_35311/main.dart b/pkg/compiler/test/deferred_loading/data/regress_35311/main.dart
index d10d45f..7c1acac 100644
--- a/pkg/compiler/test/deferred_loading/data/regress_35311/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/regress_35311/main.dart
@@ -4,6 +4,10 @@
// @dart = 2.7
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
import 'lib.dart' deferred as lib;
/*member: main:member_unit=main{}*/
diff --git a/pkg/compiler/test/deferred_loading/data/regress_43055/main.dart b/pkg/compiler/test/deferred_loading/data/regress_43055/main.dart
index dfbc3c4..486907a 100644
--- a/pkg/compiler/test/deferred_loading/data/regress_43055/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/regress_43055/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{libb}], usedBy: [], needs: []}],
+ steps=[libb=(f1)]
+*/
+
// @dart = 2.7
import 'libb.dart' deferred as libb;
import 'libc.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/shared_constant/main.dart b/pkg/compiler/test/deferred_loading/data/shared_constant/main.dart
index 8337bc0..dea65bc 100644
--- a/pkg/compiler/test/deferred_loading/data/shared_constant/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/shared_constant/main.dart
@@ -2,6 +2,13 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{s1, s2}], usedBy: [], needs: []}],
+ steps=[
+ s1=(f1),
+ s2=(f1)]
+*/
+
// @dart = 2.7
/// Regression test for issue https://github.com/dart-lang/sdk/issues/31306.
diff --git a/pkg/compiler/test/deferred_loading/data/static_separate/main.dart b/pkg/compiler/test/deferred_loading/data/static_separate/main.dart
index 78918bf..78990e9 100644
--- a/pkg/compiler/test/deferred_loading/data/static_separate/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/static_separate/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [2{lib1, lib2}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{lib1}], usedBy: [], needs: [1]},
+ f3: {units: [3{lib2}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib2=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [2{lib1, lib2}, 1{lib1}], usedBy: [2], needs: []},
+ f2: {units: [3{lib2}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1),
+ lib2=(f1, f2)]
+*/
+
// @dart = 2.7
// The class lib1.C is referenced via lib1
diff --git a/pkg/compiler/test/deferred_loading/data/type_argument_dependency/main.dart b/pkg/compiler/test/deferred_loading/data/type_argument_dependency/main.dart
index c279b65..6049f85 100644
--- a/pkg/compiler/test/deferred_loading/data/type_argument_dependency/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/type_argument_dependency/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{c}], usedBy: [], needs: []}],
+ steps=[c=(f1)]
+*/
+
// @dart = 2.7
import 'lib1.dart';
diff --git a/pkg/compiler/test/deferred_loading/data/type_arguments/main.dart b/pkg/compiler/test/deferred_loading/data/type_arguments/main.dart
index dd5873b..4cad65c 100644
--- a/pkg/compiler/test/deferred_loading/data/type_arguments/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/type_arguments/main.dart
@@ -2,6 +2,25 @@
// 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.
+/*spec|three-frag.library:
+ output_units=[
+ f1: {units: [3{lib1, lib3}], usedBy: [2, 3], needs: []},
+ f2: {units: [1{lib1}], usedBy: [], needs: [1]},
+ f3: {units: [2{lib3}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib3=(f1, f3)]
+*/
+
+/*two-frag.library:
+ output_units=[
+ f1: {units: [3{lib1, lib3}, 2{lib3}], usedBy: [2], needs: []},
+ f2: {units: [1{lib1}], usedBy: [], needs: [1]}],
+ steps=[
+ lib1=(f1, f2),
+ lib3=(f1)]
+*/
+
// @dart = 2.7
import 'lib1.dart' deferred as lib1;
diff --git a/pkg/compiler/test/deferred_loading/data/uninstantiated_type_variable/main.dart b/pkg/compiler/test/deferred_loading/data/uninstantiated_type_variable/main.dart
index 8b8ad89..3ee33186 100644
--- a/pkg/compiler/test/deferred_loading/data/uninstantiated_type_variable/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/uninstantiated_type_variable/main.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+ output_units=[f1: {units: [1{lib}], usedBy: [], needs: []}],
+ steps=[lib=(f1)]
+*/
+
// @dart = 2.7
import 'lib.dart' deferred as lib;
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
index 43f6ffa..e261408 100644
--- a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
@@ -15,6 +15,9 @@
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/model.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';
@@ -37,7 +40,9 @@
await checkTests(dataDir, const OutputUnitDataComputer(),
options: compilerOptions, args: args, setUpFunction: () {
importPrefixes.clear();
- }, testedConfigs: allSpecConfigs);
+ },
+ testedConfigs: allSpecConfigs +
+ [twoDeferredFragmentConfig, threeDeferredFragmentConfig]);
});
}
@@ -47,10 +52,7 @@
// prefix name responds to two different libraries.
Map<String, Uri> importPrefixes = {};
-/// Create a consistent string representation of [OutputUnit]s for both
-/// KImportEntities and ImportElements.
-String outputUnitString(OutputUnit unit) {
- if (unit == null) return 'none';
+String importPrefixString(OutputUnit unit) {
StringBuffer sb = StringBuffer();
bool first = true;
for (ImportEntity import in unit.importsForTesting) {
@@ -73,15 +75,48 @@
}
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<Fragment>> loadMap,
+ List<PreFragment> preDeferredFragments) {
+ Map<DeferredFragment, PreFragment> fragmentMap = {};
+ for (var preFragment in preDeferredFragments) {
+ for (var fragment in preFragment.fragments) {
+ assert(!fragmentMap.containsKey(fragment));
+ fragmentMap[fragment] = preFragment;
+ }
+ }
+
+ Map<String, List<PreFragment>> preFragmentMap = {};
+ loadMap.forEach((loadId, fragments) {
+ Set<PreFragment> preFragments = {};
+ for (var fragment in fragments) {
+ preFragments.add(fragmentMap[fragment]);
+ }
+ assert(!preFragmentMap.containsKey(loadId));
+ 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';
+ static const String steps = 'steps';
+ static const String outputUnits = 'output_units';
}
class OutputUnitDataComputer extends DataComputer<Features> {
@@ -118,10 +153,82 @@
}
@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;
+ Program program =
+ compiler.backendStrategy.emitterTask.emitter.programForTesting;
+ Map<String, List<PreFragment>> preFragmentMap =
+ buildPreFragmentMap(program.loadMap, preDeferredFragments);
+ PreFragmentsIrComputer(compiler.reporter, actualMap, preFragmentMap)
+ .computeForLibrary(node);
+ }
+
+ @override
DataInterpreter<Features> get dataValidator =>
const FeaturesDataInterpreter();
}
+class PreFragmentsIrComputer extends IrDataExtractor<Features> {
+ final Map<String, List<PreFragment>> _preFragmentMap;
+
+ PreFragmentsIrComputer(DiagnosticReporter reporter,
+ Map<Id, ActualData<Features>> actualMap, this._preFragmentMap)
+ : 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;
+
+ int index = 1;
+ Map<PreFragment, int> preFragmentIndices = {};
+ Map<int, PreFragment> reversePreFragmentIndices = {};
+ _preFragmentMap.forEach((loadId, preFragments) {
+ List<String> preFragmentNeeds = [];
+ for (var preFragment in preFragments) {
+ if (!preFragmentIndices.containsKey(preFragment)) {
+ preFragmentIndices[preFragment] = index;
+ reversePreFragmentIndices[index++] = preFragment;
+ }
+ preFragmentNeeds.add('f${preFragmentIndices[preFragment]}');
+ }
+ features.addElement(
+ Tags.steps, '$loadId=(${preFragmentNeeds.join(', ')})');
+ });
+
+ for (int i = 1; i < index; i++) {
+ var preFragment = reversePreFragmentIndices[i];
+ List<int> needs = [];
+ List<OutputUnit> supplied = [];
+ List<int> usedBy = [];
+ for (var dependent in preFragment.successors) {
+ assert(preFragmentIndices.containsKey(dependent));
+ usedBy.add(preFragmentIndices[dependent]);
+ }
+
+ for (var dependency in preFragment.predecessors) {
+ assert(preFragmentIndices.containsKey(dependency));
+ needs.add(preFragmentIndices[dependency]);
+ }
+
+ for (var fragment in preFragment.fragments) {
+ supplied.add(fragment.outputUnit);
+ }
+ var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
+ features.addElement(Tags.outputUnits,
+ 'f$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+ }
+
+ return features;
+ }
+}
+
class OutputUnitIrComputer extends IrDataExtractor<Features> {
final JsToElementMap _elementMap;
final OutputUnitData _data;
diff --git a/pkg/compiler/test/equivalence/id_equivalence_helper.dart b/pkg/compiler/test/equivalence/id_equivalence_helper.dart
index 0a9c8d6..1e22b7c 100644
--- a/pkg/compiler/test/equivalence/id_equivalence_helper.dart
+++ b/pkg/compiler/test/equivalence/id_equivalence_helper.dart
@@ -29,12 +29,24 @@
const String specMarker = 'spec';
const String prodMarker = 'prod';
+const String twoDeferredFragmentMarker = 'two-frag';
+const String threeDeferredFragmentMarker = 'three-frag';
const TestConfig specConfig = TestConfig(specMarker, 'compliance mode', []);
const TestConfig prodConfig = TestConfig(prodMarker, 'production mode',
[Flags.omitImplicitChecks, Flags.laxRuntimeTypeToString]);
+const TestConfig twoDeferredFragmentConfig = TestConfig(
+ twoDeferredFragmentMarker,
+ 'two deferred fragment mode',
+ ['${Flags.mergeFragmentsThreshold}=2']);
+
+const TestConfig threeDeferredFragmentConfig = TestConfig(
+ threeDeferredFragmentMarker,
+ 'three deferred fragment mode',
+ ['${Flags.mergeFragmentsThreshold}=3']);
+
/// Default internal configurations not including experimental features.
const List<TestConfig> defaultInternalConfigs = [specConfig, prodConfig];
diff --git a/pkg/compiler/test/js/js_size_estimator_test.dart b/pkg/compiler/test/js/js_size_estimator_test.dart
index cb5ce76..e9fbfcf 100644
--- a/pkg/compiler/test/js/js_size_estimator_test.dart
+++ b/pkg/compiler/test/js/js_size_estimator_test.dart
@@ -24,7 +24,7 @@
// Always verify the actual results from the [SizeEstimator].
// This is the actual test, though DebugSizeEstimator is pretty trivial.
- int actualEstimate = EstimateSize(node);
+ int actualEstimate = estimateSize(node);
Expect.equals(actualEstimate, debugSizeEstimator.charCount);
return debugSizeEstimator;
}
diff --git a/tests/dart2js/internal/deferred/load_in_correct_order_test.dart b/tests/dart2js/internal/deferred/load_in_correct_order_test.dart
index dc6727d..4f04a2c 100644
--- a/tests/dart2js/internal/deferred/load_in_correct_order_test.dart
+++ b/tests/dart2js/internal/deferred/load_in_correct_order_test.dart
@@ -74,7 +74,25 @@
// This test has 3 loadLibrary calls, this array contains how many hunks will be
// loaded by each call.
self.currentLoadLibraryCall = 0;
-self.filesPerLoadLibraryCall = [4, 2, 1];
+self.filesPerLoadLibraryCall = null;
+
+self.initFilesPerLoadLibraryCall = function() {
+ // We assume we load d1, then d2, then d3.
+ var loadOrder = ['d1', 'd2', 'd3'];
+ var uniques = {};
+ self.filesPerLoadLibraryCall = [];
+ for (var i = 0; i < loadOrder.length; i++) {
+ var filesToLoad = 0;
+ var parts = init.deferredLibraryParts[loadOrder[i]];
+ for (var j = 0; j < parts.length; j++) {
+ if (!uniques.hasOwnProperty(parts[j])) {
+ uniques[parts[j]] = true;
+ filesToLoad++;
+ }
+ }
+ self.filesPerLoadLibraryCall.push(filesToLoad);
+ }
+};
// Download uri via an XHR
self.download = function(uri) {
@@ -99,6 +117,9 @@
// Hook to control how we load hunks (we force them to be out of order).
self.dartDeferredLibraryLoader = function(uri, success, error) {
+ if (self.filesPerLoadLibraryCall == null) {
+ self.initFilesPerLoadLibraryCall();
+ }
self.uris.push(uri);
self.successCallbacks.push(success);
if (isD8) {
@@ -111,13 +132,13 @@
// Do the actual load of the hunk and call the corresponding success callback.
self.doLoad = function(i) {
self.setTimeout(function () {
- var uri = self.uris[i];
- if (self.isD8) {
- load(uri);
- } else {
- eval(self.content[uri]);
- }
- (self.successCallbacks[i])();
+ var uri = self.uris[i];
+ if (self.isD8) {
+ load(uri);
+ } else {
+ eval(self.content[uri]);
+ }
+ (self.successCallbacks[i])();
}, 0);
};
@@ -125,13 +146,10 @@
// purposely load the hunks out of order.
self.doActualLoads = function() {
self.currentLoadLibraryCall++;
- if (self.total == 4) {
- self.doLoad(3); // load purposely out of order!
- self.doLoad(0);
- self.doLoad(1);
- self.doLoad(2);
- } else {
- for (var i = 0; i < self.total; i++) {
+ if (self.total >= 1) {
+ // Load out of order, last first.
+ self.doLoad(self.total - 1);
+ for (var i = 0; i < self.total - 1; i++) {
self.doLoad(i);
}
}
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 4b3cf6e..79fedf6 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -642,6 +642,18 @@
"host-checked": true
}
},
+ "dart2js-hostasserts-weak-max-fragments-(linux|win)-x64-(d8|chrome)": {
+ "options": {
+ "builder-tag": "dart2js-weak",
+ "dart2js-options": [
+ "--libraries-spec=sdk/lib/libraries.json",
+ "--platform-binaries=out/ReleaseX64/",
+ "--merge-fragments-threshold=3"
+ ],
+ "timeout": 240,
+ "host-checked": true
+ }
+ },
"dart2js-hostasserts-weak-mac-x64-(d8|chrome)": {
"options": {
"builder-tag": "dart2js-weak",
@@ -664,6 +676,19 @@
"host-checked": true
}
},
+ "dart2js-hostasserts-strong-max-fragments-(linux|win)-x64-(d8|chrome)": {
+ "options": {
+ "builder-tag": "dart2js-strong",
+ "dart2js-options": [
+ "--libraries-spec=sdk/lib/libraries.json",
+ "--platform-binaries=out/ReleaseX64/",
+ "--merge-fragments-threshold=3"
+ ],
+ "timeout": 240,
+ "host-checked": true
+ }
+ },
+
"dart2js-hostasserts-strong-mac-x64-(d8|chrome)": {
"options": {
"builder-tag": "dart2js-strong",
@@ -2469,6 +2494,16 @@
"fileset": "web_platform_hostasserts_nnbd"
},
{
+ "name": "dart2js nnbd weak d8 fragment merging tests",
+ "arguments": [
+ "-ndart2js-hostasserts-weak-max-fragments-linux-x64-d8",
+ "--dart2js-batch",
+ "dart2js/deferred/"
+ ],
+ "shards": 1,
+ "fileset": "web_platform_hostasserts_nnbd"
+ },
+ {
"name": "dart2js nnbd weak chrome tests",
"arguments": [
"-ndart2js-hostasserts-weak-linux-x64-chrome",
@@ -2510,6 +2545,16 @@
"fileset": "web_platform_hostasserts_nnbd"
},
{
+ "name": "dart2js nnbd strong d8 fragment merging tests",
+ "arguments": [
+ "-ndart2js-hostasserts-strong-max-fragments-linux-x64-d8",
+ "--dart2js-batch",
+ "dart2js/deferred/"
+ ],
+ "shards": 1,
+ "fileset": "web_platform_hostasserts_nnbd"
+ },
+ {
"name": "dart2js nnbd strong chrome tests",
"arguments": [
"-ndart2js-hostasserts-strong-linux-x64-chrome",