Skip to content

Commit 8aeadb3

Browse files
mkustermannCommit Queue
authored andcommitted
[dart2wasm] Optimize fine grained partitioning for deferred loading
This saves around 730 KB (-7.7%) on essentials main module. The existing fine grained partitioning of code into wasm modules operates on static elements. That means as soon as a constructor is called, all instance methods of a class are included as well. This CL improves upon this using the insight that some instance methods are always accessed via direct calls (i.e. all call sites to the instance member are devirtualized). For those methods that are never called indirectly, the partitioning algorithm can avoid enquing those instance methods when the class is enqueued and instead enqueue them when the devirtualized call site is hit. An example where this can happen is e.g. main module allocates a proto class but doesn't access field X. Currently when hitting the constructor we'll enqueue all instance members. Now we don't enqueue X (as all uses of X are direct calls to the X getter). That means if X is only used in a deferred module, then we can move the X getter to that deferred module. Change-Id: Ib45edb6e658a62aaeb15ed7c40745733ec619ff0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467700 Reviewed-by: Ömer Ağacan <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent c01e5d2 commit 8aeadb3

9 files changed

+595
-133
lines changed

pkg/dart2wasm/lib/deferred_load/dependencies.dart

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:kernel/class_hierarchy.dart';
56
import 'package:kernel/core_types.dart';
67
import 'package:kernel/kernel.dart';
78
import 'package:kernel/library_index.dart';
89

910
import '../modules.dart';
11+
import 'devirtualization_oracle.dart';
1012

