[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 <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Alexander Markov
2023-06-23 20:14:08 +00:00
committed by Commit Queue
parent 98d7e25027
commit 57863dc64b
10 changed files with 186 additions and 8 deletions
@@ -701,6 +701,7 @@ class AnnotateKernel extends RecursiveVisitor {
class TreeShaker {
final TypeFlowAnalysis typeFlowAnalysis;
final bool treeShakeWriteOnlyFields;
final Set<Library> _usedLibraries = new Set<Library>();
final Set<Class> _usedClasses = new Set<Class>();
final Set<Class> _classesUsedInType = new Set<Class>();
final Set<Member> _usedMembers = new Set<Member>();
@@ -734,6 +735,7 @@ class TreeShaker {
_pass2.transformComponent(component);
}
bool isLibraryUsed(Library l) => _usedLibraries.contains(l);
bool isClassReferencedFromNativeCode(Class c) =>
typeFlowAnalysis.nativeCodeOracle.isClassReferencedFromNativeCode(c);
bool isClassUsed(Class c) => _usedClasses.contains(c);
@@ -772,6 +774,7 @@ class TreeShaker {
debugPrint('Class ${c.name} used in type');
}
_usedClasses.add(c);
_usedLibraries.add(c.enclosingLibrary);
visitIterable(c.supers, typeVisitor);
_pass1.transformTypeParameterList(c.typeParameters, c);
_pass1.transformExpressionList(c.annotations, c);
@@ -794,6 +797,7 @@ class TreeShaker {
}
_usedClasses.add(enclosingClass);
}
_usedLibraries.add(m.enclosingLibrary);
FunctionNode? func = null;
if (m is Field) {
@@ -891,6 +895,7 @@ class TreeShaker {
void addUsedTypedef(Typedef typedef) {
if (_usedTypedefs.add(typedef)) {
_usedLibraries.add(typedef.enclosingLibrary);
typedef.annotations = const <Expression>[];
_pass1.transformTypeParameterList(typedef.typeParameters, typedef);
typedef.type?.accept(typeVisitor);
@@ -1217,6 +1222,19 @@ class _TreeShakerPass1 extends RemovingTransformer {
return node;
}
@override
TreeNode visitLoadLibrary(LoadLibrary node, TreeNode? removalSentinel) {
shaker._usedLibraries.add(node.import.targetLibrary);
return node;
}
@override
TreeNode visitCheckLibraryIsLoaded(
CheckLibraryIsLoaded node, TreeNode? removalSentinel) {
shaker._usedLibraries.add(node.import.targetLibrary);
return node;
}
@override
TreeNode visitInstanceInvocation(
InstanceInvocation node, TreeNode? removalSentinel) {
@@ -1727,8 +1745,44 @@ class _TreeShakerPass2 extends RemovingTransformer {
}
}
final _libraryExportDeps = <Library, Set<Library>>{};
final _additionalDeps = <Library>{};
// Returns set of export dependencies of given library.
Set<Library> getLibraryExportDeps(Library node) =>
_libraryExportDeps[node] ??= calculateLibraryExportDeps(node);
Set<Library> calculateLibraryExportDeps(Library node) {
final processed = <Library>{};
final worklist = <Library>[];
final deps = <Library>{};
worklist.add(node);
processed.add(node);
while (worklist.isNotEmpty) {
final lib = worklist.removeLast();
for (final dep in lib.dependencies) {
if (!dep.isExport) {
continue;
}
final targetLibrary = dep.targetLibrary;
if (processed.add(targetLibrary)) {
if (shaker.isLibraryUsed(targetLibrary)) {
deps.add(targetLibrary);
} else {
worklist.add(targetLibrary);
}
}
}
}
return deps;
}
@override
TreeNode visitLibrary(Library node, TreeNode? removalSentinel) {
if (!shaker.isLibraryUsed(node) && node.importUri.scheme != 'dart') {
return removalSentinel!;
}
_additionalDeps.clear();
node.transformOrRemoveChildren(this);
// The transformer API does not iterate over `Library.additionalExports`,
// so we manually delete the references to shaken nodes.
@@ -1746,6 +1800,29 @@ class _TreeShakerPass2 extends RemovingTransformer {
return !shaker.isMemberUsed(node as Member);
}
});
// Add transitive export dependencies of the removed imported libraries.
// This is needed to maintain connected library graph
// which is critical for calculation of the deferred loading units.
if (_additionalDeps.isNotEmpty) {
for (final dep in node.dependencies) {
_additionalDeps.remove(dep.targetLibrary);
}
for (final lib in _additionalDeps) {
node.addDependency(LibraryDependency.import(lib));
}
_additionalDeps.clear();
}
return node;
}
@override
TreeNode visitLibraryDependency(
LibraryDependency node, TreeNode? removalSentinel) {
final targetLibrary = node.targetLibrary;
if (!shaker.isLibraryUsed(targetLibrary)) {
_additionalDeps.addAll(getLibraryExportDeps(targetLibrary));
return removalSentinel!;
}
return node;
}
@@ -1991,6 +2068,10 @@ class _TreeShakerConstantVisitor extends ConstantVisitor<Null> {
@override
visitSymbolConstant(SymbolConstant constant) {
final libraryRef = constant.libraryReference;
if (libraryRef != null) {
shaker._usedLibraries.add(libraryRef.asLibrary);
}
// The Symbol class and it's _name field are always retained.
}
@@ -1,11 +1,6 @@
library #lib;
import self as self;
import "file:pkg/vm/testcases/transformations/type_flow/transformer/const_default.lib.dart";
static method main() → dynamic {
has-declared-initializer dynamic dyn = null;
}
library const_default.lib.dart;
import self as self;
@@ -0,0 +1,22 @@
// Copyright (c) 2023, 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 'libraries_lib1.lib.dart';
import 'libraries_lib2.lib.dart';
import 'libraries_lib3.lib.dart';
void unused() {
print(Foo1());
print(bar1());
}
void used() {
print(Foo2());
print(bar2());
print(privateSymbol);
}
main() {
used();
}
@@ -0,0 +1,33 @@
library #lib;
import self as self;
import "dart:core" as core;
import "libraries_lib2.lib.dart" as lib;
import "file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib2.lib.dart";
import "file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib3.lib.dart";
static method used() → void {
core::print(new lib::Foo2::•());
core::print([@vm.inferred-type.metadata=dart.core::_OneByteString (value: "hi")] lib::bar2());
core::print(#C1);
}
static method main() → dynamic {
self::used();
}
constants {
#C1 = #file:pkg/vm/testcases/transformations/type_flow/transformer/libraries_lib3.lib.dart::_bazz
}
library libraries_lib2.lib.dart;
import self as self;
import "dart:core" as core;
class Foo2 extends core::Object {
synthetic constructor •() → self::Foo2
: super core::Object::•()
;
}
static method bar2() → core::String
return "hi";
library libraries_lib3.lib.dart;
import self as self;
@@ -0,0 +1,7 @@
// Copyright (c) 2023, 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.
class Foo1 {}
String bar1() => 'bye';
@@ -0,0 +1,7 @@
// Copyright (c) 2023, 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.
class Foo2 {}
String bar2() => 'hi';
@@ -0,0 +1,5 @@
// Copyright (c) 2023, 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.
const privateSymbol = #_bazz;
@@ -5,8 +5,17 @@ import "dart:core" as core;
import "package:test_core/scaffolding.dart" as sca;
import "package:matcher/src/expect/expect.dart" as exp;
import "package:test/test.dart";
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
import "package:matcher/src/expect/expect.dart";
import "package:test_api/hooks.dart";
import "package:test_core/scaffolding.dart";
import "package:matcher/src/core_matchers.dart";
import "package:matcher/src/description.dart";
import "package:matcher/src/equals_matcher.dart";
import "package:matcher/src/interfaces.dart";
import "package:matcher/src/operator_matchers.dart";
import "package:matcher/src/type_matcher.dart";
import "package:matcher/src/util.dart";
static method main() → dynamic {
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 {
@@ -5,8 +5,17 @@ import "package:test_core/scaffolding.dart" as sca;
import "package:matcher/src/expect/expect.dart" as exp;
import "dart:core" as core;
import "package:test/test.dart";
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
import "package:matcher/src/expect/expect.dart";
import "package:test_api/hooks.dart";
import "package:test_core/scaffolding.dart";
import "package:matcher/src/core_matchers.dart";
import "package:matcher/src/description.dart";
import "package:matcher/src/equals_matcher.dart";
import "package:matcher/src/interfaces.dart";
import "package:matcher/src/operator_matchers.dart";
import "package:matcher/src/type_matcher.dart";
import "package:matcher/src/util.dart";
[@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];
static method main() → dynamic {
@@ -7,8 +7,18 @@ import "package:protobuf/protobuf.dart" as pro;
import "package:matcher/src/expect/expect.dart" as exp;
import "package:matcher/src/expect/throws_matcher.dart" as thr;
import "package:test/test.dart";
import "file:pkg/vm/testcases/transformations/type_flow/transformer/protobuf_handler/lib/generated/foo.pb.dart";
import "package:matcher/src/expect/expect.dart";
import "package:matcher/src/expect/throws_matcher.dart";
import "package:test_api/hooks.dart";
import "package:test_core/scaffolding.dart";
import "package:matcher/src/core_matchers.dart";
import "package:matcher/src/description.dart";
import "package:matcher/src/equals_matcher.dart";
import "package:matcher/src/interfaces.dart";
import "package:matcher/src/operator_matchers.dart";
import "package:matcher/src/type_matcher.dart";
import "package:matcher/src/util.dart";
static method main() → dynamic {
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 {