| // 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/binary/ast_to_binary.dart'; |
| import 'package:kernel/core_types.dart'; |
| |
| import '../problems.dart' show unimplemented, unsupported; |
| |
| /// Serialize outlines of the nodes in libraries whose URI match [isIncluded], |
| /// and outlines of members and classes which are transitively referenced from |
| /// the included libraries. Only outlines are serialized, even for included |
| /// libraries, all function bodies are ignored. |
| void serializeTrimmedOutline( |
| Sink<List<int>> sink, Component component, bool isIncluded(Uri uri)) { |
| var data = new _RetainedDataBuilder(); |
| data._markRequired(component); |
| |
| for (var library in component.libraries) { |
| if (!isIncluded(library.importUri)) continue; |
| data.markAdditionalExports(library); |
| for (var clazz in library.classes) { |
| if (clazz.name.startsWith('_')) continue; |
| data.markClassForExport(clazz); |
| } |
| for (var field in library.fields) { |
| if (field.name.isPrivate) continue; |
| data.markMember(field); |
| } |
| for (var procedure in library.procedures) { |
| if (procedure.name.isPrivate) continue; |
| data.markMember(procedure); |
| } |
| for (var typedef in library.typedefs) { |
| if (typedef.name.startsWith('_')) continue; |
| data.markTypedef(typedef); |
| } |
| } |
| |
| new _TrimmedBinaryPrinter(sink, isIncluded, data) |
| .writeComponentFile(component); |
| } |
| |
| /// Removes unnecessary libraries, classes, and members from [component]. |
| /// |
| /// This applies a simple "tree-shaking" technique: the full body of libraries |
| /// whose URI match [isIncluded] is preserved, and so is the outline of the |
| /// members and classes which are transitively visible from the |
| /// included libraries. |
| /// |
| /// The intent is that the resulting component 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 component 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. |
| void trimProgram(Component component, bool isIncluded(Uri uri)) { |
| var data = new _RetainedDataBuilder(); |
| data._markRequired(component); |
| |
| data.markMember(component.mainMethod); |
| for (var library in component.libraries) { |
| if (isIncluded(library.importUri)) { |
| library.accept(data); |
| } |
| } |
| |
| new _KernelOutlineShaker(isIncluded, data).transform(component); |
| } |
| |
| /// Transformer that trims everything in the excluded libraries that is not |
| /// marked as preserved by the given [_RetainedData]. For every member in these |
| /// excluded libraries, this transformer also removes function bodies and |
| /// initializers. |
| class _KernelOutlineShaker extends Transformer { |
| final bool Function(Uri uri) isIncluded; |
| final _RetainedData data; |
| |
| _KernelOutlineShaker(this.isIncluded, this.data); |
| |
| @override |
| Member defaultMember(Member node) { |
| if (!data.isMemberUsed(node)) { |
| node.canonicalName?.unbind(); |
| return null; |
| } else { |
| if (node is Procedure) { |
| _clearParameterInitializers(node.function); |
| node.function.body = null; |
| } else if (node is Field) { |
| if (node.name.name == '_exports#') return null; |
| node.initializer = null; |
| } else if (node is Constructor) { |
| if (!node.isConst) { |
| _clearParameterInitializers(node.function); |
| } |
| node.initializers.clear(); |
| node.function.body = null; |
| } |
| return node; |
| } |
| } |
| |
| @override |
| TreeNode defaultTreeNode(TreeNode node) => node; |
| |
| void transform(Component component) { |
| var toRemove = new Set<Library>(); |
| for (var library in component.libraries) { |
| if (!isIncluded(library.importUri)) { |
| if (!data.isLibraryUsed(library)) { |
| toRemove.add(library); |
| } else { |
| library.isExternal = true; |
| library.transformChildren(this); |
| } |
| } |
| } |
| component.libraries.removeWhere(toRemove.contains); |
| } |
| |
| @override |
| Class visitClass(Class node) { |
| if (!data.isClassUsed(node)) { |
| node.canonicalName?.unbind(); |
| return null; // Remove the class. |
| } else { |
| node.transformChildren(this); |
| return node; |
| } |
| } |
| |
| @override |
| Typedef visitTypedef(Typedef node) { |
| if (!data.isTypedefUsed(node)) { |
| node.canonicalName?.unbind(); |
| return null; // Remove the typedef. |
| } else { |
| node.transformChildren(this); |
| return node; |
| } |
| } |
| |
| static void _clearParameterInitializers(FunctionNode function) { |
| for (var parameter in function.positionalParameters) { |
| parameter.initializer = null; |
| } |
| for (var parameter in function.namedParameters) { |
| parameter.initializer = null; |
| } |
| } |
| } |
| |
| /// Informs about which libraries, classes, and members should be retained by |
| /// the [_KernelOutlineShaker] when tree-shaking. |
| abstract class _RetainedData { |
| /// 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 the field initializer should be preserved. |
| bool isFieldInitializerUsed(Field node); |
| |
| /// Whether a library should be preserved and mark as external. |
| bool isLibraryUsed(Library library); |
| |
| /// Whether a member should be preserved. If so, its enclosing class/library |
| /// will be preserved too. |
| bool isMemberUsed(Member member); |
| |
| /// Whether the parameter initializer should be preserved. |
| bool isParameterInitializerUsed(VariableDeclaration node); |
| |
| /// Whether a typedef should be preserved. If a typedef is preserved, its |
| /// return type and types of parameters will be preserved too. |
| bool isTypedefUsed(Typedef node); |
| } |
| |
| /// A builder of [_RetainedData] that recursively marks transitive dependencies. |
| /// |
| /// When it is used as a [RecursiveVisitor], it recursively marks nodes that |
| /// are references by visited nodes. |
| class _RetainedDataBuilder extends RecursiveVisitor implements _RetainedData { |
| /// 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>(); |
| |
| /// Typedefs that are transitively reachable from the included libraries. |
| final Set<Typedef> typedefs = new Set<Typedef>(); |
| |
| /// Members that are transitively reachable from the included libraries. |
| final Set<Member> members = new Set<Member>(); |
| |
| /// Fields for which initializers should be kept because they are constants, |
| /// or are final fields of classes with constant constructors. |
| final Set<Field> fieldsWithInitializers = new Set<Field>(); |
| |
| /// Parameters for which initializers should be kept because they are |
| /// parameters of a constant constructors. |
| final Set<VariableDeclaration> parametersWithInitializers = |
| new Set<VariableDeclaration>(); |
| |
| _TypeMarker typeMarker; |
| |
| _RetainedDataBuilder() { |
| typeMarker = new _TypeMarker(this); |
| } |
| |
| @override |
| bool isClassUsed(Class cls) => classes.contains(cls); |
| |
| @override |
| bool isFieldInitializerUsed(Field node) { |
| return fieldsWithInitializers.contains(node); |
| } |
| |
| @override |
| bool isLibraryUsed(Library library) => libraries.contains(library); |
| |
| @override |
| bool isMemberUsed(Member m) => members.contains(m); |
| |
| @override |
| bool isParameterInitializerUsed(VariableDeclaration node) { |
| return parametersWithInitializers.contains(node); |
| } |
| |
| @override |
| bool isTypedefUsed(Typedef node) => typedefs.contains(node); |
| |
| void markAdditionalExports(Library node) { |
| for (var reference in node.additionalExports) { |
| var node = reference.node; |
| if (node is Class) { |
| markClassForExport(node); |
| } else if (node is Member) { |
| markMember(node); |
| } else if (node is Typedef) { |
| markTypedef(node); |
| } else { |
| unimplemented('export ${node.runtimeType}', -1, null); |
| } |
| } |
| } |
| |
| void markAnnotations(List<Expression> annotations) { |
| for (var annotation in annotations) { |
| annotation.accept(this); |
| } |
| } |
| |
| /// Mark a class and it's supertypes as used. |
| void markClass(Class cls) { |
| if (cls == null || !classes.add(cls)) return; |
| markLibrary(cls.parent); |
| markAnnotations(cls.annotations); |
| cls.typeParameters.forEach((t) => t.bound.accept(typeMarker)); |
| markSupertype(cls.supertype); |
| markSupertype(cls.mixedInType); |
| cls.implementedTypes.forEach(markSupertype); |
| |
| for (var field in cls.fields) { |
| if (!field.isStatic && !field.name.isPrivate) { |
| markMember(field); |
| } |
| } |
| for (var method in cls.procedures) { |
| if (!method.isStatic && !method.name.isPrivate) { |
| markMember(method); |
| } |
| } |
| } |
| |
| /// Mark the given class as exported, so mark all its public members. |
| void markClassForExport(Class node) { |
| markClass(node); |
| for (var field in node.fields) { |
| if (!field.name.isPrivate) { |
| markMember(field); |
| } |
| } |
| for (var constructor in node.constructors) { |
| if (!constructor.name.isPrivate) { |
| markMember(constructor); |
| } |
| } |
| for (var method in node.procedures) { |
| if (!method.name.isPrivate) { |
| markMember(method); |
| } |
| } |
| } |
| |
| /// Mark a library as used. |
| void markLibrary(Library lib) { |
| libraries.add(lib); |
| } |
| |
| /// Mark a member and types mentioned on its interface. |
| void markMember(Member node) { |
| if (node == null || !members.add(node)) return; |
| |
| var parent = node.parent; |
| if (parent is Library) { |
| markLibrary(parent); |
| } else if (parent is Class) { |
| markClass(parent); |
| } |
| |
| markAnnotations(node.annotations); |
| markMemberInterface(node); |
| |
| if (node is Field) { |
| if (_shouldKeepFieldInitializer(node)) { |
| fieldsWithInitializers.add(node); |
| node.initializer?.accept(this); |
| } |
| } |
| } |
| |
| void markMemberInterface(Member node) { |
| if (node is Field) { |
| node.type.accept(typeMarker); |
| } else if (node is Constructor) { |
| var function = node.function; |
| for (var parameter in function.positionalParameters) { |
| markParameterType(parameter); |
| if (node.isConst) { |
| markParameterInitializer(parameter); |
| } |
| } |
| for (var parameter in function.namedParameters) { |
| markParameterType(parameter); |
| if (node.isConst) { |
| markParameterInitializer(parameter); |
| } |
| } |
| // We don't mark automatically all constructors of classes. |
| // So, we need transitively mark super/redirect initializers. |
| for (var initializer in node.initializers) { |
| if (initializer is SuperInitializer) { |
| markMember(initializer.target); |
| } else if (initializer is RedirectingInitializer) { |
| markMember(initializer.target); |
| } |
| } |
| } else if (node is Procedure) { |
| var function = node.function; |
| function.typeParameters.forEach((p) => p.bound.accept(typeMarker)); |
| function.positionalParameters.forEach(markParameterType); |
| function.namedParameters.forEach(markParameterType); |
| function.returnType.accept(typeMarker); |
| } |
| } |
| |
| void markParameterInitializer(VariableDeclaration parameter) { |
| parametersWithInitializers.add(parameter); |
| parameter.initializer?.accept(this); |
| } |
| |
| void markParameterType(VariableDeclaration parameter) { |
| return parameter.type.accept(typeMarker); |
| } |
| |
| /// Mark the class and type arguments of [node]. |
| void markSupertype(Supertype node) { |
| if (node == null) return; |
| markClass(node.classNode); |
| node.typeArguments.forEach((t) => t.accept(typeMarker)); |
| } |
| |
| /// Mark the typedef. |
| void markTypedef(Typedef node) { |
| if (node == null || !typedefs.add(node)) return; |
| markLibrary(node.parent); |
| markAnnotations(node.annotations); |
| |
| DartType type = node.type; |
| if (type is FunctionType) { |
| type.returnType?.accept(typeMarker); |
| for (var positionalType in type.positionalParameters) { |
| positionalType.accept(typeMarker); |
| } |
| for (var namedType in type.namedParameters) { |
| namedType.type.accept(typeMarker); |
| } |
| } |
| } |
| |
| @override |
| visitConstructor(Constructor node) { |
| if (!node.initializers.any((i) => i is SuperInitializer)) { |
| // super() is currently implicit. |
| var supertype = node.enclosingClass.supertype; |
| if (supertype != null) { |
| for (var constructor in supertype.classNode.constructors) { |
| if (constructor.name.name == '') markMember(constructor); |
| } |
| } |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitConstructorInvocation(ConstructorInvocation node) { |
| markMember(node.target); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitDirectMethodInvocation(DirectMethodInvocation node) { |
| if (node.receiver is! ThisExpression) { |
| return unsupported("direct call not on this", node.fileOffset, null); |
| } |
| 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 |
| visitFunctionType(FunctionType node) { |
| markTypedef(node.typedefReference?.asTypedef); |
| super.visitFunctionType(node); |
| } |
| |
| @override |
| visitInterfaceType(InterfaceType node) { |
| markClass(node.classNode); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitLibrary(Library node) { |
| markAdditionalExports(node); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitMethodInvocation(MethodInvocation 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 |
| visitRedirectingInitializer(RedirectingInitializer node) { |
| markMember(node.target); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitStaticGet(StaticGet node) { |
| markMember(node.target); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitStaticInvocation(StaticInvocation node) { |
| markMember(node.target); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitStaticSet(StaticSet node) { |
| markMember(node.target); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitSuperInitializer(SuperInitializer 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 |
| visitSupertype(Supertype node) { |
| markClass(node.classNode); |
| node.visitChildren(this); |
| } |
| |
| @override |
| visitTypedefReference(Typedef node) { |
| return unimplemented("visitTypedefReference", -1, null); |
| } |
| |
| /// Marks classes and members that are assumed to exist by fasta or by |
| /// transformers. |
| // TODO(sigmund): consider being more fine-grained and only marking what is |
| // seen and used. |
| void _markRequired(Component component) { |
| var coreTypes = new CoreTypes(component); |
| 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.compileTimeErrorDefaultConstructor); |
| markMember(coreTypes.constantExpressionErrorDefaultConstructor); |
| markMember(coreTypes.duplicatedFieldInitializerErrorDefaultConstructor); |
| markMember(coreTypes.externalNameDefaultConstructor); |
| markMember(coreTypes.fallThroughErrorUrlAndLineConstructor); |
| |
| // These are needed by the continuation (async/await) transformer: |
| markClass(coreTypes.iteratorClass); |
| markClass(coreTypes.futureClass); |
| markClass(coreTypes.futureOrClass); |
| markClass(coreTypes.completerClass); |
| markMember(coreTypes.completerSyncConstructor); |
| markMember(coreTypes.syncIterableDefaultConstructor); |
| markMember(coreTypes.streamIteratorDefaultConstructor); |
| markMember(coreTypes.futureMicrotaskConstructor); |
| markMember(coreTypes.asyncStarStreamControllerDefaultConstructor); |
| markMember(coreTypes.printProcedure); |
| markMember(coreTypes.asyncThenWrapperHelperProcedure); |
| markMember(coreTypes.asyncErrorWrapperHelperProcedure); |
| markMember(coreTypes.awaitHelperProcedure); |
| |
| // These are needed by the mixin transformer |
| markMember(coreTypes.invocationMirrorWithoutTypeConstructor); |
| markMember(coreTypes.listFromConstructor); |
| } |
| |
| static bool _shouldKeepFieldInitializer(Field node) { |
| if (node.isConst) return true; |
| if (node.isFinal && !node.isStatic) { |
| var parent = node.parent; |
| if (parent is Class) { |
| for (var constructor in parent.constructors) { |
| if (constructor.isConst) return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |
| |
| /// [BinaryPrinter] that serializes outlines of all nodes in included |
| /// libraries, and outlines of nodes that are marked in the [_RetainedData]. |
| class _TrimmedBinaryPrinter extends BinaryPrinter { |
| final bool Function(Uri uri) isIncluded; |
| final _RetainedData data; |
| final List<Library> librariesToWrite = <Library>[]; |
| bool insideIncludedLibrary = false; |
| |
| _TrimmedBinaryPrinter(Sink<List<int>> sink, this.isIncluded, this.data) |
| : super(sink); |
| |
| @override |
| visitClass(Class node) { |
| var level = node.level; |
| node.level = ClassLevel.Hierarchy; |
| super.visitClass(node); |
| node.level = level; |
| } |
| |
| @override |
| visitField(Field node) { |
| if (data.isFieldInitializerUsed(node)) { |
| super.visitField(node); |
| } else { |
| var initializer = node.initializer; |
| node.initializer = null; |
| super.visitField(node); |
| node.initializer = initializer; |
| } |
| } |
| |
| @override |
| visitFunctionNode(FunctionNode node) { |
| var body = node.body; |
| node.body = null; |
| super.visitFunctionNode(node); |
| node.body = body; |
| } |
| |
| @override |
| visitLibrary(Library node) { |
| insideIncludedLibrary = isIncluded(node.importUri); |
| if (insideIncludedLibrary) { |
| super.visitLibrary(node); |
| } else { |
| var isExternal = node.isExternal; |
| var dependencies = node.dependencies.toList(); |
| var parts = node.parts.toList(); |
| |
| node.isExternal = true; |
| node.dependencies.clear(); |
| node.parts.clear(); |
| super.visitLibrary(node); |
| |
| node.isExternal = isExternal; |
| node.dependencies.addAll(dependencies); |
| node.parts.addAll(parts); |
| } |
| } |
| |
| @override |
| void writeAdditionalExports(List<Reference> additionalExports) { |
| super.writeAdditionalExports( |
| insideIncludedLibrary ? additionalExports : const <Reference>[]); |
| } |
| |
| @override |
| void writeLibraries(Component component) { |
| for (var library in component.libraries) { |
| if (isIncluded(library.importUri) || data.isLibraryUsed(library)) { |
| librariesToWrite.add(library); |
| } |
| } |
| writeList(librariesToWrite, writeNode); |
| } |
| |
| @override |
| void writeNodeList(List<Node> nodes) { |
| if (nodes.isEmpty) { |
| super.writeNodeList(nodes); |
| } else { |
| var newNodes = <Node>[]; |
| for (var node in nodes) { |
| if (node is Class) { |
| if (data.isClassUsed(node)) { |
| newNodes.add(node); |
| } |
| } else if (node is Member) { |
| if (data.isMemberUsed(node)) { |
| newNodes.add(node); |
| } |
| } else if (node is Typedef) { |
| if (data.isTypedefUsed(node)) { |
| newNodes.add(node); |
| } |
| } else { |
| newNodes.add(node); |
| } |
| } |
| super.writeNodeList(newNodes); |
| } |
| } |
| |
| @override |
| void writeComponentIndex(Component component, List<Library> libraries) { |
| super.writeComponentIndex(component, librariesToWrite); |
| } |
| |
| @override |
| writeVariableDeclaration(VariableDeclaration node) { |
| if (data.isParameterInitializerUsed(node)) { |
| super.writeVariableDeclaration(node); |
| } else { |
| var initializer = node.initializer; |
| node.initializer = null; |
| super.writeVariableDeclaration(node); |
| node.initializer = initializer; |
| } |
| } |
| } |
| |
| /// A helper visitor used to mark transitive types by the [_RetainedDataBuilder]. |
| class _TypeMarker extends DartTypeVisitor { |
| _RetainedDataBuilder data; |
| |
| _TypeMarker(this.data); |
| |
| visitFunctionType(FunctionType node) { |
| node.typeParameters.forEach((t) => t.bound.accept(this)); |
| node.positionalParameters.forEach((t) => t.accept(this)); |
| node.namedParameters.forEach((t) => t.type.accept(this)); |
| node.returnType.accept(this); |
| data.markTypedef(node.typedefReference?.asTypedef); |
| } |
| |
| visitInterfaceType(InterfaceType node) { |
| data.markClass(node.classNode); |
| node.typeArguments.forEach((t) => t.accept(this)); |
| } |
| |
| visitTypedefType(TypedefType node) { |
| node.typeArguments.forEach((t) => t.accept(this)); |
| } |
| |
| visitTypeParameterType(TypeParameterType node) { |
| // Note: node.parameter is marked by marking the enclosing element. |
| } |
| } |