From 9ba4957a316ad125e4e72f6e49772f0d1263aff2 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Thu, 9 Mar 2023 21:26:52 +0000 Subject: [PATCH] [dart2js] Add record getters as reachable targets from dynamic call infrastructure. Change-Id: I78387dffa4a7a16f20e630300bd90b08591efe6c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287480 Reviewed-by: Stephen Adams Commit-Queue: Nate Biggs --- .../lib/src/inferrer/type_graph_nodes.dart | 19 ++++++++----- .../lib/src/inferrer/type_system.dart | 16 ++++++++++- .../type_graph_nodes.dart | 19 +++++++++---- .../inferrer_experimental/type_system.dart | 17 ++++++++++- .../lib/src/js_model/element_map_impl.dart | 5 +++- pkg/compiler/lib/src/js_model/js_world.dart | 3 +- .../lib/src/js_model/js_world_builder.dart | 10 +++---- pkg/compiler/lib/src/js_model/records.dart | 28 ++++++++++++++----- .../lib/src/universe/member_hierarchy.dart | 3 +- .../test/inference/data/record_1.dart | 20 +++++++++++-- 10 files changed, 108 insertions(+), 32 deletions(-) diff --git a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart index 90b8982e9ec..8e9c734d0a9 100644 --- a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart +++ b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart @@ -2020,10 +2020,6 @@ class RecordTypeInformation extends TypeInformation with TracedTypeInformation { } } - markAsInferred() { - for (final fieldType in fieldTypes) fieldType.inferred = true; - } - @override addInput(TypeInformation other) { throw UnsupportedError('addInput'); @@ -2071,10 +2067,14 @@ class RecordTypeInformation extends TypeInformation with TracedTypeInformation { /// A [FieldInRecordTypeInformation] holds the input of one position of a /// [RecordTypeInformation]. -class FieldInRecordTypeInformation extends InferredTypeInformation { +class FieldInRecordTypeInformation extends TypeInformation { final int indexInShape; - FieldInRecordTypeInformation(super.abstractValueDomain, super.context, - this.indexInShape, super.parentType); + final TypeInformation parentType; + FieldInRecordTypeInformation(AbstractValueDomain abstractValueDomain, + MemberTypeInformation? context, this.indexInShape, this.parentType) + : super(abstractValueDomain.uncomputedType, context) { + parentType.addUser(this); + } @override accept(TypeInformationVisitor visitor) { @@ -2083,6 +2083,11 @@ class FieldInRecordTypeInformation extends InferredTypeInformation { @override String toString() => 'Field in Record $type'; + + @override + AbstractValue computeType(InferrerEngine inferrer) { + return parentType.type; + } } /// A [PhiElementTypeInformation] is an union of diff --git a/pkg/compiler/lib/src/inferrer/type_system.dart b/pkg/compiler/lib/src/inferrer/type_system.dart index d9e7901dd35..3e8cc0d9112 100644 --- a/pkg/compiler/lib/src/inferrer/type_system.dart +++ b/pkg/compiler/lib/src/inferrer/type_system.dart @@ -517,11 +517,26 @@ class TypeSystem { TypeInformation allocateRecord(ir.TreeNode node, RecordType recordType, List fieldTypes, bool isConst) { assert(fieldTypes.length == recordType.shape.fieldCount); + final getters = _closedWorld.recordData.gettersForShape(recordType.shape); FieldInRecordTypeInformation makeField(int i) { final field = FieldInRecordTypeInformation( _abstractValueDomain, currentMember, i, fieldTypes[i]); allocatedTypes.add(field); + final getter = getters[i] as FunctionEntity; + final getterType = memberTypeInformations[getter] ??= + GetterTypeInformation( + _closedWorld.abstractValueDomain, + getter, + _closedWorld.dartTypes.functionType( + _closedWorld.dartTypes.dynamicType(), + const [], + const [], + const [], + const {}, + const [], + const [])); + getterType.addInput(field); return field; } @@ -529,7 +544,6 @@ class TypeSystem { final originalType = _abstractValueDomain.recordType; final record = RecordTypeInformation( currentMember, originalType, recordType.shape, fields); - if (isConst) record.markAsInferred(); // TODO(50081): When tracing is added for records, use `allocatedRecords`. allocatedTypes.add(record); return record; diff --git a/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart index 7ced9d6d327..881c9265e79 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/type_graph_nodes.dart @@ -2019,9 +2019,6 @@ class RecordTypeInformation extends TypeInformation with TracedTypeInformation { fieldType.addUser(this); } } - markAsInferred() { - for (final fieldType in fieldTypes) fieldType.inferred = true; - } @override addInput(TypeInformation other) { @@ -2069,10 +2066,15 @@ class RecordTypeInformation extends TypeInformation with TracedTypeInformation { /// A [FieldInRecordTypeInformation] holds the input of one position of a /// [RecordTypeInformation]. -class FieldInRecordTypeInformation extends InferredTypeInformation { +class FieldInRecordTypeInformation extends TypeInformation { final int indexInShape; - FieldInRecordTypeInformation(super.abstractValueDomain, super.context, - this.indexInShape, super.parentType); + final TypeInformation parentType; + FieldInRecordTypeInformation(AbstractValueDomain abstractValueDomain, + MemberTypeInformation? context, this.indexInShape, this.parentType) + : super(abstractValueDomain.uncomputedType, context) { + parentType.addUser(this); + } + @override accept(TypeInformationVisitor visitor) { return visitor.visitFieldInRecordTypeInformation(this); @@ -2080,6 +2082,11 @@ class FieldInRecordTypeInformation extends InferredTypeInformation { @override String toString() => 'Field in Record $type'; + + @override + AbstractValue computeType(InferrerEngine inferrer) { + return parentType.type; + } } /// A [PhiElementTypeInformation] is an union of diff --git a/pkg/compiler/lib/src/inferrer_experimental/type_system.dart b/pkg/compiler/lib/src/inferrer_experimental/type_system.dart index abe87179c14..9340a388be6 100644 --- a/pkg/compiler/lib/src/inferrer_experimental/type_system.dart +++ b/pkg/compiler/lib/src/inferrer_experimental/type_system.dart @@ -543,10 +543,26 @@ class TypeSystem { TypeInformation allocateRecord(ir.TreeNode node, RecordType recordType, List fieldTypes, bool isConst) { assert(fieldTypes.length == recordType.shape.fieldCount); + final getters = _closedWorld.recordData.gettersForShape(recordType.shape); + FieldInRecordTypeInformation makeField(int i) { final field = FieldInRecordTypeInformation( _abstractValueDomain, currentMember, i, fieldTypes[i]); allocatedTypes.add(field); + final getter = getters[i] as FunctionEntity; + final getterType = memberTypeInformations[getter] ??= + GetterTypeInformation( + _closedWorld.abstractValueDomain, + getter, + _closedWorld.dartTypes.functionType( + _closedWorld.dartTypes.dynamicType(), + const [], + const [], + const [], + const {}, + const [], + const [])); + getterType.addInput(field); return field; } @@ -554,7 +570,6 @@ class TypeSystem { final originalType = _abstractValueDomain.recordType; final record = RecordTypeInformation( currentMember, originalType, recordType.shape, fields); - if (isConst) record.markAsInferred(); // TODO(50081): When tracing is added for records, use `allocatedRecords`. allocatedTypes.add(record); return record; diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart index 855fe627202..be52bba5e82 100644 --- a/pkg/compiler/lib/src/js_model/element_map_impl.dart +++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart @@ -2195,8 +2195,10 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap { return sb.toString(); } + /// [getters] is an out parameter that gathers all the getters created for + /// this shape. IndexedClass generateRecordShapeClass( - RecordShape shape, InterfaceType supertype) { + RecordShape shape, InterfaceType supertype, List getters) { JLibrary library = supertype.element.library as JLibrary; String name = _nameForShape(shape); @@ -2225,6 +2227,7 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap { : shape.fieldNames[i - shape.positionalFieldCount]; Name memberName = Name(name, null); final getter = JRecordGetter(classEntity, memberName); + getters.add(getter); // The function type of a dynamic getter is a function of no arguments // that returns `dynamic` (any other top would be ok too). diff --git a/pkg/compiler/lib/src/js_model/js_world.dart b/pkg/compiler/lib/src/js_model/js_world.dart index 8f6cb28eaf0..88f358049c4 100644 --- a/pkg/compiler/lib/src/js_model/js_world.dart +++ b/pkg/compiler/lib/src/js_model/js_world.dart @@ -52,7 +52,8 @@ class JClosedWorld implements World { // [_allFunctions] is created lazily because it is not used when we switch // from a frontend to a backend model before inference. - late final FunctionSet _allFunctions = FunctionSet(liveInstanceMembers); + late final FunctionSet _allFunctions = + FunctionSet(liveInstanceMembers.followedBy(recordData.allGetters)); final Map> mixinUses; diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart index 3b0940a5589..207dddaaff6 100644 --- a/pkg/compiler/lib/src/js_model/js_world_builder.dart +++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart @@ -384,13 +384,13 @@ class JClosedWorldBuilder { } /// Called once per [shape]. The class can be used for a record with the - /// specified shape, or subclassed to provide specialized methods. - ClassEntity buildRecordShapeClass(RecordShape shape) { + /// specified shape, or subclassed to provide specialized methods. [getters] + /// is an out parameter that gathers all the getters created for this shape. + ClassEntity buildRecordShapeClass( + RecordShape shape, List getters) { ClassEntity superclass = _commonElements.recordArityClass(shape.fieldCount); IndexedClass recordClass = _elementMap.generateRecordShapeClass( - shape, - _dartTypes.interfaceType(superclass, const []), - ); + shape, _dartTypes.interfaceType(superclass, const []), getters); // Tell the hierarchy about the superclass so we can use // .getSupertypes(class) diff --git a/pkg/compiler/lib/src/js_model/records.dart b/pkg/compiler/lib/src/js_model/records.dart index e35ec54779d..6bfa940b1b6 100644 --- a/pkg/compiler/lib/src/js_model/records.dart +++ b/pkg/compiler/lib/src/js_model/records.dart @@ -55,10 +55,11 @@ class RecordData { final JsToElementMap _elementMap; final List _representations; + final Map> _gettersByShape; final Map _classToRepresentation = {}; final Map _shapeToRepresentation = {}; - RecordData._(this._elementMap, this._representations) { + RecordData._(this._elementMap, this._representations, this._gettersByShape) { // Unpack representations into lookup maps. for (final info in _representations) { _classToRepresentation[info.cls] = info; @@ -66,13 +67,23 @@ class RecordData { } } + Iterable get allGetters => + _gettersByShape.values.expand((e) => e); + + List gettersForShape(RecordShape shape) => + _gettersByShape[shape]!; + factory RecordData.readFromDataSource( JsToElementMap elementMap, DataSourceReader source) { source.begin(tag); List representations = source.readList(() => RecordRepresentation.readFromDataSource(source)); + final shapes = + source.readList(() => RecordShape.readFromDataSource(source)); + final getters = source.readList(source.readMembers); source.end(tag); - return RecordData._(elementMap, representations); + return RecordData._( + elementMap, representations, Map.fromIterables(shapes, getters)); } /// Serializes this [RecordData] to [sink]. @@ -80,6 +91,9 @@ class RecordData { sink.begin(tag); sink.writeList( _representations, (info) => info.writeToDataSink(sink)); + sink.writeList( + _gettersByShape.keys, (RecordShape v) => v.writeToDataSink(sink)); + sink.writeList(_gettersByShape.values, sink.writeMembers); sink.end(tag); } @@ -222,6 +236,7 @@ class RecordDataBuilder { final DiagnosticReporter _reporter; final JsToElementMap _elementMap; final AnnotationsData _annotationsData; + final Map> _gettersByShape = {}; RecordDataBuilder(this._reporter, this._elementMap, this._annotationsData); @@ -238,9 +253,11 @@ class RecordDataBuilder { List representations = []; for (int i = 0; i < shapes.length; i++) { final shape = shapes[i]; + final getters = []; final cls = shape.fieldCount == 0 ? _elementMap.commonElements.emptyRecordClass - : closedWorldBuilder.buildRecordShapeClass(shape); + : closedWorldBuilder.buildRecordShapeClass(shape, getters); + _gettersByShape[shape] = getters; int shapeTag = i; bool usesList = _computeUsesGeneralClass(cls); final info = @@ -248,10 +265,7 @@ class RecordDataBuilder { representations.add(info); } - return RecordData._( - _elementMap, - representations, - ); + return RecordData._(_elementMap, representations, _gettersByShape); } bool _computeUsesGeneralClass(ClassEntity? cls) { diff --git a/pkg/compiler/lib/src/universe/member_hierarchy.dart b/pkg/compiler/lib/src/universe/member_hierarchy.dart index 311023c0059..2156e8317fa 100644 --- a/pkg/compiler/lib/src/universe/member_hierarchy.dart +++ b/pkg/compiler/lib/src/universe/member_hierarchy.dart @@ -369,7 +369,8 @@ class MemberHierarchyBuilder { void init(void Function(MemberEntity parent, MemberEntity override) join) { final liveMembers = closedWorld.liveInstanceMembers - .followedBy(closedWorld.liveAbstractInstanceMembers); + .followedBy(closedWorld.liveAbstractInstanceMembers) + .followedBy(closedWorld.recordData.allGetters); for (final member in liveMembers) { _processMember(member, join); diff --git a/pkg/compiler/test/inference/data/record_1.dart b/pkg/compiler/test/inference/data/record_1.dart index 7e6f807b5c8..64f1b66084f 100644 --- a/pkg/compiler/test/inference/data/record_1.dart +++ b/pkg/compiler/test/inference/data/record_1.dart @@ -4,14 +4,18 @@ /*member: main:[null]*/ main() { - getRecord1(); useRecord1(); + useRecord2(); + useRecord3(); } // TODO(50701): This should be a record type. /*member: getRecord1:[null|subclass=Object]*/ (num, num) getRecord1() => (1, 1); - +/*member: getRecord2:[null|subclass=Object]*/ +(bool, bool) getRecord2() => (true, false); +/*member: getRecord3:[null|subclass=Object]*/ +dynamic getRecord3() => ("a", "b"); // TODO(50701): This should be a constant or JSUint31. /*member: useRecord1:[subclass=JSNumber]*/ @@ -19,3 +23,15 @@ useRecord1() { final r = getRecord1(); return r.$1; } + +/*member: useRecord2:[subtype=bool]*/ +useRecord2() { + final r = getRecord2(); + return r.$2; +} + +/*member: useRecord3:Union([exact=JSBool], [exact=JSString], [exact=JSUInt31])*/ +useRecord3() { + final r = getRecord3(); + return r.$2; +}