blob: df987e1277b4360e947f85267d62094c3ca03a88 [file] [log] [blame]
// Copyright (c) 2021, 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.
/// Rapid type analysis on kernel AST.
import 'dart:core' hide Type;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'calls.dart' as calls
show Selector, DirectSelector, InterfaceSelector, VirtualSelector;
import 'native_code.dart'
show EntryPointsListener, NativeCodeOracle, PragmaEntryPointsVisitor;
import 'protobuf_handler.dart' show ProtobufHandler;
import 'types.dart' show TFClass, Type, ConcreteType;
import '../pragma.dart' show ConstantPragmaAnnotationParser;
class Selector {
final Name name;
final bool setter;
Selector(this.name, this.setter);
@override
int get hashCode => name.hashCode ^ setter.hashCode;
@override
bool operator ==(Object other) =>
other is Selector &&
this.name == other.name &&
this.setter == other.setter;
}
class ClassInfo extends TFClass {
final ClassInfo? superclass;
final Set<ClassInfo> supertypes; // All super-types including this.
final Set<ClassInfo> subclasses = Set<ClassInfo>();
final Set<ClassInfo> subtypes = Set<ClassInfo>();
final Set<Selector>
calledDynamicSelectors; // Selectors called with dynamic and interface calls.
final Set<Selector> calledVirtualSelectors;
bool isAllocated = false;
late final Map<Name, Member> _dispatchTargetsSetters =
_initDispatchTargets(true);
late final Map<Name, Member> _dispatchTargetsNonSetters =
_initDispatchTargets(false);
ClassInfo(int id, Class classNode, this.superclass, this.supertypes,
this.calledDynamicSelectors, this.calledVirtualSelectors)
: super(id, classNode) {
supertypes.add(this);
for (var sup in supertypes) {
sup.subtypes.add(this);
}
for (ClassInfo? sup = this; sup != null; sup = sup.superclass) {
sup.subclasses.add(this);
}
}
late final ConcreteType concreteType = ConcreteType(this, null);
Map<Name, Member> _initDispatchTargets(bool setters) {
Map<Name, Member> targets;
final superclass = this.superclass;
if (superclass != null) {
targets = Map.from(setters
? superclass._dispatchTargetsSetters
: superclass._dispatchTargetsNonSetters);
} else {
targets = {};
}
for (Field f in classNode.fields) {
if (!f.isStatic && !f.isAbstract) {
if (!setters || f.hasSetter) {
targets[f.name] = f;
}
}
}
for (Procedure p in classNode.procedures) {
if (!p.isStatic && !p.isAbstract) {
if (p.isSetter == setters) {
targets[p.name] = p;
}
}
}
return targets;
}
Member? getDispatchTarget(Selector selector) {
return (selector.setter
? _dispatchTargetsSetters
: _dispatchTargetsNonSetters)[selector.name];
}
}
class _ClassHierarchyCache {
final Map<Class, ClassInfo> classes = <Class, ClassInfo>{};
int _classIdCounter = 0;
_ClassHierarchyCache();
ClassInfo getClassInfo(Class c) {
return classes[c] ??= _createClassInfo(c);
}
ClassInfo _createClassInfo(Class c) {
final supertypes = Set<ClassInfo>();
final dynSel = Set<Selector>();
for (var sup in c.supers) {
final supInfo = getClassInfo(sup.classNode);
supertypes.addAll(supInfo.supertypes);
dynSel.addAll(supInfo.calledDynamicSelectors);
}
Class? superclassNode = c.superclass;
ClassInfo? superclass;
final virtSel = Set<Selector>();
if (superclassNode != null) {
superclass = getClassInfo(superclassNode);
virtSel.addAll(superclass.calledVirtualSelectors);
}
return ClassInfo(
++_classIdCounter, c, superclass, supertypes, dynSel, virtSel);
}
ConcreteType addAllocatedClass(Class cl, RapidTypeAnalysis rta) {
assert(!cl.isAbstract);
final ClassInfo classInfo = getClassInfo(cl);
if (!classInfo.isAllocated) {
classInfo.isAllocated = true;
for (var sel in classInfo.calledDynamicSelectors) {
final member = classInfo.getDispatchTarget(sel);
if (member != null) {
rta.addMember(member);
}
}
for (var sel in classInfo.calledVirtualSelectors) {
final member = classInfo.getDispatchTarget(sel);
if (member != null) {
rta.addMember(member);
}
}
}
return classInfo.concreteType;
}
void addDynamicCall(Selector selector, Class cl, RapidTypeAnalysis rta) {
final ClassInfo classInfo = getClassInfo(cl);
for (var sub in classInfo.subtypes) {
if (sub.calledDynamicSelectors.add(selector) && sub.isAllocated) {
final member = sub.getDispatchTarget(selector);
if (member != null) {
rta.addMember(member);
}
}
}
}
void addVirtualCall(Selector selector, Class cl, RapidTypeAnalysis rta) {
final ClassInfo classInfo = getClassInfo(cl);
for (var sub in classInfo.subclasses) {
if (sub.calledVirtualSelectors.add(selector) && sub.isAllocated) {
final member = sub.getDispatchTarget(selector);
if (member != null) {
rta.addMember(member);
}
}
}
}
}
class RapidTypeAnalysis {
final CoreTypes coreTypes;
final ClassHierarchy hierarchy;
final _ClassHierarchyCache hierarchyCache = _ClassHierarchyCache();
final ProtobufHandler? protobufHandler;
final Set<Member> visited = {};
final List<Member> workList = [];
RapidTypeAnalysis(Component component, this.coreTypes, this.hierarchy,
LibraryIndex libraryIndex, this.protobufHandler) {
Procedure? main = component.mainMethod;
if (main != null) {
addMember(main);
}
final annotationMatcher = ConstantPragmaAnnotationParser(coreTypes);
final nativeCodeOracle = NativeCodeOracle(libraryIndex, annotationMatcher);
component.accept(PragmaEntryPointsVisitor(
_EntryPointsListenerImpl(this), nativeCodeOracle, annotationMatcher));
run();
}
List<Class> get allocatedClasses {
return <Class>[
for (var entry in hierarchyCache.classes.entries)
if (entry.value.isAllocated) entry.key
];
}
bool isAllocatedClass(Class cl) =>
hierarchyCache.classes[cl]?.isAllocated ?? false;
ConcreteType addAllocatedClass(Class cl) =>
hierarchyCache.addAllocatedClass(cl, this);
void addMember(Member member) {
if (visited.add(member)) {
workList.add(member);
}
}
void addCall(Class? currentClass, Member? interfaceTarget, Name name,
bool isVirtual, bool isSetter) {
final Class cl = isVirtual
? currentClass!
: (interfaceTarget != null
? interfaceTarget.enclosingClass!
: coreTypes.objectClass);
final Selector selector = Selector(name, isSetter);
if (isVirtual) {
hierarchyCache.addVirtualCall(selector, cl, this);
} else {
hierarchyCache.addDynamicCall(selector, cl, this);
}
}
void run() {
final memberVisitor = _MemberVisitor(this);
while (workList.isNotEmpty || invalidateProtobufFields()) {
final member = workList.removeLast();
protobufHandler?.beforeSummaryCreation(member);
member.accept(memberVisitor);
}
}
bool invalidateProtobufFields() {
final protobufHandler = this.protobufHandler;
if (protobufHandler == null) {
return false;
}
final fields = protobufHandler.getInvalidatedFields();
if (fields.isEmpty) {
return false;
}
// Protobuf handler replaced contents of static field initializers.
bool invalidated = false;
for (var field in fields) {
assert(field.isStatic);
if (visited.contains(field)) {
workList.add(field);
invalidated = true;
}
}
return invalidated;
}
}
class _MemberVisitor extends RecursiveVisitor {
final RapidTypeAnalysis rta;
final _ConstantVisitor _constantVisitor;
Class? _currentClass;
ClassInfo? _superclassInfo;
_MemberVisitor(this.rta) : _constantVisitor = _ConstantVisitor(rta);
ClassInfo get superclassInfo => _superclassInfo ??=
rta.hierarchyCache.getClassInfo(_currentClass!.superclass!);
@override
void defaultMember(Member node) {
_superclassInfo = null;
_currentClass = node.enclosingClass;
node.visitChildren(this);
if (node is Constructor) {
// Make sure instance field initializers are visited.
for (var f in _currentClass!.members) {
if (f is Field && !f.isStatic) {
f.initializer?.accept(this);
}
}
}
_superclassInfo = null;
_currentClass = null;
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
rta.addAllocatedClass(node.constructedType.classNode);
rta.addMember(node.target);
node.visitChildren(this);
}
@override
void visitInstanceInvocation(InstanceInvocation node) {
rta.addCall(_currentClass, node.interfaceTarget, node.name,
node.receiver is ThisExpression, false);
node.visitChildren(this);
}
@override
void visitDynamicInvocation(DynamicInvocation node) {
rta.addCall(null, null, node.name, false, false);
node.visitChildren(this);
}
@override
void visitEqualsCall(EqualsCall node) {
rta.addCall(_currentClass, node.interfaceTarget, node.interfaceTarget.name,
node.left is ThisExpression, false);
node.visitChildren(this);
}
@override
void visitInstanceGet(InstanceGet node) {
rta.addCall(_currentClass, node.interfaceTarget, node.name,
node.receiver is ThisExpression, false);
node.visitChildren(this);
}
@override
void visitInstanceTearOff(InstanceTearOff node) {
rta.addCall(_currentClass, node.interfaceTarget, node.name,
node.receiver is ThisExpression, false);
node.visitChildren(this);
}
@override
void visitDynamicGet(DynamicGet node) {
rta.addCall(null, null, node.name, false, false);
node.visitChildren(this);
}
@override
void visitInstanceSet(InstanceSet node) {
rta.addCall(_currentClass, node.interfaceTarget, node.name,
node.receiver is ThisExpression, true);
node.visitChildren(this);
}
@override
void visitDynamicSet(DynamicSet node) {
rta.addCall(null, null, node.name, false, true);
node.visitChildren(this);
}
@override
void visitSuperMethodInvocation(SuperMethodInvocation node) {
final target = superclassInfo.getDispatchTarget(Selector(node.name, false));
if (target != null) {
rta.addMember(target);
}
node.visitChildren(this);
}
@override
void visitSuperPropertyGet(SuperPropertyGet node) {
final target = superclassInfo.getDispatchTarget(Selector(node.name, false));
if (target != null) {
rta.addMember(target);
}
node.visitChildren(this);
}
@override
void visitSuperPropertySet(SuperPropertySet node) {
final target = superclassInfo.getDispatchTarget(Selector(node.name, true));
if (target != null) {
rta.addMember(target);
}
node.visitChildren(this);
}
@override
void visitStaticGet(StaticGet node) {
rta.addMember(node.target);
node.visitChildren(this);
}
@override
void visitStaticInvocation(StaticInvocation node) {
rta.addMember(node.target);
node.visitChildren(this);
}
@override
void visitStaticSet(StaticSet node) {
rta.addMember(node.target);
node.visitChildren(this);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
rta.addMember(node.target);
node.visitChildren(this);
}
@override
void visitSuperInitializer(SuperInitializer node) {
// Re-resolve target due to partial mixin resolution.
for (var replacement in _currentClass!.superclass!.constructors) {
if (node.target.name == replacement.name) {
rta.addMember(replacement);
break;
}
}
node.visitChildren(this);
}
@override
void visitConstantExpression(ConstantExpression node) {
_constantVisitor.visit(node.constant);
}
}
class _ConstantVisitor extends ConstantVisitor<void> {
final RapidTypeAnalysis rta;
final Set<Constant> visited = {};
_ConstantVisitor(this.rta);
void visit(Constant constant) {
if (visited.add(constant)) {
constant.accept(this);
}
}
@override
void defaultConstant(Constant node) {}
@override
void visitListConstant(ListConstant constant) {
for (final entry in constant.entries) {
visit(entry);
}
}
@override
void visitMapConstant(MapConstant constant) {
for (final entry in constant.entries) {
visit(entry.key);
visit(entry.value);
}
}
@override
void visitSetConstant(SetConstant constant) {
for (final entry in constant.entries) {
visit(entry);
}
}
@override
void visitInstanceConstant(InstanceConstant constant) {
rta.addAllocatedClass(constant.classNode);
for (var value in constant.fieldValues.values) {
visit(value);
}
}
void _visitTearOffConstant(TearOffConstant constant) {
final Member member = constant.target;
rta.addMember(member);
if (member is Constructor) {
rta.addAllocatedClass(member.enclosingClass);
}
}
@override
void visitStaticTearOffConstant(StaticTearOffConstant constant) =>
_visitTearOffConstant(constant);
@override
void visitConstructorTearOffConstant(ConstructorTearOffConstant constant) =>
_visitTearOffConstant(constant);
@override
void visitRedirectingFactoryTearOffConstant(
RedirectingFactoryTearOffConstant constant) =>
_visitTearOffConstant(constant);
@override
void visitInstantiationConstant(InstantiationConstant constant) {
visit(constant.tearOffConstant);
}
}
class _EntryPointsListenerImpl implements EntryPointsListener {
final RapidTypeAnalysis rta;
_EntryPointsListenerImpl(this.rta);
@override
void addFieldUsedInConstant(Field field, Type instance, Type value) {}
@override
void addRawCall(calls.Selector selector) {
if (selector is calls.DirectSelector) {
rta.addMember(selector.member);
} else if (selector is calls.InterfaceSelector) {
rta.addCall(selector.member.enclosingClass!, selector.member,
selector.name, selector is calls.VirtualSelector, selector.isSetter);
} else {
throw 'Unexpected selector ${selector.runtimeType} $selector';
}
}
@override
ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c);
@override
void recordMemberCalledViaInterfaceSelector(Member target) =>
throw 'Unsupported operation';
@override
void recordMemberCalledViaThis(Member target) =>
throw 'Unsupported operation';
@override
void recordTearOff(Member target) => throw 'Unsupported operation';
}