[kernel] Support interface member resolution in class hierarchy.
This is useful for handling of externals in tree shaking and type propagation. BUG= R=ahe@google.com Review URL: https://chromereviews.googleplex.com/435947013 .
This commit is contained in:
@@ -62,24 +62,23 @@ class ClassHierarchy {
|
||||
return _infoFor[class_].directImplementers.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Returns the instantiation of [supertype] that is implemented by [type],
|
||||
/// or `null` if [type] does not implement [supertype] at all.
|
||||
InterfaceType getClassAsInstanceOf(Class type, Class supertype) {
|
||||
if (identical(type, supertype)) return type.thisType;
|
||||
_ClassInfo info = _infoFor[type];
|
||||
_ClassInfo superInfo = _infoFor[supertype];
|
||||
/// Returns the instantiation of [superclass] that is implemented by [class_],
|
||||
/// or `null` if [class_] does not implement [superclass] at all.
|
||||
InterfaceType getClassAsInstanceOf(Class class_, Class superclass) {
|
||||
if (identical(class_, superclass)) return class_.thisType;
|
||||
_ClassInfo info = _infoFor[class_];
|
||||
_ClassInfo superInfo = _infoFor[superclass];
|
||||
if (!info.isSubtypeOf(superInfo)) return null;
|
||||
if (supertype.typeParameters.isEmpty) return supertype.rawType;
|
||||
return info.genericSuperTypes[supertype];
|
||||
if (superclass.typeParameters.isEmpty) return superclass.rawType;
|
||||
return info.genericSuperTypes[superclass];
|
||||
}
|
||||
|
||||
/// Returns the instantiation of [supertype] that is implemented by [type],
|
||||
/// or `null` if [type] does not implement [supertype] at all.
|
||||
InterfaceType getTypeAsInstanceOf(InterfaceType type, Class supertype) {
|
||||
InterfaceType castedType = getClassAsInstanceOf(type.classNode, supertype);
|
||||
/// Returns the instantiation of [superclass] that is implemented by [type],
|
||||
/// or `null` if [type] does not implement [superclass] at all.
|
||||
InterfaceType getTypeAsInstanceOf(InterfaceType type, Class superclass) {
|
||||
InterfaceType castedType = getClassAsInstanceOf(type.classNode, superclass);
|
||||
if (castedType == null) return null;
|
||||
return substitutePairwise(
|
||||
castedType, type.classNode.typeParameters, type.typeArguments);
|
||||
return substituteThisType(castedType, type);
|
||||
}
|
||||
|
||||
/// Returns the instance member that would respond to a dynamic dispatch of
|
||||
@@ -117,6 +116,28 @@ class ClassHierarchy {
|
||||
return setters ? info.implementedSetters : info.implementedGettersAndCalls;
|
||||
}
|
||||
|
||||
/// Returns the possibly abstract interface member of [class_] with the given
|
||||
/// [name].
|
||||
///
|
||||
/// If [setters] is `false`, only fields, methods, and getters with that name
|
||||
/// will be found. If [setters] is `true`, only non-final fields and setters
|
||||
/// will be found.
|
||||
///
|
||||
/// If multiple members with that name are inherited and not overidden, the
|
||||
/// member from the first declared supertype is returned.
|
||||
Member getInterfaceMember(Class class_, Name name, {bool setter: false}) {
|
||||
List<Member> list = getInterfaceMembers(class_, setters: setter);
|
||||
return _findMemberByName(list, name);
|
||||
}
|
||||
|
||||
/// Returns the list of members denoting the interface for [class_], which
|
||||
/// may include abstract members.
|
||||
///
|
||||
/// See [getInterfaceMember].
|
||||
List<Member> getInterfaceMembers(Class class_, {bool setters: false}) {
|
||||
return _buildInterfaceMembers(class_, _infoFor[class_], setters: setters);
|
||||
}
|
||||
|
||||
ClassHierarchy._internal(Program program, int numberOfClasses)
|
||||
: classes = new List<Class>(numberOfClasses) {
|
||||
// Build the class ordering based on a topological sort.
|
||||
@@ -224,13 +245,47 @@ class ClassHierarchy {
|
||||
_inheritMembers(info.declaredSetters, inheritedSetters);
|
||||
}
|
||||
|
||||
List<Member> _buildInterfaceMembers(Class classNode, _ClassInfo info,
|
||||
{bool setters}) {
|
||||
List<Member> members =
|
||||
setters ? info.interfaceSetters : info.interfaceGettersAndCalls;
|
||||
if (members != null) return members;
|
||||
members = <Member>[];
|
||||
for (Procedure member in classNode.mixin.procedures) {
|
||||
if (member.isStatic) continue;
|
||||
if (setters != member.isSetter) continue;
|
||||
members.add(member);
|
||||
}
|
||||
for (Field member in classNode.mixin.fields) {
|
||||
if (member.isStatic) continue;
|
||||
if (setters && member.isFinal) continue;
|
||||
members.add(member);
|
||||
}
|
||||
members.sort(_compareMembers);
|
||||
void inheritFrom(InterfaceType type) {
|
||||
if (type == null) return;
|
||||
List<Member> inherited = _buildInterfaceMembers(type.classNode,
|
||||
_infoFor[type.classNode], setters: setters);
|
||||
members = _inheritMembers(members, inherited);
|
||||
}
|
||||
inheritFrom(classNode.supertype);
|
||||
inheritFrom(classNode.mixedInType);
|
||||
classNode.implementedTypes.forEach(inheritFrom);
|
||||
if (setters) {
|
||||
info.interfaceSetters = members;
|
||||
} else {
|
||||
info.interfaceGettersAndCalls = members;
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
/// Computes the list of implemented members, based on the declared instance
|
||||
/// members and inherited instance members.
|
||||
///
|
||||
/// Both lists are sorted by name beforehand.
|
||||
/// Both lists must be sorted by name beforehand.
|
||||
List<Member> _inheritMembers(List<Member> declared, List<Member> inherited) {
|
||||
List<Member> result = <Member>[]
|
||||
..length = declared.length + inherited.length;
|
||||
..length = declared.length + inherited.length;
|
||||
// Since both lists are sorted, we can fuse them like in merge sort.
|
||||
int storeIndex = 0;
|
||||
int i = 0, j = 0;
|
||||
@@ -607,5 +662,8 @@ class _ClassInfo {
|
||||
/// (declared or inherited).
|
||||
List<Member> implementedSetters;
|
||||
|
||||
List<Member> interfaceGettersAndCalls;
|
||||
List<Member> interfaceSetters;
|
||||
|
||||
_ClassInfo(this.classNode);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ class BasicClassHierarchy implements ClassHierarchy {
|
||||
final Map<Class, Map<Name, Member>> gettersAndCalls =
|
||||
<Class, Map<Name, Member>>{};
|
||||
final Map<Class, Map<Name, Member>> setters = <Class, Map<Name, Member>>{};
|
||||
final Map<Class, Map<Name, Member>> interfaceGettersAndCalls =
|
||||
<Class, Map<Name, Member>>{};
|
||||
final Map<Class, Map<Name, Member>> interfaceSetters =
|
||||
<Class, Map<Name, Member>>{};
|
||||
final List<Class> classes = <Class>[];
|
||||
final Map<Class, int> classIndex = <Class, int>{};
|
||||
|
||||
@@ -28,6 +32,7 @@ class BasicClassHierarchy implements ClassHierarchy {
|
||||
buildSuperTypeSets(classNode);
|
||||
buildSuperTypeInstantiations(classNode);
|
||||
buildDispatchTable(classNode);
|
||||
buildInterfaceTable(classNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +103,38 @@ class BasicClassHierarchy implements ClassHierarchy {
|
||||
}
|
||||
}
|
||||
|
||||
void buildInterfaceTable(Class node) {
|
||||
if (interfaceGettersAndCalls.containsKey(node)) return;
|
||||
interfaceGettersAndCalls[node] = <Name, Member>{};
|
||||
interfaceSetters[node] = <Name, Member>{};
|
||||
void inheritFrom(InterfaceType type) {
|
||||
if (type == null) return;
|
||||
buildInterfaceTable(type.classNode);
|
||||
interfaceGettersAndCalls[node]
|
||||
.addAll(interfaceGettersAndCalls[type.classNode]);
|
||||
interfaceSetters[node].addAll(interfaceSetters[type.classNode]);
|
||||
}
|
||||
node.implementedTypes.reversed.forEach(inheritFrom);
|
||||
inheritFrom(node.mixedInType);
|
||||
inheritFrom(node.supertype);
|
||||
// Overwrite map entries with declared members.
|
||||
for (Procedure procedure in node.mixin.procedures) {
|
||||
if (procedure.isStatic) continue;
|
||||
if (procedure.kind == ProcedureKind.Setter) {
|
||||
interfaceSetters[node][procedure.name] = procedure;
|
||||
} else {
|
||||
interfaceGettersAndCalls[node][procedure.name] = procedure;
|
||||
}
|
||||
}
|
||||
for (Field field in node.mixin.fields) {
|
||||
if (field.isStatic) continue;
|
||||
interfaceGettersAndCalls[node][field.name] = field;
|
||||
if (!field.isFinal) {
|
||||
interfaceSetters[node][field.name] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isSubclassOf(Class subtype, Class supertype) {
|
||||
return superclasses[subtype].contains(supertype);
|
||||
}
|
||||
@@ -124,6 +161,18 @@ class BasicClassHierarchy implements ClassHierarchy {
|
||||
: gettersAndCalls[class_].values;
|
||||
}
|
||||
|
||||
Member getInterfaceMember(Class class_, Name name, {bool setter: false}) {
|
||||
return setter
|
||||
? interfaceSetters[class_][name]
|
||||
: interfaceGettersAndCalls[class_][name];
|
||||
}
|
||||
|
||||
Iterable<Member> getInterfaceMembers(Class class_, {bool setters: false}) {
|
||||
return setters
|
||||
? interfaceSetters[class_].values
|
||||
: interfaceGettersAndCalls[class_].values;
|
||||
}
|
||||
|
||||
int getClassIndex(Class node) {
|
||||
return classIndex[node];
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import 'dart:io';
|
||||
|
||||
ArgParser argParser = new ArgParser()
|
||||
..addFlag('basic', help: 'Measure the basic implementation', negatable: false)
|
||||
..addOption('cycle', abbr: 'c',
|
||||
..addOption('cycle',
|
||||
abbr: 'c',
|
||||
help: 'Build N copies of the class hierarchy and cycle queries '
|
||||
'between them',
|
||||
'between them',
|
||||
defaultsTo: '1');
|
||||
|
||||
String usage = '''
|
||||
@@ -175,11 +176,38 @@ main(List<String> args) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
for (var _ in classHierarchy.getDispatchTargets(classNode)) {
|
||||
}
|
||||
for (var _ in classHierarchy.getDispatchTargets(classNode)) {}
|
||||
}
|
||||
int dispatchAllTargetsTime = watch.elapsedMicroseconds;
|
||||
|
||||
// Measure getInterfaceMember and getInterfaceMembers.
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
classHierarchy.getInterfaceMember(classNode, new Name('toString'));
|
||||
}
|
||||
int interfaceToStringTime = watch.elapsedMicroseconds;
|
||||
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
classHierarchy.getInterfaceMember(classNode, new Name('getFloo'));
|
||||
}
|
||||
int interfaceGenericGetTime = watch.elapsedMicroseconds;
|
||||
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
var classHierarchy = getClassHierarchy();
|
||||
int classId = rnd.nextInt(classHierarchy.classes.length);
|
||||
Class classNode = classHierarchy.classes[classId];
|
||||
for (var _ in classHierarchy.getInterfaceMembers(classNode)) {}
|
||||
}
|
||||
int interfaceAllTargetsTime = watch.elapsedMicroseconds;
|
||||
|
||||
// Estimate overhead from test case generation.
|
||||
watch.reset();
|
||||
for (int i = 0; i < numQueryTrials; i++) {
|
||||
@@ -192,6 +220,9 @@ main(List<String> args) {
|
||||
dispatchToStringTime -= dispatchTargetNoise;
|
||||
dispatchGenericGetTime -= dispatchTargetNoise;
|
||||
dispatchAllTargetsTime -= dispatchTargetNoise;
|
||||
interfaceToStringTime -= dispatchTargetNoise;
|
||||
interfaceGenericGetTime -= dispatchTargetNoise;
|
||||
interfaceAllTargetsTime -= dispatchTargetNoise;
|
||||
|
||||
String dispatchToStringPerSecond =
|
||||
perSecond(dispatchToStringTime, numQueryTrials);
|
||||
@@ -200,6 +231,13 @@ main(List<String> args) {
|
||||
String dispatchAllTargetsPerSecond =
|
||||
perSecond(dispatchAllTargetsTime, numQueryTrials);
|
||||
|
||||
String interfaceToStringPerSecond =
|
||||
perSecond(interfaceToStringTime, numQueryTrials);
|
||||
String interfaceGetPerSecond =
|
||||
perSecond(interfaceGenericGetTime, numQueryTrials);
|
||||
String interfaceAllTargetsPerSecond =
|
||||
perSecond(interfaceAllTargetsTime, numQueryTrials);
|
||||
|
||||
var classHierarchy = getClassHierarchy();
|
||||
List<int> depth = new List(classHierarchy.classes.length);
|
||||
for (int i = 0; i < depth.length; ++i) {
|
||||
@@ -229,13 +267,16 @@ main(List<String> args) {
|
||||
classes: $numberOfClasses
|
||||
build.cold: $coldBuildTime ms
|
||||
build.hot: $hotBuildTime ms
|
||||
query.isSubclassOf: $subclassPerSecond
|
||||
query.isSubmixtureOf: $submixturePerSecond
|
||||
query.isSubtypeOf: $subtypePerSecond
|
||||
query.getClassAsInstanceOf: $asInstanceOfPerSecond
|
||||
query.getDispatchTarget(toString): $dispatchToStringPerSecond
|
||||
query.getDispatchTarget(getFloo): $dispatchGetPerSecond
|
||||
query.getDispatchTargets.iterate: $dispatchAllTargetsPerSecond
|
||||
query.isSubclassOf: $subclassPerSecond
|
||||
query.isSubmixtureOf: $submixturePerSecond
|
||||
query.isSubtypeOf: $subtypePerSecond
|
||||
query.getClassAsInstanceOf: $asInstanceOfPerSecond
|
||||
query.getDispatchTarget(toString): $dispatchToStringPerSecond
|
||||
query.getDispatchTarget(getFloo): $dispatchGetPerSecond
|
||||
query.getDispatchTargets.iterate: $dispatchAllTargetsPerSecond
|
||||
query.getInterfaceMember(toString): $interfaceToStringPerSecond
|
||||
query.getInterfaceMember(getFloo): $interfaceGetPerSecond
|
||||
query.getInterfaceMembers.iterate: $interfaceAllTargetsPerSecond
|
||||
isSubtypeOf.expense-histogram: $expenseHistogram
|
||||
isSubtypeOf.compression-ratio: ${classHierarchy.getCompressionRatio()}
|
||||
asInstanceOf.table-size: ${classHierarchy.getSuperTypeHashTableSize()}
|
||||
|
||||
@@ -61,19 +61,20 @@ void testClassHierarchyOnProgram(Program program, {bool verbose: false}) {
|
||||
List<Name> nameList = names.toList();
|
||||
progress = 0;
|
||||
for (var classNode in classHierarchy.classes) {
|
||||
Iterable<Name> candidateNames =
|
||||
[basic.gettersAndCalls[classNode].keys,
|
||||
basic.setters[classNode].keys,
|
||||
pickRandom(nameList, 100)].expand((x) => x);
|
||||
Iterable<Name> candidateNames = [
|
||||
basic.gettersAndCalls[classNode].keys,
|
||||
basic.setters[classNode].keys,
|
||||
pickRandom(nameList, 100)
|
||||
].expand((x) => x);
|
||||
for (Name name in candidateNames) {
|
||||
Member expectedGetter =
|
||||
basic.getDispatchTarget(classNode, name, setter: false);
|
||||
Member expectedSetter =
|
||||
basic.getDispatchTarget(classNode, name, setter: true);
|
||||
Member actualGetter = classHierarchy
|
||||
.getDispatchTarget(classNode, name, setter: false);
|
||||
Member actualSetter = classHierarchy
|
||||
.getDispatchTarget(classNode, name, setter: true);
|
||||
Member actualGetter =
|
||||
classHierarchy.getDispatchTarget(classNode, name, setter: false);
|
||||
Member actualSetter =
|
||||
classHierarchy.getDispatchTarget(classNode, name, setter: true);
|
||||
if (actualGetter != expectedGetter) {
|
||||
fail('lookupGetter($classNode, $name) returned '
|
||||
'$actualGetter but should be $expectedGetter');
|
||||
@@ -85,7 +86,37 @@ void testClassHierarchyOnProgram(Program program, {bool verbose: false}) {
|
||||
}
|
||||
++progress;
|
||||
if (verbose) {
|
||||
stdout.write('\rEnvironment queries ${100 * progress ~/ total}%');
|
||||
stdout.write('\rDispatch queries ${100 * progress ~/ total}%');
|
||||
}
|
||||
}
|
||||
progress = 0;
|
||||
for (var classNode in classHierarchy.classes) {
|
||||
Iterable<Name> candidateNames = [
|
||||
basic.interfaceGettersAndCalls[classNode].keys,
|
||||
basic.interfaceSetters[classNode].keys,
|
||||
pickRandom(nameList, 100)
|
||||
].expand((x) => x);
|
||||
for (Name name in candidateNames) {
|
||||
Member expectedGetter =
|
||||
basic.getInterfaceMember(classNode, name, setter: false);
|
||||
Member expectedSetter =
|
||||
basic.getInterfaceMember(classNode, name, setter: true);
|
||||
Member actualGetter =
|
||||
classHierarchy.getInterfaceMember(classNode, name, setter: false);
|
||||
Member actualSetter =
|
||||
classHierarchy.getInterfaceMember(classNode, name, setter: true);
|
||||
if (actualGetter != expectedGetter) {
|
||||
fail('getInterfaceMember($classNode, $name) returned '
|
||||
'$actualGetter but should be $expectedGetter');
|
||||
}
|
||||
if (actualSetter != expectedSetter) {
|
||||
fail('getInterfaceMember($classNode, $name, setter: true) '
|
||||
'returned $actualSetter but should be $expectedSetter');
|
||||
}
|
||||
}
|
||||
++progress;
|
||||
if (verbose) {
|
||||
stdout.write('\rInterface queries ${100 * progress ~/ total}%');
|
||||
}
|
||||
}
|
||||
if (verbose) {
|
||||
|
||||
Reference in New Issue
Block a user