Skip to content

Commit 70e1517

Browse files
sigmundchcommit-bot@chromium.org
authored andcommitted
Record deferred accesses in kernel world impacts and use it for splitting.
On large apps this cuts down the time spent in the deferred loading algorithm by two thirds. To address issue #35311, this CL keeps things sound and may load in the main unit more code. On some large apps, it appears the effect of this change is not that measurable, and we even see an improvement. I'm still validating the data, but I believe this is in part because there was a different bug in the previous algorithm: constructors were never deferred because we were looking for the constructor-name, usually '', instead of the enclosing class name. My current data is that, the main unit of some large app shows: old algorithm: 13,213,191 sound algorithm: 13,150,145 unsound algorithm*: 13,147,282 fixed old algorithm: 13,146,509 * ignoring return type of closures Change-Id: I7d3e525393ef38979b26051b4d354fc1001560af Reviewed-on: https://dart-review.googlesource.com/c/85725 Commit-Queue: Sigmund Cherem <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent 5a8ec41 commit 70e1517

File tree

11 files changed

+199
-191
lines changed

11 files changed

+199
-191
lines changed

pkg/compiler/lib/src/compiler.dart

+5-4
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ abstract class Compiler {
6767
/// Options provided from command-line arguments.
6868
final CompilerOptions options;
6969

70-
/**
71-
* If true, stop compilation after type inference is complete. Used for
72-
* debugging and testing purposes only.
73-
*/
70+
// These internal flags are used to stop compilation after a specific phase.
71+
// Used only for debugging and testing purposes only.
72+
bool stopAfterClosedWorld = false;
7473
bool stopAfterTypeInference = false;
7574

7675
/// Output provider from user of Compiler API.
@@ -251,6 +250,7 @@ abstract class Compiler {
251250
generateJavaScriptCode(results);
252251
} else {
253252
KernelResult result = await kernelLoader.load(uri);
253+
reporter.log("Kernel load complete");
254254
if (result == null) return;
255255
if (compilationFailed && !options.generateCodeWithCompileTimeErrors) {
256256
return;
@@ -390,6 +390,7 @@ abstract class Compiler {
390390
selfTask.measureSubtask("compileFromKernel", () {
391391
JClosedWorld closedWorld = selfTask.measureSubtask("computeClosedWorld",
392392
() => computeClosedWorld(rootLibraryUri, libraries));
393+
if (stopAfterClosedWorld) return;
393394
if (closedWorld != null) {
394395
GlobalTypeInferenceResults globalInferenceResults =
395396
performGlobalTypeInference(closedWorld);

pkg/compiler/lib/src/deferred_load.dart

+79-87
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import 'constants/values.dart'
1414
show
1515
ConstantValue,
1616
ConstructedConstantValue,
17-
TypeConstantValue,
1817
DeferredGlobalConstantValue,
1918
InstantiationConstantValue;
2019
import 'elements/types.dart';
@@ -141,31 +140,6 @@ abstract class DeferredLoadTask extends CompilerTask {
141140
compiler.frontendStrategy.elementEnvironment;
142141
DiagnosticReporter get reporter => compiler.reporter;
143142

144-
/// Given [imports] that refer to an element from a library, determine whether
145-
/// the element is explicitly deferred.
146-
static bool _isExplicitlyDeferred(Iterable<ImportEntity> imports) {
147-
// If the element is not imported explicitly, it is implicitly imported
148-
// not deferred.
149-
if (imports.isEmpty) return false;
150-
// An element could potentially be loaded by several imports. If all of them
151-
// is explicitly deferred, we say the element is explicitly deferred.
152-
// TODO(sigurdm): We might want to give a warning if the imports do not
153-
// agree.
154-
return imports.every((ImportEntity import) => import.isDeferred);
155-
}
156-
157-
/// Returns every [ImportEntity] that imports [element] into [library].
158-
Iterable<ImportEntity> classImportsTo(
159-
ClassEntity element, LibraryEntity library);
160-
161-
/// Returns every [ImportEntity] that imports [element] into [library].
162-
Iterable<ImportEntity> memberImportsTo(
163-
MemberEntity element, LibraryEntity library);
164-
165-
/// Returns every [ImportEntity] that imports [element] into [library].
166-
Iterable<ImportEntity> typedefImportsTo(
167-
TypedefEntity element, LibraryEntity library);
168-
169143
/// Collects all direct dependencies of [element].
170144
///
171145
/// The collected dependent elements and constants are are added to
@@ -193,7 +167,7 @@ abstract class DeferredLoadTask extends CompilerTask {
193167
void addLiveInstanceMember(MemberEntity member) {
194168
if (!compiler.resolutionWorldBuilder.isMemberUsed(member)) return;
195169
if (!member.isInstanceMember) return;
196-
dependencies.members.add(member);
170+
dependencies.addMember(member);
197171
_collectDirectMemberDependencies(member, dependencies);
198172
}
199173

@@ -202,7 +176,7 @@ abstract class DeferredLoadTask extends CompilerTask {
202176
elementEnvironment.forEachSupertype(cls, (InterfaceType type) {
203177
_collectTypeDependencies(type, dependencies);
204178
});
205-
dependencies.classes.add(cls);
179+
dependencies.addClass(cls);
206180
}
207181

208182
/// Finds all elements and constants that [element] depends directly on.
@@ -216,7 +190,7 @@ abstract class DeferredLoadTask extends CompilerTask {
216190
elementEnvironment.getFunctionType(element), dependencies);
217191
}
218192
if (element.isStatic || element.isTopLevel || element.isConstructor) {
219-
dependencies.members.add(element);
193+
dependencies.addMember(element);
220194
_collectDirectMemberDependencies(element, dependencies);
221195
}
222196
if (element is ConstructorEntity && element.isGenerativeConstructor) {
@@ -243,21 +217,26 @@ abstract class DeferredLoadTask extends CompilerTask {
243217

244218
/// Recursively collects all the dependencies of [type].
245219
void _collectTypeDependencies(DartType type, Dependencies dependencies) {
246-
// TODO(het): we would like to separate out types that are only needed for
247-
// rti from types that are needed for their members.
248220
if (type is FunctionType) {
249221
_collectFunctionTypeDependencies(type, dependencies);
250222
} else if (type is TypedefType) {
251-
type.typeArguments
252-
.forEach((t) => _collectTypeDependencies(t, dependencies));
223+
_collectTypeArgumentDependencies(type.typeArguments, dependencies);
253224
_collectTypeDependencies(type.unaliased, dependencies);
254225
} else if (type is InterfaceType) {
255-
type.typeArguments
256-
.forEach((t) => _collectTypeDependencies(t, dependencies));
257-
dependencies.classes.add(type.element);
226+
_collectTypeArgumentDependencies(type.typeArguments, dependencies);
227+
// TODO(sigmund): when we are able to split classes from types in our
228+
// runtime-type representation, this should track type.element as a type
229+
// dependency instead.
230+
dependencies.addClass(type.element);
258231
}
259232
}
260233

234+
void _collectTypeArgumentDependencies(
235+
Iterable<DartType> typeArguments, Dependencies dependencies) {
236+
if (typeArguments == null) return;
237+
typeArguments.forEach((t) => _collectTypeDependencies(t, dependencies));
238+
}
239+
261240
void _collectFunctionTypeDependencies(
262241
FunctionType type, Dependencies dependencies) {
263242
for (FunctionTypeVariable typeVariable in type.typeVariables) {
@@ -285,7 +264,7 @@ abstract class DeferredLoadTask extends CompilerTask {
285264
new WorldImpactVisitorImpl(visitStaticUse: (StaticUse staticUse) {
286265
Entity usedEntity = staticUse.element;
287266
if (usedEntity is MemberEntity) {
288-
dependencies.members.add(usedEntity);
267+
dependencies.addMember(usedEntity, staticUse.deferredImport);
289268
} else {
290269
assert(usedEntity is KLocalFunction,
291270
failedAt(usedEntity, "Unexpected static use $staticUse."));
@@ -298,19 +277,23 @@ abstract class DeferredLoadTask extends CompilerTask {
298277
switch (staticUse.kind) {
299278
case StaticUseKind.CONSTRUCTOR_INVOKE:
300279
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
301-
_collectTypeDependencies(staticUse.type, dependencies);
280+
// The receiver type of generative constructors is a dependency of
281+
// the constructor (handled by `addMember` above) and not a
282+
// dependency at the call site.
283+
// Factory methods, on the other hand, are like static methods so
284+
// the target type is not relevant.
285+
// TODO(johnniwinther): Use rti need data to skip unneeded type
286+
// arguments.
287+
_collectTypeArgumentDependencies(
288+
staticUse.type.typeArguments, dependencies);
302289
break;
303290
case StaticUseKind.INVOKE:
304291
case StaticUseKind.CLOSURE_CALL:
305292
case StaticUseKind.DIRECT_INVOKE:
306293
// TODO(johnniwinther): Use rti need data to skip unneeded type
307294
// arguments.
308-
List<DartType> typeArguments = staticUse.typeArguments;
309-
if (typeArguments != null) {
310-
for (DartType typeArgument in typeArguments) {
311-
_collectTypeDependencies(typeArgument, dependencies);
312-
}
313-
}
295+
_collectTypeArgumentDependencies(
296+
staticUse.typeArguments, dependencies);
314297
break;
315298
default:
316299
}
@@ -320,7 +303,8 @@ abstract class DeferredLoadTask extends CompilerTask {
320303
case TypeUseKind.TYPE_LITERAL:
321304
if (type.isInterfaceType) {
322305
InterfaceType interface = type;
323-
dependencies.classes.add(interface.element);
306+
dependencies.addClass(
307+
interface.element, typeUse.deferredImport);
324308
}
325309
break;
326310
case TypeUseKind.INSTANTIATION:
@@ -352,12 +336,8 @@ abstract class DeferredLoadTask extends CompilerTask {
352336
}, visitDynamicUse: (DynamicUse dynamicUse) {
353337
// TODO(johnniwinther): Use rti need data to skip unneeded type
354338
// arguments.
355-
List<DartType> typeArguments = dynamicUse.typeArguments;
356-
if (typeArguments != null) {
357-
for (DartType typeArgument in typeArguments) {
358-
_collectTypeDependencies(typeArgument, dependencies);
359-
}
360-
}
339+
_collectTypeArgumentDependencies(
340+
dynamicUse.typeArguments, dependencies);
361341
}),
362342
DeferredLoadTask.IMPACT_USE);
363343
}
@@ -489,58 +469,46 @@ abstract class DeferredLoadTask extends CompilerTask {
489469

490470
void _processDependencies(LibraryEntity library, Dependencies dependencies,
491471
ImportSet oldSet, ImportSet newSet, WorkQueue queue) {
492-
for (ClassEntity cls in dependencies.classes) {
493-
Iterable<ImportEntity> imports = classImportsTo(cls, library);
494-
if (_isExplicitlyDeferred(imports)) {
472+
dependencies.classes.forEach((ClassEntity cls, DependencyInfo info) {
473+
if (info.isDeferred) {
495474
if (_shouldAddDeferredDependency(newSet)) {
496-
for (ImportEntity deferredImport in imports) {
475+
for (ImportEntity deferredImport in info.imports) {
497476
queue.addClass(cls, importSets.singleton(deferredImport));
498477
}
499478
}
500479
} else {
501480
_updateClassRecursive(cls, oldSet, newSet, queue);
502481
}
503-
}
482+
});
504483

505-
for (MemberEntity member in dependencies.members) {
506-
Iterable<ImportEntity> imports = memberImportsTo(member, library);
507-
if (_isExplicitlyDeferred(imports)) {
484+
dependencies.members.forEach((MemberEntity member, DependencyInfo info) {
485+
if (info.isDeferred) {
508486
if (_shouldAddDeferredDependency(newSet)) {
509-
for (ImportEntity deferredImport in imports) {
487+
for (ImportEntity deferredImport in info.imports) {
510488
queue.addMember(member, importSets.singleton(deferredImport));
511489
}
512490
}
513491
} else {
514492
_updateMemberRecursive(member, oldSet, newSet, queue);
515493
}
516-
}
494+
});
517495

518496
for (Local localFunction in dependencies.localFunctions) {
519497
_updateLocalFunction(localFunction, oldSet, newSet);
520498
}
521499

522-
for (ConstantValue dependency in dependencies.constants) {
523-
if (dependency is TypeConstantValue) {
524-
var type = dependency.representedType;
525-
var imports = const <ImportEntity>[];
526-
if (type is InterfaceType) {
527-
imports = classImportsTo(type.element, library);
528-
} else if (type is TypedefType) {
529-
imports = typedefImportsTo(type.element, library);
530-
}
531-
if (_isExplicitlyDeferred(imports)) {
532-
if (_shouldAddDeferredDependency(newSet)) {
533-
for (ImportEntity deferredImport in imports) {
534-
queue.addConstant(
535-
dependency, importSets.singleton(deferredImport));
536-
}
500+
dependencies.constants
501+
.forEach((ConstantValue dependency, DependencyInfo info) {
502+
if (info.isDeferred) {
503+
if (_shouldAddDeferredDependency(newSet)) {
504+
for (ImportEntity deferredImport in info.imports) {
505+
queue.addConstant(dependency, importSets.singleton(deferredImport));
537506
}
538-
continue;
539507
}
508+
} else {
509+
_updateConstantRecursive(dependency, oldSet, newSet, queue);
540510
}
541-
542-
_updateConstantRecursive(dependency, oldSet, newSet, queue);
543-
}
511+
});
544512
}
545513

546514
/// Adds extra dependencies coming from mirror usage.
@@ -767,7 +735,6 @@ abstract class DeferredLoadTask extends CompilerTask {
767735
_memberToSet = null;
768736
_localFunctionToSet = null;
769737
_constantToSet = null;
770-
cleanup();
771738
return new OutputUnitData(
772739
this.isProgramSplit && !disableProgramSplit,
773740
this._mainOutputUnit,
@@ -781,9 +748,6 @@ abstract class DeferredLoadTask extends CompilerTask {
781748
_deferredImportDescriptions);
782749
}
783750

784-
/// Frees up strategy-specific temporary data.
785-
void cleanup() {}
786-
787751
void beforeResolution(Uri rootLibraryUri, Iterable<Uri> libraries) {
788752
measureSubtask('prepare', () {
789753
for (Uri uri in libraries) {
@@ -1488,8 +1452,36 @@ String deferredPartFileName(CompilerOptions options, String name,
14881452
}
14891453

14901454
class Dependencies {
1491-
final Set<ClassEntity> classes = new Set<ClassEntity>();
1492-
final Set<MemberEntity> members = new Set<MemberEntity>();
1455+
final Map<ClassEntity, DependencyInfo> classes = {};
1456+
final Map<MemberEntity, DependencyInfo> members = {};
14931457
final Set<Local> localFunctions = new Set<Local>();
1494-
final Set<ConstantValue> constants = new Set<ConstantValue>();
1458+
final Map<ConstantValue, DependencyInfo> constants = {};
1459+
1460+
void addClass(ClassEntity cls, [ImportEntity import]) {
1461+
(classes[cls] ??= new DependencyInfo()).registerImport(import);
1462+
}
1463+
1464+
void addMember(MemberEntity m, [ImportEntity import]) {
1465+
(members[m] ??= new DependencyInfo()).registerImport(import);
1466+
}
1467+
1468+
void addConstant(ConstantValue c, [ImportEntity import]) {
1469+
(constants[c] ??= new DependencyInfo()).registerImport(import);
1470+
}
1471+
}
1472+
1473+
class DependencyInfo {
1474+
bool isDeferred = true;
1475+
List<ImportEntity> imports;
1476+
1477+
registerImport(ImportEntity import) {
1478+
if (!isDeferred) return;
1479+
// A null import represents a direct non-deferred dependency.
1480+
if (import != null) {
1481+
(imports ??= []).add(import);
1482+
} else {
1483+
imports = null;
1484+
isDeferred = false;
1485+
}
1486+
}
14951487
}

pkg/compiler/lib/src/ir/util.dart

+31
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,34 @@ NullAwareExpression getNullAwareExpression(ir.TreeNode node) {
111111
}
112112
return null;
113113
}
114+
115+
/// Check whether [node] is immediately guarded by a
116+
/// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access.
117+
ir.LibraryDependency getDeferredImport(ir.TreeNode node) {
118+
// Note: this code relies on the CFE generating the code as we expect it here.
119+
// If one day we optimize away redundant CheckLibraryIsLoaded instructions,
120+
// we'd need to derive this information directly from the CFE (See #35005),
121+
ir.TreeNode parent = node.parent;
122+
123+
// TODO(sigmund): remove when CFE generates the correct tree (#35320). For
124+
// instance, it currently generates
125+
//
126+
// let _ = check(prefix) in (prefix::field.property)
127+
//
128+
// instead of:
129+
//
130+
// (let _ = check(prefix) in prefix::field).property
131+
if (node is ir.StaticGet) {
132+
while (parent is ir.PropertyGet || parent is ir.MethodInvocation) {
133+
parent = parent.parent;
134+
}
135+
}
136+
137+
if (parent is ir.Let) {
138+
var initializer = parent.variable.initializer;
139+
if (initializer is ir.CheckLibraryIsLoaded) {
140+
return initializer.import;
141+
}
142+
}
143+
return null;
144+
}

0 commit comments

Comments
 (0)