Record deferred accesses in kernel world impacts and use it for splitting only
under a flag.
This reapplies commit 70e1517d985e234ffcb1df637dc650eb1cd1386c, but adds a flag
to gradually migrate users before enabling it by default.
Patchset 1 matches the old CL
Change-Id: Iaf7ee3dec8d4aa658f0b4334549b507e5a610a68
Reviewed-on: https://dart-review.googlesource.com/c/86444
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3a62fd..681e1c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,64 @@
### Tool Changes
+#### dart2js
+
+* We fixed a bug in how deferred constructor calls were incorrectly not
+ marked as deferred. The old behavior didn't cause breakages, but was imprecise
+ and pushed more code to the main output unit.
+
+* A new deferred split algorithm implementation was added.
+
+ This implementation fixes a soundness bug and addresses performance issues of
+ the previous implementation, because of that it can have a visible impact
+ on apps. In particular,
+
+ * We fixed a performance issue which was introduced when we migrated to the
+ Common front-end. On large apps, the fix can cut down 2/3 of the time
+ spent on this task.
+
+ * We fixed a bug in how inferred types were miscategorized (#35311). The old
+ behavior was unsound and could produce broken programs. The fix may cause
+ more code to be pulled into the main output unit.
+
+ This shows up frequently when returning deferred values from closures
+ since the closure's inferred return type is the deferred type.
+ For example, if you have:
+
+ ```dart
+ () async {
+ await deferred_prefix.loadLibrary();
+ return new deferred_prefix.Foo();
+ }
+ ```
+
+ The closure's return type is `Future<Foo>`. The old implementation defers
+ `Foo`, and incorrectly makes the return type `Future<dynamic>`. This may
+ break in places where the correct type is expected.
+
+ The new implementation will not defer `Foo`, and will place it in the main
+ output unit. If your intent is to defer it, then you need to ensure the
+ return type is not inferred to be `Foo`. For example, you can do so by
+ changing the code to a named closure with a declared type, or by ensuring
+ that the return expression has the type you want, like:
+
+ ```dart
+ () async {
+ await deferred_prefix.loadLibrary();
+ return new deferred_prefix.Foo() as dynamic;
+ }
+ ```
+
+ * Because the new implementation might require you to inspect and fix
+ your app, we exposed two temporary flags:
+
+ * `--report-invalid-deferred-types`: when provided, we will run
+ both the old and new algorithm and report where the issue was
+ detected.
+
+ * `--new-deferred-split`: enables the new algorithm.
+
+
#### Pub
#### Linter
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index 391cdd9..a3cf93f 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -78,6 +78,10 @@
static const String serverMode = '--server-mode';
+ static const String newDeferredSplit = '--new-deferred-split';
+ static const String reportInvalidInferredDeferredTypes =
+ '--report-invalid-deferred-types';
+
/// Flag for a combination of flags for 'production' mode.
static const String benchmarkingProduction = '--benchmarking-production';
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 85cc18e..1c04d76 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -67,10 +67,9 @@
/// Options provided from command-line arguments.
final CompilerOptions options;
- /**
- * If true, stop compilation after type inference is complete. Used for
- * debugging and testing purposes only.
- */
+ // These internal flags are used to stop compilation after a specific phase.
+ // Used only for debugging and testing purposes only.
+ bool stopAfterClosedWorld = false;
bool stopAfterTypeInference = false;
/// Output provider from user of Compiler API.
@@ -251,6 +250,7 @@
generateJavaScriptCode(results);
} else {
KernelResult result = await kernelLoader.load(uri);
+ reporter.log("Kernel load complete");
if (result == null) return;
if (compilationFailed && !options.generateCodeWithCompileTimeErrors) {
return;
@@ -390,6 +390,7 @@
selfTask.measureSubtask("compileFromKernel", () {
JClosedWorld closedWorld = selfTask.measureSubtask("computeClosedWorld",
() => computeClosedWorld(rootLibraryUri, libraries));
+ if (stopAfterClosedWorld) return;
if (closedWorld != null) {
GlobalTypeInferenceResults globalInferenceResults =
performGlobalTypeInference(closedWorld);
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index dfab97f..e13dae6 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -377,6 +377,8 @@
new OptionHandler(Flags.disableRtiOptimization, passThrough),
new OptionHandler(Flags.terse, passThrough),
new OptionHandler('--deferred-map=.+', passThrough),
+ new OptionHandler(Flags.newDeferredSplit, passThrough),
+ new OptionHandler(Flags.reportInvalidInferredDeferredTypes, passThrough),
new OptionHandler(Flags.dumpInfo, passThrough),
new OptionHandler('--disallow-unsafe-eval', ignoreOption),
new OptionHandler(Option.showPackageWarnings, passThrough),
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index 8aafc91..d2fd134 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -130,6 +130,9 @@
final Compiler compiler;
bool get disableProgramSplit => compiler.options.disableProgramSplit;
+ bool get newDeferredSplit => compiler.options.newDeferredSplit;
+ bool get reportInvalidInferredDeferredTypes =>
+ compiler.options.reportInvalidInferredDeferredTypes;
DeferredLoadTask(this.compiler) : super(compiler.measurer) {
_mainOutputUnit = new OutputUnit(true, 'main', new Set<ImportEntity>());
@@ -193,7 +196,7 @@
void addLiveInstanceMember(MemberEntity member) {
if (!compiler.resolutionWorldBuilder.isMemberUsed(member)) return;
if (!member.isInstanceMember) return;
- dependencies.members.add(member);
+ dependencies.addMember(member);
_collectDirectMemberDependencies(member, dependencies);
}
@@ -202,7 +205,7 @@
elementEnvironment.forEachSupertype(cls, (InterfaceType type) {
_collectTypeDependencies(type, dependencies);
});
- dependencies.classes.add(cls);
+ dependencies.addClass(cls);
}
/// Finds all elements and constants that [element] depends directly on.
@@ -216,7 +219,7 @@
elementEnvironment.getFunctionType(element), dependencies);
}
if (element.isStatic || element.isTopLevel || element.isConstructor) {
- dependencies.members.add(element);
+ dependencies.addMember(element);
_collectDirectMemberDependencies(element, dependencies);
}
if (element is ConstructorEntity && element.isGenerativeConstructor) {
@@ -243,21 +246,26 @@
/// Recursively collects all the dependencies of [type].
void _collectTypeDependencies(DartType type, Dependencies dependencies) {
- // TODO(het): we would like to separate out types that are only needed for
- // rti from types that are needed for their members.
if (type is FunctionType) {
_collectFunctionTypeDependencies(type, dependencies);
} else if (type is TypedefType) {
- type.typeArguments
- .forEach((t) => _collectTypeDependencies(t, dependencies));
+ _collectTypeArgumentDependencies(type.typeArguments, dependencies);
_collectTypeDependencies(type.unaliased, dependencies);
} else if (type is InterfaceType) {
- type.typeArguments
- .forEach((t) => _collectTypeDependencies(t, dependencies));
- dependencies.classes.add(type.element);
+ _collectTypeArgumentDependencies(type.typeArguments, dependencies);
+ // TODO(sigmund): when we are able to split classes from types in our
+ // runtime-type representation, this should track type.element as a type
+ // dependency instead.
+ dependencies.addClass(type.element);
}
}
+ void _collectTypeArgumentDependencies(
+ Iterable<DartType> typeArguments, Dependencies dependencies) {
+ if (typeArguments == null) return;
+ typeArguments.forEach((t) => _collectTypeDependencies(t, dependencies));
+ }
+
void _collectFunctionTypeDependencies(
FunctionType type, Dependencies dependencies) {
for (FunctionTypeVariable typeVariable in type.typeVariables) {
@@ -285,7 +293,7 @@
new WorldImpactVisitorImpl(visitStaticUse: (StaticUse staticUse) {
Entity usedEntity = staticUse.element;
if (usedEntity is MemberEntity) {
- dependencies.members.add(usedEntity);
+ dependencies.addMember(usedEntity, staticUse.deferredImport);
} else {
assert(usedEntity is KLocalFunction,
failedAt(usedEntity, "Unexpected static use $staticUse."));
@@ -298,19 +306,23 @@
switch (staticUse.kind) {
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
- _collectTypeDependencies(staticUse.type, dependencies);
+ // The receiver type of generative constructors is a dependency of
+ // the constructor (handled by `addMember` above) and not a
+ // dependency at the call site.
+ // Factory methods, on the other hand, are like static methods so
+ // the target type is not relevant.
+ // TODO(johnniwinther): Use rti need data to skip unneeded type
+ // arguments.
+ _collectTypeArgumentDependencies(
+ staticUse.type.typeArguments, dependencies);
break;
case StaticUseKind.INVOKE:
case StaticUseKind.CLOSURE_CALL:
case StaticUseKind.DIRECT_INVOKE:
// TODO(johnniwinther): Use rti need data to skip unneeded type
// arguments.
- List<DartType> typeArguments = staticUse.typeArguments;
- if (typeArguments != null) {
- for (DartType typeArgument in typeArguments) {
- _collectTypeDependencies(typeArgument, dependencies);
- }
- }
+ _collectTypeArgumentDependencies(
+ staticUse.typeArguments, dependencies);
break;
default:
}
@@ -320,7 +332,8 @@
case TypeUseKind.TYPE_LITERAL:
if (type.isInterfaceType) {
InterfaceType interface = type;
- dependencies.classes.add(interface.element);
+ dependencies.addClass(
+ interface.element, typeUse.deferredImport);
}
break;
case TypeUseKind.INSTANTIATION:
@@ -352,12 +365,8 @@
}, visitDynamicUse: (DynamicUse dynamicUse) {
// TODO(johnniwinther): Use rti need data to skip unneeded type
// arguments.
- List<DartType> typeArguments = dynamicUse.typeArguments;
- if (typeArguments != null) {
- for (DartType typeArgument in typeArguments) {
- _collectTypeDependencies(typeArgument, dependencies);
- }
- }
+ _collectTypeArgumentDependencies(
+ dynamicUse.typeArguments, dependencies);
}),
DeferredLoadTask.IMPACT_USE);
}
@@ -428,7 +437,8 @@
Dependencies dependencies = new Dependencies();
_collectAllElementsAndConstantsResolvedFromClass(element, dependencies);
LibraryEntity library = element.library;
- _processDependencies(library, dependencies, oldSet, newSet, queue);
+ _processDependencies(
+ library, dependencies, oldSet, newSet, queue, element);
} else {
queue.addClass(element, newSet);
}
@@ -455,7 +465,8 @@
_collectAllElementsAndConstantsResolvedFromMember(element, dependencies);
LibraryEntity library = element.library;
- _processDependencies(library, dependencies, oldSet, newSet, queue);
+ _processDependencies(
+ library, dependencies, oldSet, newSet, queue, element);
} else {
queue.addMember(element, newSet);
}
@@ -487,60 +498,111 @@
/// same nodes we have already seen.
_shouldAddDeferredDependency(ImportSet newSet) => newSet.length <= 1;
+ void _fixDependencyInfo(DependencyInfo info, List<ImportEntity> imports,
+ String prefix, String name, Spannable context) {
+ var isDeferred = _isExplicitlyDeferred(imports);
+ if (isDeferred) {
+ if (!newDeferredSplit) {
+ info.isDeferred = true;
+ info.imports = imports;
+ }
+ if (reportInvalidInferredDeferredTypes) {
+ reporter.reportErrorMessage(context, MessageKind.GENERIC, {
+ 'text': "$prefix '$name' is deferred but appears to be inferred as"
+ " a return type or a type parameter (dartbug.com/35311)."
+ });
+ }
+ }
+ }
+
+ // The following 3 methods are used to check whether the new deferred split
+ // algorithm and the old one match. Because of a soundness bug in the old
+ // algorithm the new algorithm can pull in a lot of code to the main output
+ // unit. This logic detects it and will make it easier for us to migrate code
+ // off it incrementally.
+ // Note: we only expect discrepancies on class-dependency-info due to how
+ // inferred types expose deferred types in type-variables and return types
+ // (Issue #35311). We added the other two methods to test our transition, but
+ // we don't expect to detect any mismatches there.
+ //
+ // TODO(sigmund): delete once the new implementation is on by default.
+ void _fixClassDependencyInfo(DependencyInfo info, ClassEntity cls,
+ LibraryEntity library, Spannable context) {
+ if (info.isDeferred) return;
+ if (newDeferredSplit && !reportInvalidInferredDeferredTypes) return;
+ var imports = classImportsTo(cls, library);
+ _fixDependencyInfo(info, imports, "Class", cls.name, context);
+ }
+
+ void _fixMemberDependencyInfo(DependencyInfo info, MemberEntity member,
+ LibraryEntity library, Spannable context) {
+ if (info.isDeferred || compiler.options.newDeferredSplit) return;
+ var imports = memberImportsTo(member, library);
+ _fixDependencyInfo(info, imports, "Member", member.name, context);
+ }
+
+ void _fixConstantDependencyInfo(DependencyInfo info, ConstantValue constant,
+ LibraryEntity library, Spannable context) {
+ if (info.isDeferred || compiler.options.newDeferredSplit) return;
+ if (constant is TypeConstantValue) {
+ var type = constant.representedType;
+ if (type is InterfaceType) {
+ var imports = classImportsTo(type.element, library);
+ _fixDependencyInfo(
+ info, imports, "Class (in constant) ", type.element.name, context);
+ } else if (type is TypedefType) {
+ var imports = typedefImportsTo(type.element, library);
+ _fixDependencyInfo(
+ info, imports, "Typedef ", type.element.name, context);
+ }
+ }
+ }
+
void _processDependencies(LibraryEntity library, Dependencies dependencies,
- ImportSet oldSet, ImportSet newSet, WorkQueue queue) {
- for (ClassEntity cls in dependencies.classes) {
- Iterable<ImportEntity> imports = classImportsTo(cls, library);
- if (_isExplicitlyDeferred(imports)) {
+ ImportSet oldSet, ImportSet newSet, WorkQueue queue, Spannable context) {
+ dependencies.classes.forEach((ClassEntity cls, DependencyInfo info) {
+ _fixClassDependencyInfo(info, cls, library, context);
+ if (info.isDeferred) {
if (_shouldAddDeferredDependency(newSet)) {
- for (ImportEntity deferredImport in imports) {
+ for (ImportEntity deferredImport in info.imports) {
queue.addClass(cls, importSets.singleton(deferredImport));
}
}
} else {
_updateClassRecursive(cls, oldSet, newSet, queue);
}
- }
+ });
- for (MemberEntity member in dependencies.members) {
- Iterable<ImportEntity> imports = memberImportsTo(member, library);
- if (_isExplicitlyDeferred(imports)) {
+ dependencies.members.forEach((MemberEntity member, DependencyInfo info) {
+ _fixMemberDependencyInfo(info, member, library, context);
+ if (info.isDeferred) {
if (_shouldAddDeferredDependency(newSet)) {
- for (ImportEntity deferredImport in imports) {
+ for (ImportEntity deferredImport in info.imports) {
queue.addMember(member, importSets.singleton(deferredImport));
}
}
} else {
_updateMemberRecursive(member, oldSet, newSet, queue);
}
- }
+ });
for (Local localFunction in dependencies.localFunctions) {
_updateLocalFunction(localFunction, oldSet, newSet);
}
- for (ConstantValue dependency in dependencies.constants) {
- if (dependency is TypeConstantValue) {
- var type = dependency.representedType;
- var imports = const <ImportEntity>[];
- if (type is InterfaceType) {
- imports = classImportsTo(type.element, library);
- } else if (type is TypedefType) {
- imports = typedefImportsTo(type.element, library);
- }
- if (_isExplicitlyDeferred(imports)) {
- if (_shouldAddDeferredDependency(newSet)) {
- for (ImportEntity deferredImport in imports) {
- queue.addConstant(
- dependency, importSets.singleton(deferredImport));
- }
+ dependencies.constants
+ .forEach((ConstantValue constant, DependencyInfo info) {
+ _fixConstantDependencyInfo(info, constant, library, context);
+ if (info.isDeferred) {
+ if (_shouldAddDeferredDependency(newSet)) {
+ for (ImportEntity deferredImport in info.imports) {
+ queue.addConstant(constant, importSets.singleton(deferredImport));
}
- continue;
}
+ } else {
+ _updateConstantRecursive(constant, oldSet, newSet, queue);
}
-
- _updateConstantRecursive(dependency, oldSet, newSet, queue);
- }
+ });
}
/// Adds extra dependencies coming from mirror usage.
@@ -1488,8 +1550,36 @@
}
class Dependencies {
- final Set<ClassEntity> classes = new Set<ClassEntity>();
- final Set<MemberEntity> members = new Set<MemberEntity>();
+ final Map<ClassEntity, DependencyInfo> classes = {};
+ final Map<MemberEntity, DependencyInfo> members = {};
final Set<Local> localFunctions = new Set<Local>();
- final Set<ConstantValue> constants = new Set<ConstantValue>();
+ final Map<ConstantValue, DependencyInfo> constants = {};
+
+ void addClass(ClassEntity cls, [ImportEntity import]) {
+ (classes[cls] ??= new DependencyInfo()).registerImport(import);
+ }
+
+ void addMember(MemberEntity m, [ImportEntity import]) {
+ (members[m] ??= new DependencyInfo()).registerImport(import);
+ }
+
+ void addConstant(ConstantValue c, [ImportEntity import]) {
+ (constants[c] ??= new DependencyInfo()).registerImport(import);
+ }
+}
+
+class DependencyInfo {
+ bool isDeferred = true;
+ List<ImportEntity> imports;
+
+ registerImport(ImportEntity import) {
+ if (!isDeferred) return;
+ // A null import represents a direct non-deferred dependency.
+ if (import != null) {
+ (imports ??= []).add(import);
+ } else {
+ imports = null;
+ isDeferred = false;
+ }
+ }
}
diff --git a/pkg/compiler/lib/src/ir/util.dart b/pkg/compiler/lib/src/ir/util.dart
index fb6ba1e..fb57fed 100644
--- a/pkg/compiler/lib/src/ir/util.dart
+++ b/pkg/compiler/lib/src/ir/util.dart
@@ -111,3 +111,34 @@
}
return null;
}
+
+/// Check whether [node] is immediately guarded by a
+/// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access.
+ir.LibraryDependency getDeferredImport(ir.TreeNode node) {
+ // Note: this code relies on the CFE generating the code as we expect it here.
+ // If one day we optimize away redundant CheckLibraryIsLoaded instructions,
+ // we'd need to derive this information directly from the CFE (See #35005),
+ ir.TreeNode parent = node.parent;
+
+ // TODO(sigmund): remove when CFE generates the correct tree (#35320). For
+ // instance, it currently generates
+ //
+ // let _ = check(prefix) in (prefix::field.property)
+ //
+ // instead of:
+ //
+ // (let _ = check(prefix) in prefix::field).property
+ if (node is ir.StaticGet) {
+ while (parent is ir.PropertyGet || parent is ir.MethodInvocation) {
+ parent = parent.parent;
+ }
+ }
+
+ if (parent is ir.Let) {
+ var initializer = parent.variable.initializer;
+ if (initializer is ir.CheckLibraryIsLoaded) {
+ return initializer.import;
+ }
+ }
+ return null;
+}
diff --git a/pkg/compiler/lib/src/kernel/deferred_load.dart b/pkg/compiler/lib/src/kernel/deferred_load.dart
index 0abcdb3..ecdbd9f 100644
--- a/pkg/compiler/lib/src/kernel/deferred_load.dart
+++ b/pkg/compiler/lib/src/kernel/deferred_load.dart
@@ -11,6 +11,7 @@
import '../constants/values.dart';
import '../deferred_load.dart';
import '../elements/entities.dart';
+import '../ir/util.dart';
import 'element_map.dart';
class KernelDeferredLoadTask extends DeferredLoadTask {
@@ -25,6 +26,7 @@
return measureSubtask('find-imports', () {
List<ImportEntity> imports = [];
ir.Library source = _elementMap.getLibraryNode(library);
+ if (!source.dependencies.any((d) => d.isDeferred)) return const [];
for (ir.LibraryDependency dependency in source.dependencies) {
if (dependency.isExport) continue;
if (!_isVisible(dependency.combinators, nodeName)) continue;
@@ -55,7 +57,11 @@
Iterable<ImportEntity> memberImportsTo(
Entity element, LibraryEntity library) {
ir.Member node = _elementMap.getMemberNode(element);
- return _findImportsTo(node, node.name.name, node.enclosingLibrary, library);
+ return _findImportsTo(
+ node is ir.Constructor ? node.enclosingClass : node,
+ node is ir.Constructor ? node.enclosingClass.name : node.name.name,
+ node.enclosingLibrary,
+ library);
}
@override
@@ -138,7 +144,8 @@
ConstantValue constant =
elementMap.getConstantValue(node, requireConstant: required);
if (constant != null) {
- dependencies.constants.add(constant);
+ dependencies.addConstant(
+ constant, elementMap.getImport(getDeferredImport(node)));
}
}
diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart
index 3db40e0..abc103e 100644
--- a/pkg/compiler/lib/src/kernel/element_map_impl.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart
@@ -707,6 +707,7 @@
}
ImportEntity getImport(ir.LibraryDependency node) {
+ if (node == null) return null;
ir.Library library = node.parent;
KLibraryData data = libraries.getData(getLibraryInternal(library));
return data.imports[node];
diff --git a/pkg/compiler/lib/src/kernel/kernel_impact.dart b/pkg/compiler/lib/src/kernel/kernel_impact.dart
index a38cd9e..249418d 100644
--- a/pkg/compiler/lib/src/kernel/kernel_impact.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_impact.dart
@@ -252,10 +252,12 @@
InterfaceType type = elementMap.createInterfaceType(
target.enclosingClass, node.arguments.types);
CallStructure callStructure = elementMap.getCallStructure(node.arguments);
+ ImportEntity deferredImport = elementMap.getImport(getDeferredImport(node));
impactBuilder.registerStaticUse(isConst
- ? new StaticUse.constConstructorInvoke(constructor, callStructure, type)
+ ? new StaticUse.constConstructorInvoke(
+ constructor, callStructure, type, deferredImport)
: new StaticUse.typedConstructorInvoke(
- constructor, callStructure, type));
+ constructor, callStructure, type, deferredImport));
if (type.typeArguments.any((DartType type) => !type.isDynamic)) {
impactBuilder.registerFeature(Feature.TYPE_VARIABLE_BOUNDS_CHECK);
}
@@ -321,8 +323,13 @@
_handleExtractTypeArguments(node, target, typeArguments);
return;
}
+ ImportEntity deferredImport =
+ elementMap.getImport(getDeferredImport(node));
impactBuilder.registerStaticUse(new StaticUse.staticInvoke(
- target, elementMap.getCallStructure(node.arguments), typeArguments));
+ target,
+ elementMap.getCallStructure(node.arguments),
+ typeArguments,
+ deferredImport));
}
switch (elementMap.getForeignKind(node)) {
case ForeignKind.JS:
@@ -382,17 +389,20 @@
ir.Member target = node.target;
if (target is ir.Procedure && target.kind == ir.ProcedureKind.Method) {
FunctionEntity method = elementMap.getMethod(target);
- impactBuilder.registerStaticUse(new StaticUse.staticTearOff(method));
+ impactBuilder.registerStaticUse(new StaticUse.staticTearOff(
+ method, elementMap.getImport(getDeferredImport(node))));
} else {
MemberEntity member = elementMap.getMember(target);
- impactBuilder.registerStaticUse(new StaticUse.staticGet(member));
+ impactBuilder.registerStaticUse(new StaticUse.staticGet(
+ member, elementMap.getImport(getDeferredImport(node))));
}
}
@override
void handleStaticSet(ir.StaticSet node, ir.DartType valueType) {
MemberEntity member = elementMap.getMember(node.target);
- impactBuilder.registerStaticUse(new StaticUse.staticSet(member));
+ impactBuilder.registerStaticUse(new StaticUse.staticSet(
+ member, elementMap.getImport(getDeferredImport(node))));
}
void handleSuperInvocation(ir.Name name, ir.Node arguments) {
@@ -725,8 +735,9 @@
@override
void handleTypeLiteral(ir.TypeLiteral node) {
- impactBuilder.registerTypeUse(
- new TypeUse.typeLiteral(elementMap.getDartType(node.type)));
+ ImportEntity deferredImport = elementMap.getImport(getDeferredImport(node));
+ impactBuilder.registerTypeUse(new TypeUse.typeLiteral(
+ elementMap.getDartType(node.type), deferredImport));
if (node.type is ir.FunctionType) {
ir.FunctionType functionType = node.type;
assert(functionType.typedef != null);
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index ed6fcc8..7b10c51 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -95,6 +95,25 @@
/// libraries are subdivided.
Uri deferredMapUri;
+ /// Whether to apply the new deferred split fixes. The fixes improve on
+ /// performance and fix a soundness issue with inferred types. The latter will
+ /// move more code to the main output unit, because of that we are not
+ /// enabling the feature by default right away.
+ ///
+ /// When [reportInvalidInferredDeferredTypes] shows no errors, we expect this
+ /// flag to produce the same or better results than the current unsound
+ /// implementation.
+ bool newDeferredSplit = false;
+
+ /// Show errors when a deferred type is inferred as a return type of a closure
+ /// or in a type parameter. Those cases cause the compiler today to behave
+ /// unsoundly by putting the code in a deferred output unit. In the future
+ /// when [newDeferredSplit] is on by default, those cases will be treated
+ /// soundly and will cause more code to be moved to the main output unit.
+ ///
+ /// This flag is presented to help developers find and fix the affected code.
+ bool reportInvalidInferredDeferredTypes = false;
+
/// Whether to disable inlining during the backend optimizations.
// TODO(sigmund): negate, so all flags are positive
bool disableInlining = false;
@@ -282,6 +301,9 @@
_extractStringOption(options, '--build-id=', _UNDETERMINED_BUILD_ID)
..compileForServer = _hasOption(options, Flags.serverMode)
..deferredMapUri = _extractUriOption(options, '--deferred-map=')
+ ..newDeferredSplit = _hasOption(options, Flags.newDeferredSplit)
+ ..reportInvalidInferredDeferredTypes =
+ _hasOption(options, Flags.reportInvalidInferredDeferredTypes)
..fatalWarnings = _hasOption(options, Flags.fatalWarnings)
..terseDiagnostics = _hasOption(options, Flags.terse)
..suppressWarnings = _hasOption(options, Flags.suppressWarnings)
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index a5e2b41..1336828 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -173,12 +173,22 @@
final int hashCode;
final InterfaceType type;
final CallStructure callStructure;
+ final ImportEntity deferredImport;
StaticUse.internal(Entity element, this.kind,
- {this.type, this.callStructure, typeArgumentsHash: 0})
+ {this.type,
+ this.callStructure,
+ this.deferredImport,
+ typeArgumentsHash: 0})
: this.element = element,
- this.hashCode = Hashing.objectsHash(
- element, kind, type, typeArgumentsHash, callStructure);
+ this.hashCode = Hashing.listHash([
+ element,
+ kind,
+ type,
+ typeArgumentsHash,
+ callStructure,
+ deferredImport
+ ]);
/// Short textual representation use for testing.
String get shortText {
@@ -232,30 +242,33 @@
/// [callStructure].
factory StaticUse.staticInvoke(
FunctionEntity element, CallStructure callStructure,
- [List<DartType> typeArguments]) {
+ [List<DartType> typeArguments, ImportEntity deferredImport]) {
assert(
element.isStatic || element.isTopLevel,
failedAt(
element,
"Static invoke element $element must be a top-level "
"or static method."));
- return new GenericStaticUse(
- element, StaticUseKind.INVOKE, callStructure, typeArguments);
+ return new GenericStaticUse(element, StaticUseKind.INVOKE, callStructure,
+ typeArguments, deferredImport);
}
/// Closurization of a static or top-level function [element].
- factory StaticUse.staticTearOff(FunctionEntity element) {
+ factory StaticUse.staticTearOff(FunctionEntity element,
+ [ImportEntity deferredImport]) {
assert(
element.isStatic || element.isTopLevel,
failedAt(
element,
"Static tear-off element $element must be a top-level "
"or static method."));
- return new StaticUse.internal(element, StaticUseKind.STATIC_TEAR_OFF);
+ return new StaticUse.internal(element, StaticUseKind.STATIC_TEAR_OFF,
+ deferredImport: deferredImport);
}
/// Read access of a static or top-level field or getter [element].
- factory StaticUse.staticGet(MemberEntity element) {
+ factory StaticUse.staticGet(MemberEntity element,
+ [ImportEntity deferredImport]) {
assert(
element.isStatic || element.isTopLevel,
failedAt(
@@ -266,11 +279,13 @@
element.isField || element.isGetter,
failedAt(element,
"Static get element $element must be a field or a getter."));
- return new StaticUse.internal(element, StaticUseKind.GET);
+ return new StaticUse.internal(element, StaticUseKind.GET,
+ deferredImport: deferredImport);
}
/// Write access of a static or top-level field or setter [element].
- factory StaticUse.staticSet(MemberEntity element) {
+ factory StaticUse.staticSet(MemberEntity element,
+ [ImportEntity deferredImport]) {
assert(
element.isStatic || element.isTopLevel,
failedAt(
@@ -281,7 +296,8 @@
element.isField || element.isSetter,
failedAt(element,
"Static set element $element must be a field or a setter."));
- return new StaticUse.internal(element, StaticUseKind.SET);
+ return new StaticUse.internal(element, StaticUseKind.SET,
+ deferredImport: deferredImport);
}
/// Invocation of the lazy initializer for a static or top-level field
@@ -432,8 +448,11 @@
/// Constructor invocation of [element] with the given [callStructure] on
/// [type].
- factory StaticUse.typedConstructorInvoke(ConstructorEntity element,
- CallStructure callStructure, InterfaceType type) {
+ factory StaticUse.typedConstructorInvoke(
+ ConstructorEntity element,
+ CallStructure callStructure,
+ InterfaceType type,
+ ImportEntity deferredImport) {
assert(type != null,
failedAt(element, "No type provided for constructor invocation."));
assert(
@@ -443,13 +462,18 @@
"Typed constructor invocation element $element "
"must be a constructor."));
return new StaticUse.internal(element, StaticUseKind.CONSTRUCTOR_INVOKE,
- type: type, callStructure: callStructure);
+ type: type,
+ callStructure: callStructure,
+ deferredImport: deferredImport);
}
/// Constant constructor invocation of [element] with the given
/// [callStructure] on [type].
- factory StaticUse.constConstructorInvoke(ConstructorEntity element,
- CallStructure callStructure, InterfaceType type) {
+ factory StaticUse.constConstructorInvoke(
+ ConstructorEntity element,
+ CallStructure callStructure,
+ InterfaceType type,
+ ImportEntity deferredImport) {
assert(type != null,
failedAt(element, "No type provided for constructor invocation."));
assert(
@@ -460,7 +484,9 @@
"must be a constructor."));
return new StaticUse.internal(
element, StaticUseKind.CONST_CONSTRUCTOR_INVOKE,
- type: type, callStructure: callStructure);
+ type: type,
+ callStructure: callStructure,
+ deferredImport: deferredImport);
}
/// Constructor redirection to [element] on [type].
@@ -563,9 +589,11 @@
final List<DartType> typeArguments;
GenericStaticUse(Entity entity, StaticUseKind kind,
- CallStructure callStructure, this.typeArguments)
+ CallStructure callStructure, this.typeArguments,
+ [ImportEntity deferredImport])
: super.internal(entity, kind,
callStructure: callStructure,
+ deferredImport: deferredImport,
typeArgumentsHash: Hashing.listHash(typeArguments)) {
assert(
(callStructure?.typeArgumentCount ?? 0) == (typeArguments?.length ?? 0),
@@ -599,11 +627,12 @@
final DartType type;
final TypeUseKind kind;
final int hashCode;
+ final ImportEntity deferredImport;
- TypeUse.internal(DartType type, TypeUseKind kind)
+ TypeUse.internal(DartType type, TypeUseKind kind, [this.deferredImport])
: this.type = type,
this.kind = kind,
- this.hashCode = Hashing.objectHash(type, Hashing.objectHash(kind));
+ this.hashCode = Hashing.objectsHash(type, kind, deferredImport);
/// Short textual representation use for testing.
String get shortText {
@@ -678,8 +707,8 @@
}
/// [type] used as a type literal, like `foo() => T;`.
- factory TypeUse.typeLiteral(DartType type) {
- return new TypeUse.internal(type, TypeUseKind.TYPE_LITERAL);
+ factory TypeUse.typeLiteral(DartType type, ImportEntity deferredImport) {
+ return new TypeUse.internal(type, TypeUseKind.TYPE_LITERAL, deferredImport);
}
/// [type] used in an instantiation, like `new T();`.
diff --git a/tests/compiler/dart2js/deferred_loading/data/static_separate.dart b/tests/compiler/dart2js/deferred_loading/data/static_separate.dart
index ac5b27d..1f5a958 100644
--- a/tests/compiler/dart2js/deferred_loading/data/static_separate.dart
+++ b/tests/compiler/dart2js/deferred_loading/data/static_separate.dart
@@ -15,7 +15,9 @@
/*element: main:OutputUnit(main, {})*/
void main() {
asyncStart();
- Expect.throws(/*OutputUnit(main, {})*/ () => new lib1.C());
+ Expect.throws(/*OutputUnit(main, {})*/ () {
+ new lib1.C();
+ });
lib1.loadLibrary().then(/*OutputUnit(main, {})*/ (_) {
lib2.loadLibrary().then(/*OutputUnit(main, {})*/ (_) {
print("HERE");
diff --git a/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib1.dart b/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib1.dart
index 7587f4e..194e27d 100644
--- a/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib1.dart
+++ b/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib1.dart
@@ -4,6 +4,6 @@
import "deferred_overlapping_lib3.dart";
-/*class: C1:OutputUnit(1, {lib1})*/
-/*element: C1.:OutputUnit(1, {lib1})*/
+/*class: C1:OutputUnit(2, {lib1})*/
+/*element: C1.:OutputUnit(2, {lib1})*/
class C1 extends C3 {}
diff --git a/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib3.dart b/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib3.dart
index 5447ff9..5167cc6 100644
--- a/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib3.dart
+++ b/tests/compiler/dart2js/deferred_loading/libs/deferred_overlapping_lib3.dart
@@ -2,6 +2,6 @@
// 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.
-/*class: C3:OutputUnit(2, {lib1, lib2})*/
-/*element: C3.:OutputUnit(2, {lib1, lib2})*/
+/*class: C3:OutputUnit(1, {lib1, lib2})*/
+/*element: C3.:OutputUnit(1, {lib1, lib2})*/
class C3 {}
diff --git a/tests/compiler/dart2js/model/enqueuer_test.dart b/tests/compiler/dart2js/model/enqueuer_test.dart
index 60c3fe6..8a8a6a6 100644
--- a/tests/compiler/dart2js/model/enqueuer_test.dart
+++ b/tests/compiler/dart2js/model/enqueuer_test.dart
@@ -177,8 +177,8 @@
elementEnvironment.lookupConstructor(cls, '');
InterfaceType type = elementEnvironment.getRawType(cls);
WorldImpact impact = new WorldImpactBuilderImpl()
- ..registerStaticUse(new StaticUse.typedConstructorInvoke(
- constructor, constructor.parameterStructure.callStructure, type));
+ ..registerStaticUse(new StaticUse.typedConstructorInvoke(constructor,
+ constructor.parameterStructure.callStructure, type, null));
enqueuer.applyImpact(impact);
}