[dart2js] Add tracing of types through records in global inference.

Change-Id: I4abdb76caecc91c89792396ff7d3fe4060cc7bc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/289904
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
Nate Biggs
2023-04-04 02:11:01 +00:00
committed by Commit Queue
parent 93f7e368f7
commit 7ceea1ec11
15 changed files with 243 additions and 46 deletions
+13
View File
@@ -34,6 +34,7 @@ import 'debug.dart' as debug;
import 'locals_handler.dart';
import 'list_tracer.dart';
import 'map_tracer.dart';
import 'record_tracer.dart';
import 'set_tracer.dart';
import 'type_graph_dump.dart';
import 'type_graph_nodes.dart';
@@ -311,6 +312,18 @@ class InferrerEngine {
_workQueue.add(info);
}
void analyzeRecordAndEnqueue(RecordTypeInformation info) {
if (info.analyzed) return;
info.analyzed = true;
RecordTracerVisitor tracer = RecordTracerVisitor(info, this);
bool succeeded = tracer.run();
if (!succeeded) return;
info.bailedOut = false;
_workQueue.add(info);
}
void runOverAllElements() {
metrics.time.measure(_runOverAllElements);
}
+32 -11
View File
@@ -104,30 +104,35 @@ 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();
TracerVisitor(this.tracedType, this.inferrer);
/// Work list that gets populated with [TypeInformation] that could
/// contain the container.
final List<TypeInformation> workList = <TypeInformation>[];
final List<TypeInformation> workList = [];
/// Work list of lists to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// lists and we must check how it escapes from these lists.
final List<ListTypeInformation> listsToAnalyze = <ListTypeInformation>[];
final List<ListTypeInformation> listsToAnalyze = [];
/// Work list of sets to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these sets
/// and we must check how it escapes from these sets.
final List<SetTypeInformation> setsToAnalyze = <SetTypeInformation>[];
final List<SetTypeInformation> setsToAnalyze = [];
/// Work list of maps to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// maps and we must check how it escapes from these maps.
final List<MapTypeInformation> mapsToAnalyze = <MapTypeInformation>[];
final List<MapTypeInformation> mapsToAnalyze = [];
final Setlet<TypeInformation> flowsInto = Setlet<TypeInformation>();
/// Work list of records to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// records and we must check how it escapes from these records.
final List<RecordTypeInformation> recordsToAnalyze = [];
final Setlet<TypeInformation> flowsInto = Setlet();
// The current [TypeInformation] in the analysis.
TypeInformation? currentUser;
@@ -175,6 +180,9 @@ abstract class TracerVisitor implements TypeInformationVisitor {
while (!mapsToAnalyze.isEmpty) {
analyzeStoredIntoMap(mapsToAnalyze.removeLast());
}
while (!recordsToAnalyze.isEmpty) {
analyzeStoredIntoRecord(recordsToAnalyze.removeLast());
}
if (!continueAnalyzing) break;
}
}
@@ -229,9 +237,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
@override
void visitRecordFieldAccessTypeInformation(
RecordFieldAccessTypeInformation info) {
addNewEscapeInformation(info);
}
RecordFieldAccessTypeInformation info) {}
@override
void visitValueInMapTypeInformation(ValueInMapTypeInformation info) {
@@ -255,8 +261,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
@override
void visitRecordTypeInformation(RecordTypeInformation info) {
// TODO(50701): Implement better inference for records.
bailout('Used as field of Record');
recordsToAnalyze.add(info);
}
@override
@@ -346,6 +351,22 @@ abstract class TracerVisitor implements TypeInformationVisitor {
}
}
void analyzeStoredIntoRecord(RecordTypeInformation record) {
inferrer.analyzeRecordAndEnqueue(record);
record.flowsInto.forEach((TypeInformation flow) {
flow.users.forEach((TypeInformation user) {
if (user is RecordFieldAccessTypeInformation) {
final getterIndex =
record.recordShape.indexOfGetterName(user.getterName);
if (user.receiver != flow ||
record.fieldTypes.indexOf(currentUser!) != getterIndex) return;
addNewEscapeInformation(user);
}
});
});
}
/// Checks whether this is a call to a list adding method. The definition of
/// what list adding means has to stay in sync with
/// [isParameterOfListAddingMethod].
@@ -0,0 +1,19 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'node_tracer.dart';
import 'type_graph_nodes.dart';
class RecordTracerVisitor extends TracerVisitor {
RecordTracerVisitor(super.tracedType, super.inferrer);
bool run() {
analyze();
final record = tracedType as RecordTypeInformation;
if (continueAnalyzing) {
record.addFlowsIntoTargets(flowsInto);
return true;
}
return false;
}
}
@@ -2052,7 +2052,7 @@ class ValueInMapTypeInformation extends InferredTypeInformation {
/// A [RecordTypeInformation] is the constructor for a record, used for Record
/// constants and literals.
class RecordTypeInformation extends TypeInformation {
class RecordTypeInformation extends TypeInformation with TracedTypeInformation {
final RecordShape recordShape;
final AbstractValue originalType;
@@ -35,6 +35,7 @@ import 'debug.dart' as debug;
import 'locals_handler.dart';
import 'list_tracer.dart';
import 'map_tracer.dart';
import 'record_tracer.dart';
import 'set_tracer.dart';
import 'type_graph_dump.dart';
import 'type_graph_nodes.dart';
@@ -319,6 +320,18 @@ class InferrerEngine {
_workQueue.add(info);
}
void analyzeRecordAndEnqueue(RecordTypeInformation info) {
if (info.analyzed) return;
info.analyzed = true;
RecordTracerVisitor tracer = RecordTracerVisitor(info, this);
bool succeeded = tracer.run();
if (!succeeded) return;
info.bailedOut = false;
_workQueue.add(info);
}
void runOverAllElements() {
metrics.time.measure(_runOverAllElements);
}
@@ -104,30 +104,35 @@ 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();
TracerVisitor(this.tracedType, this.inferrer);
/// Work list that gets populated with [TypeInformation] that could
/// contain the container.
final List<TypeInformation> workList = <TypeInformation>[];
final List<TypeInformation> workList = [];
/// Work list of lists to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// lists and we must check how it escapes from these lists.
final List<ListTypeInformation> listsToAnalyze = <ListTypeInformation>[];
final List<ListTypeInformation> listsToAnalyze = [];
/// Work list of sets to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these sets
/// and we must check how it escapes from these sets.
final List<SetTypeInformation> setsToAnalyze = <SetTypeInformation>[];
final List<SetTypeInformation> setsToAnalyze = [];
/// Work list of maps to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// maps and we must check how it escapes from these maps.
final List<MapTypeInformation> mapsToAnalyze = <MapTypeInformation>[];
final List<MapTypeInformation> mapsToAnalyze = [];
final Setlet<TypeInformation> flowsInto = Setlet<TypeInformation>();
/// Work list of records to analyze after analyzing the users of a
/// [TypeInformation]. We know the [tracedType] has been stored in these
/// records and we must check how it escapes from these records.
final List<RecordTypeInformation> recordsToAnalyze = [];
final Setlet<TypeInformation> flowsInto = Setlet();
// The current [TypeInformation] in the analysis.
TypeInformation? currentUser;
@@ -175,6 +180,9 @@ abstract class TracerVisitor implements TypeInformationVisitor {
while (!mapsToAnalyze.isEmpty) {
analyzeStoredIntoMap(mapsToAnalyze.removeLast());
}
while (!recordsToAnalyze.isEmpty) {
analyzeStoredIntoRecord(recordsToAnalyze.removeLast());
}
if (!continueAnalyzing) break;
}
}
@@ -229,9 +237,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
@override
void visitRecordFieldAccessTypeInformation(
RecordFieldAccessTypeInformation info) {
addNewEscapeInformation(info);
}
RecordFieldAccessTypeInformation info) {}
@override
void visitValueInMapTypeInformation(ValueInMapTypeInformation info) {
@@ -255,8 +261,7 @@ abstract class TracerVisitor implements TypeInformationVisitor {
@override
void visitRecordTypeInformation(RecordTypeInformation info) {
// TODO(50701): Implement better inference for records.
bailout('Used as field of Record');
recordsToAnalyze.add(info);
}
@override
@@ -346,6 +351,22 @@ abstract class TracerVisitor implements TypeInformationVisitor {
}
}
void analyzeStoredIntoRecord(RecordTypeInformation record) {
inferrer.analyzeRecordAndEnqueue(record);
record.flowsInto.forEach((TypeInformation flow) {
flow.users.forEach((TypeInformation user) {
if (user is RecordFieldAccessTypeInformation) {
final getterIndex =
record.recordShape.indexOfGetterName(user.getterName);
if (user.receiver != flow ||
record.fieldTypes.indexOf(currentUser!) != getterIndex) return;
addNewEscapeInformation(user);
}
});
});
}
/// Checks whether this is a call to a list adding method. The definition of
/// what list adding means has to stay in sync with
/// [isParameterOfListAddingMethod].
@@ -0,0 +1,19 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'node_tracer.dart';
import 'type_graph_nodes.dart';
class RecordTracerVisitor extends TracerVisitor {
RecordTracerVisitor(super.tracedType, super.inferrer);
bool run() {
analyze();
final record = tracedType as RecordTypeInformation;
if (continueAnalyzing) {
record.addFlowsIntoTargets(flowsInto);
return true;
}
return false;
}
}
@@ -2058,7 +2058,7 @@ class ValueInMapTypeInformation extends InferredTypeInformation {
/// A [RecordTypeInformation] is the constructor for a record, used for Record
/// constants and literals.
class RecordTypeInformation extends TypeInformation {
class RecordTypeInformation extends TypeInformation with TracedTypeInformation {
final RecordShape recordShape;
final AbstractValue originalType;
+1
View File
@@ -10,6 +10,7 @@ analyzer:
exclude:
- '**/data/*'
- '**/inference_data/*'
- 'rti/emission/*'
- '**/model_data/*'
- 'deferred_loading/libs/*'
@@ -57,11 +57,11 @@ testStoredInMapOfList() {
dynamic b = <dynamic, dynamic>{'foo': 1};
b
/*update: Dictionary([exact=JsLinkedHashMap], key: [exact=JSString], value: Union(null, [exact=JSExtendableArray], [exact=JSUInt31]), map: {foo: [exact=JSUInt31], bar: Container([null|exact=JSExtendableArray], element: [subclass=Closure], length: 1)})*/
/*update: Dictionary([subclass=JsLinkedHashMap], key: [exact=JSString], value: Union(null, [exact=JSExtendableArray], [exact=JSUInt31]), map: {foo: [exact=JSUInt31], bar: Container([null|exact=JSExtendableArray], element: [subclass=Closure], length: 1)})*/
['bar'] = a;
b
/*Dictionary([exact=JsLinkedHashMap], key: [exact=JSString], value: Union(null, [exact=JSExtendableArray], [exact=JSUInt31]), map: {foo: [exact=JSUInt31], bar: Container([null|exact=JSExtendableArray], element: [subclass=Closure], length: 1)})*/
/*Dictionary([subclass=JsLinkedHashMap], key: [exact=JSString], value: Union(null, [exact=JSExtendableArray], [exact=JSUInt31]), map: {foo: [exact=JSUInt31], bar: Container([null|exact=JSExtendableArray], element: [subclass=Closure], length: 1)})*/
['bar']
/*Container([null|exact=JSExtendableArray], element: [subclass=Closure], length: 1)*/
@@ -127,10 +127,10 @@ testStoredInListOfListUsingAdd() {
return res;
}
/*member: testStoredInRecord:[null|subclass=Object]*/
/*member: testStoredInRecord:[null|exact=JSUInt31]*/
testStoredInRecord() {
var res;
/*[null|subclass=Object]*/ closure(/*[null|subclass=Object]*/ a) => res = a;
/*[exact=JSUInt31]*/ closure(/*[exact=JSUInt31]*/ a) => res = a;
final a = (3, closure);
a. /*[Record(RecordShape(2), [[exact=JSUInt31], [subclass=Closure]])]*/ $2(
@@ -203,6 +203,41 @@ testStaticClosure4() {
return topLevel4;
}
/*member: bar1:[subclass=Closure]*/
int Function(int, [int]) bar1(
int /*[exact=JSUInt31]*/ a) => /*[subclass=JSInt]*/
(int /*spec.[null|subclass=Object]*/ /*prod.[null|subclass=JSInt]*/ b,
[int /*spec.[null|subclass=Object]*/ /*prod.[null|subclass=JSInt]*/
c = 17]) =>
a /*invoke: [exact=JSUInt31]*/ + b /*invoke: [subclass=JSInt]*/ + c;
/*member: bar2:[subclass=Closure]*/
int Function(int, [int]) bar2(
int /*[exact=JSUInt31]*/ a) => /*[subclass=JSInt]*/
(int /*spec.[null|subclass=Object]*/ /*prod.[null|subclass=JSInt]*/ b,
[int /*spec.[null|subclass=Object]*/ /*prod.[null|subclass=JSInt]*/
c = 17]) =>
a /*invoke: [exact=JSUInt31]*/ + b /*invoke: [subclass=JSInt]*/ + c;
/*member: bar3:[subclass=Closure]*/
int Function(int, [int]) bar3(
int /*[exact=JSUInt31]*/ a) => /*[subclass=JSPositiveInt]*/
(int /*[exact=JSUInt31]*/ b, [int /*[exact=JSUInt31]*/ c = 17]) =>
a /*invoke: [exact=JSUInt31]*/ + b /*invoke: [subclass=JSUInt32]*/ + c;
/*member: testFunctionApply:[null|subclass=Object]*/
testFunctionApply() {
return Function.apply(bar1(10), [20]);
}
/*member: testRecordFunctionApply:[null|subclass=Object]*/
testRecordFunctionApply() {
final rec = (bar2(10), bar3(10));
(rec. /*[Record(RecordShape(2), [[subclass=Closure], [subclass=Closure]])]*/ $2)(
2, 3);
return Function.apply(
rec. /*[Record(RecordShape(2), [[subclass=Closure], [subclass=Closure]])]*/ $1,
[20]);
}
/*member: main:[null]*/
main() {
testFunctionStatement();
@@ -219,4 +254,6 @@ main() {
testStaticClosure2();
testStaticClosure3();
testStaticClosure4();
testFunctionApply();
testRecordFunctionApply();
}
@@ -4,25 +4,25 @@ void main() {
testClosure1();
}
/*member: testList:Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null)*/
/*member: testList:Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: null)*/
testList() {
dynamic list = [];
final rec = (list, 3);
final myList = rec
. /*[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null), [exact=JSUInt31]])]*/ $1;
. /*[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: null), [exact=JSUInt31]])]*/ $1;
myList
. /*invoke: Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null)*/ add(
. /*invoke: Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: null)*/ add(
1);
return list;
}
/*member: testClosure1:Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null)*/
/*member: testClosure1:Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: 2)*/
testClosure1() {
return getRecord()
. /*[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null), [exact=JSUInt31]])]*/ $1;
. /*[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: 2), [exact=JSUInt31]])]*/ $1;
}
/*member: getRecord:[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null), [exact=JSUInt31]])]*/
/*member: getRecord:[Record(RecordShape(2), [Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: 2), [exact=JSUInt31]])]*/
getRecord() {
return ([1, 2], 3);
}
@@ -0,0 +1,52 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
main() {
directUnusedTest();
recordUnusedTest();
directCalledTest();
directAppliedTest();
recordCalledTest();
recordAppliedTest();
}
int Function(int, [int]) _recordUnused(int a) =>
(int b, [int c = 17]) => a + b + c;
int Function(int, [int]) _recordCalled(int a) =>
(int b, [int c = 17]) => a + b + c;
int Function(int, [int]) _recordApplied(int a) =>
/*apply*/ (int b, [int c = 17]) => a + b + c;
int Function(int, [int]) _directUnused(int a) =>
(int b, [int c = 17]) => a + b + c;
int Function(int, [int]) _directCalled(int a) =>
(int b, [int c = 17]) => a + b + c;
int Function(int, [int]) _directApplied(int a) =>
/*apply*/ (int b, [int c = 17]) => a + b + c;
directUnusedTest() {
return _directUnused(10);
}
recordUnusedTest() {
final rec = (_recordUnused(10), 4);
return rec.$1;
}
directCalledTest() {
return _directCalled(10)(20);
}
directAppliedTest() {
return Function.apply(_directApplied(10), [20]);
}
recordCalledTest() {
final rec = (4, _recordCalled(10));
return (rec.$2)(20);
}
recordAppliedTest() {
final rec = (_recordApplied(10), 4);
return Function.apply(rec.$1, [20]);
}
@@ -265,10 +265,10 @@ doTest(String allocation, {required bool nullify}) async {
checkType('listPassedAsNamedParameter', commonMasks.numType);
checkType('listStoredInList', commonMasks.uint31Type);
checkType('listStoredInListButEscapes', commonMasks.dynamicType);
checkType('listStoredInRecordWithIndexAccess', commonMasks.dynamicType);
checkType('listStoredInRecordWithNameAccess', commonMasks.dynamicType);
checkType('listStoredInRecordWithDynamicAccess', commonMasks.dynamicType);
checkType('listStoredInRecordWithoutAccess', commonMasks.dynamicType);
checkType('listStoredInRecordWithIndexAccess', commonMasks.numType);
checkType('listStoredInRecordWithNameAccess', commonMasks.numType);
checkType('listStoredInRecordWithDynamicAccess', commonMasks.numType);
checkType('listStoredInRecordWithoutAccess', commonMasks.uint31Type);
if (!allocation.contains('filled')) {
checkType('listUnset', TypeMask.nonNullEmpty());
@@ -313,14 +313,14 @@ doTest(String allocation,
checkType('mapStoredInMap', K(aKeyType), V(commonMasks.uint31Type));
checkType('mapStoredInMapButEscapes', K(commonMasks.dynamicType),
V(commonMasks.dynamicType));
checkType('mapStoredInRecordWithIndexAccess', K(commonMasks.dynamicType),
V(commonMasks.dynamicType));
checkType('mapStoredInRecordWithNameAccess', K(commonMasks.dynamicType),
V(commonMasks.dynamicType));
checkType('mapStoredInRecordWithDynamicAccess', K(commonMasks.dynamicType),
V(commonMasks.dynamicType));
checkType('mapStoredInRecordWithoutAccess', K(commonMasks.dynamicType),
V(commonMasks.dynamicType));
checkType(
'mapStoredInRecordWithIndexAccess', K(aKeyType), V(commonMasks.numType));
checkType(
'mapStoredInRecordWithNameAccess', K(aKeyType), V(commonMasks.numType));
checkType('mapStoredInRecordWithDynamicAccess', K(aKeyType),
V(commonMasks.numType));
checkType('mapStoredInRecordWithoutAccess', K(aKeyType),
V(commonMasks.positiveIntType));
checkType('mapUnset', K(emptyType), V(emptyType));
checkType('mapOnlySetWithConstraint', K(aKeyType), V(emptyType));
+1
View File
@@ -9,6 +9,7 @@
"."
],
"exclude": [
"^pkg/compiler/test/.*/inference_data/.*",
"^pkg/compiler/test/.*/data/.*",
"^pkg/compiler/test/.*/data_2/.*",
"^pkg/compiler/test/.*/emission/.*",