From 5dca4cbd427fa663c9a2e4f2b41fc2c0f6d5a651 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Thu, 8 Dec 2022 21:19:54 +0000 Subject: [PATCH] [dart2js] Distinguish virtual calls from direct calls to recover some lost accuracy in types. This recovers the accuracy of types that were previously being widened and even performs better than today's algorithm for some specific cases. Change-Id: I0139a0fd662a91315282ba94e4c1a29d44056b9d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273501 Reviewed-by: Mayank Patke Commit-Queue: Nate Biggs --- .../src/inferrer/abstract_value_domain.dart | 2 +- pkg/compiler/lib/src/inferrer/computable.dart | 2 +- .../lib/src/inferrer/powersets/powersets.dart | 6 +- pkg/compiler/lib/src/inferrer/trivial.dart | 2 +- .../lib/src/inferrer/typemasks/masks.dart | 2 +- pkg/compiler/lib/src/inferrer/wrapped.dart | 2 +- .../inferrer_experimental/closure_tracer.dart | 6 +- .../lib/src/inferrer_experimental/engine.dart | 122 ++++++++++++++---- .../inferrer_experimental/list_tracer.dart | 2 +- .../src/inferrer_experimental/map_tracer.dart | 2 +- .../inferrer_experimental/node_tracer.dart | 2 +- .../powersets/powersets.dart | 6 +- .../src/inferrer_experimental/set_tracer.dart | 2 +- .../src/inferrer_experimental/trivial.dart | 2 +- .../type_graph_nodes.dart | 53 +++++--- .../inferrer_experimental/type_system.dart | 29 +++++ .../typemasks/flat_type_mask.dart | 11 +- .../typemasks/forwarding_type_mask.dart | 2 +- .../typemasks/masks.dart | 2 +- .../typemasks/type_mask.dart | 2 +- .../typemasks/union_type_mask.dart | 4 +- .../src/inferrer_experimental/wrapped.dart | 2 +- .../lib/src/universe/member_hierarchy.dart | 71 ++++++++-- 23 files changed, 249 insertions(+), 87 deletions(-) diff --git a/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart b/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart index 77a6fa68a9d..171a91d4589 100644 --- a/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart +++ b/pkg/compiler/lib/src/inferrer/abstract_value_domain.dart @@ -631,7 +631,7 @@ abstract class AbstractValueDomain { /// Returns a set of members that are ancestors of all possible targets for /// a call targeting [selector] on an entity with type represented by /// [receiver]. - Iterable findRootsOfTargets(AbstractValue receiver, + Iterable findRootsOfTargets(AbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder); /// Deserializes an [AbstractValue] for this domain from [source]. diff --git a/pkg/compiler/lib/src/inferrer/computable.dart b/pkg/compiler/lib/src/inferrer/computable.dart index 75d3440e77e..fe60cf63a7d 100644 --- a/pkg/compiler/lib/src/inferrer/computable.dart +++ b/pkg/compiler/lib/src/inferrer/computable.dart @@ -590,7 +590,7 @@ class ComputableAbstractValueDomain with AbstractValueDomain { _wrappedDomain.isFixedLengthJsIndexable(_unwrap(value)); @override - Iterable findRootsOfTargets(AbstractValue receiver, + Iterable findRootsOfTargets(AbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) { return _wrappedDomain.findRootsOfTargets( receiver, selector, memberHierarchyBuilder); diff --git a/pkg/compiler/lib/src/inferrer/powersets/powersets.dart b/pkg/compiler/lib/src/inferrer/powersets/powersets.dart index 425ff9e9e9f..7c144e6a08b 100644 --- a/pkg/compiler/lib/src/inferrer/powersets/powersets.dart +++ b/pkg/compiler/lib/src/inferrer/powersets/powersets.dart @@ -809,8 +809,10 @@ class PowersetDomain with AbstractValueDomain { _abstractValueDomain.typeType, _powersetBitsDomain.typeType); @override - Iterable findRootsOfTargets(covariant PowersetValue receiver, - Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => + Iterable findRootsOfTargets( + covariant PowersetValue receiver, + Selector selector, + MemberHierarchyBuilder memberHierarchyBuilder) => _abstractValueDomain.findRootsOfTargets( receiver.abstractValue, selector, memberHierarchyBuilder); } diff --git a/pkg/compiler/lib/src/inferrer/trivial.dart b/pkg/compiler/lib/src/inferrer/trivial.dart index eef7b853b2c..1331647c98e 100644 --- a/pkg/compiler/lib/src/inferrer/trivial.dart +++ b/pkg/compiler/lib/src/inferrer/trivial.dart @@ -370,7 +370,7 @@ class TrivialAbstractValueDomain with AbstractValueDomain { } @override - Iterable findRootsOfTargets( + Iterable findRootsOfTargets( covariant TrivialAbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => diff --git a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart index 9d422c20548..a1565c50a3a 100644 --- a/pkg/compiler/lib/src/inferrer/typemasks/masks.dart +++ b/pkg/compiler/lib/src/inferrer/typemasks/masks.dart @@ -926,7 +926,7 @@ class CommonMasks with AbstractValueDomain { } @override - Iterable findRootsOfTargets(covariant TypeMask receiver, + Iterable findRootsOfTargets(covariant TypeMask receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) { return const []; } diff --git a/pkg/compiler/lib/src/inferrer/wrapped.dart b/pkg/compiler/lib/src/inferrer/wrapped.dart index 7c8bb73239c..1bfc711c761 100644 --- a/pkg/compiler/lib/src/inferrer/wrapped.dart +++ b/pkg/compiler/lib/src/inferrer/wrapped.dart @@ -602,7 +602,7 @@ class WrappedAbstractValueDomain with AbstractValueDomain { WrappedAbstractValue(_abstractValueDomain.typeType); @override - Iterable findRootsOfTargets( + Iterable findRootsOfTargets( covariant WrappedAbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => diff --git a/pkg/compiler/lib/src/inferrer_experimental/closure_tracer.dart b/pkg/compiler/lib/src/inferrer_experimental/closure_tracer.dart index bd5372d5044..0ad8c8641ee 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/closure_tracer.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/closure_tracer.dart @@ -119,16 +119,16 @@ class ClosureTracerVisitor extends TracerVisitor { if (selector.isCall) { if (info.arguments!.contains(user)) { if (info.hasClosureCallTargets || - info.concreteTargets.any((element) => !element.isFunction)) { + info.callees.any((element) => !element.isFunction)) { bailout('Passed to a closure'); } - if (info.concreteTargets.any(_checkIfFunctionApply)) { + if (info.callees.any(_checkIfFunctionApply)) { _tagAsFunctionApplyTarget("dynamic call"); } } else { if (user is MemberTypeInformation) { final currentUserMember = user.member; - if (info.concreteTargets.contains(currentUserMember)) { + if (info.callees.contains(currentUserMember)) { _registerCallForLaterAnalysis(info); } } diff --git a/pkg/compiler/lib/src/inferrer_experimental/engine.dart b/pkg/compiler/lib/src/inferrer_experimental/engine.dart index cb9e1a07cc8..221397be9a9 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/engine.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/engine.dart @@ -478,7 +478,7 @@ class InferrerEngine { if (info.hasClosureCallTargets) { print(''); } - for (MemberEntity target in info.concreteTargets) { + for (MemberEntity target in info.callees) { if (target is FunctionEntity) { print('${types.getInferredSignatureOfMethod(target)} ' 'for ${target}'); @@ -761,14 +761,19 @@ class InferrerEngine { /// Update the inputs to parameters in the graph. [remove] tells whether /// inputs must be added or removed. If [addToQueue] is `true`, parameters are /// added to the work queue. Returns `true` if the call requires [callee] to - /// be closurized. + /// be closurized. If [virtualCall] is `true` inputs are added and removed + /// from the virtual types for [callee]. bool updateParameterInputs(TypeInformation callSiteType, MemberEntity callee, ArgumentsTypes? arguments, Selector? selector, - {required bool remove, required bool addToQueue}) { + {required bool remove, + required bool addToQueue, + bool virtualCall = false}) { if (callee.name == Identifiers.noSuchMethod_) return false; if (callee is FieldEntity) { if (selector!.isSetter) { - ElementTypeInformation info = types.getInferredTypeOfMember(callee); + ElementTypeInformation info = virtualCall + ? types.getInferredTypeOfVirtualMember(callee) + : types.getInferredTypeOfMember(callee); if (remove) { info.removeInput(arguments!.positional[0]); } else { @@ -781,7 +786,9 @@ class InferrerEngine { } else if (selector != null && selector.isGetter) { // We are tearing a function off and thus create a closure. assert(callee.isFunction); - final memberInfo = types.getInferredTypeOfMember(callee); + final memberInfo = virtualCall + ? types.getInferredTypeOfVirtualMember(callee) + : types.getInferredTypeOfMember(callee); _markForClosurization(memberInfo, callSiteType, remove: remove, addToQueue: addToQueue); return true; @@ -800,7 +807,9 @@ class InferrerEngine { type = localArguments.positional[parameterIndex]; } if (type == null) type = getDefaultTypeOfParameter(parameter); - TypeInformation info = types.getInferredTypeOfParameter(parameter); + TypeInformation info = virtualCall + ? types.getInferredTypeOfVirtualParameter(parameter) + : types.getInferredTypeOfParameter(parameter); if (remove) { info.removeInput(type); } else { @@ -813,6 +822,29 @@ class InferrerEngine { return false; } + void _setupVirtualCall( + MemberTypeInformation virtualCallType, MemberEntity member) { + if (member is FieldEntity || member.isGetter) { + final realMember = types.getInferredTypeOfMember(member); + virtualCallType.addInput(realMember); + if (member.isAssignable) { + realMember.addInput(virtualCallType); + } + } else { + assert(member.isSetter || member.isFunction); + types.strategy.forEachParameter(member as FunctionEntity, + (Local parameter) { + final virtualParamInfo = + types.getInferredTypeOfVirtualParameter(parameter); + final realParamInfo = types.getInferredTypeOfParameter(parameter); + realParamInfo.addInput(virtualParamInfo); + }); + if (member.isFunction) { + virtualCallType.addInput(types.getInferredTypeOfMember(member)); + } + } + } + void _joinOverrideParameters(MemberEntity parent, MemberEntity override) { final method = parent as FunctionEntity; ParameterStructure parameterStructure = method.parameterStructure; @@ -821,7 +853,7 @@ class InferrerEngine { final List positional = []; final Map named = {}; types.strategy.forEachParameter(parent, (Local parameter) { - TypeInformation type = types.getInferredTypeOfParameter(parameter); + TypeInformation type = types.getInferredTypeOfVirtualParameter(parameter); if (parameterIndex < parameterStructure.requiredPositionalParameters) { positional.add(type); } else if (parameterStructure.namedParameters.isNotEmpty) { @@ -850,12 +882,20 @@ class InferrerEngine { // default value will be used within the body of the override. parentParamInfo ??= getDefaultTypeOfParameter(parameter); TypeInformation overrideParamInfo = - types.getInferredTypeOfParameter(parameter); + types.getInferredTypeOfVirtualParameter(parameter); overrideParamInfo.addInput(parentParamInfo); parameterIndex++; }); } + MemberTypeInformation _getAndSetupVirtualMember(MemberEntity member) { + final memberType = types.getCachedOrInferredTypeOfVirtualMember(member); + if (memberType.b && !member.isAbstract) { + _setupVirtualCall(memberType.a, member); + } + return memberType.a; + } + /// Adds edges between [parent] and [override] based on the type of member /// each is. /// @@ -870,15 +910,21 @@ class InferrerEngine { /// - method/method void _joinOverriddenMember(MemberEntity parent, MemberEntity override) { if (parent.name == Identifiers.noSuchMethod_) return; - final parentType = types.getInferredTypeOfMember(parent); - final overrideType = types.getInferredTypeOfMember(override); + final parentType = _getAndSetupVirtualMember(parent); + final overrideType = _getAndSetupVirtualMember(override); + + _joinOverriddenMemberTypes(parent, override, parentType, overrideType); + } + + void _joinOverriddenMemberTypes(MemberEntity parent, MemberEntity override, + MemberTypeInformation parentType, MemberTypeInformation overrideType) { if (parent is FieldEntity) { if (override.isGetter) { parentType.addInput(overrideType); } else if (override.isSetter) { types.strategy.forEachParameter(override as FunctionEntity, (Local parameter) { - final paramInfo = types.getInferredTypeOfParameter(parameter); + final paramInfo = types.getInferredTypeOfVirtualParameter(parameter); paramInfo.addInput(parentType); }); } else { @@ -937,12 +983,13 @@ class InferrerEngine { } void _registerOverridesCalled( - MemberEntity callee, + DynamicCallTarget target, DynamicCallSiteTypeInformation callSiteType, ir.Node? callSite, - Set visited, - {required bool isClosurized}) { - memberHierarchyBuilder.forEachOverride(callee, (override) { + Set visited) { + final member = target.member; + final isClosurized = callSiteType.closurizedTargets.contains(member); + void handleTarget(MemberEntity override) { if (override.isAbstract || !visited.add(override)) return; MemberTypeInformation info = types.getInferredTypeOfMember(override); info.addCall(callSiteType.caller, callSite); @@ -951,17 +998,20 @@ class InferrerEngine { _markForClosurization(info, callSiteType, remove: false, addToQueue: false); } - }); + } + + handleTarget(member); + if (target.isVirtual) { + memberHierarchyBuilder.forEachOverride(member, handleTarget); + } } void _updateOverrideClosurizations() { final Set visited = {}; for (final call in types.allocatedCalls) { if (call is! DynamicCallSiteTypeInformation) continue; - for (final target in call.callees) { - if (!visited.add(target)) continue; - _registerOverridesCalled(target, call, null, visited, - isClosurized: call.closurizedTargets.contains(target)); + for (final target in call.concreteTargets) { + _registerOverridesCalled(target, call, null, visited); } } } @@ -979,6 +1029,9 @@ class InferrerEngine { info.inputs.replace(existing, type); // Also forward all users. type.addUsersOf(existing); + TypeInformation virtualInfo = + types.getInferredTypeOfVirtualParameter(parameter); + virtualInfo.inputs.replace(existing, type); } else { assert(existing == null); } @@ -1003,16 +1056,28 @@ class InferrerEngine { return types.getInferredTypeOfParameter(element); } + MemberTypeInformation _inferredTypeOfMember(MemberEntity element, + {required bool isVirtual}) { + return isVirtual + ? types.getInferredTypeOfVirtualMember(element) + : types.getInferredTypeOfMember(element); + } + + MemberTypeInformation inferredTypeOfTarget(DynamicCallTarget target) { + return _inferredTypeOfMember(target.member, isVirtual: target.isVirtual); + } + /// Returns the type of [element]. - TypeInformation typeOfMember(MemberEntity element) { + TypeInformation typeOfMember(MemberEntity element, {bool isVirtual = false}) { if (element is FunctionEntity) return types.functionType; - return types.getInferredTypeOfMember(element); + return _inferredTypeOfMember(element, isVirtual: isVirtual); } /// Returns the return type of [element]. - TypeInformation returnTypeOfMember(MemberEntity element) { + TypeInformation returnTypeOfMember(MemberEntity element, + {bool isVirtual = false}) { if (element is! FunctionEntity) return types.dynamicType; - return types.getInferredTypeOfMember(element); + return _inferredTypeOfMember(element, isVirtual: isVirtual); } /// Records that [element] is of type [type]. @@ -1236,7 +1301,8 @@ class InferrerEngine { /// Returns the type of [element] when being called with [selector]. TypeInformation typeOfMemberWithSelector( - MemberEntity element, Selector? selector) { + MemberEntity element, Selector? selector, + {required bool isVirtual}) { if (element.name == Identifiers.noSuchMethod_ && selector!.name != element.name) { // An invocation can resolve to a [noSuchMethod], in which case @@ -1246,9 +1312,9 @@ class InferrerEngine { if (element.isFunction) { return types.functionType; } else if (element is FieldEntity) { - return typeOfMember(element); + return typeOfMember(element, isVirtual: isVirtual); } else if (element.isGetter) { - return returnTypeOfMember(element); + return returnTypeOfMember(element, isVirtual: isVirtual); } else { assert(false, failedAt(element, "Unexpected member $element")); return types.dynamicType; @@ -1257,7 +1323,7 @@ class InferrerEngine { assert(selector.isCall || selector.isSetter); return types.dynamicType; } else { - return returnTypeOfMember(element); + return returnTypeOfMember(element, isVirtual: isVirtual); } } diff --git a/pkg/compiler/lib/src/inferrer_experimental/list_tracer.dart b/pkg/compiler/lib/src/inferrer_experimental/list_tracer.dart index 6124abfb85f..efcb7d31f04 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/list_tracer.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/list_tracer.dart @@ -213,7 +213,7 @@ class ListTracerVisitor extends TracerVisitor { } } else if (selector.isCall && (info.hasClosureCallTargets || - info.concreteTargets.any((element) => !element.isFunction))) { + info.callees.any((element) => !element.isFunction))) { bailout('Passed to a closure'); return; } diff --git a/pkg/compiler/lib/src/inferrer_experimental/map_tracer.dart b/pkg/compiler/lib/src/inferrer_experimental/map_tracer.dart index fe3bf6bb324..85c8ecc4e62 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/map_tracer.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/map_tracer.dart @@ -126,7 +126,7 @@ class MapTracerVisitor extends TracerVisitor { } } else if (selector.isCall && (info.hasClosureCallTargets || - info.concreteTargets.any((element) => !element.isFunction))) { + info.callees.any((element) => !element.isFunction))) { bailout('Passed to a closure'); return; } diff --git a/pkg/compiler/lib/src/inferrer_experimental/node_tracer.dart b/pkg/compiler/lib/src/inferrer_experimental/node_tracer.dart index 15554576a7d..3767b2868c7 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/node_tracer.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/node_tracer.dart @@ -475,7 +475,7 @@ abstract class TracerVisitor implements TypeInformationVisitor { final user = currentUser; if (user is MemberTypeInformation) { - if (info.concreteTargets.contains(user.member)) { + if (info.callees.contains(user.member)) { addNewEscapeInformation(info); } } diff --git a/pkg/compiler/lib/src/inferrer_experimental/powersets/powersets.dart b/pkg/compiler/lib/src/inferrer_experimental/powersets/powersets.dart index 0f06384b2ae..dd73c52bc42 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/powersets/powersets.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/powersets/powersets.dart @@ -809,8 +809,10 @@ class PowersetDomain with AbstractValueDomain { _abstractValueDomain.typeType, _powersetBitsDomain.typeType); @override - Iterable findRootsOfTargets(covariant PowersetValue receiver, - Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => + Iterable findRootsOfTargets( + covariant PowersetValue receiver, + Selector selector, + MemberHierarchyBuilder memberHierarchyBuilder) => _abstractValueDomain.findRootsOfTargets( receiver.abstractValue, selector, memberHierarchyBuilder); } diff --git a/pkg/compiler/lib/src/inferrer_experimental/set_tracer.dart b/pkg/compiler/lib/src/inferrer_experimental/set_tracer.dart index 817a3467a3a..d2d40e07a8f 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/set_tracer.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/set_tracer.dart @@ -123,7 +123,7 @@ class SetTracerVisitor extends TracerVisitor { } } else if (selector.isCall && (info.hasClosureCallTargets || - info.concreteTargets.any((element) => !element.isFunction))) { + info.callees.any((element) => !element.isFunction))) { bailout('Passed to a closure'); return; } diff --git a/pkg/compiler/lib/src/inferrer_experimental/trivial.dart b/pkg/compiler/lib/src/inferrer_experimental/trivial.dart index de0c93dcfd6..cf975f15926 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/trivial.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/trivial.dart @@ -370,7 +370,7 @@ class TrivialAbstractValueDomain with AbstractValueDomain { } @override - Iterable findRootsOfTargets( + Iterable findRootsOfTargets( covariant TrivialAbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => diff --git a/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart index d04d73e1a25..05acc1d5465 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart @@ -13,6 +13,7 @@ import '../constants/values.dart'; import '../elements/entities.dart'; import '../elements/types.dart'; import '../js_model/js_world.dart' show JClosedWorld; +import '../universe/member_hierarchy.dart'; import '../universe/selector.dart' show Selector; import '../util/util.dart' show Setlet; import '../inferrer/abstract_value_domain.dart'; @@ -1018,7 +1019,8 @@ class StaticCallSiteTypeInformation extends CallSiteTypeInformation { } TypeInformation _getCalledTypeInfoWithSelector(InferrerEngine inferrer) { - return inferrer.typeOfMemberWithSelector(calledElement, selector); + return inferrer.typeOfMemberWithSelector(calledElement, selector, + isVirtual: false); } @override @@ -1067,7 +1069,7 @@ class DynamicCallSiteTypeInformation bool? _hasClosureCallTargets; /// Cached concrete targets of this call. - Iterable? _concreteTargets; + Iterable? _concreteTargets; /// Recomputed when _concreteTargets changes. bool? _targetsIncludeComplexNoSuchMethod; @@ -1097,10 +1099,9 @@ class DynamicCallSiteTypeInformation late final Set closurizedTargets = {}; - void _handleCalledTarget(MemberEntity target, InferrerEngine inferrer, + void _handleCalledTarget(DynamicCallTarget target, InferrerEngine inferrer, {required bool addToQueue, required bool remove}) { - MemberTypeInformation targetType = - inferrer.types.getInferredTypeOfMember(target); + MemberTypeInformation targetType = inferrer.inferredTypeOfTarget(target); if (remove) { _removeCall(targetType); targetType.removeUser(this); @@ -1108,14 +1109,15 @@ class DynamicCallSiteTypeInformation _addCall(targetType); targetType.addUser(this); } + final member = target.member; final isClosurized = inferrer.updateParameterInputs( - this, target, arguments, selector, - addToQueue: addToQueue, remove: remove); + this, member, arguments, selector, + addToQueue: addToQueue, remove: remove, virtualCall: target.isVirtual); if (isClosurized) { if (remove) { - closurizedTargets.remove(target); + closurizedTargets.remove(member); } else { - closurizedTargets.add(target); + closurizedTargets.add(member); } } } @@ -1144,10 +1146,18 @@ class DynamicCallSiteTypeInformation /// All concrete targets of this invocation. If [hasClosureCallTargets] is /// `true` the invocation can additional target an unknown set of 'call' /// methods on closures. - Iterable get concreteTargets => _concreteTargets!; + Iterable get concreteTargets => _concreteTargets!; @override - Iterable get callees => concreteTargets; + Iterable get callees => + _callees ??= concreteTargets.map((e) => e.member).toSet(); + + Iterable? _callees; + + void updateTargets(Iterable targets) { + _concreteTargets = targets; + _callees = null; + } AbstractValue? computeTypedSelector(InferrerEngine inferrer) { AbstractValue receiverType = receiver.type; @@ -1161,8 +1171,7 @@ class DynamicCallSiteTypeInformation } bool targetsIncludeComplexNoSuchMethod(InferrerEngine inferrer) { - return _targetsIncludeComplexNoSuchMethod ??= - _concreteTargets!.any((MemberEntity e) { + return _targetsIncludeComplexNoSuchMethod ??= callees.any((MemberEntity e) { return e.isFunction && e.isInstanceMember && e.name == Identifiers.noSuchMethod_ && @@ -1298,14 +1307,14 @@ class DynamicCallSiteTypeInformation // Add calls to new targets to the graph. concreteTargets .where((target) => !oldTargets.contains(target)) - .forEach((MemberEntity target) { + .forEach((DynamicCallTarget target) { _handleCalledTarget(target, inferrer, addToQueue: true, remove: false); }); // Walk over the old targets, and remove calls that cannot happen anymore. oldTargets .where((target) => !concreteTargets.contains(target)) - .forEach((MemberEntity target) { + .forEach((DynamicCallTarget target) { _handleCalledTarget(target, inferrer, addToQueue: true, remove: true); }); } @@ -1317,7 +1326,8 @@ class DynamicCallSiteTypeInformation result = abstractValueDomain.dynamicType; } else { result = inferrer.types - .joinTypeMasks(concreteTargets.map((MemberEntity element) { + .joinTypeMasks(concreteTargets.map((DynamicCallTarget target) { + final element = target.member; if (typeMask != null && inferrer.returnsListElementType(localSelector, typeMask)) { return abstractValueDomain.getContainerElementType(receiver.type); @@ -1355,7 +1365,10 @@ class DynamicCallSiteTypeInformation final info = handleIntrisifiedSelector(localSelector, typeMask, inferrer); if (info != null) return info.type; - return inferrer.typeOfMemberWithSelector(element, selector).type; + return inferrer + .typeOfMemberWithSelector(element, selector, + isVirtual: target.isVirtual) + .type; } })); } @@ -1381,7 +1394,7 @@ class DynamicCallSiteTypeInformation final newConcreteTargets = _concreteTargets = inferrer.memberHierarchyBuilder.rootsForCall(mask, localSelector); _targetsIncludeComplexNoSuchMethod = null; - for (MemberEntity target in newConcreteTargets) { + for (final target in newConcreteTargets) { if (!oldTargets.contains(target)) { _handleCalledTarget(target, inferrer, addToQueue: true, remove: false); @@ -1393,7 +1406,7 @@ class DynamicCallSiteTypeInformation @override void removeAndClearReferences(InferrerEngine inferrer) { - for (MemberEntity element in concreteTargets) { + for (MemberEntity element in callees) { MemberTypeInformation callee = inferrer.types.getInferredTypeOfMember(element); callee.removeUser(this); @@ -1415,7 +1428,7 @@ class DynamicCallSiteTypeInformation @override bool hasStableType(InferrerEngine inferrer) { return receiver.isStable && - concreteTargets.every((MemberEntity element) => + callees.every((MemberEntity element) => inferrer.types.getInferredTypeOfMember(element).isStable) && (arguments == null || arguments!.every((info) => info.isStable)) && super.hasStableType(inferrer); diff --git a/pkg/compiler/lib/src/inferrer_experimental/type_system.dart b/pkg/compiler/lib/src/inferrer_experimental/type_system.dart index 68d1039352c..80575d92736 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/type_system.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/type_system.dart @@ -9,6 +9,7 @@ import '../elements/entities.dart'; import '../elements/types.dart'; import '../inferrer/abstract_value_domain.dart'; import '../js_model/js_world.dart'; +import '../util/util.dart'; import 'type_graph_nodes.dart'; /// Strategy for creating type information from members and parameters and type @@ -63,6 +64,11 @@ class TypeSystem { final Map memberTypeInformations = Map(); + final Map virtualParameterTypeInformations = + Map(); + final Map virtualCallTypeInformations = + Map(); + /// [ListTypeInformation] for allocated lists. final Map allocatedLists = Map(); @@ -325,6 +331,16 @@ class TypeSystem { }); } + ParameterTypeInformation getInferredTypeOfVirtualParameter(Local parameter) { + return virtualParameterTypeInformations.putIfAbsent(parameter, () { + ParameterTypeInformation typeInformation = + strategy.createParameterTypeInformation( + _abstractValueDomain, parameter, this); + _orderedTypeInformations.add(typeInformation); + return typeInformation; + }); + } + void forEachParameterType( void f(Local parameter, ParameterTypeInformation typeInformation)) { parameterTypeInformations.forEach(f); @@ -334,6 +350,19 @@ class TypeSystem { return memberTypeInformations[member] ??= _getInferredTypeOfMember(member); } + Pair getCachedOrInferredTypeOfVirtualMember( + MemberEntity member) { + final cached = virtualCallTypeInformations[member]; + if (cached != null) return Pair(cached, false); + final newType = virtualCallTypeInformations[member] = + strategy.createMemberTypeInformation(_abstractValueDomain, member); + return Pair(newType, true); + } + + MemberTypeInformation getInferredTypeOfVirtualMember(MemberEntity member) { + return getCachedOrInferredTypeOfVirtualMember(member).a; + } + void forEachMemberType( void f(MemberEntity member, MemberTypeInformation typeInformation)) { memberTypeInformations.forEach(f); diff --git a/pkg/compiler/lib/src/inferrer_experimental/typemasks/flat_type_mask.dart b/pkg/compiler/lib/src/inferrer_experimental/typemasks/flat_type_mask.dart index 7804f55362a..60254bcbbd7 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/typemasks/flat_type_mask.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/typemasks/flat_type_mask.dart @@ -703,7 +703,7 @@ class FlatTypeMask extends TypeMask { } @override - Iterable findRootsOfTargets(Selector selector, + Iterable findRootsOfTargets(Selector selector, MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) { if (isEmptyOrFlagged) return const []; final baseCls = base!; @@ -718,15 +718,16 @@ class FlatTypeMask extends TypeMask { if (isExact) { // Return the member that declares the selector on the mask's class // (check only superclasses). - final match = - memberHierarchyBuilder.findMatchInSuperclass(baseCls, selector); + final match = memberHierarchyBuilder + .findSuperclassTarget(baseCls, selector, virtualResult: false); return match != null ? [match] : const []; } if (isSubclass) { // Try to find a superclass that contains a matching member. - final superclassMatch = - memberHierarchyBuilder.findMatchInSuperclass(baseCls, selector); + final superclassMatch = memberHierarchyBuilder + .findSuperclassTarget(baseCls, selector, virtualResult: true); + if (superclassMatch != null) return [superclassMatch]; } diff --git a/pkg/compiler/lib/src/inferrer_experimental/typemasks/forwarding_type_mask.dart b/pkg/compiler/lib/src/inferrer_experimental/typemasks/forwarding_type_mask.dart index 6809ce46f4d..a409e0fe6b9 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/typemasks/forwarding_type_mask.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/typemasks/forwarding_type_mask.dart @@ -136,7 +136,7 @@ abstract class ForwardingTypeMask extends TypeMask { } @override - Iterable findRootsOfTargets(Selector selector, + Iterable findRootsOfTargets(Selector selector, MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) { return forwardTo.findRootsOfTargets( selector, memberHierarchyBuilder, closedWorld); diff --git a/pkg/compiler/lib/src/inferrer_experimental/typemasks/masks.dart b/pkg/compiler/lib/src/inferrer_experimental/typemasks/masks.dart index 8b39e71d149..ac79c09dbcf 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/typemasks/masks.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/typemasks/masks.dart @@ -926,7 +926,7 @@ class CommonMasks with AbstractValueDomain { } @override - Iterable findRootsOfTargets(covariant TypeMask receiver, + Iterable findRootsOfTargets(covariant TypeMask receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) { return receiver.findRootsOfTargets( selector, memberHierarchyBuilder, _closedWorld); diff --git a/pkg/compiler/lib/src/inferrer_experimental/typemasks/type_mask.dart b/pkg/compiler/lib/src/inferrer_experimental/typemasks/type_mask.dart index 4645097c2d8..71a5173a7a7 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/typemasks/type_mask.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/typemasks/type_mask.dart @@ -427,6 +427,6 @@ abstract class TypeMask implements AbstractValue { /// Returns a set of members that are ancestors of all possible targets for /// a call targeting [selector] on a receiver with the type represented by /// this mask. - Iterable findRootsOfTargets(Selector selector, + Iterable findRootsOfTargets(Selector selector, MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld); } diff --git a/pkg/compiler/lib/src/inferrer_experimental/typemasks/union_type_mask.dart b/pkg/compiler/lib/src/inferrer_experimental/typemasks/union_type_mask.dart index c00a9b6cb17..52a11217bcc 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/typemasks/union_type_mask.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/typemasks/union_type_mask.dart @@ -450,11 +450,11 @@ class UnionTypeMask extends TypeMask { } @override - Iterable findRootsOfTargets(Selector selector, + Iterable findRootsOfTargets(Selector selector, MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) { // Find the ancestors for each disjoint mask separately and combine the // results. - final Set results = {}; + final Set results = {}; for (final submask in disjointMasks) { results.addAll(submask.findRootsOfTargets( selector, memberHierarchyBuilder, closedWorld)); diff --git a/pkg/compiler/lib/src/inferrer_experimental/wrapped.dart b/pkg/compiler/lib/src/inferrer_experimental/wrapped.dart index 9677db299be..4cfca4c4650 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/wrapped.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/wrapped.dart @@ -602,7 +602,7 @@ class WrappedAbstractValueDomain with AbstractValueDomain { WrappedAbstractValue(_abstractValueDomain.typeType); @override - Iterable findRootsOfTargets( + Iterable findRootsOfTargets( covariant WrappedAbstractValue receiver, Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) => diff --git a/pkg/compiler/lib/src/universe/member_hierarchy.dart b/pkg/compiler/lib/src/universe/member_hierarchy.dart index b0ba6212422..71225f74b11 100644 --- a/pkg/compiler/lib/src/universe/member_hierarchy.dart +++ b/pkg/compiler/lib/src/universe/member_hierarchy.dart @@ -30,9 +30,34 @@ class _QueuedMember { _QueuedMember(this.member, this.cls, {required this.isDeclaration}); } +class DynamicCallTarget { + final MemberEntity member; + final bool isVirtual; + + factory DynamicCallTarget.virtual(MemberEntity member) => + DynamicCallTarget(member, isVirtual: true); + factory DynamicCallTarget.concrete(MemberEntity member) => + DynamicCallTarget(member, isVirtual: false); + DynamicCallTarget(this.member, {required this.isVirtual}); + + @override + int get hashCode => Object.hash(member, isVirtual); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DynamicCallTarget && + member == other.member && + isVirtual == other.isVirtual); + } + + @override + String toString() => 'TargetResult($member, virtual: $isVirtual)'; +} + class MemberHierarchyBuilder { final JClosedWorld closedWorld; - final Map> _callCache = {}; + final Map> _callCache = {}; final Map> _selectorRoots = {}; final Map> _overrides = {}; @@ -56,7 +81,18 @@ class MemberHierarchyBuilder { _forEachOverride(entity, f, {}); } - MemberEntity? findMatchInSuperclass(ClassEntity cls, Selector selector) { + /// Finds the first non-strict superclass of [cls] that contains a member + /// matching [selector] and returns that member. + /// + /// Returns the first non-abstract match found while ascending the class + /// hierarchy. If no non-abstract matches are found then the first abstract + /// match is used. + /// + /// If [virtualResult] is true, the resulting [DynamicCallTarget] will be + /// virtual when the match is non-abstract and has overrides. + /// Otherwise the resulting [DynamicCallTarget] is concrete. + DynamicCallTarget? findSuperclassTarget(ClassEntity cls, Selector selector, + {required bool virtualResult}) { MemberEntity? firstAbstractMatch; ClassEntity? current = cls; final elementEnv = closedWorld.elementEnvironment; @@ -67,22 +103,25 @@ class MemberHierarchyBuilder { if (match.isAbstract) { firstAbstractMatch ??= match; } else { - return match; + return DynamicCallTarget(match, + isVirtual: virtualResult && _hasOverride(match)); } } current = elementEnv.getSuperClass(current); } - return firstAbstractMatch; + return firstAbstractMatch != null + ? DynamicCallTarget.virtual(firstAbstractMatch) + : null; } /// For each subclass/subtype try to find a member matching selector. /// /// If we find a match then it covers the entire subtree below that match so /// we do not need to check subclasses/subtypes below it. - Iterable findMatchingAncestors( + Iterable findMatchingAncestors( ClassEntity baseCls, Selector selector, {required bool isSubtype}) { - final Set results = {}; + final Set results = {}; final Queue toVisit = Queue(); final Set visited = {}; void addSubclasses(ClassEntity cls) { @@ -113,7 +152,8 @@ class MemberHierarchyBuilder { } while (toVisit.isNotEmpty) { final current = toVisit.removeFirst(); - final match = findMatchInSuperclass(current, selector); + final match = + findSuperclassTarget(current, selector, virtualResult: true); if (match != null) { results.add(match); } else { @@ -134,7 +174,8 @@ class MemberHierarchyBuilder { /// consider each mixin application to declare a "copy" of each member of that /// mixin. They share the same [MemberEntity] but each copy is considered a /// separate potential root. - Iterable rootsForSelector(ClassEntity cls, Selector selector) { + Iterable rootsForSelector( + ClassEntity cls, Selector selector) { final roots = _selectorRoots[_normalizeSelector(selector)]; if (roots == null) return const []; final classHierarchy = closedWorld.classHierarchy; @@ -142,6 +183,7 @@ class MemberHierarchyBuilder { .where((r) => selector.appliesStructural(r) && classHierarchy.isSubclassOf(r.enclosingClass!, cls)) + .map((r) => DynamicCallTarget(r, isVirtual: _hasOverride(r))) .toSet(); } @@ -153,7 +195,7 @@ class MemberHierarchyBuilder { /// `null` the receiver is considered dynamic and any member that matches /// [selector] is a possible target. Also adds relevant [noSuchMethod] and /// `null` targets if needed for the call. - Iterable rootsForCall( + Iterable rootsForCall( AbstractValue? receiverType, Selector selector) { final domain = closedWorld.abstractValueDomain; receiverType ??= domain.dynamicType; @@ -161,7 +203,7 @@ class MemberHierarchyBuilder { final cachedResult = _callCache[selectorMask]; if (cachedResult != null) return cachedResult; - Iterable targetsForReceiver = + Iterable targetsForReceiver = domain.findRootsOfTargets(receiverType, selector, this); // TODO(natebiggs): Can we calculate this as part of the above call to @@ -178,7 +220,10 @@ class MemberHierarchyBuilder { final nullMatch = closedWorld.elementEnvironment.lookupLocalClassMember( closedWorld.commonElements.jsNullClass, selector.memberName); if (nullMatch != null) { - targetsForReceiver = {...targetsForReceiver, nullMatch}; + targetsForReceiver = { + ...targetsForReceiver, + DynamicCallTarget.concrete(nullMatch) + }; } } @@ -205,6 +250,10 @@ class MemberHierarchyBuilder { return _skipMemberInternal(member) || !selector.appliesStructural(member); } + bool _hasOverride(MemberEntity member) { + return _overrides.containsKey(member); + } + static List _selectorsForMember(MemberEntity member) { final List result = []; if (member is FieldEntity) {