From b61129ee84fdfc28bb913a2f0df42495ce56e468 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 27 May 2016 11:25:00 +0200 Subject: [PATCH] [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 . --- pkg/kernel/lib/class_hierarchy.dart | 90 +++++++++++++++++---- pkg/kernel/test/class_hierarchy_basic.dart | 49 +++++++++++ pkg/kernel/test/class_hierarchy_bench.dart | 63 ++++++++++++--- pkg/kernel/test/class_hierarchy_tester.dart | 49 ++++++++--- 4 files changed, 215 insertions(+), 36 deletions(-) diff --git a/pkg/kernel/lib/class_hierarchy.dart b/pkg/kernel/lib/class_hierarchy.dart index 83c6715930b..6ea0a243375 100644 --- a/pkg/kernel/lib/class_hierarchy.dart +++ b/pkg/kernel/lib/class_hierarchy.dart @@ -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 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 getInterfaceMembers(Class class_, {bool setters: false}) { + return _buildInterfaceMembers(class_, _infoFor[class_], setters: setters); + } + ClassHierarchy._internal(Program program, int numberOfClasses) : classes = new List(numberOfClasses) { // Build the class ordering based on a topological sort. @@ -224,13 +245,47 @@ class ClassHierarchy { _inheritMembers(info.declaredSetters, inheritedSetters); } + List _buildInterfaceMembers(Class classNode, _ClassInfo info, + {bool setters}) { + List members = + setters ? info.interfaceSetters : info.interfaceGettersAndCalls; + if (members != null) return members; + members = []; + 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 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 _inheritMembers(List declared, List inherited) { List result = [] - ..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 implementedSetters; + List interfaceGettersAndCalls; + List interfaceSetters; + _ClassInfo(this.classNode); } diff --git a/pkg/kernel/test/class_hierarchy_basic.dart b/pkg/kernel/test/class_hierarchy_basic.dart index 940b3cbab83..f6ece1a73ce 100644 --- a/pkg/kernel/test/class_hierarchy_basic.dart +++ b/pkg/kernel/test/class_hierarchy_basic.dart @@ -19,6 +19,10 @@ class BasicClassHierarchy implements ClassHierarchy { final Map> gettersAndCalls = >{}; final Map> setters = >{}; + final Map> interfaceGettersAndCalls = + >{}; + final Map> interfaceSetters = + >{}; final List classes = []; final Map classIndex = {}; @@ -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] = {}; + interfaceSetters[node] = {}; + 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 getInterfaceMembers(Class class_, {bool setters: false}) { + return setters + ? interfaceSetters[class_].values + : interfaceGettersAndCalls[class_].values; + } + int getClassIndex(Class node) { return classIndex[node]; } diff --git a/pkg/kernel/test/class_hierarchy_bench.dart b/pkg/kernel/test/class_hierarchy_bench.dart index e5da97723cf..d9b50b3e6db 100644 --- a/pkg/kernel/test/class_hierarchy_bench.dart +++ b/pkg/kernel/test/class_hierarchy_bench.dart @@ -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 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 args) { dispatchToStringTime -= dispatchTargetNoise; dispatchGenericGetTime -= dispatchTargetNoise; dispatchAllTargetsTime -= dispatchTargetNoise; + interfaceToStringTime -= dispatchTargetNoise; + interfaceGenericGetTime -= dispatchTargetNoise; + interfaceAllTargetsTime -= dispatchTargetNoise; String dispatchToStringPerSecond = perSecond(dispatchToStringTime, numQueryTrials); @@ -200,6 +231,13 @@ main(List 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 depth = new List(classHierarchy.classes.length); for (int i = 0; i < depth.length; ++i) { @@ -229,13 +267,16 @@ main(List 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()} diff --git a/pkg/kernel/test/class_hierarchy_tester.dart b/pkg/kernel/test/class_hierarchy_tester.dart index 8a5d41fd05c..7830bc6d5e9 100644 --- a/pkg/kernel/test/class_hierarchy_tester.dart +++ b/pkg/kernel/test/class_hierarchy_tester.dart @@ -61,19 +61,20 @@ void testClassHierarchyOnProgram(Program program, {bool verbose: false}) { List nameList = names.toList(); progress = 0; for (var classNode in classHierarchy.classes) { - Iterable candidateNames = - [basic.gettersAndCalls[classNode].keys, - basic.setters[classNode].keys, - pickRandom(nameList, 100)].expand((x) => x); + Iterable 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 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) {