[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 /// Returns a set of members that are ancestors of all possible targets for
/// a call targeting [selector] on an entity with type represented by /// a call targeting [selector] on an entity with type represented by
/// [receiver]. /// [receiver].
Iterable<MemberEntity> findRootsOfTargets(AbstractValue receiver, Iterable<DynamicCallTarget> findRootsOfTargets(AbstractValue receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder); Selector selector, MemberHierarchyBuilder memberHierarchyBuilder);
/// Deserializes an [AbstractValue] for this domain from [source]. /// Deserializes an [AbstractValue] for this domain from [source].

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -119,16 +119,16 @@ class ClosureTracerVisitor extends TracerVisitor {
if (selector.isCall) { if (selector.isCall) {
if (info.arguments!.contains(user)) { if (info.arguments!.contains(user)) {
if (info.hasClosureCallTargets || if (info.hasClosureCallTargets ||
info.concreteTargets.any((element) => !element.isFunction)) { info.callees.any((element) => !element.isFunction)) {
bailout('Passed to a closure'); bailout('Passed to a closure');
} }
if (info.concreteTargets.any(_checkIfFunctionApply)) { if (info.callees.any(_checkIfFunctionApply)) {
_tagAsFunctionApplyTarget("dynamic call"); _tagAsFunctionApplyTarget("dynamic call");
} }
} else { } else {
if (user is MemberTypeInformation) { if (user is MemberTypeInformation) {
final currentUserMember = user.member; final currentUserMember = user.member;
if (info.concreteTargets.contains(currentUserMember)) { if (info.callees.contains(currentUserMember)) {
_registerCallForLaterAnalysis(info); _registerCallForLaterAnalysis(info);
} }
} }

View file

@ -478,7 +478,7 @@ class InferrerEngine {
if (info.hasClosureCallTargets) { if (info.hasClosureCallTargets) {
print('<Closure.call>'); print('<Closure.call>');
} }
for (MemberEntity target in info.concreteTargets) { for (MemberEntity target in info.callees) {
if (target is FunctionEntity) { if (target is FunctionEntity) {
print('${types.getInferredSignatureOfMethod(target)} ' print('${types.getInferredSignatureOfMethod(target)} '
'for ${target}'); 'for ${target}');
@ -761,14 +761,19 @@ class InferrerEngine {
/// Update the inputs to parameters in the graph. [remove] tells whether /// Update the inputs to parameters in the graph. [remove] tells whether
/// inputs must be added or removed. If [addToQueue] is `true`, parameters are /// 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 /// 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, bool updateParameterInputs(TypeInformation callSiteType, MemberEntity callee,
ArgumentsTypes? arguments, Selector? selector, 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.name == Identifiers.noSuchMethod_) return false;
if (callee is FieldEntity) { if (callee is FieldEntity) {
if (selector!.isSetter) { if (selector!.isSetter) {
ElementTypeInformation info = types.getInferredTypeOfMember(callee); ElementTypeInformation info = virtualCall
? types.getInferredTypeOfVirtualMember(callee)
: types.getInferredTypeOfMember(callee);
if (remove) { if (remove) {
info.removeInput(arguments!.positional[0]); info.removeInput(arguments!.positional[0]);
} else { } else {
@ -781,7 +786,9 @@ class InferrerEngine {
} else if (selector != null && selector.isGetter) { } else if (selector != null && selector.isGetter) {
// We are tearing a function off and thus create a closure. // We are tearing a function off and thus create a closure.
assert(callee.isFunction); assert(callee.isFunction);
final memberInfo = types.getInferredTypeOfMember(callee); final memberInfo = virtualCall
? types.getInferredTypeOfVirtualMember(callee)
: types.getInferredTypeOfMember(callee);
_markForClosurization(memberInfo, callSiteType, _markForClosurization(memberInfo, callSiteType,
remove: remove, addToQueue: addToQueue); remove: remove, addToQueue: addToQueue);
return true; return true;
@ -800,7 +807,9 @@ class InferrerEngine {
type = localArguments.positional[parameterIndex]; type = localArguments.positional[parameterIndex];
} }
if (type == null) type = getDefaultTypeOfParameter(parameter); if (type == null) type = getDefaultTypeOfParameter(parameter);
TypeInformation info = types.getInferredTypeOfParameter(parameter); TypeInformation info = virtualCall
? types.getInferredTypeOfVirtualParameter(parameter)
: types.getInferredTypeOfParameter(parameter);
if (remove) { if (remove) {
info.removeInput(type); info.removeInput(type);
} else { } else {
@ -813,6 +822,29 @@ class InferrerEngine {
return false; 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) { void _joinOverrideParameters(MemberEntity parent, MemberEntity override) {
final method = parent as FunctionEntity; final method = parent as FunctionEntity;
ParameterStructure parameterStructure = method.parameterStructure; ParameterStructure parameterStructure = method.parameterStructure;
@ -821,7 +853,7 @@ class InferrerEngine {
final List<TypeInformation> positional = []; final List<TypeInformation> positional = [];
final Map<String, TypeInformation> named = {}; final Map<String, TypeInformation> named = {};
types.strategy.forEachParameter(parent, (Local parameter) { types.strategy.forEachParameter(parent, (Local parameter) {
TypeInformation type = types.getInferredTypeOfParameter(parameter); TypeInformation type = types.getInferredTypeOfVirtualParameter(parameter);
if (parameterIndex < parameterStructure.requiredPositionalParameters) { if (parameterIndex < parameterStructure.requiredPositionalParameters) {
positional.add(type); positional.add(type);
} else if (parameterStructure.namedParameters.isNotEmpty) { } else if (parameterStructure.namedParameters.isNotEmpty) {
@ -850,12 +882,20 @@ class InferrerEngine {
// default value will be used within the body of the override. // default value will be used within the body of the override.
parentParamInfo ??= getDefaultTypeOfParameter(parameter); parentParamInfo ??= getDefaultTypeOfParameter(parameter);
TypeInformation overrideParamInfo = TypeInformation overrideParamInfo =
types.getInferredTypeOfParameter(parameter); types.getInferredTypeOfVirtualParameter(parameter);
overrideParamInfo.addInput(parentParamInfo); overrideParamInfo.addInput(parentParamInfo);
parameterIndex++; 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 /// Adds edges between [parent] and [override] based on the type of member
/// each is. /// each is.
/// ///
@ -870,15 +910,21 @@ class InferrerEngine {
/// - method/method /// - method/method
void _joinOverriddenMember(MemberEntity parent, MemberEntity override) { void _joinOverriddenMember(MemberEntity parent, MemberEntity override) {
if (parent.name == Identifiers.noSuchMethod_) return; if (parent.name == Identifiers.noSuchMethod_) return;
final parentType = types.getInferredTypeOfMember(parent); final parentType = _getAndSetupVirtualMember(parent);
final overrideType = types.getInferredTypeOfMember(override); final overrideType = _getAndSetupVirtualMember(override);
_joinOverriddenMemberTypes(parent, override, parentType, overrideType);
}
void _joinOverriddenMemberTypes(MemberEntity parent, MemberEntity override,
MemberTypeInformation parentType, MemberTypeInformation overrideType) {
if (parent is FieldEntity) { if (parent is FieldEntity) {
if (override.isGetter) { if (override.isGetter) {
parentType.addInput(overrideType); parentType.addInput(overrideType);
} else if (override.isSetter) { } else if (override.isSetter) {
types.strategy.forEachParameter(override as FunctionEntity, types.strategy.forEachParameter(override as FunctionEntity,
(Local parameter) { (Local parameter) {
final paramInfo = types.getInferredTypeOfParameter(parameter); final paramInfo = types.getInferredTypeOfVirtualParameter(parameter);
paramInfo.addInput(parentType); paramInfo.addInput(parentType);
}); });
} else { } else {
@ -937,12 +983,13 @@ class InferrerEngine {
} }
void _registerOverridesCalled( void _registerOverridesCalled(
MemberEntity callee, DynamicCallTarget target,
DynamicCallSiteTypeInformation callSiteType, DynamicCallSiteTypeInformation callSiteType,
ir.Node? callSite, ir.Node? callSite,
Set<MemberEntity> visited, Set<MemberEntity> visited) {
{required bool isClosurized}) { final member = target.member;
memberHierarchyBuilder.forEachOverride(callee, (override) { final isClosurized = callSiteType.closurizedTargets.contains(member);
void handleTarget(MemberEntity override) {
if (override.isAbstract || !visited.add(override)) return; if (override.isAbstract || !visited.add(override)) return;
MemberTypeInformation info = types.getInferredTypeOfMember(override); MemberTypeInformation info = types.getInferredTypeOfMember(override);
info.addCall(callSiteType.caller, callSite); info.addCall(callSiteType.caller, callSite);
@ -951,17 +998,20 @@ class InferrerEngine {
_markForClosurization(info, callSiteType, _markForClosurization(info, callSiteType,
remove: false, addToQueue: false); remove: false, addToQueue: false);
} }
}); }
handleTarget(member);
if (target.isVirtual) {
memberHierarchyBuilder.forEachOverride(member, handleTarget);
}
} }
void _updateOverrideClosurizations() { void _updateOverrideClosurizations() {
final Set<MemberEntity> visited = {}; final Set<MemberEntity> visited = {};
for (final call in types.allocatedCalls) { for (final call in types.allocatedCalls) {
if (call is! DynamicCallSiteTypeInformation) continue; if (call is! DynamicCallSiteTypeInformation) continue;
for (final target in call.callees) { for (final target in call.concreteTargets) {
if (!visited.add(target)) continue; _registerOverridesCalled(target, call, null, visited);
_registerOverridesCalled(target, call, null, visited,
isClosurized: call.closurizedTargets.contains(target));
} }
} }
} }
@ -979,6 +1029,9 @@ class InferrerEngine {
info.inputs.replace(existing, type); info.inputs.replace(existing, type);
// Also forward all users. // Also forward all users.
type.addUsersOf(existing); type.addUsersOf(existing);
TypeInformation virtualInfo =
types.getInferredTypeOfVirtualParameter(parameter);
virtualInfo.inputs.replace(existing, type);
} else { } else {
assert(existing == null); assert(existing == null);
} }
@ -1003,16 +1056,28 @@ class InferrerEngine {
return types.getInferredTypeOfParameter(element); 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]. /// Returns the type of [element].
TypeInformation typeOfMember(MemberEntity element) { TypeInformation typeOfMember(MemberEntity element, {bool isVirtual = false}) {
if (element is FunctionEntity) return types.functionType; if (element is FunctionEntity) return types.functionType;
return types.getInferredTypeOfMember(element); return _inferredTypeOfMember(element, isVirtual: isVirtual);
} }
/// Returns the return type of [element]. /// Returns the return type of [element].
TypeInformation returnTypeOfMember(MemberEntity element) { TypeInformation returnTypeOfMember(MemberEntity element,
{bool isVirtual = false}) {
if (element is! FunctionEntity) return types.dynamicType; if (element is! FunctionEntity) return types.dynamicType;
return types.getInferredTypeOfMember(element); return _inferredTypeOfMember(element, isVirtual: isVirtual);
} }
/// Records that [element] is of type [type]. /// Records that [element] is of type [type].
@ -1236,7 +1301,8 @@ class InferrerEngine {
/// Returns the type of [element] when being called with [selector]. /// Returns the type of [element] when being called with [selector].
TypeInformation typeOfMemberWithSelector( TypeInformation typeOfMemberWithSelector(
MemberEntity element, Selector? selector) { MemberEntity element, Selector? selector,
{required bool isVirtual}) {
if (element.name == Identifiers.noSuchMethod_ && if (element.name == Identifiers.noSuchMethod_ &&
selector!.name != element.name) { selector!.name != element.name) {
// An invocation can resolve to a [noSuchMethod], in which case // An invocation can resolve to a [noSuchMethod], in which case
@ -1246,9 +1312,9 @@ class InferrerEngine {
if (element.isFunction) { if (element.isFunction) {
return types.functionType; return types.functionType;
} else if (element is FieldEntity) { } else if (element is FieldEntity) {
return typeOfMember(element); return typeOfMember(element, isVirtual: isVirtual);
} else if (element.isGetter) { } else if (element.isGetter) {
return returnTypeOfMember(element); return returnTypeOfMember(element, isVirtual: isVirtual);
} else { } else {
assert(false, failedAt(element, "Unexpected member $element")); assert(false, failedAt(element, "Unexpected member $element"));
return types.dynamicType; return types.dynamicType;
@ -1257,7 +1323,7 @@ class InferrerEngine {
assert(selector.isCall || selector.isSetter); assert(selector.isCall || selector.isSetter);
return types.dynamicType; return types.dynamicType;
} else { } else {
return returnTypeOfMember(element); return returnTypeOfMember(element, isVirtual: isVirtual);
} }
} }

View file

@ -213,7 +213,7 @@ class ListTracerVisitor extends TracerVisitor {
} }
} else if (selector.isCall && } else if (selector.isCall &&
(info.hasClosureCallTargets || (info.hasClosureCallTargets ||
info.concreteTargets.any((element) => !element.isFunction))) { info.callees.any((element) => !element.isFunction))) {
bailout('Passed to a closure'); bailout('Passed to a closure');
return; return;
} }

View file

@ -126,7 +126,7 @@ class MapTracerVisitor extends TracerVisitor {
} }
} else if (selector.isCall && } else if (selector.isCall &&
(info.hasClosureCallTargets || (info.hasClosureCallTargets ||
info.concreteTargets.any((element) => !element.isFunction))) { info.callees.any((element) => !element.isFunction))) {
bailout('Passed to a closure'); bailout('Passed to a closure');
return; return;
} }

View file

@ -475,7 +475,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
final user = currentUser; final user = currentUser;
if (user is MemberTypeInformation) { if (user is MemberTypeInformation) {
if (info.concreteTargets.contains(user.member)) { if (info.callees.contains(user.member)) {
addNewEscapeInformation(info); addNewEscapeInformation(info);
} }
} }

View file

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

View file

@ -123,7 +123,7 @@ class SetTracerVisitor extends TracerVisitor {
} }
} else if (selector.isCall && } else if (selector.isCall &&
(info.hasClosureCallTargets || (info.hasClosureCallTargets ||
info.concreteTargets.any((element) => !element.isFunction))) { info.callees.any((element) => !element.isFunction))) {
bailout('Passed to a closure'); bailout('Passed to a closure');
return; return;
} }

View file

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

View file

@ -13,6 +13,7 @@ import '../constants/values.dart';
import '../elements/entities.dart'; import '../elements/entities.dart';
import '../elements/types.dart'; import '../elements/types.dart';
import '../js_model/js_world.dart' show JClosedWorld; import '../js_model/js_world.dart' show JClosedWorld;
import '../universe/member_hierarchy.dart';
import '../universe/selector.dart' show Selector; import '../universe/selector.dart' show Selector;
import '../util/util.dart' show Setlet; import '../util/util.dart' show Setlet;
import '../inferrer/abstract_value_domain.dart'; import '../inferrer/abstract_value_domain.dart';
@ -1018,7 +1019,8 @@ class StaticCallSiteTypeInformation extends CallSiteTypeInformation {
} }
TypeInformation _getCalledTypeInfoWithSelector(InferrerEngine inferrer) { TypeInformation _getCalledTypeInfoWithSelector(InferrerEngine inferrer) {
return inferrer.typeOfMemberWithSelector(calledElement, selector); return inferrer.typeOfMemberWithSelector(calledElement, selector,
isVirtual: false);
} }
@override @override
@ -1067,7 +1069,7 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
bool? _hasClosureCallTargets; bool? _hasClosureCallTargets;
/// Cached concrete targets of this call. /// Cached concrete targets of this call.
Iterable<MemberEntity>? _concreteTargets; Iterable<DynamicCallTarget>? _concreteTargets;
/// Recomputed when _concreteTargets changes. /// Recomputed when _concreteTargets changes.
bool? _targetsIncludeComplexNoSuchMethod; bool? _targetsIncludeComplexNoSuchMethod;
@ -1097,10 +1099,9 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
late final Set<MemberEntity> closurizedTargets = {}; late final Set<MemberEntity> closurizedTargets = {};
void _handleCalledTarget(MemberEntity target, InferrerEngine inferrer, void _handleCalledTarget(DynamicCallTarget target, InferrerEngine inferrer,
{required bool addToQueue, required bool remove}) { {required bool addToQueue, required bool remove}) {
MemberTypeInformation targetType = MemberTypeInformation targetType = inferrer.inferredTypeOfTarget(target);
inferrer.types.getInferredTypeOfMember(target);
if (remove) { if (remove) {
_removeCall(targetType); _removeCall(targetType);
targetType.removeUser(this); targetType.removeUser(this);
@ -1108,14 +1109,15 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
_addCall(targetType); _addCall(targetType);
targetType.addUser(this); targetType.addUser(this);
} }
final member = target.member;
final isClosurized = inferrer.updateParameterInputs( final isClosurized = inferrer.updateParameterInputs(
this, target, arguments, selector, this, member, arguments, selector,
addToQueue: addToQueue, remove: remove); addToQueue: addToQueue, remove: remove, virtualCall: target.isVirtual);
if (isClosurized) { if (isClosurized) {
if (remove) { if (remove) {
closurizedTargets.remove(target); closurizedTargets.remove(member);
} else { } 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 /// All concrete targets of this invocation. If [hasClosureCallTargets] is
/// `true` the invocation can additional target an unknown set of 'call' /// `true` the invocation can additional target an unknown set of 'call'
/// methods on closures. /// methods on closures.
Iterable<MemberEntity> get concreteTargets => _concreteTargets!; Iterable<DynamicCallTarget> get concreteTargets => _concreteTargets!;
@override @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? computeTypedSelector(InferrerEngine inferrer) {
AbstractValue receiverType = receiver.type; AbstractValue receiverType = receiver.type;
@ -1161,8 +1171,7 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
} }
bool targetsIncludeComplexNoSuchMethod(InferrerEngine inferrer) { bool targetsIncludeComplexNoSuchMethod(InferrerEngine inferrer) {
return _targetsIncludeComplexNoSuchMethod ??= return _targetsIncludeComplexNoSuchMethod ??= callees.any((MemberEntity e) {
_concreteTargets!.any((MemberEntity e) {
return e.isFunction && return e.isFunction &&
e.isInstanceMember && e.isInstanceMember &&
e.name == Identifiers.noSuchMethod_ && e.name == Identifiers.noSuchMethod_ &&
@ -1298,14 +1307,14 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
// Add calls to new targets to the graph. // Add calls to new targets to the graph.
concreteTargets concreteTargets
.where((target) => !oldTargets.contains(target)) .where((target) => !oldTargets.contains(target))
.forEach((MemberEntity target) { .forEach((DynamicCallTarget target) {
_handleCalledTarget(target, inferrer, addToQueue: true, remove: false); _handleCalledTarget(target, inferrer, addToQueue: true, remove: false);
}); });
// Walk over the old targets, and remove calls that cannot happen anymore. // Walk over the old targets, and remove calls that cannot happen anymore.
oldTargets oldTargets
.where((target) => !concreteTargets.contains(target)) .where((target) => !concreteTargets.contains(target))
.forEach((MemberEntity target) { .forEach((DynamicCallTarget target) {
_handleCalledTarget(target, inferrer, addToQueue: true, remove: true); _handleCalledTarget(target, inferrer, addToQueue: true, remove: true);
}); });
} }
@ -1317,7 +1326,8 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
result = abstractValueDomain.dynamicType; result = abstractValueDomain.dynamicType;
} else { } else {
result = inferrer.types result = inferrer.types
.joinTypeMasks(concreteTargets.map((MemberEntity element) { .joinTypeMasks(concreteTargets.map((DynamicCallTarget target) {
final element = target.member;
if (typeMask != null && if (typeMask != null &&
inferrer.returnsListElementType(localSelector, typeMask)) { inferrer.returnsListElementType(localSelector, typeMask)) {
return abstractValueDomain.getContainerElementType(receiver.type); return abstractValueDomain.getContainerElementType(receiver.type);
@ -1355,7 +1365,10 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
final info = final info =
handleIntrisifiedSelector(localSelector, typeMask, inferrer); handleIntrisifiedSelector(localSelector, typeMask, inferrer);
if (info != null) return info.type; 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 = final newConcreteTargets = _concreteTargets =
inferrer.memberHierarchyBuilder.rootsForCall(mask, localSelector); inferrer.memberHierarchyBuilder.rootsForCall(mask, localSelector);
_targetsIncludeComplexNoSuchMethod = null; _targetsIncludeComplexNoSuchMethod = null;
for (MemberEntity target in newConcreteTargets) { for (final target in newConcreteTargets) {
if (!oldTargets.contains(target)) { if (!oldTargets.contains(target)) {
_handleCalledTarget(target, inferrer, _handleCalledTarget(target, inferrer,
addToQueue: true, remove: false); addToQueue: true, remove: false);
@ -1393,7 +1406,7 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
@override @override
void removeAndClearReferences(InferrerEngine inferrer) { void removeAndClearReferences(InferrerEngine inferrer) {
for (MemberEntity element in concreteTargets) { for (MemberEntity element in callees) {
MemberTypeInformation callee = MemberTypeInformation callee =
inferrer.types.getInferredTypeOfMember(element); inferrer.types.getInferredTypeOfMember(element);
callee.removeUser(this); callee.removeUser(this);
@ -1415,7 +1428,7 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
@override @override
bool hasStableType(InferrerEngine inferrer) { bool hasStableType(InferrerEngine inferrer) {
return receiver.isStable && return receiver.isStable &&
concreteTargets.every((MemberEntity element) => callees.every((MemberEntity element) =>
inferrer.types.getInferredTypeOfMember(element).isStable) && inferrer.types.getInferredTypeOfMember(element).isStable) &&
(arguments == null || arguments!.every((info) => info.isStable)) && (arguments == null || arguments!.every((info) => info.isStable)) &&
super.hasStableType(inferrer); super.hasStableType(inferrer);

View file

@ -9,6 +9,7 @@ import '../elements/entities.dart';
import '../elements/types.dart'; import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart'; import '../inferrer/abstract_value_domain.dart';
import '../js_model/js_world.dart'; import '../js_model/js_world.dart';
import '../util/util.dart';
import 'type_graph_nodes.dart'; import 'type_graph_nodes.dart';
/// Strategy for creating type information from members and parameters and type /// Strategy for creating type information from members and parameters and type
@ -63,6 +64,11 @@ class TypeSystem {
final Map<MemberEntity, MemberTypeInformation> memberTypeInformations = final Map<MemberEntity, MemberTypeInformation> memberTypeInformations =
Map<MemberEntity, MemberTypeInformation>(); Map<MemberEntity, MemberTypeInformation>();
final Map<Local, ParameterTypeInformation> virtualParameterTypeInformations =
Map<Local, ParameterTypeInformation>();
final Map<MemberEntity, MemberTypeInformation> virtualCallTypeInformations =
Map<MemberEntity, MemberTypeInformation>();
/// [ListTypeInformation] for allocated lists. /// [ListTypeInformation] for allocated lists.
final Map<ir.TreeNode, ListTypeInformation> allocatedLists = final Map<ir.TreeNode, ListTypeInformation> allocatedLists =
Map<ir.TreeNode, ListTypeInformation>(); 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 forEachParameterType(
void f(Local parameter, ParameterTypeInformation typeInformation)) { void f(Local parameter, ParameterTypeInformation typeInformation)) {
parameterTypeInformations.forEach(f); parameterTypeInformations.forEach(f);
@ -334,6 +350,19 @@ class TypeSystem {
return memberTypeInformations[member] ??= _getInferredTypeOfMember(member); 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 forEachMemberType(
void f(MemberEntity member, MemberTypeInformation typeInformation)) { void f(MemberEntity member, MemberTypeInformation typeInformation)) {
memberTypeInformations.forEach(f); memberTypeInformations.forEach(f);

View file

@ -703,7 +703,7 @@ class FlatTypeMask extends TypeMask {
} }
@override @override
Iterable<MemberEntity> findRootsOfTargets(Selector selector, Iterable<DynamicCallTarget> findRootsOfTargets(Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) { MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld) {
if (isEmptyOrFlagged) return const []; if (isEmptyOrFlagged) return const [];
final baseCls = base!; final baseCls = base!;
@ -718,15 +718,16 @@ class FlatTypeMask extends TypeMask {
if (isExact) { if (isExact) {
// Return the member that declares the selector on the mask's class // Return the member that declares the selector on the mask's class
// (check only superclasses). // (check only superclasses).
final match = final match = memberHierarchyBuilder
memberHierarchyBuilder.findMatchInSuperclass(baseCls, selector); .findSuperclassTarget(baseCls, selector, virtualResult: false);
return match != null ? [match] : const []; return match != null ? [match] : const [];
} }
if (isSubclass) { if (isSubclass) {
// Try to find a superclass that contains a matching member. // Try to find a superclass that contains a matching member.
final superclassMatch = final superclassMatch = memberHierarchyBuilder
memberHierarchyBuilder.findMatchInSuperclass(baseCls, selector); .findSuperclassTarget(baseCls, selector, virtualResult: true);
if (superclassMatch != null) return [superclassMatch]; if (superclassMatch != null) return [superclassMatch];
} }

View file

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

View file

@ -926,7 +926,7 @@ class CommonMasks with AbstractValueDomain {
} }
@override @override
Iterable<MemberEntity> findRootsOfTargets(covariant TypeMask receiver, Iterable<DynamicCallTarget> findRootsOfTargets(covariant TypeMask receiver,
Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) { Selector selector, MemberHierarchyBuilder memberHierarchyBuilder) {
return receiver.findRootsOfTargets( return receiver.findRootsOfTargets(
selector, memberHierarchyBuilder, _closedWorld); 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 /// 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 /// a call targeting [selector] on a receiver with the type represented by
/// this mask. /// this mask.
Iterable<MemberEntity> findRootsOfTargets(Selector selector, Iterable<DynamicCallTarget> findRootsOfTargets(Selector selector,
MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld); MemberHierarchyBuilder memberHierarchyBuilder, JClosedWorld closedWorld);
} }

View file

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

View file

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

View file

@ -30,9 +30,34 @@ class _QueuedMember {
_QueuedMember(this.member, this.cls, {required this.isDeclaration}); _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 { class MemberHierarchyBuilder {
final JClosedWorld closedWorld; final JClosedWorld closedWorld;
final Map<SelectorMask, Iterable<MemberEntity>> _callCache = {}; final Map<SelectorMask, Iterable<DynamicCallTarget>> _callCache = {};
final Map<Selector, Set<MemberEntity>> _selectorRoots = {}; final Map<Selector, Set<MemberEntity>> _selectorRoots = {};
final Map<MemberEntity, Set<MemberEntity>> _overrides = {}; final Map<MemberEntity, Set<MemberEntity>> _overrides = {};
@ -56,7 +81,18 @@ class MemberHierarchyBuilder {
_forEachOverride(entity, f, {}); _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; MemberEntity? firstAbstractMatch;
ClassEntity? current = cls; ClassEntity? current = cls;
final elementEnv = closedWorld.elementEnvironment; final elementEnv = closedWorld.elementEnvironment;
@ -67,22 +103,25 @@ class MemberHierarchyBuilder {
if (match.isAbstract) { if (match.isAbstract) {
firstAbstractMatch ??= match; firstAbstractMatch ??= match;
} else { } else {
return match; return DynamicCallTarget(match,
isVirtual: virtualResult && _hasOverride(match));
} }
} }
current = elementEnv.getSuperClass(current); current = elementEnv.getSuperClass(current);
} }
return firstAbstractMatch; return firstAbstractMatch != null
? DynamicCallTarget.virtual(firstAbstractMatch)
: null;
} }
/// For each subclass/subtype try to find a member matching selector. /// 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 /// 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. /// we do not need to check subclasses/subtypes below it.
Iterable<MemberEntity> findMatchingAncestors( Iterable<DynamicCallTarget> findMatchingAncestors(
ClassEntity baseCls, Selector selector, ClassEntity baseCls, Selector selector,
{required bool isSubtype}) { {required bool isSubtype}) {
final Set<MemberEntity> results = {}; final Set<DynamicCallTarget> results = {};
final Queue<ClassEntity> toVisit = Queue(); final Queue<ClassEntity> toVisit = Queue();
final Set<ClassEntity> visited = {}; final Set<ClassEntity> visited = {};
void addSubclasses(ClassEntity cls) { void addSubclasses(ClassEntity cls) {
@ -113,7 +152,8 @@ class MemberHierarchyBuilder {
} }
while (toVisit.isNotEmpty) { while (toVisit.isNotEmpty) {
final current = toVisit.removeFirst(); final current = toVisit.removeFirst();
final match = findMatchInSuperclass(current, selector); final match =
findSuperclassTarget(current, selector, virtualResult: true);
if (match != null) { if (match != null) {
results.add(match); results.add(match);
} else { } else {
@ -134,7 +174,8 @@ class MemberHierarchyBuilder {
/// consider each mixin application to declare a "copy" of each member of that /// 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 /// mixin. They share the same [MemberEntity] but each copy is considered a
/// separate potential root. /// separate potential root.
Iterable<MemberEntity> rootsForSelector(ClassEntity cls, Selector selector) { Iterable<DynamicCallTarget> rootsForSelector(
ClassEntity cls, Selector selector) {
final roots = _selectorRoots[_normalizeSelector(selector)]; final roots = _selectorRoots[_normalizeSelector(selector)];
if (roots == null) return const []; if (roots == null) return const [];
final classHierarchy = closedWorld.classHierarchy; final classHierarchy = closedWorld.classHierarchy;
@ -142,6 +183,7 @@ class MemberHierarchyBuilder {
.where((r) => .where((r) =>
selector.appliesStructural(r) && selector.appliesStructural(r) &&
classHierarchy.isSubclassOf(r.enclosingClass!, cls)) classHierarchy.isSubclassOf(r.enclosingClass!, cls))
.map((r) => DynamicCallTarget(r, isVirtual: _hasOverride(r)))
.toSet(); .toSet();
} }
@ -153,7 +195,7 @@ class MemberHierarchyBuilder {
/// `null` the receiver is considered dynamic and any member that matches /// `null` the receiver is considered dynamic and any member that matches
/// [selector] is a possible target. Also adds relevant [noSuchMethod] and /// [selector] is a possible target. Also adds relevant [noSuchMethod] and
/// `null` targets if needed for the call. /// `null` targets if needed for the call.
Iterable<MemberEntity> rootsForCall( Iterable<DynamicCallTarget> rootsForCall(
AbstractValue? receiverType, Selector selector) { AbstractValue? receiverType, Selector selector) {
final domain = closedWorld.abstractValueDomain; final domain = closedWorld.abstractValueDomain;
receiverType ??= domain.dynamicType; receiverType ??= domain.dynamicType;
@ -161,7 +203,7 @@ class MemberHierarchyBuilder {
final cachedResult = _callCache[selectorMask]; final cachedResult = _callCache[selectorMask];
if (cachedResult != null) return cachedResult; if (cachedResult != null) return cachedResult;
Iterable<MemberEntity> targetsForReceiver = Iterable<DynamicCallTarget> targetsForReceiver =
domain.findRootsOfTargets(receiverType, selector, this); domain.findRootsOfTargets(receiverType, selector, this);
// TODO(natebiggs): Can we calculate this as part of the above call to // TODO(natebiggs): Can we calculate this as part of the above call to
@ -178,7 +220,10 @@ class MemberHierarchyBuilder {
final nullMatch = closedWorld.elementEnvironment.lookupLocalClassMember( final nullMatch = closedWorld.elementEnvironment.lookupLocalClassMember(
closedWorld.commonElements.jsNullClass, selector.memberName); closedWorld.commonElements.jsNullClass, selector.memberName);
if (nullMatch != null) { if (nullMatch != null) {
targetsForReceiver = {...targetsForReceiver, nullMatch}; targetsForReceiver = {
...targetsForReceiver,
DynamicCallTarget.concrete(nullMatch)
};
} }
} }
@ -205,6 +250,10 @@ class MemberHierarchyBuilder {
return _skipMemberInternal(member) || !selector.appliesStructural(member); return _skipMemberInternal(member) || !selector.appliesStructural(member);
} }
bool _hasOverride(MemberEntity member) {
return _overrides.containsKey(member);
}
static List<Selector> _selectorsForMember(MemberEntity member) { static List<Selector> _selectorsForMember(MemberEntity member) {
final List<Selector> result = []; final List<Selector> result = [];
if (member is FieldEntity) { if (member is FieldEntity) {