[dart2js] Add inferrer engine changes for type graph call linearization.

Change-Id: I23c1f12f4b619be9f4409b23f83cf54cd0fcc301
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/267062
Reviewed-by: Mayank Patke <fishythefish@google.com>
This commit is contained in:
Nate Biggs 2022-12-05 20:22:25 +00:00 committed by Commit Queue
parent 6173ba5dab
commit efe18b5c4a
7 changed files with 207 additions and 46 deletions

View file

@ -23,7 +23,8 @@ abstract class ClosureData {
void writeToDataSink(DataSinkWriter sink);
/// Look up information about the variables that have been mutated and are
/// used inside the scope of [node].
/// used inside the scope of [node]. Assumes member is not abstract and
/// therefore scope info exists.
ScopeInfo getScopeInfo(MemberEntity member);
ClosureRepresentationInfo getClosureInfo(ir.LocalFunction localFunction);

View file

@ -225,10 +225,12 @@ class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation?>
// be handled specially, in that we are computing their LUB at
// each update, and reading them yields the type that was found in a
// previous analysis of [outermostElement].
ScopeInfo scopeInfo = _closureDataLookup.getScopeInfo(_analyzedMember);
scopeInfo.forEachBoxedVariable(_localsMap, (variable, field) {
_capturedAndBoxed[variable] = field;
});
if (!_analyzedMember.isAbstract) {
ScopeInfo scopeInfo = _closureDataLookup.getScopeInfo(_analyzedMember);
scopeInfo.forEachBoxedVariable(_localsMap, (variable, field) {
_capturedAndBoxed[variable] = field;
});
}
return visit(_analyzedNode)!;
}

View file

@ -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,

View file

@ -104,7 +104,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
int.fromEnvironment('dart2js.tracing.limit', defaultValue: 32);
// TODO(natebiggs): We allow null here to maintain current functionality
// but we should verify we actually need to allow it.
final Setlet<MemberEntity> analyzedElements = Setlet<MemberEntity>();
final Setlet<MemberEntity?> analyzedElements = Setlet<MemberEntity?>();
TracerVisitor(this.tracedType, this.inferrer);
@ -163,7 +163,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
break;
}
for (final info in user.users) {
analyzedElements.add(info.owner!);
analyzedElements.add(info.owner);
info.accept(this);
}
while (!listsToAnalyze.isEmpty) {

View file

@ -1095,6 +1095,9 @@ class DynamicCallSiteTypeInformation<T extends ir.Node>
callee.removeCall(caller, _call!);
}
// TODO(natebiggs): Populate this below.
late final Set<MemberEntity> closurizedTargets = {};
@override
void addToGraph(InferrerEngine inferrer) {
assert((receiver as dynamic) != null); // TODO(48820): Remove when sound.

View file

@ -331,8 +331,6 @@ class TypeSystem {
}
MemberTypeInformation getInferredTypeOfMember(MemberEntity member) {
assert(!member.isAbstract,
failedAt(member, "Unexpected abstract member $member."));
return memberTypeInformations[member] ??= _getInferredTypeOfMember(member);
}

View file

@ -323,7 +323,7 @@ class GlobalTypeInferenceResultsImpl implements GlobalTypeInferenceResults {
(MemberEntity member, GlobalTypeInferenceMemberResult result) =>
result.writeToDataSink(
sink,
elementMap.getMemberContextNode(member)!,
elementMap.getMemberContextNode(member),
closedWorld.abstractValueDomain)));
sink.writeDeferrable(() => sink.writeLocalMap(
_parameterResults.loaded(),