Chromium Code Reviews| Index: pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart |
| diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3873795ccf6445c56911127b6ce00b36d34ea925 |
| --- /dev/null |
| +++ b/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart |
| @@ -0,0 +1,433 @@ |
| +// Copyright (c) 2016, 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. |
| + |
| +/// A transformation to create a self-contained modular kernel without |
| +/// unnecessary references to other libraries. |
| +library fasta.kernel.kernel_outline_shaker; |
| + |
| +import 'package:kernel/ast.dart'; |
| +import 'package:kernel/core_types.dart'; |
| + |
| +import '../errors.dart' show internalError; |
| + |
| +/// Removes from [program] unnecessary libraries, classes, and members. |
|
ahe
2017/05/22 12:04:24
Removes unnecessary libraries, classes, and member
|
| +/// |
| +/// This applies a simple "tree-shaking" technique: the full body of libraries |
| +/// whose URI match [isIncluded] are preserved, and so is the outline of the |
| +/// members and classes which are referred to transitively from these included |
| +/// libraries. |
| +/// |
| +/// The intent is that the resulting program has the entire code that is meant |
| +/// to be included and the minimum required to prevent dangling references and |
| +/// allow modular program transformations. |
| +/// |
| +/// Note that the resulting program may include libraries not in [isIncluded], |
| +/// but those will be marked as external. There should be no method bodies for |
| +/// any members of those libraries. |
| +/// |
| +/// When [retainClassMembers] is true, all members of retained classes will be |
| +/// retained as well. |
| +void trimProgram(Program program, bool isIncluded(Uri uri), |
| + {bool retainClassMembers: true}) { |
| + var result = new _Analysis(program, isIncluded, retainClassMembers).run(); |
| + new _Shaker(result, isIncluded).transform(program); |
| +} |
| + |
| +/// Result of analyzing a program before tree-shaking it. |
| +abstract class _AnalysisResult { |
| + /// Whether a library should be preserved and mark as external. |
| + bool isLibraryUsed(Library library); |
| + |
| + /// Whether a class should be preserved. If a class is preserved, its |
| + /// supertypes will be preserved too, but some of it members may not be |
| + /// included. |
| + bool isClassUsed(Class cls); |
| + |
| + /// Whether a member should be preserved. If so, its enclosing class/library |
| + /// will be preserved too. |
| + bool isMemberUsed(Member member); |
| +} |
| + |
| +/// Visitor used by [trimProgram] to collects which libraries, classes, and |
| +/// members should be preserved. |
| +class _Analysis extends RecursiveVisitor implements _AnalysisResult { |
| + /// The program being analyzed. |
| + final Program program; |
| + |
| + /// Helper to fetch classes and members of the core libraries. |
| + final CoreTypes coreTypes; |
| + |
| + /// Filter to determine libraries that should be analyzed. |
| + final Filter isIncluded; |
| + |
| + /// When retaining a class, whether to retain all of its members. |
| + // TODO(sigmund): delete this option once we figure out the long term plan. Do |
| + // we need to do this? Today it seems necessary because some transformations |
| + // require it, but maybe we don't need them all (e.g. only constructors and |
| + // members whose inferenceTarget is referred do in the sources?) Do we need a |
| + // different retention policy for including the type hierarchy with and |
| + // without members? |
| + final bool retainClassMembers; |
| + |
| + /// Libraries that contained code that is transitively reachable from the |
| + /// included libraries. |
| + final Set<Library> _libraries = new Set<Library>(); |
| + |
| + /// Classes that are transitively reachable from the included libraries. |
| + final Set<Class> _classes = new Set<Class>(); |
| + |
| + /// Members that are transitively reachable from the included libraries. |
| + final Set<Member> _members = new Set<Member>(); |
| + |
| + @override |
| + bool isLibraryUsed(Library library) => _libraries.contains(library); |
| + |
| + @override |
| + bool isClassUsed(Class cls) => _classes.contains(cls); |
| + |
| + @override |
| + bool isMemberUsed(Member m) => _members.contains(m); |
| + |
| + _Analysis(this.program, this.isIncluded, this.retainClassMembers) |
| + : coreTypes = new CoreTypes(program); |
| + |
| + _AnalysisResult run() { |
| + _markRequired(); |
| + _markMember(program.mainMethod); |
| + for (var library in program.libraries) { |
| + if (isIncluded(library.importUri)) { |
| + library.accept(this); |
| + } |
| + } |
| + return this; |
| + } |
| + |
| + /// Marks classes and members that are assumed to exist by fasta or by |
| + /// transformers. |
| + void _markRequired() { |
| + coreTypes.objectClass.members.forEach(_markMember); |
| + |
| + // These are assumed to be available by fasta: |
| + _markClass(coreTypes.objectClass); |
| + _markClass(coreTypes.nullClass); |
| + _markClass(coreTypes.boolClass); |
| + _markClass(coreTypes.intClass); |
| + _markClass(coreTypes.numClass); |
| + _markClass(coreTypes.doubleClass); |
| + _markClass(coreTypes.stringClass); |
| + _markClass(coreTypes.listClass); |
| + _markClass(coreTypes.mapClass); |
| + _markClass(coreTypes.iterableClass); |
| + _markClass(coreTypes.iteratorClass); |
| + _markClass(coreTypes.futureClass); |
| + _markClass(coreTypes.streamClass); |
| + _markClass(coreTypes.symbolClass); |
| + _markClass(coreTypes.internalSymbolClass); |
| + _markClass(coreTypes.typeClass); |
| + _markClass(coreTypes.functionClass); |
| + _markClass(coreTypes.invocationClass); |
| + _markMember(coreTypes.getMember("dart:_internal", "ExternalName", "")); |
| + |
| + // These are needed by the continuation (async/await) transformer: |
| + _markClass(coreTypes.getClass('dart:core', 'Iterator')); |
| + _markClass(coreTypes.getClass('dart:async', 'Future')); |
| + _markClass(coreTypes.getClass('dart:async', 'FutureOr')); |
| + _markClass(coreTypes.getClass('dart:async', 'Completer')); |
| + _markMember(coreTypes.getMember('dart:async', 'Completer', 'sync')); |
| + _markMember(coreTypes.getMember('dart:core', '_SyncIterable', '')); |
| + _markMember(coreTypes.getMember('dart:async', '_StreamIterator', '')); |
| + _markMember(coreTypes.getMember('dart:async', 'Future', 'microtask')); |
| + _markMember( |
| + coreTypes.getMember('dart:async', '_AsyncStarStreamController', '')); |
| + _markMember(coreTypes.getTopLevelMember('dart:core', 'print')); |
| + _markMember( |
| + coreTypes.getTopLevelMember('dart:async', '_asyncThenWrapperHelper')); |
| + _markMember( |
| + coreTypes.getTopLevelMember('dart:async', '_asyncErrorWrapperHelper')); |
| + _markMember(coreTypes.getTopLevelMember('dart:async', '_awaitHelper')); |
| + |
| + // These are needed by the mixin transformer |
| + _markMember(coreTypes.getMember('dart:core', '_InvocationMirror', '')); |
| + _markMember(coreTypes.getMember('dart:core', 'List', 'from')); |
| + } |
| + |
| + /// Mark a library as used. |
| + _markLibrary(Library lib) { |
| + if (!_libraries.add(lib)) return; |
| + } |
| + |
| + /// Mark a class and it's supertypes as used. |
| + void _markClass(Class cls) { |
| + if (cls == null || !_classes.add(cls)) return; |
| + _markLibrary(cls.parent); |
| + visitList(cls.annotations, this); |
| + cls.supertype?.accept(this); |
| + cls.mixedInType?.accept(this); |
| + visitList(cls.implementedTypes, this); |
| + visitList(cls.typeParameters, this); |
| + |
| + if (retainClassMembers) cls.members.forEach(_markMember); |
| + } |
| + |
| + _markMember(Member m) { |
| + if (m == null || !_members.add(m)) return; |
| + _markMemberInterface(m); |
| + var parent = m.parent; |
| + if (parent is Library) { |
| + _markLibrary(parent); |
| + } else if (parent is Class) { |
| + _markClass(parent); |
| + } |
| + } |
| + |
| + void _markMemberInterface(Member node) { |
| + if (node is Field) { |
| + node.type.accept(this); |
| + } else if (node is Procedure) { |
| + _markFunctionInterface(node.function); |
| + } |
| + } |
| + |
| + _markFunctionInterface(FunctionNode node) { |
| + for (var parameter in node.typeParameters) { |
| + parameter.bound.accept(this); |
| + } |
| + for (var parameter in node.positionalParameters) { |
| + parameter.type.accept(this); |
| + } |
| + for (var parameter in node.namedParameters) { |
| + parameter.type.accept(this); |
| + } |
| + node.returnType.accept(this); |
| + } |
| + |
| + @override |
| + visitFunctionNode(FunctionNode node) { |
| + switch (node.asyncMarker) { |
| + case AsyncMarker.Sync: |
| + break; |
| + case AsyncMarker.SyncStar: |
| + _markClass(coreTypes.iterableClass); |
|
scheglov
2017/05/18 15:54:08
These classes are already included in _markRequire
Siggi Cherem (dart-lang)
2017/05/19 04:37:32
good point. I removed them from here. Also - now t
|
| + break; |
| + case AsyncMarker.Async: |
| + _markClass(coreTypes.futureClass); |
| + break; |
| + case AsyncMarker.AsyncStar: |
| + _markClass(coreTypes.streamClass); |
| + break; |
| + case AsyncMarker.SyncYielding: |
| + break; |
| + } |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitSuperInitializer(SuperInitializer node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitRedirectingInitializer(RedirectingInitializer node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitConstructorInvocation(ConstructorInvocation node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitStaticInvocation(StaticInvocation node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitDirectMethodInvocation(DirectMethodInvocation node) { |
| + if (node.receiver is! ThisExpression) { |
| + return internalError('Direct calls are only supported on "this"'); |
| + } |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitMethodInvocation(MethodInvocation node) { |
| + _markMember(node.interfaceTarget); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitStaticGet(StaticGet node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitStaticSet(StaticSet node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitDirectPropertyGet(DirectPropertyGet node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitDirectPropertySet(DirectPropertySet node) { |
| + _markMember(node.target); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitSuperPropertyGet(SuperPropertyGet node) { |
| + _markMember(node.interfaceTarget); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitSuperPropertySet(SuperPropertySet node) { |
| + _markMember(node.interfaceTarget); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitPropertyGet(PropertyGet node) { |
| + _markMember(node.interfaceTarget); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitPropertySet(PropertySet node) { |
| + _markMember(node.interfaceTarget); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitListLiteral(ListLiteral node) { |
| + _markClass(coreTypes.listClass); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitMapLiteral(MapLiteral node) { |
| + _markClass(coreTypes.mapClass); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitStringConcatenation(StringConcatenation node) { |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitInterfaceType(InterfaceType node) { |
| + _markClass(node.classNode); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitSupertype(Supertype node) { |
| + _markClass(node.classNode); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitDoubleLiteral(DoubleLiteral node) { |
| + _markClass(coreTypes.doubleClass); |
| + } |
| + |
| + @override |
| + visitSymbolLiteral(SymbolLiteral node) { |
| + _markClass(coreTypes.symbolClass); |
| + } |
| + |
| + @override |
| + visitTypeLiteral(TypeLiteral node) { |
| + _markClass(coreTypes.typeClass); |
| + node.visitChildren(this); |
| + } |
| + |
| + @override |
| + visitTypedefReference(Typedef node) { |
| + return internalError('not implemented'); |
| + } |
| +} |
| + |
| +/// Transformer that trims everything in the excluded libraries that is not |
| +/// marked as preserved by the [_Analysis] above. For every member in these |
| +/// excluded libraries, this transformer also removes function bodies and |
| +/// initializers. |
| +class _Shaker extends Transformer { |
| + final _AnalysisResult result; |
| + final Filter isIncluded; |
| + |
| + _Shaker(this.result, this.isIncluded); |
| + |
| + void transform(Program program) { |
| + var toRemove = new Set<Library>(); |
| + for (var library in program.libraries) { |
| + if (!isIncluded(library.importUri)) { |
| + if (!result.isLibraryUsed(library)) { |
| + toRemove.add(library); |
| + } else { |
| + library.isExternal = true; |
| + library.transformChildren(this); |
| + } |
| + } |
| + } |
| + program.libraries.removeWhere(toRemove.contains); |
| + } |
| + |
| + Class visitClass(Class node) { |
| + if (!result.isClassUsed(node)) { |
| + node.canonicalName?.unbind(); |
| + return null; // Remove the class. |
| + } else { |
| + node.transformChildren(this); |
| + return node; |
| + } |
| + } |
| + |
| + Member defaultMember(Member node) { |
| + if (!result.isMemberUsed(node)) { |
| + node.canonicalName?.unbind(); |
| + return null; |
| + } else { |
| + if (node is Procedure) { |
| + _clearFunction(node.function); |
| + } else if (node is Field) { |
| + node.initializer = null; |
| + } else if (node is Constructor) { |
| + node.initializers.clear(); |
| + _clearFunction(node.function); |
| + } |
| + return node; |
| + } |
| + } |
| + |
| + _clearFunction(FunctionNode function) { |
| + function.body = null; |
| + // TODO(sigmund): Fix directly the continuation transformer. The async/await |
| + // continuation transformer fails if it finds a function with no body that |
| + // it is marked with async/async*/sync*, we shouldn't be patching that here. |
| + function.dartAsyncMarker = function.asyncMarker; |
| + function.asyncMarker = AsyncMarker.Sync; |
| + } |
| + |
| + /// Types appear to be encoded directly, so we have no need to preserve |
| + /// typedefs. |
| + // TODO(sigmund): revisit if this is not the case, the `inputError` in the |
| + // analysis step is meant to detect this. |
| + Typedef visitTypedef(Typedef node) => null; |
| + |
| + TreeNode defaultTreeNode(TreeNode node) => node; |
| +} |
| + |
| +typedef bool Filter(Uri uri); |