mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:29:47 +00:00
[dart2js] Basic records
Records can be constructed, returned, and field accessed in non-dynamic code. Type checks don't work, so records cannot be added to generic collections like `List<(int,int>)`. - Add runtime for base classes for records of various arities. - Use impacts to collect record static types used. - Create record classes for each record shape. Record classes have extra metadata properties on the prototype. This allows slower, general operations at the root of the hierarchy that can be overridden with specialized operations lower in the hierarchy. Change-Id: Ic1b38a5076c2d05f2ecff0a9ed3255b43645386e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278699 Commit-Queue: Stephen Adams <sra@google.com> Reviewed-by: Nate Biggs <natebiggs@google.com>
This commit is contained in:
parent
052a6b4227
commit
ef6ce1e88a
|
@ -629,6 +629,23 @@ abstract class CommonElements {
|
|||
// impacts + constant emitter accordingly.
|
||||
late final ClassEntity constSetLiteralClass = unmodifiableSetClass;
|
||||
|
||||
/// Base class for all records.
|
||||
late final ClassEntity recordBaseClass = _findHelperClass('_Record');
|
||||
|
||||
/// A function that is used to model the back-end impacts of record lowering
|
||||
/// in the front-end.
|
||||
late final FunctionEntity recordImpactModel =
|
||||
_findHelperFunction('_RecordImpactModel');
|
||||
|
||||
/// Base class for records with N fields. Can be a fixed-arity class or a
|
||||
/// general class that works for any arity.
|
||||
ClassEntity recordArityClass(int n) {
|
||||
return _findClassOrNull(jsHelperLibrary, '_Record$n') ??
|
||||
recordGeneralBaseClass;
|
||||
}
|
||||
|
||||
late final ClassEntity recordGeneralBaseClass = _findHelperClass('_RecordN');
|
||||
|
||||
late final ClassEntity jsInvocationMirrorClass =
|
||||
_findHelperClass('JSInvocationMirror');
|
||||
|
||||
|
|
|
@ -125,6 +125,11 @@ class ImpactBuilder extends StaticTypeVisitor implements ImpactRegistry {
|
|||
isConst: node.isConst, isEmpty: node.entries.isEmpty);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleRecordLiteral(ir.RecordLiteral node) {
|
||||
registerRecordLiteral(node.recordType, isConst: node.isConst);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleStaticGet(
|
||||
ir.Expression node, ir.Member target, ir.DartType resultType) {
|
||||
|
|
|
@ -1410,6 +1410,18 @@ abstract class StaticTypeVisitor extends StaticTypeBase {
|
|||
return const ir.VoidType();
|
||||
}
|
||||
|
||||
void handleRecordLiteral(ir.RecordLiteral node) {}
|
||||
|
||||
@override
|
||||
ir.DartType visitRecordLiteral(ir.RecordLiteral node) {
|
||||
visitNodes(node.positional);
|
||||
for (final namedExpression in node.named) {
|
||||
visitNode(namedExpression.value);
|
||||
}
|
||||
handleRecordLiteral(node);
|
||||
return super.visitRecordLiteral(node);
|
||||
}
|
||||
|
||||
void handleFunctionExpression(ir.FunctionExpression node) {}
|
||||
|
||||
@override
|
||||
|
|
|
@ -634,4 +634,10 @@ class BackendImpacts {
|
|||
_commonElements.throwLateFieldADI,
|
||||
],
|
||||
);
|
||||
|
||||
late final BackendImpact recordInstantiation = BackendImpact(
|
||||
globalUses: [
|
||||
_commonElements.recordImpactModel,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1943,6 +1943,10 @@ abstract class ModularNamer {
|
|||
return instanceFieldPropertyName(_commonElements.rtiAsField);
|
||||
case JsGetName.RTI_FIELD_IS:
|
||||
return instanceFieldPropertyName(_commonElements.rtiIsField);
|
||||
case JsGetName.RECORD_SHAPE_TAG_PROPERTY:
|
||||
return asName(fixedNames.recordShapeTag);
|
||||
case JsGetName.RECORD_SHAPE_TYPE_PROPERTY:
|
||||
return asName(fixedNames.recordShapeRecipe);
|
||||
default:
|
||||
throw failedAt(spannable ?? CURRENT_ELEMENT_SPANNABLE,
|
||||
'Error: Namer has no name for "$name".');
|
||||
|
@ -2147,6 +2151,9 @@ class FixedNames {
|
|||
String get operatorSignature => r'$signature';
|
||||
String get requiredParameterField => r'$requiredArgCount';
|
||||
String get rtiName => r'$ti';
|
||||
|
||||
String get recordShapeRecipe => r'$recipe';
|
||||
String get recordShapeTag => r'$shape';
|
||||
}
|
||||
|
||||
/// Minified version of the fixed names usage by the namer.
|
||||
|
@ -2171,6 +2178,11 @@ class MinifiedFixedNames extends FixedNames {
|
|||
String get defaultValuesField => r'$D';
|
||||
@override
|
||||
String get operatorSignature => r'$S';
|
||||
|
||||
@override
|
||||
String get recordShapeRecipe => r'$r';
|
||||
@override
|
||||
String get recordShapeTag => r'$s';
|
||||
}
|
||||
|
||||
String? operatorNameToIdentifier(String? name) {
|
||||
|
|
|
@ -119,6 +119,7 @@ class CodeEmitterTask extends CompilerTask {
|
|||
this,
|
||||
closedWorld,
|
||||
closedWorld.fieldAnalysis,
|
||||
closedWorld.recordData,
|
||||
inferredData,
|
||||
_backendStrategy.sourceInformationStrategy,
|
||||
closedWorld.sorter,
|
||||
|
|
|
@ -210,6 +210,9 @@ class ClassTypeData {
|
|||
check.cls == commonElements.objectClass || check.cls == element);
|
||||
}
|
||||
|
||||
// TODO(sra): There are a lot of fields here that apply in limited cases
|
||||
// (e.g. isClosureBaseClass is true for one class). Can we refactor the special
|
||||
// case information, for example, into a subclass, or an extension object?
|
||||
class Class {
|
||||
/// The element should only be used during the transition to the new model.
|
||||
/// Uses indicate missing information in the model.
|
||||
|
@ -255,6 +258,9 @@ class Class {
|
|||
// metadata table, then this field contains the index into that field.
|
||||
final js.Expression? functionTypeIndex;
|
||||
|
||||
final int? recordShapeTag;
|
||||
final js.Expression? recordShapeRecipe;
|
||||
|
||||
/// Whether the class must be evaluated eagerly.
|
||||
bool isEager = false;
|
||||
|
||||
|
@ -286,7 +292,9 @@ class Class {
|
|||
required this.isNative,
|
||||
required this.isClosureBaseClass,
|
||||
this.sharedClosureApplyMetadata,
|
||||
required this.isMixinApplicationWithMembers});
|
||||
required this.isMixinApplicationWithMembers,
|
||||
this.recordShapeRecipe,
|
||||
this.recordShapeTag});
|
||||
|
||||
bool get isSimpleMixinApplication => false;
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import '../../js_backend/runtime_types_resolution.dart' show RuntimeTypesNeed;
|
|||
import '../../js_model/elements.dart'
|
||||
show JField, JGeneratorBody, JSignatureMethod;
|
||||
import '../../js_model/js_world.dart';
|
||||
import '../../js_model/records.dart' show RecordData, RecordRepresentation;
|
||||
import '../../js_model/type_recipe.dart'
|
||||
show FullTypeEnvironmentStructure, TypeExpressionRecipe;
|
||||
import '../../native/enqueue.dart' show NativeCodegenEnqueuer;
|
||||
|
@ -77,6 +78,7 @@ class ProgramBuilder {
|
|||
final CodeEmitterTask _task;
|
||||
final JClosedWorld _closedWorld;
|
||||
final JFieldAnalysis _fieldAnalysis;
|
||||
final RecordData _recordData;
|
||||
final InferredData _inferredData;
|
||||
final SourceInformationStrategy _sourceInformationStrategy;
|
||||
|
||||
|
@ -123,6 +125,7 @@ class ProgramBuilder {
|
|||
this._task,
|
||||
this._closedWorld,
|
||||
this._fieldAnalysis,
|
||||
this._recordData,
|
||||
this._inferredData,
|
||||
this._sourceInformationStrategy,
|
||||
this._sorter,
|
||||
|
@ -571,6 +574,21 @@ class ProgramBuilder {
|
|||
sharedClosureApplyMetadata = 2;
|
||||
}
|
||||
|
||||
int? recordShapeTag;
|
||||
js.Expression? recordShapeRecipe;
|
||||
RecordRepresentation? record = _recordData.representationForClass(cls);
|
||||
if (record != null && record.definesShape) {
|
||||
recordShapeTag = record.shapeTag;
|
||||
// A top-type for a record shape. We choose `dynamic` as the top-type for
|
||||
// the fields since is encodes concisely.
|
||||
// TODO(50081): Move this somewhere to improve consistency with Rti
|
||||
// runtime.
|
||||
final recordTop = _dartTypes.recordType(record.shape,
|
||||
List.filled(record.shape.fieldCount, _dartTypes.dynamicType()));
|
||||
recordShapeRecipe = _rtiRecipeEncoder.encodeGroundRecipe(
|
||||
_task.emitter, TypeExpressionRecipe(recordTop));
|
||||
}
|
||||
|
||||
List<Method> methods = [];
|
||||
List<StubMethod> callStubs = [];
|
||||
|
||||
|
@ -719,6 +737,8 @@ class ProgramBuilder {
|
|||
assert(methods.isEmpty);
|
||||
assert(!isClosureBaseClass);
|
||||
assert(sharedClosureApplyMetadata == null);
|
||||
assert(recordShapeTag == null);
|
||||
assert(recordShapeRecipe == null);
|
||||
|
||||
result = MixinApplication(cls, typeData, name, instanceFields, callStubs,
|
||||
checkedSetters, gettersSetters, isChecks, typeTests.functionTypeIndex,
|
||||
|
@ -746,7 +766,9 @@ class ProgramBuilder {
|
|||
isNative: _nativeData.isNativeClass(cls),
|
||||
isClosureBaseClass: isClosureBaseClass,
|
||||
sharedClosureApplyMetadata: sharedClosureApplyMetadata,
|
||||
isMixinApplicationWithMembers: isMixinApplicationWithMembers);
|
||||
isMixinApplicationWithMembers: isMixinApplicationWithMembers,
|
||||
recordShapeTag: recordShapeTag,
|
||||
recordShapeRecipe: recordShapeRecipe);
|
||||
}
|
||||
_classes[cls] = result;
|
||||
return result;
|
||||
|
|
|
@ -1107,6 +1107,16 @@ class FragmentEmitter {
|
|||
js.string(_namer.fixedNames.defaultValuesField), js.LiteralNull()));
|
||||
}
|
||||
|
||||
// `prototype` properties for record classes.
|
||||
if (cls.recordShapeRecipe != null) {
|
||||
properties.add(js.Property(js.string(_namer.fixedNames.recordShapeRecipe),
|
||||
cls.recordShapeRecipe!));
|
||||
}
|
||||
if (cls.recordShapeTag != null) {
|
||||
properties.add(js.Property(js.string(_namer.fixedNames.recordShapeTag),
|
||||
js.number(cls.recordShapeTag!)));
|
||||
}
|
||||
|
||||
return js.ObjectInitializer(properties);
|
||||
}
|
||||
|
||||
|
|
|
@ -508,6 +508,43 @@ class ClosureMemberDefinition implements MemberDefinition {
|
|||
String toString() => 'ClosureMemberDefinition(kind:$kind,location:$location)';
|
||||
}
|
||||
|
||||
/// Definition for a Record member. This is almost useless, since there is no
|
||||
/// location or corresponding ir.Node.
|
||||
class RecordMemberDefinition implements MemberDefinition {
|
||||
/// Tag used for identifying serialized [RecordMemberDefinition] objects in a
|
||||
/// debugging data stream.
|
||||
static const String tag = 'record-member-definition';
|
||||
|
||||
@override
|
||||
final SourceSpan location;
|
||||
@override
|
||||
final MemberKind kind;
|
||||
|
||||
@override
|
||||
ir.TreeNode get node => throw UnsupportedError('RecordMemberDefinition.node');
|
||||
|
||||
RecordMemberDefinition(this.location, this.kind);
|
||||
|
||||
factory RecordMemberDefinition.readFromDataSource(
|
||||
DataSourceReader source, MemberKind kind) {
|
||||
source.begin(tag);
|
||||
SourceSpan location = source.readSourceSpan();
|
||||
source.end(tag);
|
||||
return RecordMemberDefinition(location, kind);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.writeEnum(kind);
|
||||
sink.begin(tag);
|
||||
sink.writeSourceSpan(location);
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'RecordMemberDefinition(kind:$kind,location:$location)';
|
||||
}
|
||||
|
||||
void forEachOrderedParameterByFunctionNode(
|
||||
ir.FunctionNode node,
|
||||
ParameterStructure parameterStructure,
|
||||
|
@ -588,6 +625,7 @@ enum ClassKind {
|
|||
// TODO(efortuna, johnniwinther): Context is not a class, but is
|
||||
// masquerading as one currently for consistency with the old element model.
|
||||
context,
|
||||
record,
|
||||
}
|
||||
|
||||
/// Definition information for a [ClassEntity].
|
||||
|
@ -612,6 +650,8 @@ abstract class ClassDefinition {
|
|||
return ClosureClassDefinition.readFromDataSource(source);
|
||||
case ClassKind.context:
|
||||
return ContextContainerDefinition.readFromDataSource(source);
|
||||
case ClassKind.record:
|
||||
return RecordClassDefinition.readFromDataSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -729,3 +769,39 @@ class ContextContainerDefinition implements ClassDefinition {
|
|||
String toString() =>
|
||||
'ContextContainerDefinition(kind:$kind,location:$location)';
|
||||
}
|
||||
|
||||
class RecordClassDefinition implements ClassDefinition {
|
||||
/// Tag used for identifying serialized [RecordClassDefinition] objects in a
|
||||
/// debugging data stream.
|
||||
static const String tag = 'record-class-definition';
|
||||
|
||||
@override
|
||||
final SourceSpan location;
|
||||
|
||||
RecordClassDefinition(this.location);
|
||||
|
||||
factory RecordClassDefinition.readFromDataSource(DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
SourceSpan location = source.readSourceSpan();
|
||||
source.end(tag);
|
||||
return RecordClassDefinition(location);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.writeEnum(ClassKind.record);
|
||||
sink.begin(tag);
|
||||
sink.writeSourceSpan(location);
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
@override
|
||||
ClassKind get kind => ClassKind.record;
|
||||
|
||||
@override
|
||||
ir.Node get node =>
|
||||
throw UnsupportedError('RecordClassDefinition.node for $location');
|
||||
|
||||
@override
|
||||
String toString() => 'RecordClassDefinition(kind:$kind,location:$location)';
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import '../ordered_typeset.dart';
|
|||
import '../serialization/serialization.dart';
|
||||
import '../universe/call_structure.dart';
|
||||
import '../universe/member_usage.dart';
|
||||
import '../universe/record_shape.dart';
|
||||
import '../universe/selector.dart';
|
||||
|
||||
import 'closure.dart';
|
||||
|
@ -54,6 +55,7 @@ import 'elements.dart';
|
|||
import 'element_map.dart';
|
||||
import 'env.dart';
|
||||
import 'locals.dart';
|
||||
import 'records.dart' show JRecordClass, RecordClassData;
|
||||
|
||||
class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
|
||||
/// Tag used for identifying serialized [JsKernelToElementMap] objects in a
|
||||
|
@ -365,7 +367,7 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
|
|||
if (env.cls != null) {
|
||||
classMap[env.cls!] = cls;
|
||||
}
|
||||
if (cls is! JContext && cls is! JClosureClass) {
|
||||
if (cls is! JContext && cls is! JClosureClass && cls is! JRecordClass) {
|
||||
// Synthesized classes are not part of the library environment.
|
||||
libraries.getEnv(cls.library).registerClass(cls.name, env);
|
||||
}
|
||||
|
@ -2166,6 +2168,53 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
|
|||
}
|
||||
return generatorBody;
|
||||
}
|
||||
|
||||
String _nameForShape(RecordShape shape) {
|
||||
final sb = StringBuffer();
|
||||
sb.write('_Record_');
|
||||
sb.write(shape.fieldCount);
|
||||
for (String name in shape.fieldNames) {
|
||||
sb.write('_');
|
||||
// 'hex' escape to remove `$` and `_`.
|
||||
// `send_$_bux` --> `sendx5Fx24x5Fbux78`.
|
||||
sb.write(name
|
||||
.replaceAll(r'x', r'x78')
|
||||
.replaceAll(r'$', r'x24')
|
||||
.replaceAll(r'_', r'x5F'));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
IndexedClass generateRecordShapeClass(
|
||||
RecordShape shape, InterfaceType supertype) {
|
||||
JLibrary library = supertype.element.library as JLibrary;
|
||||
|
||||
String name = _nameForShape(shape);
|
||||
SourceSpan location = SourceSpan.unknown(); // TODO(50081): What to use?
|
||||
|
||||
Map<Name, IndexedMember> memberMap = {};
|
||||
IndexedClass classEntity = JRecordClass(
|
||||
library,
|
||||
name,
|
||||
isAbstract: false,
|
||||
);
|
||||
|
||||
// Create a classData and set up the interfaces and subclass
|
||||
// relationships that _ensureSupertypes and _ensureThisAndRawType are doing
|
||||
InterfaceType thisType =
|
||||
types.interfaceType(classEntity, const <DartType>[]);
|
||||
RecordClassData recordData = RecordClassData(
|
||||
RecordClassDefinition(location),
|
||||
thisType,
|
||||
supertype,
|
||||
getOrderedTypeSet(supertype.element as IndexedClass)
|
||||
.extendClass(types, thisType));
|
||||
classes.register(classEntity, recordData, RecordClassEnv(memberMap));
|
||||
|
||||
// TODO(49718): Implement `==` specialized to the shape.
|
||||
|
||||
return classEntity;
|
||||
}
|
||||
}
|
||||
|
||||
class JsElementEnvironment extends ElementEnvironment
|
||||
|
|
|
@ -12,6 +12,7 @@ import '../elements/types.dart';
|
|||
import '../serialization/serialization.dart';
|
||||
import '../universe/class_set.dart' show ClassHierarchyNodesMapKey;
|
||||
import 'closure.dart';
|
||||
import 'records.dart' show JRecordClass;
|
||||
|
||||
const String jsElementPrefix = 'j:';
|
||||
|
||||
|
@ -53,7 +54,7 @@ class JLibrary extends IndexedLibrary {
|
|||
}
|
||||
|
||||
/// Enum used for identifying [JClass] subclasses in serialization.
|
||||
enum JClassKind { node, closure, context }
|
||||
enum JClassKind { node, closure, context, record }
|
||||
|
||||
class JClass extends IndexedClass with ClassHierarchyNodesMapKey {
|
||||
/// Tag used for identifying serialized [JClass] objects in a
|
||||
|
@ -85,6 +86,8 @@ class JClass extends IndexedClass with ClassHierarchyNodesMapKey {
|
|||
return JClosureClass.readFromDataSource(source);
|
||||
case JClassKind.context:
|
||||
return JContext.readFromDataSource(source);
|
||||
case JClassKind.record:
|
||||
return JRecordClass.readFromDataSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import 'element_map.dart'
|
|||
forEachOrderedParameterByFunctionNode;
|
||||
import 'element_map_impl.dart';
|
||||
import 'elements.dart';
|
||||
import 'records.dart' show RecordClassData;
|
||||
|
||||
/// Environment for fast lookup of component libraries.
|
||||
class JProgramEnv {
|
||||
|
@ -179,7 +180,7 @@ class JLibraryData {
|
|||
}
|
||||
|
||||
/// Enum used for identifying [JClassEnv] subclasses in serialization.
|
||||
enum JClassEnvKind { node, closure, context }
|
||||
enum JClassEnvKind { node, closure, context, record }
|
||||
|
||||
/// Member data for a class.
|
||||
abstract class JClassEnv {
|
||||
|
@ -193,6 +194,8 @@ abstract class JClassEnv {
|
|||
return ClosureClassEnv.readFromDataSource(source);
|
||||
case JClassEnvKind.context:
|
||||
return ContextEnv.readFromDataSource(source);
|
||||
case JClassEnvKind.record:
|
||||
return RecordClassEnv.readFromDataSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,8 +407,71 @@ class ClosureClassEnv extends ContextEnv {
|
|||
}
|
||||
}
|
||||
|
||||
class RecordClassEnv implements JClassEnv {
|
||||
/// Tag used for identifying serialized [ContextEnv] objects in a debugging
|
||||
/// data stream.
|
||||
static const String tag = 'record-env';
|
||||
|
||||
final Map<Name, IndexedMember> _memberMap;
|
||||
|
||||
RecordClassEnv(this._memberMap);
|
||||
|
||||
factory RecordClassEnv.readFromDataSource(DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
Map<Name, IndexedMember> _memberMap =
|
||||
source.readNameMap(() => source.readMember() as IndexedMember)!;
|
||||
source.end(tag);
|
||||
return RecordClassEnv(_memberMap);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.writeEnum(JClassEnvKind.record);
|
||||
sink.begin(tag);
|
||||
sink.writeNameMap(
|
||||
_memberMap, (IndexedMember member) => sink.writeMember(member));
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachConstructorBody(void f(ConstructorBodyEntity constructor)) {
|
||||
// We do not create constructor bodies for containers.
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachConstructor(
|
||||
IrToElementMap elementMap, void f(ConstructorEntity constructor)) {
|
||||
// We do not create constructors for containers.
|
||||
}
|
||||
|
||||
@override
|
||||
ConstructorEntity? lookupConstructor(IrToElementMap elementMap, String name) {
|
||||
// We do not create constructors for containers.
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachMember(IrToElementMap elementMap, void f(MemberEntity member)) {
|
||||
_memberMap.values.forEach(f);
|
||||
}
|
||||
|
||||
@override
|
||||
MemberEntity? lookupMember(IrToElementMap elementMap, Name name) {
|
||||
return _memberMap[name];
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isUnnamedMixinApplication => false;
|
||||
|
||||
@override
|
||||
bool get isMixinApplicationWithMembers => false;
|
||||
|
||||
@override
|
||||
ir.Class? get cls => null;
|
||||
}
|
||||
|
||||
/// Enum used for identifying [JClassData] subclasses in serialization.
|
||||
enum JClassDataKind { node, closure, context }
|
||||
enum JClassDataKind { node, closure, context, record }
|
||||
|
||||
abstract class JClassData {
|
||||
/// Deserializes a [JClassData] object from [source].
|
||||
|
@ -418,6 +484,8 @@ abstract class JClassData {
|
|||
return ClosureClassData.readFromDataSource(source);
|
||||
case JClassDataKind.context:
|
||||
return ContextClassData.readFromDataSource(source);
|
||||
case JClassDataKind.record:
|
||||
return RecordClassData.readFromDataSource(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ import 'element_map_impl.dart';
|
|||
import 'js_world.dart';
|
||||
import 'js_world_builder.dart' show JClosedWorldBuilder;
|
||||
import 'locals.dart';
|
||||
import 'records.dart' show RecordDataBuilder;
|
||||
|
||||
/// JS Strategy pattern that defines the element model used in type inference
|
||||
/// and code generation.
|
||||
|
@ -170,9 +171,12 @@ class JsBackendStrategy {
|
|||
closedWorld.annotationsData);
|
||||
ClosureDataBuilder closureDataBuilder = ClosureDataBuilder(
|
||||
_compiler.reporter, _elementMap, closedWorld.annotationsData);
|
||||
RecordDataBuilder recordDataBuilder = RecordDataBuilder(
|
||||
_compiler.reporter, _elementMap, closedWorld.annotationsData);
|
||||
JClosedWorldBuilder closedWorldBuilder = JClosedWorldBuilder(
|
||||
_elementMap,
|
||||
closureDataBuilder,
|
||||
recordDataBuilder,
|
||||
_compiler.options,
|
||||
_compiler.reporter,
|
||||
_compiler.abstractValueStrategy);
|
||||
|
|
|
@ -31,6 +31,10 @@ abstract class JsToFrontendMap {
|
|||
ConstantValue? toBackendConstant(ConstantValue? value,
|
||||
{bool allowNull = false});
|
||||
|
||||
Set<DartType> toBackendTypeSet(Iterable<DartType> set) {
|
||||
return {for (final type in set.map(toBackendType)) type!};
|
||||
}
|
||||
|
||||
Set<LibraryEntity> toBackendLibrarySet(Iterable<LibraryEntity> set) {
|
||||
return set.map(toBackendLibrary).toSet();
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import '../world.dart';
|
|||
import 'element_map.dart';
|
||||
import 'element_map_impl.dart';
|
||||
import 'locals.dart';
|
||||
import 'records.dart' show RecordData;
|
||||
|
||||
class JClosedWorld implements World {
|
||||
static const String tag = 'closed-world';
|
||||
|
@ -109,6 +110,7 @@ class JClosedWorld implements World {
|
|||
final JFieldAnalysis fieldAnalysis;
|
||||
final AnnotationsData annotationsData;
|
||||
final ClosureData closureDataLookup;
|
||||
final RecordData recordData;
|
||||
final OutputUnitData outputUnitData;
|
||||
|
||||
/// The [Sorter] used for sorting elements in the generated code.
|
||||
|
@ -137,6 +139,7 @@ class JClosedWorld implements World {
|
|||
AbstractValueStrategy abstractValueStrategy,
|
||||
this.annotationsData,
|
||||
this.closureDataLookup,
|
||||
this.recordData,
|
||||
this.outputUnitData,
|
||||
this.memberAccess) {
|
||||
_abstractValueDomain = abstractValueStrategy.createDomain(this);
|
||||
|
@ -188,6 +191,7 @@ class JClosedWorld implements World {
|
|||
|
||||
ClosureData closureData =
|
||||
ClosureData.readFromDataSource(elementMap, source);
|
||||
RecordData recordData = RecordData.readFromDataSource(elementMap, source);
|
||||
|
||||
OutputUnitData outputUnitData = OutputUnitData.readFromDataSource(source);
|
||||
elementMap.lateOutputUnitDataBuilder =
|
||||
|
@ -219,6 +223,7 @@ class JClosedWorld implements World {
|
|||
abstractValueStrategy,
|
||||
annotationsData,
|
||||
closureData,
|
||||
recordData,
|
||||
outputUnitData,
|
||||
memberAccess);
|
||||
}
|
||||
|
@ -247,6 +252,7 @@ class JClosedWorld implements World {
|
|||
(Set<ClassEntity> set) => sink.writeClasses(set));
|
||||
annotationsData.writeToDataSink(sink);
|
||||
closureDataLookup.writeToDataSink(sink);
|
||||
recordData.writeToDataSink(sink);
|
||||
outputUnitData.writeToDataSink(sink);
|
||||
sink.writeMemberMap(
|
||||
memberAccess,
|
||||
|
|
|
@ -29,12 +29,14 @@ import '../universe/class_hierarchy.dart';
|
|||
import '../universe/class_set.dart';
|
||||
import '../universe/feature.dart';
|
||||
import '../universe/member_usage.dart';
|
||||
import '../universe/record_shape.dart';
|
||||
import '../universe/selector.dart';
|
||||
import 'closure.dart';
|
||||
import 'elements.dart';
|
||||
import 'element_map_impl.dart';
|
||||
import 'js_to_frontend_map.dart';
|
||||
import 'js_world.dart';
|
||||
import 'records.dart';
|
||||
|
||||
class JClosedWorldBuilder {
|
||||
final JsKernelToElementMap _elementMap;
|
||||
|
@ -42,12 +44,18 @@ class JClosedWorldBuilder {
|
|||
ClassHierarchyNodesMap();
|
||||
final Map<ClassEntity, ClassSet> _classSets = <ClassEntity, ClassSet>{};
|
||||
final ClosureDataBuilder _closureDataBuilder;
|
||||
final RecordDataBuilder _recordDataBuilder;
|
||||
final CompilerOptions _options;
|
||||
final DiagnosticReporter _reporter;
|
||||
final AbstractValueStrategy _abstractValueStrategy;
|
||||
|
||||
JClosedWorldBuilder(this._elementMap, this._closureDataBuilder, this._options,
|
||||
this._reporter, this._abstractValueStrategy);
|
||||
JClosedWorldBuilder(
|
||||
this._elementMap,
|
||||
this._closureDataBuilder,
|
||||
this._recordDataBuilder,
|
||||
this._options,
|
||||
this._reporter,
|
||||
this._abstractValueStrategy);
|
||||
|
||||
ElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
|
||||
CommonElements get _commonElements => _elementMap.commonElements;
|
||||
|
@ -136,6 +144,11 @@ class JClosedWorldBuilder {
|
|||
|
||||
List<FunctionEntity> callMethods = <FunctionEntity>[];
|
||||
ClosureData closureData;
|
||||
RecordData recordData;
|
||||
|
||||
final recordTypes = Set<RecordType>.from(
|
||||
map.toBackendTypeSet(closedWorld.instantiatedRecordTypes));
|
||||
|
||||
if (_options.disableRtiOptimization) {
|
||||
rtiNeed = TrivialRuntimeTypesNeed(_elementMap.elementEnvironment);
|
||||
closureData = _closureDataBuilder.createClosureEntities(
|
||||
|
@ -143,6 +156,7 @@ class JClosedWorldBuilder {
|
|||
map.toBackendMemberMap(closureModels, identity),
|
||||
const TrivialClosureRtiNeed(),
|
||||
callMethods);
|
||||
recordData = _recordDataBuilder.createRecordData(this, recordTypes);
|
||||
} else {
|
||||
RuntimeTypesNeedImpl kernelRtiNeed =
|
||||
closedWorld.rtiNeed as RuntimeTypesNeedImpl;
|
||||
|
@ -170,6 +184,8 @@ class JClosedWorldBuilder {
|
|||
localFunctionsNodesNeedingSignature),
|
||||
callMethods);
|
||||
|
||||
recordData = _recordDataBuilder.createRecordData(this, recordTypes);
|
||||
|
||||
List<FunctionEntity> callMethodsNeedingSignature = <FunctionEntity>[];
|
||||
for (ir.LocalFunction node in localFunctionsNodesNeedingSignature) {
|
||||
callMethodsNeedingSignature
|
||||
|
@ -188,6 +204,7 @@ class JClosedWorldBuilder {
|
|||
}
|
||||
|
||||
(map as JsToFrontendMapImpl)._registerClosureData(closureData);
|
||||
//(map as JsToFrontendMapImpl)._registerRecordData(recordData);
|
||||
|
||||
BackendUsage backendUsage =
|
||||
_convertBackendUsage(map, closedWorld.backendUsage as BackendUsageImpl);
|
||||
|
@ -239,6 +256,7 @@ class JClosedWorldBuilder {
|
|||
_abstractValueStrategy,
|
||||
annotationsData,
|
||||
closureData,
|
||||
recordData,
|
||||
outputUnitData,
|
||||
memberAccess);
|
||||
}
|
||||
|
@ -365,6 +383,27 @@ class JClosedWorldBuilder {
|
|||
return _commonElements.closureClass;
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
ClassEntity superclass = _commonElements.recordArityClass(shape.fieldCount);
|
||||
IndexedClass recordClass = _elementMap.generateRecordShapeClass(
|
||||
shape,
|
||||
_dartTypes.interfaceType(superclass, const []),
|
||||
);
|
||||
|
||||
// Tell the hierarchy about the superclass so we can use
|
||||
// .getSupertypes(class)
|
||||
ClassHierarchyNode parentNode = _classHierarchyNodes[superclass]!;
|
||||
ClassHierarchyNode node = ClassHierarchyNode(
|
||||
parentNode, recordClass, parentNode.hierarchyDepth + 1);
|
||||
_classHierarchyNodes[recordClass] = node;
|
||||
_classSets[recordClass] = ClassSet(node);
|
||||
node.isDirectlyInstantiated = true;
|
||||
|
||||
return recordClass;
|
||||
}
|
||||
|
||||
OutputUnitData _convertOutputUnitData(JsToFrontendMapImpl map,
|
||||
OutputUnitData data, ClosureData closureDataLookup) {
|
||||
// Convert front-end maps containing K-class and K-local function keys to a
|
||||
|
|
336
pkg/compiler/lib/src/js_model/records.dart
Normal file
336
pkg/compiler/lib/src/js_model/records.dart
Normal file
|
@ -0,0 +1,336 @@
|
|||
// 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.
|
||||
|
||||
/// Records are implemented as classes introduced in the K- to J- lowering.
|
||||
///
|
||||
/// The record classes are arranged in a hierarchy, with single base class. The
|
||||
/// base class has subclasses for each 'basic' shape, the number of fields. This
|
||||
/// is called the 'arity' class. The arity class has subclasses for each actual
|
||||
/// record shape. There can be further subclasses of the shape class to allow
|
||||
/// specialization on the basis of the value stored in the field.
|
||||
///
|
||||
/// Example
|
||||
///
|
||||
/// _Record - base class
|
||||
///
|
||||
/// _Record2 - class for Record arity (number of fields)
|
||||
///
|
||||
/// _Record_2_end_start - class for shape `(start:, end:)`
|
||||
///
|
||||
/// _Record_2_end_start__int_int - class for specialization within
|
||||
/// shape when the field are known to in `int`s. This allows more
|
||||
/// efficient `==` and `.hashCode` operations.
|
||||
///
|
||||
/// RecordDataBuilder creates the new classes. The arity classes exist as Dart
|
||||
/// code in `js_runtime/lib/records.dart`. RecordDataBuilder creates shape
|
||||
/// classes and specialization classes.
|
||||
///
|
||||
/// (Specialization classes have not yet been implemented).
|
||||
library dart2js.js_model.records;
|
||||
|
||||
import '../common.dart';
|
||||
import '../elements/entities.dart';
|
||||
import '../elements/names.dart';
|
||||
import '../elements/types.dart';
|
||||
|
||||
import '../js_backend/annotations.dart';
|
||||
import '../js_model/element_map.dart';
|
||||
import '../ordered_typeset.dart';
|
||||
import '../serialization/serialization.dart';
|
||||
import '../universe/record_shape.dart';
|
||||
import 'elements.dart';
|
||||
import 'env.dart';
|
||||
import 'js_world_builder.dart' show JClosedWorldBuilder;
|
||||
|
||||
class RecordData {
|
||||
/// Tag used for identifying serialized [RecordData] objects in a
|
||||
/// debugging data stream.
|
||||
static const String tag = 'record-data';
|
||||
|
||||
final JsToElementMap _elementMap;
|
||||
final List<RecordRepresentation> _representations;
|
||||
|
||||
final Map<ClassEntity, RecordRepresentation> _classToRepresentation = {};
|
||||
final Map<RecordShape, RecordRepresentation> _shapeToRepresentation = {};
|
||||
|
||||
RecordData._(this._elementMap, this._representations) {
|
||||
// Unpack representations into lookup maps.
|
||||
for (final info in _representations) {
|
||||
_classToRepresentation[info.cls] = info;
|
||||
if (info.definesShape) _shapeToRepresentation[info.shape] = info;
|
||||
}
|
||||
}
|
||||
|
||||
factory RecordData.readFromDataSource(
|
||||
JsToElementMap elementMap, DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
List<RecordRepresentation> representations =
|
||||
source.readList(() => RecordRepresentation.readFromDataSource(source));
|
||||
source.end(tag);
|
||||
return RecordData._(elementMap, representations);
|
||||
}
|
||||
|
||||
/// Serializes this [RecordData] to [sink].
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.begin(tag);
|
||||
sink.writeList<RecordRepresentation>(
|
||||
_representations, (info) => info.writeToDataSink(sink));
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
RecordRepresentation representationForShape(RecordShape shape) {
|
||||
return _shapeToRepresentation[shape] ??
|
||||
(throw StateError('representationForShape $shape'));
|
||||
}
|
||||
|
||||
RecordRepresentation representationForStaticType(RecordType type) {
|
||||
// TODO(49718): Implement specialization when fields have types that allow
|
||||
// better code for `==` and `.hashCode`.
|
||||
return representationForShape(type.shape);
|
||||
}
|
||||
|
||||
/// Returns `null` if [cls] is not a record representation.
|
||||
RecordRepresentation? representationForClass(ClassEntity cls) {
|
||||
return _classToRepresentation[cls];
|
||||
}
|
||||
|
||||
/// Returns field and possibly index for accessing into a shape.
|
||||
RecordAccessPath pathForAccess(RecordShape shape, int indexInShape) {
|
||||
// TODO(sra): Cache lookup.
|
||||
final representation = representationForShape(shape);
|
||||
final cls = representation.cls;
|
||||
if (representation.usesList) {
|
||||
final field = _elementMap.elementEnvironment
|
||||
.lookupClassMember(cls, Name('_values', cls.library.canonicalUri));
|
||||
return RecordAccessPath(field as FieldEntity, indexInShape);
|
||||
} else {
|
||||
final field = _elementMap.elementEnvironment.lookupClassMember(
|
||||
cls, Name('_$indexInShape', cls.library.canonicalUri));
|
||||
return RecordAccessPath(field as FieldEntity, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How to access a field of a record. Currently there are two forms, a single
|
||||
/// field acccess (e.g. `r._2`), used for small records, or a field access
|
||||
/// followed by an indexing, used for records that hold the values in a JSArray
|
||||
/// (e.g. `r._values[2]`).
|
||||
class RecordAccessPath {
|
||||
final FieldEntity field;
|
||||
final int? index; // `null` for single field access.
|
||||
RecordAccessPath(this.field, this.index);
|
||||
}
|
||||
|
||||
class RecordRepresentation {
|
||||
static const String tag = 'record-class-info';
|
||||
|
||||
/// There is one [RecordRepresentation] per class.
|
||||
final ClassEntity cls;
|
||||
|
||||
/// The record shape of [cls]. There can be many classes defining records of
|
||||
/// the same shape, for example, when there are specializations of a record
|
||||
/// shape.
|
||||
final RecordShape shape;
|
||||
|
||||
/// [definesShape] is `true` if this record class is a shape class. There may
|
||||
/// be subclasses of this class which share the same shape. In this case
|
||||
/// [definesShape] is `false`, as the subclasses can inherit shape metadata.
|
||||
///
|
||||
/// A shape class defines some metadata properties on the prototype:
|
||||
///
|
||||
/// (1) The shapeTag, a small integer (see below).
|
||||
/// (2) The the top-type recipe for the shape. The recipe is a function of the
|
||||
/// [shape]. e.g. `"+end,start(@,@)"`.
|
||||
final bool definesShape;
|
||||
|
||||
/// `true` if this class is based on the general record class that uses a
|
||||
/// `List` to store the fields.
|
||||
final bool usesList;
|
||||
|
||||
/// [shapeTag] is a small integer that is a function of the shape. The
|
||||
/// shapeTag can be used as an index into runtime computed derived data.
|
||||
final int shapeTag;
|
||||
|
||||
/// This is non-null for a specialization subclass of a shape class.
|
||||
// TODO(50081): This is a placeholder for the specialization key. We might do
|
||||
// something like interceptors, where 'i' means 'int', 's' means 'string', so
|
||||
// they key 's_i_is' would be a specialization for a record where the fields
|
||||
// are {int}, {string} and {int,string}. Operator `==` could be specialized to
|
||||
// use `===` for each field, but `.hashCode` would need a dispatch for the
|
||||
// last field. Or we could do something completely different like have a
|
||||
// `List` of inferred types.
|
||||
final String? _specializationKey;
|
||||
|
||||
RecordRepresentation._(this.cls, this.shape, this.definesShape, this.usesList,
|
||||
this.shapeTag, this._specializationKey);
|
||||
|
||||
factory RecordRepresentation.readFromDataSource(DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
final cls = source.readClass();
|
||||
final shape = RecordShape.readFromDataSource(source);
|
||||
final definesShape = source.readBool();
|
||||
final usesList = source.readBool();
|
||||
final shapeTag = source.readInt();
|
||||
final specializationKey = source.readStringOrNull();
|
||||
source.end(tag);
|
||||
return RecordRepresentation._(
|
||||
cls, shape, definesShape, usesList, shapeTag, specializationKey);
|
||||
}
|
||||
|
||||
/// Serializes this [RecordData] to [sink].
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.begin(tag);
|
||||
sink.writeClass(cls);
|
||||
shape.writeToDataSink(sink);
|
||||
sink.writeBool(definesShape);
|
||||
sink.writeBool(usesList);
|
||||
sink.writeInt(shapeTag);
|
||||
sink.writeStringOrNull(_specializationKey);
|
||||
sink.end(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion of records to classes.
|
||||
class RecordDataBuilder {
|
||||
final DiagnosticReporter _reporter;
|
||||
final JsToElementMap _elementMap;
|
||||
final AnnotationsData _annotationsData;
|
||||
|
||||
RecordDataBuilder(this._reporter, this._elementMap, this._annotationsData);
|
||||
|
||||
RecordData createRecordData(JClosedWorldBuilder closedWorldBuilder,
|
||||
Iterable<RecordType> recordTypes) {
|
||||
_reporter;
|
||||
_annotationsData;
|
||||
|
||||
// Sorted shapes lead to a more consistent class ordering in the generated
|
||||
// code.
|
||||
final shapes = recordTypes.map((type) => type.shape).toSet().toList()
|
||||
..sort(RecordShape.compare);
|
||||
|
||||
List<RecordRepresentation> representations = [];
|
||||
for (int i = 0; i < shapes.length; i++) {
|
||||
final shape = shapes[i];
|
||||
final cls = closedWorldBuilder.buildRecordShapeClass(shape);
|
||||
int shapeTag = i;
|
||||
bool usesList = _computeUsesGeneralClass(cls);
|
||||
final info =
|
||||
RecordRepresentation._(cls, shape, true, usesList, shapeTag, null);
|
||||
representations.add(info);
|
||||
}
|
||||
|
||||
return RecordData._(
|
||||
_elementMap,
|
||||
representations,
|
||||
);
|
||||
}
|
||||
|
||||
bool _computeUsesGeneralClass(ClassEntity? cls) {
|
||||
while (cls != null) {
|
||||
if (cls == _elementMap.commonElements.recordGeneralBaseClass) return true;
|
||||
cls = _elementMap.elementEnvironment.getSuperClass(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sra): Use a regular JClass with a different Definition?
|
||||
class JRecordClass extends JClass {
|
||||
/// Tag used for identifying serialized [JRecordClass] objects in a
|
||||
/// debugging data stream.
|
||||
static const String tag = 'record-class';
|
||||
|
||||
JRecordClass(super.library, super.name, {required super.isAbstract});
|
||||
|
||||
factory JRecordClass.readFromDataSource(DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
JLibrary library = source.readLibrary() as JLibrary;
|
||||
String name = source.readString();
|
||||
bool isAbstract = source.readBool();
|
||||
source.end(tag);
|
||||
return JRecordClass(library, name, isAbstract: isAbstract);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.writeEnum(JClassKind.record);
|
||||
sink.begin(tag);
|
||||
sink.writeLibrary(library);
|
||||
sink.writeString(name);
|
||||
sink.writeBool(isAbstract);
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '${jsElementPrefix}record_class($name)';
|
||||
}
|
||||
|
||||
class RecordClassData implements JClassData {
|
||||
/// Tag used for identifying serialized [RecordClassData] objects in a
|
||||
/// debugging data stream.
|
||||
static const String tag = 'record-class-data';
|
||||
|
||||
@override
|
||||
final ClassDefinition definition;
|
||||
|
||||
@override
|
||||
final InterfaceType? thisType;
|
||||
|
||||
@override
|
||||
final OrderedTypeSet orderedTypeSet;
|
||||
|
||||
@override
|
||||
final InterfaceType? supertype;
|
||||
|
||||
RecordClassData(
|
||||
this.definition, this.thisType, this.supertype, this.orderedTypeSet);
|
||||
|
||||
factory RecordClassData.readFromDataSource(DataSourceReader source) {
|
||||
source.begin(tag);
|
||||
ClassDefinition definition = ClassDefinition.readFromDataSource(source);
|
||||
InterfaceType thisType = source.readDartType() as InterfaceType;
|
||||
InterfaceType supertype = source.readDartType() as InterfaceType;
|
||||
OrderedTypeSet orderedTypeSet = OrderedTypeSet.readFromDataSource(source);
|
||||
source.end(tag);
|
||||
return RecordClassData(definition, thisType, supertype, orderedTypeSet);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToDataSink(DataSinkWriter sink) {
|
||||
sink.writeEnum(JClassDataKind.record);
|
||||
sink.begin(tag);
|
||||
definition.writeToDataSink(sink);
|
||||
sink.writeDartType(thisType!);
|
||||
sink.writeDartType(supertype!);
|
||||
orderedTypeSet.writeToDataSink(sink);
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isMixinApplication => false;
|
||||
|
||||
@override
|
||||
bool get isEnumClass => false;
|
||||
|
||||
@override
|
||||
FunctionType? get callType => null;
|
||||
|
||||
@override
|
||||
List<InterfaceType> get interfaces => const <InterfaceType>[];
|
||||
|
||||
@override
|
||||
InterfaceType? get mixedInType => null;
|
||||
|
||||
@override
|
||||
InterfaceType? get jsInteropType => thisType;
|
||||
|
||||
@override
|
||||
InterfaceType? get rawType => thisType;
|
||||
|
||||
@override
|
||||
InterfaceType? get instantiationToBounds => thisType;
|
||||
|
||||
@override
|
||||
List<Variance> getVariances() => [];
|
||||
}
|
|
@ -119,7 +119,8 @@ class Dart2jsTarget extends Target {
|
|||
(uri.path == 'core' ||
|
||||
uri.path == 'typed_data' ||
|
||||
uri.path == '_interceptors' ||
|
||||
uri.path == '_native_typed_data');
|
||||
uri.path == '_native_typed_data' ||
|
||||
uri.path == '_js_helper');
|
||||
|
||||
@override
|
||||
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
|
||||
|
|
|
@ -307,6 +307,7 @@ class KernelImpactConverter implements ImpactRegistry {
|
|||
@override
|
||||
void registerRecordLiteral(ir.RecordType recordType,
|
||||
{required bool isConst}) {
|
||||
registerBackendImpact(_impacts.recordInstantiation);
|
||||
final type = elementMap.getDartType(recordType) as RecordType;
|
||||
impactBuilder.registerTypeUse(TypeUse.recordInstantiation(type));
|
||||
}
|
||||
|
|
|
@ -240,7 +240,8 @@ class ObjectsInMemorySerializationStrategy
|
|||
DataSourceIndices indices,
|
||||
List<Object> globalTypeInferenceResultsData) {
|
||||
DataSourceReader globalTypeInferenceResultsSource = DataSourceReader(
|
||||
ObjectDataSource(globalTypeInferenceResultsData), options);
|
||||
ObjectDataSource(globalTypeInferenceResultsData), options,
|
||||
useDataKinds: useDataKinds);
|
||||
return DataAndIndices(
|
||||
deserializeGlobalTypeInferenceResultsFromSource(
|
||||
options,
|
||||
|
|
|
@ -44,6 +44,7 @@ import '../js_model/js_strategy.dart';
|
|||
import '../js_model/js_world.dart' show JClosedWorld;
|
||||
import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor;
|
||||
import '../js_model/type_recipe.dart';
|
||||
import '../js_model/records.dart' show RecordData;
|
||||
import '../kernel/invocation_mirror_constants.dart';
|
||||
import '../native/behavior.dart';
|
||||
import '../native/js.dart';
|
||||
|
@ -51,6 +52,7 @@ import '../options.dart';
|
|||
import '../tracer.dart';
|
||||
import '../universe/call_structure.dart';
|
||||
import '../universe/feature.dart';
|
||||
import '../universe/record_shape.dart';
|
||||
import '../universe/selector.dart';
|
||||
import '../universe/target_checks.dart' show TargetChecks;
|
||||
import '../universe/use.dart' show ConstantUse, StaticUse, TypeUse;
|
||||
|
@ -122,6 +124,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
|
|||
final JClosedWorld closedWorld;
|
||||
final CodegenRegistry registry;
|
||||
final ClosureData _closureDataLookup;
|
||||
final RecordData _recordData;
|
||||
final Tracer _tracer;
|
||||
|
||||
/// A stack of [InterfaceType]s that have been seen during inlining of
|
||||
|
@ -180,6 +183,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
|
|||
this._inlineDataCache)
|
||||
: this.targetElement = _effectiveTargetElementFor(_initialTargetElement),
|
||||
this._closureDataLookup = closedWorld.closureDataLookup,
|
||||
this._recordData = closedWorld.recordData,
|
||||
_memberContextNode =
|
||||
_elementMap.getMemberContextNode(_initialTargetElement) {
|
||||
_enterFrame(targetElement, null);
|
||||
|
@ -1733,7 +1737,7 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
|
|||
|
||||
@override
|
||||
void defaultNode(ir.Node node) {
|
||||
throw UnsupportedError("Unhandled node $node (${node.runtimeType})");
|
||||
throw UnsupportedError('Unhandled node $node (${node.runtimeType})');
|
||||
}
|
||||
|
||||
/// Returns the current source element. This is used by the type builder.
|
||||
|
@ -3385,6 +3389,99 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
|
|||
'ir.MapEntry should be handled in visitMapLiteral');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRecordLiteral(ir.RecordLiteral node) {
|
||||
SourceInformation? sourceInformation =
|
||||
_sourceInformationBuilder.buildCreate(node);
|
||||
assert(!node.isConst);
|
||||
|
||||
List<HInstruction> inputs = [];
|
||||
for (ir.Expression expression in node.positional) {
|
||||
expression.accept(this);
|
||||
inputs.add(pop());
|
||||
}
|
||||
for (ir.NamedExpression namedExpression in node.named) {
|
||||
namedExpression.value.accept(this);
|
||||
inputs.add(pop());
|
||||
}
|
||||
|
||||
// TODO(50701): Choose class depending in inferred type of record fields
|
||||
// which might be better than the static type.
|
||||
RecordType dartType =
|
||||
_elementMap.getDartType(node.recordType) as RecordType;
|
||||
if (dartType.containsTypeVariables) {
|
||||
dartType = localsHandler.substInContext(dartType) as RecordType;
|
||||
}
|
||||
|
||||
final recordRepresentation =
|
||||
_recordData.representationForStaticType(dartType);
|
||||
ClassEntity recordClass = recordRepresentation.cls;
|
||||
|
||||
if (recordRepresentation.usesList) {
|
||||
// TODO(50081): Can we use `.constListType`?
|
||||
push(HLiteralList(inputs, _abstractValueDomain.fixedListType));
|
||||
inputs = [pop()];
|
||||
}
|
||||
|
||||
AbstractValue type = _abstractValueDomain.createNonNullExact(recordClass);
|
||||
|
||||
final allocation = HCreate(recordClass, inputs, type, sourceInformation);
|
||||
|
||||
// TODO(50701): With traced record types there might be a better type.
|
||||
// AbstractValue type =
|
||||
// _typeInferenceMap.typeOfRecordLiteral(node, _abstractValueDomain);
|
||||
// if (_abstractValueDomain.containsAll(type).isDefinitelyFalse) {
|
||||
// allocation.instructionType = type;
|
||||
// }
|
||||
push(allocation);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRecordIndexGet(ir.RecordIndexGet node) {
|
||||
final shape = recordShapeOfRecordType(node.receiverType);
|
||||
return _handleRecordFieldGet(node, node.receiver, shape, node.index);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRecordNameGet(ir.RecordNameGet node) {
|
||||
final shape = recordShapeOfRecordType(node.receiverType);
|
||||
int index = shape.indexOfName(node.name);
|
||||
return _handleRecordFieldGet(node, node.receiver, shape, index);
|
||||
}
|
||||
|
||||
void _handleRecordFieldGet(ir.Expression node, ir.TreeNode receiverNode,
|
||||
RecordShape shape, int indexInShape) {
|
||||
receiverNode.accept(this);
|
||||
HInstruction receiver = pop();
|
||||
|
||||
SourceInformation? sourceInformation =
|
||||
_sourceInformationBuilder.buildGet(node);
|
||||
|
||||
// TODO(50701): Type inference should improve on the static type.
|
||||
// AbstractValue type =
|
||||
// _typeInferenceMap.typeOfRecordGet(node, _abstractValueDomain);
|
||||
StaticType staticType = _getStaticType(node);
|
||||
AbstractValue type = _abstractValueDomain
|
||||
.createFromStaticType(staticType.type,
|
||||
classRelation: staticType.relation, nullable: true)
|
||||
.abstractValue;
|
||||
|
||||
final path = _recordData.pathForAccess(shape, indexInShape);
|
||||
if (path.index == null) {
|
||||
HFieldGet fieldGet = HFieldGet(
|
||||
path.field, receiver, type, sourceInformation,
|
||||
isAssignable: false);
|
||||
push(fieldGet);
|
||||
} else {
|
||||
HFieldGet fieldGet = HFieldGet(path.field, receiver,
|
||||
_abstractValueDomain.constListType, sourceInformation,
|
||||
isAssignable: false);
|
||||
push(fieldGet);
|
||||
final list = pop();
|
||||
push(HIndex(list, graph.addConstantInt(indexInShape, closedWorld), type));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTypeLiteral(ir.TypeLiteral node) {
|
||||
final sourceInformation = _sourceInformationBuilder.buildGet(node);
|
||||
|
|
|
@ -33,6 +33,7 @@ import '../js_backend/type_reference.dart' show TypeReference;
|
|||
import '../js_emitter/js_emitter.dart' show ModularEmitter;
|
||||
import '../js_model/elements.dart' show JGeneratorBody;
|
||||
import '../js_model/js_world.dart' show JClosedWorld;
|
||||
import '../js_model/records.dart' show JRecordClass;
|
||||
import '../js_model/type_recipe.dart';
|
||||
import '../native/behavior.dart';
|
||||
import '../options.dart';
|
||||
|
@ -2414,6 +2415,11 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
|
|||
// ignore:deprecated_member_use_from_same_package
|
||||
.registerInstantiatedClass(node.element);
|
||||
}
|
||||
if (node.element is JRecordClass) {
|
||||
_registry
|
||||
// ignore:deprecated_member_use_from_same_package
|
||||
.registerInstantiatedClass(node.element);
|
||||
}
|
||||
node.instantiatedTypes?.forEach(_registry.registerInstantiation);
|
||||
final callMethod = node.callMethod;
|
||||
if (callMethod != null) {
|
||||
|
|
|
@ -65,6 +65,12 @@ enum JsGetName {
|
|||
|
||||
/// Property name for Rti._is field.
|
||||
RTI_FIELD_IS,
|
||||
|
||||
/// Property name for shape tag property in record class prototype.
|
||||
RECORD_SHAPE_TAG_PROPERTY,
|
||||
|
||||
/// Property name for shape recipe property in record class prototype.
|
||||
RECORD_SHAPE_TYPE_PROPERTY,
|
||||
}
|
||||
|
||||
enum JsBuiltin {
|
||||
|
|
|
@ -77,6 +77,7 @@ part 'native_helper.dart';
|
|||
part 'regexp_helper.dart';
|
||||
part 'string_helper.dart';
|
||||
part 'linked_hash_map.dart';
|
||||
part 'records.dart';
|
||||
|
||||
/// Marks the internal map in dart2js, so that internal libraries can is-check
|
||||
/// them.
|
||||
|
|
248
sdk/lib/_internal/js_runtime/lib/records.dart
Normal file
248
sdk/lib/_internal/js_runtime/lib/records.dart
Normal file
|
@ -0,0 +1,248 @@
|
|||
// 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.
|
||||
|
||||
part of _js_helper;
|
||||
|
||||
/// Base class for all records.
|
||||
abstract class _Record implements Record {
|
||||
const _Record();
|
||||
|
||||
int get _shapeTag => JS('JSUInt31', '#[#]', this,
|
||||
JS_GET_NAME(JsGetName.RECORD_SHAPE_TAG_PROPERTY));
|
||||
|
||||
bool _sameShape(_Record other) => _shapeTag == other._shapeTag;
|
||||
|
||||
/// Field values in canonical order.
|
||||
// TODO(50081): Replace with a Map view.
|
||||
List<Object?> _getFieldValues();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final keys = _fieldKeys();
|
||||
final values = _getFieldValues();
|
||||
assert(keys.length == values.length);
|
||||
final sb = StringBuffer();
|
||||
String separator = '';
|
||||
sb.write('(');
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
sb.write(separator);
|
||||
Object key = keys[i];
|
||||
if (key is String) {
|
||||
sb.write(key);
|
||||
sb.write(': ');
|
||||
}
|
||||
sb.write(values[i]);
|
||||
separator = ', ';
|
||||
}
|
||||
sb.write(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/// Returns a list of integers and strings corresponding to the indexed and
|
||||
/// named fields of this record.
|
||||
List<Object> _fieldKeys() {
|
||||
int shapeTag = _shapeTag;
|
||||
while (_computedFieldKeys.length <= shapeTag) _computedFieldKeys.add(null);
|
||||
return _computedFieldKeys[shapeTag] ??= _computeFieldKeys();
|
||||
}
|
||||
|
||||
List<Object> _computeFieldKeys() {
|
||||
String recipe =
|
||||
JS('', '#[#]', this, JS_GET_NAME(JsGetName.RECORD_SHAPE_TYPE_PROPERTY));
|
||||
|
||||
// TODO(50081): The Rti recipe format is agnostic to what the record shape
|
||||
// key is. We happen to use a comma-separated list of the names for the
|
||||
// named arguments. `"+a,b(@,@,@,@)"` is the 4-record with two named fields
|
||||
// `a` and `b`. We should refactor the code so that rti.dart returns the
|
||||
// arity and the Rti's shape key which are interpreted here.
|
||||
String joinedNames =
|
||||
JS('', '#.substring(1, #.indexOf(#))', recipe, recipe, '(');
|
||||
String atSigns = JS('', '#.replace(/[^@]/g, "")', recipe);
|
||||
int arity = atSigns.length;
|
||||
|
||||
List<Object> result = List.generate(arity, (i) => i);
|
||||
if (joinedNames != '') {
|
||||
List<String> names = joinedNames.split(',');
|
||||
int last = arity;
|
||||
int i = names.length;
|
||||
while (i > 0) result[--last] = names[--i];
|
||||
}
|
||||
|
||||
return List.unmodifiable(result);
|
||||
}
|
||||
|
||||
static final List<List<Object>?> _computedFieldKeys = [];
|
||||
}
|
||||
|
||||
/// The empty record.
|
||||
class _EmptyRecord extends _Record {
|
||||
const _EmptyRecord();
|
||||
|
||||
@override
|
||||
List<Object?> _getFieldValues() => const [];
|
||||
|
||||
@override
|
||||
String toString() => '()';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(other, const _EmptyRecord());
|
||||
|
||||
@override
|
||||
int get hashCode => 43 * 67;
|
||||
}
|
||||
|
||||
/// Base class for all records with two fields.
|
||||
// TODO(49718): Generate this class.
|
||||
class _Record2 extends _Record {
|
||||
final Object? _0;
|
||||
final Object? _1;
|
||||
|
||||
_Record2(this._0, this._1);
|
||||
|
||||
@override
|
||||
List<Object?> _getFieldValues() => [_0, _1];
|
||||
|
||||
bool _equalFields(_Record2 other) {
|
||||
return _0 == other._0 && _1 == other._1;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO(49718): Add specializations in shape class that combines is-check with
|
||||
// shape check.
|
||||
//
|
||||
// TODO(49718): Add specializations in type specialization that that combines
|
||||
// is-check with shape check and inlines and specializes `_equalFields`.
|
||||
bool operator ==(Object other) {
|
||||
return other is _Record2 && _sameShape(other) && _equalFields(other);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(_shapeTag, _0, _1);
|
||||
}
|
||||
|
||||
class _Record1 extends _Record {
|
||||
final Object? _0;
|
||||
|
||||
_Record1(this._0);
|
||||
|
||||
@override
|
||||
List<Object?> _getFieldValues() => [_0];
|
||||
|
||||
bool _equalFields(_Record1 other) {
|
||||
return _0 == other._0;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO(49718): Same as _Record2.
|
||||
bool operator ==(Object other) {
|
||||
return other is _Record1 && _sameShape(other) && _equalFields(other);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(_shapeTag, _0);
|
||||
}
|
||||
|
||||
class _Record3 extends _Record {
|
||||
final Object? _0;
|
||||
final Object? _1;
|
||||
final Object? _2;
|
||||
|
||||
_Record3(this._0, this._1, this._2);
|
||||
|
||||
@override
|
||||
List<Object?> _getFieldValues() => [_0, _1, _2];
|
||||
|
||||
bool _equalFields(_Record3 other) {
|
||||
return _0 == other._0 && _1 == other._1 && _2 == other._2;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO(49718): Same as _Record2.
|
||||
bool operator ==(Object other) {
|
||||
return other is _Record3 && _sameShape(other) && _equalFields(other);
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO(49718): Incorporate shape in `hashCode`.
|
||||
int get hashCode => Object.hash(_shapeTag, _0, _1, _2);
|
||||
}
|
||||
|
||||
class _RecordN extends _Record {
|
||||
final JSArray _values;
|
||||
|
||||
_RecordN(this._values);
|
||||
|
||||
@override
|
||||
List<Object?> _getFieldValues() => _values;
|
||||
|
||||
bool _equalFields(_RecordN other) => _equalValues(_values, other._values);
|
||||
|
||||
static bool _equalValues(JSArray a, JSArray b) {
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _RecordN && _sameShape(other) && _equalFields(other);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(_shapeTag, Object.hashAll(_values));
|
||||
}
|
||||
|
||||
/// This function models the use of `_Record` and its subclasses. In the
|
||||
/// resolution phase this function is assumed to be called in order to add
|
||||
/// impacts for all the uses in code injected in lowering from K-world to
|
||||
/// J-world.
|
||||
void _RecordImpactModel() {
|
||||
// Record classes are instantiated.
|
||||
Object? anything() => _inscrutable(0);
|
||||
final r0 = const _EmptyRecord();
|
||||
final r1 = _Record1(anything());
|
||||
final r2 = _Record2(anything(), anything());
|
||||
final r3 = _Record3(anything(), anything(), anything());
|
||||
final rN = _RecordN(anything() as JSArray);
|
||||
|
||||
// Assume the `==` methods are called.
|
||||
r0 == anything();
|
||||
r1 == anything();
|
||||
r2 == anything();
|
||||
r3 == anything();
|
||||
rN == anything();
|
||||
}
|
||||
|
||||
// TODO(50081): Can this be `external`?
|
||||
@pragma('dart2js:assumeDynamic')
|
||||
Object? _inscrutable(Object? x) => x;
|
||||
|
||||
// /// Class for all records with two unnamed fields.
|
||||
// // TODO(49718): Generate this class.
|
||||
// class _Record2$ extends _Record2 {
|
||||
// _Record2$(super._0, super._1);
|
||||
//
|
||||
// bool operator ==(Object other) => other is _Record2$ && _equalFields(other);
|
||||
//
|
||||
// // Dynamic getters. Static field access does not use these getters.
|
||||
// Object? get $0 => _0;
|
||||
// Object? get $1 => _1;
|
||||
// }
|
||||
//
|
||||
// /// Class for all records with two unnamed fields containing `int`s.
|
||||
// // TODO(49718): Generate this class.
|
||||
// class _Record2$_int_int extends _Record2 {
|
||||
// _Record2$_int_int(super._0, super._1);
|
||||
//
|
||||
// @pragma('dart2js:as:trust')
|
||||
// bool _equalFields(_Record2 other) =>
|
||||
// _0 as int == other._0 && _1 as int == other._1;
|
||||
//
|
||||
// // Dynamic getters. Static field access does not use these getters.
|
||||
// @pragma('dart2js:as:trust')
|
||||
// int get $0 => _0 as int;
|
||||
// @pragma('dart2js:as:trust')
|
||||
// int get $1 => _1 as int;
|
||||
// }
|
|
@ -65,6 +65,12 @@ enum JsGetName {
|
|||
|
||||
/// Property name for Rti._is field.
|
||||
RTI_FIELD_IS,
|
||||
|
||||
/// Property name for shape tag property in record class prototype.
|
||||
RECORD_SHAPE_TAG_PROPERTY,
|
||||
|
||||
/// Property name for shape recipe property in record class prototype.
|
||||
RECORD_SHAPE_TYPE_PROPERTY,
|
||||
}
|
||||
|
||||
enum JsBuiltin {
|
||||
|
|
Loading…
Reference in a new issue