[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 <fishythefish@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs 2022-12-08 21:19:54 +00:00 committed by Commit Queue
parent b58f1b4ef3
commit 5dca4cbd42
23 changed files with 249 additions and 87 deletions

View file

@ -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<MemberEntity> findRootsOfTargets(AbstractValue receiver,
Iterable<DynamicCallTarget> findRootsOfTargets(AbstractValue receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder);
/// Deserializes an [AbstractValue] for this domain from [source].

View file

@ -590,7 +590,7 @@ class ComputableAbstractValueDomain with AbstractValueDomain {
_wrappedDomain.isFixedLengthJsIndexable(_unwrap(value));
@override
Iterable<MemberEntity> findRootsOfTargets(AbstractValue receiver,
Iterable<DynamicCallTarget> findRootsOfTargets(AbstractValue receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) {
return _wrappedDomain.findRootsOfTargets(
receiver, selector, memberHierarchyBuilder);

View file

@ -809,8 +809,10 @@ class PowersetDomain with AbstractValueDomain {
_abstractValueDomain.typeType, _powersetBitsDomain.typeType);
@override
Iterable<MemberEntity> findRootsOfTargets(covariant PowersetValue receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) =>
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant PowersetValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>
_abstractValueDomain.findRootsOfTargets(
receiver.abstractValue, selector, memberHierarchyBuilder);
}

View file

@ -370,7 +370,7 @@ class TrivialAbstractValueDomain with AbstractValueDomain {
}
@override
Iterable<MemberEntity> findRootsOfTargets(
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant TrivialAbstractValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>

View file

@ -926,7 +926,7 @@ class CommonMasks with AbstractValueDomain {
}
@override
Iterable<MemberEntity> findRootsOfTargets(covariant TypeMask receiver,
Iterable<DynamicCallTarget> findRootsOfTargets(covariant TypeMask receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) {
return const [];
}

View file

@ -602,7 +602,7 @@ class WrappedAbstractValueDomain with AbstractValueDomain {
WrappedAbstractValue(_abstractValueDomain.typeType);
@override
Iterable<MemberEntity> findRootsOfTargets(
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant WrappedAbstractValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>

View file

@ -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);
}
}

View file

@ -478,7 +478,7 @@ class InferrerEngine {
if (info.hasClosureCallTargets) {
print('<Closure.call>');
}
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<TypeInformation> positional = [];
final Map<String, TypeInformation> 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<MemberEntity> visited,
{required bool isClosurized}) {
memberHierarchyBuilder.forEachOverride(callee, (override) {
Set<MemberEntity> 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<MemberEntity> 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);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -809,8 +809,10 @@ class PowersetDomain with AbstractValueDomain {
_abstractValueDomain.typeType, _powersetBitsDomain.typeType);
@override
Iterable<MemberEntity> findRootsOfTargets(covariant PowersetValue receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) =>
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant PowersetValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>
_abstractValueDomain.findRootsOfTargets(
receiver.abstractValue, selector, memberHierarchyBuilder);
}

View file

@ -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;
}

View file

@ -370,7 +370,7 @@ class TrivialAbstractValueDomain with AbstractValueDomain {
}
@override
Iterable<MemberEntity> findRootsOfTargets(
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant TrivialAbstractValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>

View file

@ -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<T extends ir.Node>
bool? _hasClosureCallTargets;
/// Cached concrete targets of this call.
Iterable<MemberEntity>? _concreteTargets;
Iterable<DynamicCallTarget>? _concreteTargets;
/// Recomputed when _concreteTargets changes.
bool? _targetsIncludeComplexNoSuchMethod;
@ -1097,10 +1099,9 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
late final Set<MemberEntity> 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<T extends ir.Node>
_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<T extends ir.Node>
/// All concrete targets of this invocation. If [hasClosureCallTargets] is
/// `true` the invocation can additional target an unknown set of 'call'
/// methods on closures.
Iterable<MemberEntity> get concreteTargets => _concreteTargets!;
Iterable<DynamicCallTarget> get concreteTargets => _concreteTargets!;
@override
Iterable<MemberEntity> get callees => concreteTargets;
Iterable<MemberEntity> get callees =>
_callees ??= concreteTargets.map((e) => e.member).toSet();
Iterable<MemberEntity>? _callees;
void updateTargets(Iterable<DynamicCallTarget> targets) {
_concreteTargets = targets;
_callees = null;
}
AbstractValue? computeTypedSelector(InferrerEngine inferrer) {
AbstractValue receiverType = receiver.type;
@ -1161,8 +1171,7 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
}
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<T extends ir.Node>
// 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<T extends ir.Node>
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<T extends ir.Node>
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<T extends ir.Node>
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<T extends ir.Node>
@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<T extends ir.Node>
@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);

View file

@ -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<MemberEntity, MemberTypeInformation> memberTypeInformations =
Map<MemberEntity, MemberTypeInformation>();
final Map<Local, ParameterTypeInformation> virtualParameterTypeInformations =
Map<Local, ParameterTypeInformation>();
final Map<MemberEntity, MemberTypeInformation> virtualCallTypeInformations =
Map<MemberEntity, MemberTypeInformation>();
/// [ListTypeInformation] for allocated lists.
final Map<ir.TreeNode, ListTypeInformation> allocatedLists =
Map<ir.TreeNode, ListTypeInformation>();
@ -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<MemberTypeInformation, bool> 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);

View file

@ -703,7 +703,7 @@ class FlatTypeMask extends TypeMask {
}
@override
Iterable<MemberEntity> findRootsOfTargets(Selector selector,
Iterable<DynamicCallTarget> 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];
}

View file

@ -136,7 +136,7 @@ abstract class ForwardingTypeMask extends TypeMask {
}
@override
Iterable<MemberEntity> findRootsOfTargets(Selector selector,
Iterable<DynamicCallTarget> findRootsOfTargets(Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) {
return forwardTo.findRootsOfTargets(
selector, memberHierarchyBuilder, closedWorld);

View file

@ -926,7 +926,7 @@ class CommonMasks with AbstractValueDomain {
}
@override
Iterable<MemberEntity> findRootsOfTargets(covariant TypeMask receiver,
Iterable<DynamicCallTarget> findRootsOfTargets(covariant TypeMask receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) {
return receiver.findRootsOfTargets(
selector, memberHierarchyBuilder, _closedWorld);

View file

@ -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<MemberEntity> findRootsOfTargets(Selector selector,
Iterable<DynamicCallTarget> findRootsOfTargets(Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld);
}

View file

@ -450,11 +450,11 @@ class UnionTypeMask extends TypeMask {
}
@override
Iterable<MemberEntity> findRootsOfTargets(Selector selector,
Iterable<DynamicCallTarget> findRootsOfTargets(Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) {
// Find the ancestors for each disjoint mask separately and combine the
// results.
final Set<MemberEntity> results = {};
final Set<DynamicCallTarget> results = {};
for (final submask in disjointMasks) {
results.addAll(submask.findRootsOfTargets(
selector, memberHierarchyBuilder, closedWorld));

View file

@ -602,7 +602,7 @@ class WrappedAbstractValueDomain with AbstractValueDomain {
WrappedAbstractValue(_abstractValueDomain.typeType);
@override
Iterable<MemberEntity> findRootsOfTargets(
Iterable<DynamicCallTarget> findRootsOfTargets(
covariant WrappedAbstractValue receiver,
Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder) =>

View file

@ -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<SelectorMask, Iterable<MemberEntity>> _callCache = {};
final Map<SelectorMask, Iterable<DynamicCallTarget>> _callCache = {};
final Map<Selector, Set<MemberEntity>> _selectorRoots = {};
final Map<MemberEntity, Set<MemberEntity>> _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<MemberEntity> findMatchingAncestors(
Iterable<DynamicCallTarget> findMatchingAncestors(
ClassEntity baseCls, Selector selector,
{required bool isSubtype}) {
final Set<MemberEntity> results = {};
final Set<DynamicCallTarget> results = {};
final Queue<ClassEntity> toVisit = Queue();
final Set<ClassEntity> 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<MemberEntity> rootsForSelector(ClassEntity cls, Selector selector) {
Iterable<DynamicCallTarget> 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<MemberEntity> rootsForCall(
Iterable<DynamicCallTarget> 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<MemberEntity> targetsForReceiver =
Iterable<DynamicCallTarget> 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<Selector> _selectorsForMember(MemberEntity member) {
final List<Selector> result = [];
if (member is FieldEntity) {