1113
class DependenciesCollector {
1214
final CoreTypes _coreTypes;
15+
final ClosedWorldClassHierarchy _classHierarchy;
16+
final DevirtualizionOracle _devirtualizionOracle;
1317
final DeferredModuleLoadingMap _loadingMap;
1418

1519
late final _checkLibraryIsLoadedFromLoadId = _coreTypes.index.getProcedure(
1620
'dart:_internal',
1721
LibraryIndex.topLevel,
1822
'checkLibraryIsLoadedFromLoadId');
1923

20-
DependenciesCollector(this._coreTypes, this._loadingMap);
24+
DependenciesCollector(this._coreTypes, this._classHierarchy,
25+
this._devirtualizionOracle, this._loadingMap);
2126

2227
/// Returns the set of constants referred to by the (possibly composed)
2328
/// [constant].
@@ -38,7 +43,11 @@ class DependenciesCollector {
3843
}
3944

4045
final collector = _ReferenceDependenciesCollector._(
41-
_recognizeDeferredLoadingGuard, reference, deps);
46+
_recognizeDeferredLoadingGuard,
47+
_classHierarchy,
48+
_devirtualizionOracle,
49+
reference,
50+
deps);
4251
if (node is Procedure) {
4352
node.accept(collector);
4453
return deps;
@@ -90,19 +99,31 @@ class DependenciesCollector {
9099
return null;
91100
}
92101

93-
static void _enqueueInstanceMembers(
94-
Class klass, DirectReferenceDependencies deps) {
102+
void _enqueueInstanceMembers(Class klass, DirectReferenceDependencies deps) {
95103
final superReference = klass.superclass?.reference;
96104
if (superReference != null) {
97105
deps.references.add(superReference);
98106
}
99107
for (final m in klass.members) {
100108
if (m.isInstanceMember) {
101109
if (m is Field) {
102-
deps.references.add(m.fieldReference);
110+
if (!_devirtualizionOracle
111+
.isAlwaysStaticallyDispatchedTo(m.getterReference)) {
112+
deps.references.add(m.getterReference);
113+
}
114+
if (m.hasSetter) {
115+
if (!_devirtualizionOracle
116+
.isAlwaysStaticallyDispatchedTo(m.setterReference!)) {
117+
deps.references.add(m.setterReference!);
118+
}
119+
}
103120
continue;
104121
}
105-
deps.references.add(m.reference);
122+
assert(m is Procedure);
123+
if (!_devirtualizionOracle
124+
.isAlwaysStaticallyDispatchedTo(m.reference)) {
125+
deps.references.add(m.reference);
126+
}
106127
}
107128
}
108129
}
@@ -120,13 +141,19 @@ class _ConstantDependenciesCollector extends RecursiveVisitor {
120141

121142
class _ReferenceDependenciesCollector extends RecursiveVisitor {
122143
final LibraryDependency? Function(Let node) recognizeDeferredLoadingGuard;
144+
final DevirtualizionOracle _devirtualizionOracle;
145+
final ClosedWorldClassHierarchy _classHierarchy;
123146

124147
final Reference reference;
125148
final DirectReferenceDependencies deps;
126149
final List<LibraryDependency> _activeLoadGuards = [];
127150

128151
_ReferenceDependenciesCollector._(
129-
this.recognizeDeferredLoadingGuard, this.reference, this.deps);
152+
this.recognizeDeferredLoadingGuard,
153+
this._classHierarchy,
154+
this._devirtualizionOracle,
155+
this.reference,
156+
this.deps);
130157

131158
@override
132159
void visitLet(Let node) {
@@ -143,6 +170,42 @@ class _ReferenceDependenciesCollector extends RecursiveVisitor {
143170
}
144171
}
145172

173+
@override
174+
void visitSuperPropertyGet(SuperPropertyGet node) {
175+
_addSuperTargetReference(node.interfaceTarget, setter: false);
176+
}
177+
178+
@override
179+
void visitSuperPropertySet(SuperPropertySet node) {
180+
_addSuperTargetReference(node.interfaceTarget, setter: true);
181+
}
182+
183+
@override
184+
void visitSuperMethodInvocation(SuperMethodInvocation node) {
185+
_addSuperTargetReference(node.interfaceTarget, setter: false);
186+
}
187+
188+
@override
189+
void visitInstanceGet(InstanceGet node) {
190+
super.visitInstanceGet(node);
191+
final target = _devirtualizionOracle.staticDispatchTargetForGet(node);
192+
if (target != null) addReference(target);
193+
}
194+
195+
@override
196+
void visitInstanceSet(InstanceSet node) {
197+
super.visitInstanceSet(node);
198+
final target = _devirtualizionOracle.staticDispatchTargetForSet(node);
199+
if (target != null) addReference(target);
200+
}
201+
202+
@override
203+
void visitInstanceInvocation(InstanceInvocation node) {
204+
super.visitInstanceInvocation(node);
205+
final target = _devirtualizionOracle.staticDispatchTargetForCall(node);
206+
if (target != null) addReference(target);
207+
}
208+
146209
@override
147210
void visitStaticGet(StaticGet node) {
148211
super.visitStaticGet(node);
@@ -256,6 +319,19 @@ class _ReferenceDependenciesCollector extends RecursiveVisitor {
256319
deps.deferredConstants[used] = {_activeLoadGuards.last};
257320
}
258321
}
322+
323+
void _addSuperTargetReference(Member interfaceTarget,
324+
{required bool setter}) {
325+
final member = _classHierarchy.getDispatchTarget(
326+
(reference.asMember).enclosingClass!.superclass!, interfaceTarget.name,
327+
setter: setter)!;
328+
if (setter) {
329+
addReference(
330+
member is Field ? member.setterReference! : member.reference);
331+
} else {
332+
addReference(member is Field ? member.getterReference : member.reference);
333+
}
334+
}
259335
}
260336

261337
class DirectReferenceDependencies {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:kernel/kernel.dart';
6+
import 'package:vm/metadata/direct_call.dart';
7+
import 'package:vm/metadata/procedure_attributes.dart';
8+
import 'package:vm/metadata/table_selector.dart';
9+
10+
/// Oracle that knows whether instance members will be always called directly
11+
/// (i.e. all call sites are devirtualized direct calls instead of instance
12+
/// calls).
13+
class DevirtualizionOracle {
14+
final Component component;
15+
16+
late final Map<TreeNode, DirectCallMetadata> _directCallMetadata =
17+
(component.metadata[DirectCallMetadataRepository.repositoryTag]
18+
as DirectCallMetadataRepository)
19+
.mapping;
20+
21+
late final Map<TreeNode, ProcedureAttributesMetadata>
22+
_procedureAttributeMetadata =
23+
(component.metadata[ProcedureAttributesMetadataRepository.repositoryTag]
24+
as ProcedureAttributesMetadataRepository)
25+
.mapping;
26+
late final List<TableSelectorInfo> _selectorMetadata =
27+
(component.metadata[TableSelectorMetadataRepository.repositoryTag]
28+
as TableSelectorMetadataRepository)
29+
.mapping[component]!
30+
.selectors;
31+
32+
DevirtualizionOracle(this.component);
33+
34+
/// Whether all call sites are guaranteed to be devirtualized.
35+
bool isAlwaysStaticallyDispatchedTo(Reference reference) {
36+
final member = reference.asMember;
37+
assert(member.isInstanceMember);
38+
final metadata = _procedureAttributeMetadata[member]!;
39+
40+
final bool isGetter =
41+
member is Field && reference == member.getterReference ||
42+
member is Procedure && member.isGetter;
43+
44+
if (isGetter) {
45+
if (metadata.getterCalledDynamically) return false;
46+
47+
final getterId = metadata.getterSelectorId;
48+
if (getterId != ProcedureAttributesMetadata.kInvalidSelectorId) {
49+
final selector = _selectorMetadata[getterId];
50+
if (selector.callCount != 0 || selector.tornOff) {
51+
return false;
52+
}
53+
}
54+
} else {
55+
if (metadata.methodOrSetterCalledDynamically) return false;
56+
// This method may be dynamically torn off.
57+
if (metadata.getterCalledDynamically) return false;
58+
59+
final methodOrSetterId = metadata.methodOrSetterSelectorId;
60+
if (methodOrSetterId != ProcedureAttributesMetadata.kInvalidSelectorId) {
61+
final selector = _selectorMetadata[methodOrSetterId];
62+
if (selector.callCount != 0 || selector.tornOff) {
63+
return false;
64+
}
65+
}
66+
}
67+
68+
// All uses of this [member] will be devirtualized uses. The deferred
69+
// loading partitioning algorithm will - on all call sites (which are
70+
// guaranteed to be devirtualized) - make this target a direct dependency
71+
// (via calling `staticDispatchTargetFor*` methods below) instead of
72+
// conservatively enquing this method whenever the class is allocated.
73+
return true;
74+
}
75+
76+
Reference? staticDispatchTargetForGet(InstanceGet node) {
77+
final devirtualizedTarget = _directCallMetadata[node]?.targetMember;
78+
if (devirtualizedTarget == null) return null;
79+
if (devirtualizedTarget is Field) {
80+
return devirtualizedTarget.getterReference;
81+
}
82+
return (devirtualizedTarget as Procedure).reference;
83+
}
84+
85+
Reference? staticDispatchTargetForSet(InstanceSet node) {
86+
final devirtualizedTarget = _directCallMetadata[node]?.targetMember;
87+
if (devirtualizedTarget == null) return null;
88+
if (devirtualizedTarget is Field) {
89+
return devirtualizedTarget.setterReference;
90+
}
91+
return (devirtualizedTarget as Procedure).reference;
92+
}
93+
94+
Reference? staticDispatchTargetForCall(InstanceInvocation node) {
95+
final devirtualizedTarget = _directCallMetadata[node]?.targetMember;
96+
if (devirtualizedTarget == null) return null;
97+
return (devirtualizedTarget as Procedure).reference;
98+
}
99+
}

pkg/dart2wasm/lib/deferred_load/partition.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
import 'dart:collection';
66

7+
import 'package:kernel/class_hierarchy.dart';
78
import 'package:kernel/core_types.dart';
89
import 'package:kernel/kernel.dart';
910

1011
import '../modules.dart' show DeferredModuleLoadingMap;
1112
import 'dependencies.dart';
13+
import 'devirtualization_oracle.dart';
1214
import 'import_set.dart';
1315

1416
export 'import_set.dart' show Part;
@@ -23,7 +25,11 @@ Partitioning partitionAppplication(CoreTypes coreTypes, Component component,
2325
}
2426
}
2527
}
26-
final depsCollector = DependenciesCollector(coreTypes, loadingMap);
28+
final classHierarchy =
29+
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy;
30+
final devirtualizionOracle = DevirtualizionOracle(component);
31+
final depsCollector = DependenciesCollector(
32+
coreTypes, classHierarchy, devirtualizionOracle, loadingMap);
2733
final algorithm = _Algorithm(component, depsCollector, allDeferredImports);
2834
return algorithm.run(roots);
2935
}

0 commit comments

Comments
 (0)