blob: 687e5ff334708be6400fc3b5230454c9618095de [file] [log] [blame]
// 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.
}
}