Skip to content

Commit 57863dc

Browse files
alexmarkovCommit Queue
authored andcommitted
[vm/aot/tfa] Tree-shake unused libraries
On a large app (which apparently has too many unused dependencies), size of the AOT kernel file: before: 464 MB after: 108 MB TEST=pkg/vm/testcases/transformations/type_flow/transformer/libraries.dart Bug: b/287638965 Change-Id: I79a26305c00741babb6a69a18919983b398109e5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/310772 Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Alexander Markov <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 98d7e25 commit 57863dc

File tree

10 files changed

+186
-8
lines changed

10 files changed

+186
-8
lines changed

pkg/vm/lib/transformations/type_flow/transformer.dart

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ class AnnotateKernel extends RecursiveVisitor {
701701
class TreeShaker {
702702
final TypeFlowAnalysis typeFlowAnalysis;
703703
final bool treeShakeWriteOnlyFields;
704+
final Set<Library> _usedLibraries = new Set<Library>();
704705
final Set<Class> _usedClasses = new Set<Class>();
705706
final Set<Class> _classesUsedInType = new Set<Class>();
706707
final Set<Member> _usedMembers = new Set<Member>();
@@ -734,6 +735,7 @@ class TreeShaker {
734735
_pass2.transformComponent(component);
735736
}
736737

738+
bool isLibraryUsed(Library l) => _usedLibraries.contains(l);
737739
bool isClassReferencedFromNativeCode(Class c) =>
738740
typeFlowAnalysis.nativeCodeOracle.isClassReferencedFromNativeCode(c);
739741
bool isClassUsed(Class c) => _usedClasses.contains(c);
@@ -772,6 +774,7 @@ class TreeShaker {
772774
debugPrint('Class ${c.name} used in type');
773775
}
774776
_usedClasses.add(c);
777+
_usedLibraries.add(c.enclosingLibrary);
775778
visitIterable(c.supers, typeVisitor);
776779
_pass1.transformTypeParameterList(c.typeParameters, c);
777780
_pass1.transformExpressionList(c.annotations, c);
@@ -794,6 +797,7 @@ class TreeShaker {
794797
}
795798
_usedClasses.add(enclosingClass);
796799
}
800+
_usedLibraries.add(m.enclosingLibrary);
797801

798802
FunctionNode? func = null;
799803
if (m is Field) {
@@ -891,6 +895,7 @@ class TreeShaker {
891895

892896
void addUsedTypedef(Typedef typedef) {
893897
if (_usedTypedefs.add(typedef)) {
898+
_usedLibraries.add(typedef.enclosingLibrary);
894899
typedef.annotations = const <Expression>[];
895900
_pass1.transformTypeParameterList(typedef.typeParameters, typedef);
896901
typedef.type?.accept(typeVisitor);
@@ -1217,6 +1222,19 @@ class _TreeShakerPass1 extends RemovingTransformer {
12171222
return node;
12181223
}
12191224

1225+
@override
1226+
TreeNode visitLoadLibrary(LoadLibrary node, TreeNode? removalSentinel) {
1227+
shaker._usedLibraries.add(node.import.targetLibrary);
1228+
return node;
1229+
}
1230+
1231+
@override
1232+
TreeNode visitCheckLibraryIsLoaded(
1233+
CheckLibraryIsLoaded node, TreeNode? removalSentinel) {
1234+
shaker._usedLibraries.add(node.import.targetLibrary);
1235+
return node;
1236+
}
1237+
12201238
@override
12211239
TreeNode visitInstanceInvocation(
12221240
InstanceInvocation node, TreeNode? removalSentinel) {
@@ -1727,8 +1745,44 @@ class _TreeShakerPass2 extends RemovingTransformer {
17271745
}
17281746
}
17291747

1748+
final _libraryExportDeps = <Library, Set<Library>>{};
1749+
final _additionalDeps = <Library>{};
1750+
1751+
// Returns set of export dependencies of given library.
1752+
Set<Library> getLibraryExportDeps(Library node) =>
1753+
_libraryExportDeps[node] ??= calculateLibraryExportDeps(node);
1754+
1755+
Set<Library> calculateLibraryExportDeps(Library node) {
1756+
final processed = <Library>{};
1757+
final worklist = <Library>[];
1758+
final deps = <Library>{};
1759+
worklist.add(node);
1760+
processed.add(node);
1761+
while (worklist.isNotEmpty) {
1762+
final lib = worklist.removeLast();
1763+
for (final dep in lib.dependencies) {
1764+
if (!dep.isExport) {
1765+
continue;
1766+
}
1767+
final targetLibrary = dep.targetLibrary;
1768+
if (processed.add(targetLibrary)) {
1769+
if (shaker.isLibraryUsed(targetLibrary)) {
1770+
deps.add(targetLibrary);
1771+
} else {
1772+
worklist.add(targetLibrary);
1773+
}
1774+
}
1775+
}
1776+
}
1777+
return deps;
1778+
}
1779+
17301780
@override
17311781
TreeNode visitLibrary(Library node, TreeNode? removalSentinel) {
1782+
if (!shaker.isLibraryUsed(node) && node.importUri.scheme != 'dart') {
1783+
return removalSentinel!;
1784+
}
1785+
_additionalDeps.clear();
17321786
node.transformOrRemoveChildren(this);
17331787
// The transformer API does not iterate over `Library.additionalExports`,
17341788
// so we manually delete the references to shaken nodes.
@@ -1746,6 +1800,29 @@ class _TreeShakerPass2 extends RemovingTransformer {
17461800
return !shaker.isMemberUsed(node as Member);
17471801
}
17481802
});
1803+
// Add transitive export dependencies of the removed imported libraries.
1804+
// This is needed to maintain connected library graph
1805+
// which is critical for calculation of the deferred loading units.
1806+
if (_additionalDeps.isNotEmpty) {
1807+
for (final dep in node.dependencies) {
1808+
_additionalDeps.remove(dep.targetLibrary);
1809+
}
1810+
for (final lib in _additionalDeps) {
1811+
node.addDependency(LibraryDependency.import(lib));
1812+
}
1813+
_additionalDeps.clear();
1814+
}
1815+
return node;
1816+
}
1817+
1818+
@override
1819+
TreeNode visitLibraryDependency(
1820+
LibraryDependency node, TreeNode? removalSentinel) {
1821+
final targetLibrary = node.targetLibrary;
1822+
if (!shaker.isLibraryUsed(targetLibrary)) {
1823+
_additionalDeps.addAll(getLibraryExportDeps(targetLibrary));
1824+
return removalSentinel!;
1825+
}
17491826
return node;
17501827
}
17511828

@@ -1991,6 +2068,10 @@ class _TreeShakerConstantVisitor extends ConstantVisitor<Null> {
19912068

19922069
@override
19932070
visitSymbolConstant(SymbolConstant constant) {
2071+
final libraryRef = constant.libraryReference;
2072+
if (libraryRef != null) {
2073+
shaker._usedLibraries.add(libraryRef.asLibrary);
2074+
}
19942075
// The Symbol class and it's _name field are always retained.
19952076
}
19962077

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
library #lib;
22
import self as self;
33

4-
import "file:pkg/vm/testcases/transformations/type_flow/transformer/const_default.lib.dart";
5-
64
static method main() → dynamic {
75
has-declared-initializer dynamic dyn = null;
86
}
9-
library const_default.lib.dart;
10-
import self as self;
11-
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2023, 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 'libraries_lib1.lib.dart';
6+
import 'libraries_lib2.lib.dart';
7+
import 'libraries_lib3.lib.dart';
8+
9+
void unused() {
10+
print(Foo1());
11+
print(bar1());
12+
}
13+
14+
void used() {
15+
print(Foo2());
16+
print(bar2());
17+
print(privateSymbol);
18+
}
19+
20+
main() {
21+
used();
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
library #lib;
2+
import self as self;
3+
import "dart:core" as core;
4+
import "libraries_lib2.lib.dart" as lib;
5+
6+
import "file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib2.lib.dart";
7+
import "file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib3.lib.dart";
8+
9+
static method used() → void {
10+
core::print(new lib::Foo2::•());
11+
core::print([@vm.inferred-type.metadata=dart.core::_OneByteString (value: "hi")] lib::bar2());
12+
core::print(#C1);
13+
}
14+
static method main() → dynamic {
15+
self::used();
16+
}
17+
constants {
18+
#C1 = #file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib3.lib.dart::_bazz
19+
}
20+
library libraries_lib2.lib.dart;
21+
import self as self;
22+
import "dart:core" as core;
23+
24+
class Foo2 extends core::Object {
25+
synthetic constructor •() → self::Foo2
26+
: super core::Object::•()
27+
;
28+
}
29+
static method bar2() → core::String
30+
return "hi";
31+
library libraries_lib3.lib.dart;
32+
import self as self;
33+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2023, 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+
class Foo1 {}
6+
7+
String bar1() => 'bye';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2023, 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+
class Foo2 {}
6+
7+
String bar2() => 'hi';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) 2023, 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+
const privateSymbol = #_bazz;

pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/create_test.dart.expect

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@ import "dart:core" as core;
55
import "package:test_core/scaffolding.dart" as sca;
66
import "package:matcher/src/expect/expect.dart" as exp;
77

8-
import "package:test/test.dart";
98
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
9+
import "package:matcher/src/expect/expect.dart";
10+
import "package:test_api/hooks.dart";
11+
import "package:test_core/scaffolding.dart";
12+
import "package:matcher/src/core_matchers.dart";
13+
import "package:matcher/src/description.dart";
14+
import "package:matcher/src/equals_matcher.dart";
15+
import "package:matcher/src/interfaces.dart";
16+
import "package:matcher/src/operator_matchers.dart";
17+
import "package:matcher/src/type_matcher.dart";
18+
import "package:matcher/src/util.dart";
1019

1120
static method main() → dynamic {
1221
pb::FooKeep foo = let final pb::FooKeep #t1 = [@vm.inferred-type.metadata=library file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart::FooKeep] pb::FooKeep::•() in block {

pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/decode_test.dart.expect

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@ import "package:test_core/scaffolding.dart" as sca;
55
import "package:matcher/src/expect/expect.dart" as exp;
66
import "dart:core" as core;
77

8-
import "package:test/test.dart";
98
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
9+
import "package:matcher/src/expect/expect.dart";
10+
import "package:test_api/hooks.dart";
11+
import "package:test_core/scaffolding.dart";
12+
import "package:matcher/src/core_matchers.dart";
13+
import "package:matcher/src/description.dart";
14+
import "package:matcher/src/equals_matcher.dart";
15+
import "package:matcher/src/interfaces.dart";
16+
import "package:matcher/src/operator_matchers.dart";
17+
import "package:matcher/src/type_matcher.dart";
18+
import "package:matcher/src/util.dart";
1019

1120
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int>]static field core::List<core::int> buffer = <core::int>[10, 4, 8, 5, 16, 4, 26, 9, 10, 3, 102, 111, 111, 18, 2, 8, 42, 34, 9, 10, 3, 122, 111, 112, 18, 2, 8, 3, 40, 43, 50, 0, 58, 0];
1221
static method main() → dynamic {

pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/freeze_test.dart.expect

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ import "package:protobuf/protobuf.dart" as pro;
77
import "package:matcher/src/expect/expect.dart" as exp;
88
import "package:matcher/src/expect/throws_matcher.dart" as thr;
99

10-
import "package:test/test.dart";
1110
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
11+
import "package:matcher/src/expect/expect.dart";
12+
import "package:matcher/src/expect/throws_matcher.dart";
13+
import "package:test_api/hooks.dart";
14+
import "package:test_core/scaffolding.dart";
15+
import "package:matcher/src/core_matchers.dart";
16+
import "package:matcher/src/description.dart";
17+
import "package:matcher/src/equals_matcher.dart";
18+
import "package:matcher/src/interfaces.dart";
19+
import "package:matcher/src/operator_matchers.dart";
20+
import "package:matcher/src/type_matcher.dart";
21+
import "package:matcher/src/util.dart";
1222

1323
static method main() → dynamic {
1424
pb::FooKeep foo = let final pb::FooKeep #t1 = [@vm.inferred-type.metadata=library file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart::FooKeep] pb::FooKeep::•() in block {

0 commit comments

Comments
 (0)