|
|
|
@ -25,6 +25,7 @@ import '../native/behavior.dart';
|
|
|
|
|
import '../options.dart';
|
|
|
|
|
import '../serialization/serialization.dart';
|
|
|
|
|
import '../universe/call_structure.dart';
|
|
|
|
|
import '../universe/member_hierarchy.dart';
|
|
|
|
|
import '../universe/selector.dart';
|
|
|
|
|
import '../universe/side_effects.dart';
|
|
|
|
|
import '../inferrer/abstract_value_domain.dart';
|
|
|
|
@ -80,7 +81,9 @@ class InferrerEngine {
|
|
|
|
|
/// The maximum number of times we allow a node in the graph to
|
|
|
|
|
/// change types. If a node reaches that limit, we give up
|
|
|
|
|
/// inferencing on it and give it the dynamic type.
|
|
|
|
|
final int _MAX_CHANGE_COUNT = 6;
|
|
|
|
|
/// TODO(natebiggs): This value is needed right now because some types
|
|
|
|
|
/// do not converge. See https://github.com/dart-lang/sdk/issues/50626
|
|
|
|
|
final int _MAX_CHANGE_COUNT = 12;
|
|
|
|
|
|
|
|
|
|
int _overallRefineCount = 0;
|
|
|
|
|
int _addedInGraph = 0;
|
|
|
|
@ -108,6 +111,8 @@ class InferrerEngine {
|
|
|
|
|
// [ClosureWorldRefiner].
|
|
|
|
|
NoSuchMethodData get noSuchMethodData => closedWorld.noSuchMethodData;
|
|
|
|
|
|
|
|
|
|
final MemberHierarchyBuilder memberHierarchyBuilder;
|
|
|
|
|
|
|
|
|
|
InferrerEngine(
|
|
|
|
|
this._options,
|
|
|
|
|
this._progress,
|
|
|
|
@ -118,7 +123,8 @@ class InferrerEngine {
|
|
|
|
|
this.globalLocalsMap,
|
|
|
|
|
this.inferredDataBuilder)
|
|
|
|
|
: this.types = TypeSystem(closedWorld,
|
|
|
|
|
KernelTypeSystemStrategy(closedWorld, globalLocalsMap));
|
|
|
|
|
KernelTypeSystemStrategy(closedWorld, globalLocalsMap)),
|
|
|
|
|
memberHierarchyBuilder = MemberHierarchyBuilder(closedWorld);
|
|
|
|
|
|
|
|
|
|
/// Applies [f] to all elements in the universe that match [selector] and
|
|
|
|
|
/// [mask]. If [f] returns false, aborts the iteration.
|
|
|
|
@ -309,6 +315,8 @@ class InferrerEngine {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _runOverAllElements() {
|
|
|
|
|
metrics.memberHierarchy
|
|
|
|
|
.measure(() => memberHierarchyBuilder.init(_joinOverriddenMember));
|
|
|
|
|
metrics.analyze.measure(_analyzeAllElements);
|
|
|
|
|
final dump =
|
|
|
|
|
debug.PRINT_GRAPH ? TypeGraphDump(_compilerOutput, this) : null;
|
|
|
|
@ -317,6 +325,10 @@ class InferrerEngine {
|
|
|
|
|
_buildWorkQueue();
|
|
|
|
|
metrics.refine1.measure(_refine);
|
|
|
|
|
|
|
|
|
|
// Update overrides that need to be considered for closurization before
|
|
|
|
|
// tracing.
|
|
|
|
|
_updateOverrideClosurizations();
|
|
|
|
|
|
|
|
|
|
metrics.trace.measure(() {
|
|
|
|
|
// Try to infer element types of lists and compute their escape information.
|
|
|
|
|
types.allocatedLists.values.forEach((ListTypeInformation info) {
|
|
|
|
@ -397,7 +409,7 @@ class InferrerEngine {
|
|
|
|
|
// of this closure call are not a root to trace but an intermediate
|
|
|
|
|
// for some other function.
|
|
|
|
|
Iterable<FunctionEntity> elements = List<FunctionEntity>.from(
|
|
|
|
|
info.callees.where((e) => e.isFunction));
|
|
|
|
|
info.callees.where((e) => e.isFunction && !e.isAbstract));
|
|
|
|
|
trace(elements, ClosureTracerVisitor(elements, info, this));
|
|
|
|
|
}
|
|
|
|
|
} else if (info is MemberTypeInformation) {
|
|
|
|
@ -429,6 +441,10 @@ class InferrerEngine {
|
|
|
|
|
_workQueue.addAll(seenTypes);
|
|
|
|
|
metrics.refine2.measure(_refine);
|
|
|
|
|
|
|
|
|
|
// Update overrides that need to be considered for closurization after
|
|
|
|
|
// the final round of refines.
|
|
|
|
|
_updateOverrideClosurizations();
|
|
|
|
|
|
|
|
|
|
if (debug.PRINT_SUMMARY) {
|
|
|
|
|
types.allocatedLists.values.forEach((_info) {
|
|
|
|
|
ListTypeInformation info = _info;
|
|
|
|
@ -494,11 +510,11 @@ class InferrerEngine {
|
|
|
|
|
|
|
|
|
|
/// Call [analyze] for all live members.
|
|
|
|
|
void _analyzeAllElements() {
|
|
|
|
|
Iterable<MemberEntity> processedMembers = closedWorld.processedMembers
|
|
|
|
|
.where((MemberEntity member) => !member.isAbstract);
|
|
|
|
|
|
|
|
|
|
_progress.startPhase();
|
|
|
|
|
processedMembers.forEach((MemberEntity member) {
|
|
|
|
|
final toProcess = closedWorld.processedMembers
|
|
|
|
|
.followedBy(closedWorld.liveAbstractInstanceMembers)
|
|
|
|
|
.toSet();
|
|
|
|
|
toProcess.forEach((MemberEntity member) {
|
|
|
|
|
_progress.showProgress(
|
|
|
|
|
'Added ', _addedInGraph, ' elements in inferencing graph.');
|
|
|
|
|
// This also forces the creation of the [ElementTypeInformation] to ensure
|
|
|
|
@ -616,12 +632,13 @@ class InferrerEngine {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
final method = element as FunctionEntity;
|
|
|
|
|
recordReturnType(method, type!);
|
|
|
|
|
// Abstract methods don't have a body so they won't have a return type.
|
|
|
|
|
if (!method.isAbstract) recordReturnType(method, type!);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Visits [body] to compute the [TypeInformation] node for [member].
|
|
|
|
|
TypeInformation? _computeMemberTypeInformation(
|
|
|
|
|
TypeInformation _computeMemberTypeInformation(
|
|
|
|
|
MemberEntity member, ir.Node? body) {
|
|
|
|
|
KernelTypeGraphBuilder visitor = KernelTypeGraphBuilder(
|
|
|
|
|
_options,
|
|
|
|
@ -742,12 +759,13 @@ class InferrerEngine {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Update the inputs to parameters in the graph. [remove] tells whether
|
|
|
|
|
/// inputs must be added or removed. If [init] is false, parameters are
|
|
|
|
|
/// added to the work queue.
|
|
|
|
|
void updateParameterInputs(TypeInformation caller, MemberEntity callee,
|
|
|
|
|
/// 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.
|
|
|
|
|
bool updateParameterInputs(TypeInformation callSiteType, MemberEntity callee,
|
|
|
|
|
ArgumentsTypes? arguments, Selector? selector,
|
|
|
|
|
{required bool remove, required bool addToQueue}) {
|
|
|
|
|
if (callee.name == Identifiers.noSuchMethod_) return;
|
|
|
|
|
if (callee.name == Identifiers.noSuchMethod_) return false;
|
|
|
|
|
if (callee is FieldEntity) {
|
|
|
|
|
if (selector!.isSetter) {
|
|
|
|
|
ElementTypeInformation info = types.getInferredTypeOfMember(callee);
|
|
|
|
@ -759,30 +777,14 @@ class InferrerEngine {
|
|
|
|
|
if (addToQueue) _workQueue.add(info);
|
|
|
|
|
}
|
|
|
|
|
} else if (callee.isGetter) {
|
|
|
|
|
return;
|
|
|
|
|
return false;
|
|
|
|
|
} else if (selector != null && selector.isGetter) {
|
|
|
|
|
// We are tearing a function off and thus create a closure.
|
|
|
|
|
assert(callee.isFunction);
|
|
|
|
|
MemberTypeInformation info = types.getInferredTypeOfMember(callee);
|
|
|
|
|
if (remove) {
|
|
|
|
|
info.closurizedCount--;
|
|
|
|
|
} else {
|
|
|
|
|
info.closurizedCount++;
|
|
|
|
|
if (callee.isStatic || callee.isTopLevel) {
|
|
|
|
|
types.allocatedClosures.add(info);
|
|
|
|
|
} else {
|
|
|
|
|
// We add the call-site type information here so that we
|
|
|
|
|
// can benefit from further refinement of the selector.
|
|
|
|
|
types.allocatedClosures.add(caller);
|
|
|
|
|
}
|
|
|
|
|
types.strategy.forEachParameter(callee as FunctionEntity,
|
|
|
|
|
(Local parameter) {
|
|
|
|
|
ParameterTypeInformation info =
|
|
|
|
|
types.getInferredTypeOfParameter(parameter);
|
|
|
|
|
info.tagAsTearOffClosureParameter(this);
|
|
|
|
|
if (addToQueue) _workQueue.add(info);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
final memberInfo = types.getInferredTypeOfMember(callee);
|
|
|
|
|
_markForClosurization(memberInfo, callSiteType,
|
|
|
|
|
remove: remove, addToQueue: addToQueue);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
final method = callee as FunctionEntity;
|
|
|
|
|
ParameterStructure parameterStructure = method.parameterStructure;
|
|
|
|
@ -808,6 +810,160 @@ class InferrerEngine {
|
|
|
|
|
if (addToQueue) _workQueue.add(info);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _joinOverrideParameters(MemberEntity parent, MemberEntity override) {
|
|
|
|
|
final method = parent as FunctionEntity;
|
|
|
|
|
ParameterStructure parameterStructure = method.parameterStructure;
|
|
|
|
|
int parameterIndex = 0;
|
|
|
|
|
// Collect the parent parameter type infos.
|
|
|
|
|
final List<TypeInformation> positional = [];
|
|
|
|
|
final Map<String, TypeInformation> named = {};
|
|
|
|
|
types.strategy.forEachParameter(parent, (Local parameter) {
|
|
|
|
|
TypeInformation type = types.getInferredTypeOfParameter(parameter);
|
|
|
|
|
if (parameterIndex < parameterStructure.requiredPositionalParameters) {
|
|
|
|
|
positional.add(type);
|
|
|
|
|
} else if (parameterStructure.namedParameters.isNotEmpty) {
|
|
|
|
|
named[parameter.name!] = type;
|
|
|
|
|
} else if (parameterIndex < parameterStructure.positionalParameters) {
|
|
|
|
|
positional.add(type);
|
|
|
|
|
}
|
|
|
|
|
parameterIndex++;
|
|
|
|
|
});
|
|
|
|
|
parameterIndex = 0;
|
|
|
|
|
|
|
|
|
|
// Add the parent parameter type infos as inputs to the override's
|
|
|
|
|
// parameters.
|
|
|
|
|
types.strategy.forEachParameter(override as FunctionEntity,
|
|
|
|
|
(Local parameter) {
|
|
|
|
|
TypeInformation? parentParamInfo;
|
|
|
|
|
if (parameterIndex < parameterStructure.requiredPositionalParameters) {
|
|
|
|
|
parentParamInfo = positional[parameterIndex];
|
|
|
|
|
} else if (parameterStructure.namedParameters.isNotEmpty) {
|
|
|
|
|
parentParamInfo = named[parameter.name];
|
|
|
|
|
} else if (parameterIndex < positional.length) {
|
|
|
|
|
parentParamInfo = positional[parameterIndex];
|
|
|
|
|
}
|
|
|
|
|
// If the override includes parameters that the parent doesn't
|
|
|
|
|
// (optional parameters) then use the override's default type as any
|
|
|
|
|
// default value will be used within the body of the override.
|
|
|
|
|
parentParamInfo ??= getDefaultTypeOfParameter(parameter);
|
|
|
|
|
TypeInformation overrideParamInfo =
|
|
|
|
|
types.getInferredTypeOfParameter(parameter);
|
|
|
|
|
overrideParamInfo.addInput(parentParamInfo);
|
|
|
|
|
parameterIndex++;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds edges between [parent] and [override] based on the type of member
|
|
|
|
|
/// each is.
|
|
|
|
|
///
|
|
|
|
|
/// Possible override configurations (parent/override):
|
|
|
|
|
/// - field/getter
|
|
|
|
|
/// - field/setter
|
|
|
|
|
/// - field/field
|
|
|
|
|
/// - getter/getter
|
|
|
|
|
/// - getter/field
|
|
|
|
|
/// - setter/setter
|
|
|
|
|
/// - setter/field
|
|
|
|
|
/// - method/method
|
|
|
|
|
void _joinOverriddenMember(MemberEntity parent, MemberEntity override) {
|
|
|
|
|
if (parent.name == Identifiers.noSuchMethod_) return;
|
|
|
|
|
final parentType = types.getInferredTypeOfMember(parent);
|
|
|
|
|
final overrideType = types.getInferredTypeOfMember(override);
|
|
|
|
|
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);
|
|
|
|
|
paramInfo.addInput(parentType);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
assert(override is FieldEntity);
|
|
|
|
|
parentType.addInput(overrideType);
|
|
|
|
|
if (parent.isAssignable) {
|
|
|
|
|
// Parent has an implicit setter so the types of set values need to
|
|
|
|
|
// flow into the override.
|
|
|
|
|
overrideType.addInput(parentType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (parent.isGetter) {
|
|
|
|
|
assert(override.isGetter || override is FieldEntity);
|
|
|
|
|
parentType.addInput(overrideType);
|
|
|
|
|
} else if (parent.isSetter) {
|
|
|
|
|
if (override.isSetter) {
|
|
|
|
|
_joinOverrideParameters(parent, override);
|
|
|
|
|
} else {
|
|
|
|
|
assert(override is FieldEntity);
|
|
|
|
|
types.strategy.forEachParameter(parent as FunctionEntity,
|
|
|
|
|
(Local parameter) {
|
|
|
|
|
final paramInfo = types.getInferredTypeOfParameter(parameter);
|
|
|
|
|
overrideType.addInput(paramInfo);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
assert(parent.isFunction && override.isFunction);
|
|
|
|
|
parentType.addInput(overrideType);
|
|
|
|
|
_joinOverrideParameters(parent, override);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _markForClosurization(
|
|
|
|
|
MemberTypeInformation memberInfo, TypeInformation callSiteType,
|
|
|
|
|
{required bool remove, required bool addToQueue}) {
|
|
|
|
|
final member = memberInfo.member;
|
|
|
|
|
if (remove) {
|
|
|
|
|
memberInfo.closurizedCount--;
|
|
|
|
|
} else {
|
|
|
|
|
memberInfo.closurizedCount++;
|
|
|
|
|
if (member.isStatic || member.isTopLevel) {
|
|
|
|
|
types.allocatedClosures.add(memberInfo);
|
|
|
|
|
} else {
|
|
|
|
|
// We add the call-site type information here so that we
|
|
|
|
|
// can benefit from further refinement of the selector.
|
|
|
|
|
types.allocatedClosures.add(callSiteType);
|
|
|
|
|
}
|
|
|
|
|
types.strategy.forEachParameter(member as FunctionEntity,
|
|
|
|
|
(Local parameter) {
|
|
|
|
|
ParameterTypeInformation info =
|
|
|
|
|
types.getInferredTypeOfParameter(parameter);
|
|
|
|
|
info.tagAsTearOffClosureParameter(this);
|
|
|
|
|
if (addToQueue) _workQueue.add(info);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _registerOverridesCalled(
|
|
|
|
|
MemberEntity callee,
|
|
|
|
|
DynamicCallSiteTypeInformation callSiteType,
|
|
|
|
|
ir.Node? callSite,
|
|
|
|
|
Set<MemberEntity> visited,
|
|
|
|
|
{required bool isClosurized}) {
|
|
|
|
|
memberHierarchyBuilder.forEachOverride(callee, (override) {
|
|
|
|
|
if (override.isAbstract || !visited.add(override)) return;
|
|
|
|
|
MemberTypeInformation info = types.getInferredTypeOfMember(override);
|
|
|
|
|
info.addCall(callSiteType.caller, callSite);
|
|
|
|
|
|
|
|
|
|
if (isClosurized) {
|
|
|
|
|
_markForClosurization(info, callSiteType,
|
|
|
|
|
remove: false, addToQueue: false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sets the type of a parameter's default value to [type]. If the global
|
|
|
|
@ -871,8 +1027,7 @@ class InferrerEngine {
|
|
|
|
|
// Even if x.== doesn't return a bool, 'x == null' evaluates to 'false'.
|
|
|
|
|
info.addInput(types.boolType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.inputs.isEmpty) info.addInput(type);
|
|
|
|
|
info.addInput(type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Notifies to the inferrer that [analyzedElement] can have return type
|
|
|
|
@ -1140,6 +1295,7 @@ class InferrerEngine {
|
|
|
|
|
class _InferrerEngineMetrics extends MetricsBase {
|
|
|
|
|
final time = DurationMetric('time');
|
|
|
|
|
final analyze = DurationMetric('time.analyze');
|
|
|
|
|
final memberHierarchy = DurationMetric('time.memberHierarchy');
|
|
|
|
|
final refine1 = DurationMetric('time.refine1');
|
|
|
|
|
final trace = DurationMetric('time.trace');
|
|
|
|
|
final refine2 = DurationMetric('time.refine2');
|
|
|
|
@ -1152,6 +1308,7 @@ class _InferrerEngineMetrics extends MetricsBase {
|
|
|
|
|
primary = [time, ...subMetrics.primary];
|
|
|
|
|
secondary = [
|
|
|
|
|
analyze,
|
|
|
|
|
memberHierarchy,
|
|
|
|
|
refine1,
|
|
|
|
|
trace,
|
|
|
|
|
refine2,
|
|
|
|
|