[tfa,dart2wasm] Create separate class for each record shape in TFA

This allows us to better track all kinds of accesses to record
implementation classes in dart2wasm, which generates separate
record implementation class per record shape.

This change also allows us to remove mutable dispatch targets
which were used to implement dynamic accesses to record fields,
and make tracking of record field types more accurate
(record fields are now versioned per shape).

This is also a step towards inferring actual record types.

TEST=pkg/vm/testcases/transformations/type_flow/transformer/records.dart
TEST=pkg/vm/testcases/transformations/type_flow/transformer/records_dart2wasm.dart
TEST=language/records/simple/dynamic_field_access_test

Issue https://github.com/dart-lang/sdk/issues/49719
Fixes https://github.com/dart-lang/sdk/issues/51363

Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-release-x64-try
Change-Id: Icba62a7ca8cfd8ddbc7f2b7c38aeabbef5caec4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286950
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2023-03-07 18:06:42 +00:00 committed by Commit Queue
parent 61775822db
commit 110b0f0eba
15 changed files with 206 additions and 178 deletions

View file

@ -21,7 +21,6 @@ import 'package:front_end/src/api_unstable/vm.dart'
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/verifier.dart';
import 'package:vm/kernel_front_end.dart' show writeDepfile;
@ -58,7 +57,7 @@ Future<CompilerOutput?> compileToModule(compiler.CompilerOptions options,
handleDiagnosticMessage(message);
}
Target target =
final WasmTarget target =
WasmTarget(constantBranchPruning: options.constantBranchPruning);
CompilerOptions compilerOptions = CompilerOptions()
..target = target
@ -96,6 +95,7 @@ Future<CompilerOutput?> compileToModule(compiler.CompilerOptions options,
final Map<RecordShape, Class> recordClasses =
generateRecordClasses(component, coreTypes);
target.recordClasses = recordClasses;
globalTypeFlow.transformComponent(target, coreTypes, component,
treeShakeSignatures: true,

View file

@ -250,16 +250,10 @@ class DispatchTable {
final cls = member.enclosingClass;
final isWasmType = cls != null && translator.isWasmType(cls);
// TODO(51363): Dynamic call metadata is not accurate for record fields, so
// we consider them to be dynamically called.
final isRecordMember =
member.enclosingClass?.superclass == translator.recordInfo.cls;
final calledDynamically = !isWasmType &&
(metadata.getterCalledDynamically ||
metadata.methodOrSetterCalledDynamically ||
member.name.text == "call") ||
isRecordMember;
(metadata.getterCalledDynamically ||
metadata.methodOrSetterCalledDynamically ||
member.name.text == "call");
final selector = _selectorInfo.putIfAbsent(
selectorId,

View file

@ -24,13 +24,16 @@ class RecordShape {
/// Total number of fields.
int get numFields => positionals + _names.length;
/// Create a new [RecordShape] object by given number of
/// positional fields and named fields.
/// [names] should be sorted.
RecordShape(this.positionals, Iterable<String> names)
: _names = SplayTreeMap.fromIterables(
names, Iterable.generate(names.length, (i) => i + positionals));
RecordShape.fromType(RecordType recordType)
: positionals = recordType.positional.length,
// RecordType.named is already sorted
_names = SplayTreeMap.fromIterables(
recordType.named.map((ty) => ty.name),
Iterable.generate(recordType.named.length,
(i) => i + recordType.positional.length));
: this(recordType.positional.length,
recordType.named.map((ty) => ty.name));
@override
String toString() => 'Record(positionals: $positionals, names: $_names)';

View file

@ -30,6 +30,7 @@ import 'package:front_end/src/api_prototype/const_conditional_simplifier.dart'
import 'package:dart2wasm/ffi_native_transformer.dart' as wasmFfiNativeTrans;
import 'package:dart2wasm/transformers.dart' as wasmTrans;
import 'package:dart2wasm/records.dart' show RecordShape;
class WasmTarget extends Target {
WasmTarget({this.constantBranchPruning = true});
@ -362,4 +363,11 @@ class WasmTarget extends Target {
@override
bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:");
late final Map<RecordShape, Class> recordClasses;
@override
Class getRecordImplementationClass(CoreTypes coreTypes,
int numPositionalFields, List<String> namedFields) =>
recordClasses[RecordShape(numPositionalFields, namedFields)]!;
}

View file

@ -526,7 +526,9 @@ abstract class Target {
Class? concreteConstMapLiteralClass(CoreTypes coreTypes) => null;
Class? concreteSetLiteralClass(CoreTypes coreTypes) => null;
Class? concreteConstSetLiteralClass(CoreTypes coreTypes) => null;
Class? concreteRecordClass(CoreTypes coreTypes) => null;
Class getRecordImplementationClass(CoreTypes coreTypes,
int numPositionalFields, List<String> namedFields) =>
throw UnsupportedError('Target.getRecordImplementationClass');
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) => null;
Class? concreteDoubleLiteralClass(CoreTypes coreTypes, double value) => null;

View file

@ -452,7 +452,8 @@ class VmTarget extends Target {
}
@override
Class concreteRecordClass(CoreTypes coreTypes) {
Class getRecordImplementationClass(
CoreTypes coreTypes, int numPositionalFields, List<String> namedFields) {
return _record ??= coreTypes.index.getClass('dart:core', '_Record');
}

View file

@ -170,7 +170,7 @@ abstract class _Invocation extends _DependencyTracker
final nsmArgs = new Args<Type>([
receiver,
typeFlowAnalysis.hierarchyCache.fromStaticType(
typeFlowAnalysis.environment.coreTypes.invocationLegacyRawType, false)
typeFlowAnalysis.coreTypes.invocationLegacyRawType, false)
]);
final nsmInvocation =
@ -557,9 +557,6 @@ class _DispatchableInvocation extends _Invocation {
final cls = receiver.cls as _TFClassImpl;
Member? target = cls.getDispatchTarget(selector);
if (cls.hasMutableDispatchTargets) {
cls.dependencyTracker.addDependentInvocation(this);
}
if (target != null) {
if (kPrintTrace) {
@ -595,7 +592,7 @@ class _DispatchableInvocation extends _Invocation {
// TODO(alexmarkov): support generic types and make sure inferred types
// are always same or better than static types.
// assert(selector.member.enclosingClass ==
// _typeFlowAnalysis.environment.coreTypes.objectClass);
// _typeFlowAnalysis.coreTypes.objectClass);
selector = new DynamicSelector(selector.callKind, selector.name);
}
@ -1009,7 +1006,7 @@ class _TFClassImpl extends TFClass {
late final Map<Name, Member> _dispatchTargetsNonSetters =
_initDispatchTargets(false);
final _DependencyTracker dependencyTracker = new _DependencyTracker();
final bool hasMutableDispatchTargets;
final RecordShape? recordShape;
/// Flag indicating if this class has a noSuchMethod() method not inherited
/// from Object.
@ -1017,7 +1014,7 @@ class _TFClassImpl extends TFClass {
bool? hasNonTrivialNoSuchMethod;
_TFClassImpl(int id, Class classNode, this.superclass, this.supertypes,
{required this.hasMutableDispatchTargets})
this.recordShape)
: super(id, classNode) {
supertypes.add(this);
}
@ -1056,7 +1053,6 @@ class _TFClassImpl extends TFClass {
}
void addAllocatedSubtype(_TFClassImpl subType) {
assert(subType == this || !hasMutableDispatchTargets);
_allocatedSubtypes.add(subType);
_specializedConeType = null; // Reset cached specialization.
}
@ -1065,7 +1061,6 @@ class _TFClassImpl extends TFClass {
Map<Name, Member> targets;
final superclass = this.superclass;
if (superclass != null) {
assert(!superclass.hasMutableDispatchTargets);
targets = Map.from(setters
? superclass._dispatchTargetsSetters
: superclass._dispatchTargetsNonSetters);
@ -1086,6 +1081,20 @@ class _TFClassImpl extends TFClass {
}
}
}
final recordShape = this.recordShape;
if (recordShape != null && !setters) {
for (int i = 0; i < recordShape.numFields; ++i) {
final name = Name(recordShape.fieldName(i));
final member = targets[name];
if (member == null) {
final Field field = Field.immutable(name, fileUri: artificialNodeUri);
field.parent = classNode;
targets[name] = field;
} else if (member is! Field) {
throw 'Invalid record class $classNode: $member (at ${member.location}) should be a field.';
}
}
}
return targets;
}
@ -1095,15 +1104,6 @@ class _TFClassImpl extends TFClass {
: _dispatchTargetsNonSetters)[selector.name];
}
void _addField(Field field) {
assert(hasMutableDispatchTargets);
assert(!field.isStatic && !field.isAbstract);
_dispatchTargetsNonSetters[field.name] = field;
if (field.hasSetter) {
_dispatchTargetsSetters[field.name] = field;
}
}
String dump() => "$this {supers: $supertypes}";
}
@ -1178,20 +1178,19 @@ class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
// TODO(alexmarkov): Rename to _TypeHierarchyImpl.
class _ClassHierarchyCache extends TypeHierarchy {
final TypeFlowAnalysis _typeFlowAnalysis;
final TypeEnvironment environment;
final Set<Class> allocatedClasses = new Set<Class>();
final Map<Class, _TFClassImpl> classes = <Class, _TFClassImpl>{};
final CoreTypes coreTypes;
final GenericInterfacesInfo genericInterfacesInfo;
final Map<Class, _TFClassImpl> classes = <Class, _TFClassImpl>{};
final Set<Class> allocatedClasses = Set<Class>();
final Set<_TFClassImpl> allocatedTFClasses = Set<_TFClassImpl>();
final Map<RecordShape, _TFClassImpl> recordClasses =
<RecordShape, _TFClassImpl>{};
/// Object.noSuchMethod().
final Member objectNoSuchMethod;
static final Name noSuchMethodName = new Name("noSuchMethod");
// Class of all record instances (could be synthetic if Target
// doesn't provide one).
final Class recordClass;
/// Class hierarchy is sealed after analysis is finished.
/// Once it is sealed, no new allocated classes may be added and no new
/// targets of invocations may appear.
@ -1203,65 +1202,23 @@ class _ClassHierarchyCache extends TypeHierarchy {
final Map<DynamicSelector, _DynamicTargetSet> _dynamicTargets =
<DynamicSelector, _DynamicTargetSet>{};
final Map<String, Field> _recordFields = <String, Field>{};
@override
late final Type recordType = addAllocatedClass(recordClass);
late final _TFClassImpl _recordTFClass = getTFClass(recordClass);
late final _TFClassImpl _objectTFClass =
getTFClass(environment.coreTypes.objectClass);
late final _TFClassImpl _objectTFClass = getTFClass(coreTypes.objectClass);
late final _TFClassImpl _nullTFClass =
getTFClass(environment.coreTypes.deprecatedNullClass);
getTFClass(coreTypes.deprecatedNullClass);
_ClassHierarchyCache(this._typeFlowAnalysis, this.genericInterfacesInfo,
this.environment, bool nullSafety)
: objectNoSuchMethod = environment.coreTypes.index
.getProcedure('dart:core', 'Object', 'noSuchMethod'),
recordClass = _typeFlowAnalysis.target
.concreteRecordClass(environment.coreTypes) ??
_createArtificialRecordClass(environment.coreTypes),
super(environment.coreTypes, nullSafety);
static Class _createArtificialRecordClass(CoreTypes coreTypes) {
// Override Object methods in order to make sure they are
// not monomorphic.
final procedures = <Procedure>[];
List<VariableDeclaration> copyParameters(List<VariableDeclaration> list) =>
list.map((v) => VariableDeclaration(v.name, type: v.type)).toList();
for (final p in coreTypes.objectClass.procedures) {
if (p.isInstanceMember && !p.name.isPrivate) {
final f = p.function;
final proc = Procedure(
p.name,
p.kind,
FunctionNode(null,
positionalParameters: copyParameters(f.positionalParameters),
namedParameters: copyParameters(f.namedParameters),
requiredParameterCount: f.requiredParameterCount,
returnType: f.returnType),
isExternal: true,
fileUri: artificialNodeUri);
procedures.add(proc);
}
}
return Class(
name: "&&Record",
supertype: Supertype(coreTypes.objectClass, []),
implementedTypes: [Supertype(coreTypes.recordClass, [])],
procedures: procedures,
fileUri: artificialNodeUri)
..parent = Library(artificialNodeUri, fileUri: artificialNodeUri);
}
this.coreTypes, bool nullSafety)
: objectNoSuchMethod =
coreTypes.index.getProcedure('dart:core', 'Object', 'noSuchMethod'),
super(coreTypes, nullSafety);
@override
_TFClassImpl getTFClass(Class c) {
return classes[c] ??= _createTFClass(c);
return classes[c] ??= _createTFClass(c, null);
}
_TFClassImpl _createTFClass(Class c) {
_TFClassImpl _createTFClass(Class c, RecordShape? recordShape) {
final supertypes = new Set<_TFClassImpl>();
for (var sup in c.supers) {
supertypes.addAll(getTFClass(sup.classNode).supertypes);
@ -1269,33 +1226,56 @@ class _ClassHierarchyCache extends TypeHierarchy {
Class? superclassNode = c.superclass;
_TFClassImpl? superclass =
superclassNode != null ? getTFClass(superclassNode) : null;
return new _TFClassImpl(++_classIdCounter, c, superclass, supertypes,
hasMutableDispatchTargets: c == recordClass);
return _TFClassImpl(
++_classIdCounter, c, superclass, supertypes, recordShape);
}
ConcreteType addAllocatedClass(Class cl) {
assert(!cl.isAbstract);
ConcreteType addAllocatedClass(_TFClassImpl cls) {
assert(!cls.classNode.isAbstract);
assert(!_sealed);
final _TFClassImpl classImpl = getTFClass(cl);
if (allocatedTFClasses.add(cls)) {
allocatedClasses.add(cls.classNode);
if (allocatedClasses.add(cl)) {
classImpl.addAllocatedSubtype(classImpl);
classImpl.dependencyTracker
cls.addAllocatedSubtype(cls);
cls.dependencyTracker
.invalidateDependentInvocations(_typeFlowAnalysis.workList);
for (var supertype in classImpl.supertypes) {
supertype.addAllocatedSubtype(classImpl);
for (var supertype in cls.supertypes) {
supertype.addAllocatedSubtype(cls);
supertype.dependencyTracker
.invalidateDependentInvocations(_typeFlowAnalysis.workList);
}
for (var targetSet in _dynamicTargets.values) {
_addDynamicTarget(cl, targetSet);
_addDynamicTarget(cls, targetSet);
}
}
return classImpl.concreteType;
return cls.concreteType;
}
@override
Type getRecordType(RecordShape shape, bool allocated) {
final cls = getRecordClass(shape);
return allocated ? addAllocatedClass(cls) : ConeType(cls);
}
_TFClassImpl getRecordClass(RecordShape shape) =>
recordClasses[shape] ??= _createRecordClass(shape);
_TFClassImpl _createRecordClass(RecordShape shape) {
final Class c = _typeFlowAnalysis.target.getRecordImplementationClass(
coreTypes, shape.numPositionalFields, shape.namedFields);
if (c.isAbstract) {
throw 'Record class $c should not be abstract';
}
return _createTFClass(c, shape);
}
Field getRecordField(RecordShape shape, String name) {
final cls = getRecordClass(shape);
return cls._dispatchTargetsNonSetters[Name(name)] as Field;
}
void seal() {
@ -1362,16 +1342,16 @@ class _ClassHierarchyCache extends TypeHierarchy {
final isObjectMethod = _objectTFClass.getDispatchTarget(selector) != null;
final targetSet = new _DynamicTargetSet(selector, isObjectMethod);
for (Class c in allocatedClasses) {
_addDynamicTarget(c, targetSet);
for (final cls in allocatedTFClasses) {
_addDynamicTarget(cls, targetSet);
}
return targetSet;
}
void _addDynamicTarget(Class c, _DynamicTargetSet targetSet) {
void _addDynamicTarget(_TFClassImpl cls, _DynamicTargetSet targetSet) {
assert(!_sealed);
final selector = targetSet.selector;
final member = getTFClass(c).getDispatchTarget(selector);
final member = cls.getDispatchTarget(selector);
if (member != null) {
if (targetSet.targets.add(member)) {
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
@ -1379,35 +1359,6 @@ class _ClassHierarchyCache extends TypeHierarchy {
}
}
Field getRecordField(String name) {
return _recordFields[name] ??= _addRecordField(Name(name));
}
Field _addRecordField(Name name) {
assert(!_sealed);
final Field field = Field.immutable(name, fileUri: artificialNodeUri);
field.parent = recordClass;
// Add field to the record class for future queries.
_recordTFClass._addField(field);
_recordTFClass.dependencyTracker
.invalidateDependentInvocations(_typeFlowAnalysis.workList);
// Update dynamic target sets collected so far.
_DynamicTargetSet? targetSet =
_dynamicTargets[DynamicSelector(CallKind.PropertyGet, name)];
if (targetSet != null) {
if (targetSet.targets.add(field)) {
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
}
}
targetSet = _dynamicTargets[DynamicSelector(CallKind.Method, name)];
if (targetSet != null) {
if (targetSet.targets.add(field)) {
targetSet.invalidateDependentInvocations(_typeFlowAnalysis.workList);
}
}
return field;
}
@override
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
@ -1608,6 +1559,7 @@ class _WorkList {
class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
final Target target;
final TypeEnvironment environment;
final CoreTypes coreTypes;
final LibraryIndex libraryIndex;
final PragmaAnnotationParser annotationMatcher;
final ProtobufHandler? protobufHandler;
@ -1629,7 +1581,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
TypeFlowAnalysis(
this.target,
Component component,
CoreTypes coreTypes,
this.coreTypes,
ClosedWorldClassHierarchy hierarchy,
this._genericInterfacesInfo,
this.environment,
@ -1639,8 +1591,8 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
: annotationMatcher =
matcher ?? new ConstantPragmaAnnotationParser(coreTypes, target) {
nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher);
hierarchyCache = new _ClassHierarchyCache(this, _genericInterfacesInfo,
environment, target.flags.soundNullSafety);
hierarchyCache = new _ClassHierarchyCache(
this, _genericInterfacesInfo, coreTypes, target.flags.soundNullSafety);
summaryCollector = new SummaryCollector(
target,
environment,
@ -1852,15 +1804,16 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
if (kPrintDebug) {
debugPrint("ADD ALLOCATED CLASS: $c");
}
return hierarchyCache.addAllocatedClass(c);
return hierarchyCache.addAllocatedClass(hierarchyCache.getTFClass(c));
}
@override
Field getRecordPositionalField(int pos) =>
getRecordNamedField("\$${pos + 1}");
Field getRecordPositionalField(RecordShape shape, int pos) =>
hierarchyCache.getRecordField(shape, shape.fieldName(pos));
@override
Field getRecordNamedField(String name) => hierarchyCache.getRecordField(name);
Field getRecordNamedField(RecordShape shape, String name) =>
hierarchyCache.getRecordField(shape, name);
@override
void recordMemberCalledViaInterfaceSelector(Member target) {

View file

@ -23,11 +23,11 @@ abstract class EntryPointsListener {
/// Add instantiation of the given class.
ConcreteType addAllocatedClass(Class c);
/// Returns synthetic Field representing positional field of a record.
Field getRecordPositionalField(int pos);
/// Returns Field representing positional field of a record with given shape.
Field getRecordPositionalField(RecordShape shape, int pos);
/// Returns synthetic Field representing named field of a record.
Field getRecordNamedField(String name);
/// Returns Field representing named field of a record with given shape.
Field getRecordNamedField(RecordShape shape, String name);
/// Record the fact that given member is called via interface selector
/// (not dynamically, and not from `this`).

View file

@ -17,7 +17,7 @@ import 'calls.dart' as calls
import 'native_code.dart'
show EntryPointsListener, NativeCodeOracle, PragmaEntryPointsVisitor;
import 'protobuf_handler.dart' show ProtobufHandler;
import 'types.dart' show TFClass, Type, ConcreteType;
import 'types.dart' show TFClass, Type, ConcreteType, RecordShape;
import '../pragma.dart' show ConstantPragmaAnnotationParser;
class Selector {
@ -527,10 +527,12 @@ class _EntryPointsListenerImpl implements EntryPointsListener {
ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c);
@override
Field getRecordPositionalField(int pos) => throw 'Unsupported operation';
Field getRecordPositionalField(RecordShape shape, int pos) =>
throw 'Unsupported operation';
@override
Field getRecordNamedField(String name) => throw 'Unsupported operation';
Field getRecordNamedField(RecordShape shape, String name) =>
throw 'Unsupported operation';
@override
void recordMemberCalledViaInterfaceSelector(Member target) =>

View file

@ -1629,16 +1629,19 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
@override
TypeExpr visitRecordLiteral(RecordLiteral node) {
final Type receiver = _typesBuilder.recordType;
final recordShape = RecordShape(node.recordType);
final Type receiver = _typesBuilder.getRecordType(recordShape, true);
for (int i = 0; i < node.positional.length; ++i) {
final Field f = _entryPointsListener.getRecordPositionalField(i);
final Field f =
_entryPointsListener.getRecordPositionalField(recordShape, i);
final TypeExpr value = _visit(node.positional[i]);
final args = Args<TypeExpr>([receiver, value]);
_makeCall(node,
DirectSelector(f, callKind: CallKind.SetFieldInConstructor), args);
}
for (var expr in node.named) {
final Field f = _entryPointsListener.getRecordNamedField(expr.name);
final Field f =
_entryPointsListener.getRecordNamedField(recordShape, expr.name);
final TypeExpr value = _visit(expr.value);
final args = Args<TypeExpr>([receiver, value]);
_makeCall(node,
@ -1651,8 +1654,8 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
@override
TypeExpr visitRecordIndexGet(RecordIndexGet node) {
final receiver = _visit(node.receiver);
final Field field =
_entryPointsListener.getRecordPositionalField(node.index);
final Field field = _entryPointsListener.getRecordPositionalField(
RecordShape(node.receiverType), node.index);
final args = Args<TypeExpr>([receiver]);
return _makeCall(
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
@ -1661,7 +1664,8 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
@override
TypeExpr visitRecordNameGet(RecordNameGet node) {
final receiver = _visit(node.receiver);
final Field field = _entryPointsListener.getRecordNamedField(node.name);
final Field field = _entryPointsListener.getRecordNamedField(
RecordShape(node.receiverType), node.name);
final args = Args<TypeExpr>([receiver]);
return _makeCall(
node, DirectSelector(field, callKind: CallKind.PropertyGet), args);
@ -2628,14 +2632,16 @@ class ConstantAllocationCollector extends ConstantVisitor<Type> {
@override
Type visitRecordConstant(RecordConstant constant) {
final epl = summaryCollector._entryPointsListener;
final Type receiver = summaryCollector._typesBuilder.recordType;
final recordShape = RecordShape(constant.recordType);
final Type receiver =
summaryCollector._typesBuilder.getRecordType(recordShape, true);
for (int i = 0; i < constant.positional.length; ++i) {
final Field f = epl.getRecordPositionalField(i);
final Field f = epl.getRecordPositionalField(recordShape, i);
final Type value = typeFor(constant.positional[i]);
epl.addFieldUsedInConstant(f, receiver, value);
}
constant.named.forEach((String fieldName, Constant fieldValue) {
final Field f = epl.getRecordNamedField(fieldName);
final Field f = epl.getRecordNamedField(recordShape, fieldName);
final Type value = typeFor(fieldValue);
epl.addFieldUsedInConstant(f, receiver, value);
});

View file

@ -12,8 +12,14 @@ import 'package:kernel/core_types.dart';
import 'utils.dart';
/// Dart class representation used in type flow analysis.
/// For each Dart class there is a unique instance of [TFClass].
/// Class representation used in the type flow analysis.
///
/// For each ordinary Dart class there is a unique instance of [TFClass].
///
/// There are distinct [TFClass] objects for each record shape.
/// They may map to the same or distinct implementation Dart classes
/// depending on the [Target.getRecordImplementationClass].
///
/// Each [TFClass] has unique id which could be used to sort classes.
class TFClass {
final int id;
@ -38,6 +44,49 @@ class TFClass {
String toString() => nodeToText(classNode);
}
/// Shape of a record (number of positional fields and a set of named fields).
class RecordShape {
final int numPositionalFields;
final List<String> namedFields; // Sorted.
final int _hash = 0;
RecordShape(RecordType type)
: numPositionalFields = type.positional.length,
namedFields = type.named.isEmpty
? const <String>[]
: type.named.map((nt) => nt.name).toList();
int get numFields => numPositionalFields + namedFields.length;
String fieldName(int i) {
if (i < numPositionalFields) {
return "\$${i + 1}";
} else {
return namedFields[i - numPositionalFields];
}
}
@override
int get hashCode => (_hash == 0) ? _computeHashCode() : _hash;
int _computeHashCode() {
int hash =
(((numPositionalFields * 31) & kHashMask) + listHashCode(namedFields)) &
kHashMask;
if (hash == 0) {
hash = 1;
}
return hash;
}
@override
bool operator ==(other) =>
identical(this, other) ||
(other is RecordShape &&
this.numPositionalFields == other.numPositionalFields &&
listEquals(this.namedFields, other.namedFields));
}
abstract class GenericInterfacesInfo {
// Return a type arguments vector which contains the immediate type parameters
// to 'klass' as well as the type arguments to all generic supertypes of
@ -70,8 +119,13 @@ abstract class TypesBuilder {
/// Return [TFClass] corresponding to the given [classNode].
TFClass getTFClass(Class classNode);
/// Return [Type] corresponding to the given record shape.
/// [allocated] flag indicates that an instance of
/// this record shape is allocated.
Type getRecordType(RecordShape shape, bool allocated) =>
ConeType(getTFClass(coreTypes.recordClass));
late final Type functionType = ConeType(getTFClass(coreTypes.functionClass));
late final Type recordType = ConeType(getTFClass(coreTypes.recordClass));
/// Create a Type which corresponds to a set of instances constrained by
/// Dart type annotation [dartType].
@ -81,7 +135,7 @@ abstract class TypesBuilder {
Type result;
if (type is InterfaceType) {
final cls = type.classNode;
result = new ConeType(getTFClass(cls));
result = ConeType(getTFClass(cls));
} else if (type == const DynamicType() || type == const VoidType()) {
result = const AnyType();
} else if (type is NeverType || type is NullType) {
@ -90,8 +144,7 @@ abstract class TypesBuilder {
// TODO(alexmarkov): support inference of function types
result = functionType;
} else if (type is RecordType) {
// TODO(dartbug.com/49719): support inference of record types
result = recordType;
result = getRecordType(RecordShape(type), false);
} else if (type is FutureOrType) {
// TODO(alexmarkov): support FutureOr types
result = const AnyType();

View file

@ -54,11 +54,11 @@ class FakeEntryPointsListener implements EntryPointsListener {
}
@override
Field getRecordPositionalField(int pos) =>
getRecordNamedField("\$${pos + 1}");
Field getRecordPositionalField(RecordShape shape, int pos) =>
getRecordNamedField(shape, shape.fieldName(pos));
@override
Field getRecordNamedField(String name) =>
Field getRecordNamedField(RecordShape shape, String name) =>
Field.immutable(Name(name), fileUri: dummyUri);
@override

View file

@ -4,6 +4,8 @@
import 'dart:io';
import 'package:dart2wasm/record_class_generator.dart'
show generateRecordClasses;
import 'package:dart2wasm/target.dart' show WasmTarget;
import 'package:kernel/target/targets.dart';
import 'package:kernel/ast.dart';
@ -35,6 +37,10 @@ void runTestCase(Uri source, List<Uri>? linkedDependencies,
final coreTypes = new CoreTypes(component);
if (target is WasmTarget) {
target.recordClasses = generateRecordClasses(component, coreTypes);
}
component = transformComponent(target, coreTypes, component,
matcher: new ConstantPragmaAnnotationParser(coreTypes, target),
treeShakeProtobufs: true);

View file

@ -14,7 +14,7 @@ static method recordDynamicFieldAccess([@vm.inferred-type.metadata=!] dynamic x)
static method main() → dynamic {
core::print(#C7);
core::print([@vm.inferred-type.metadata=dart.core::_Record] self::recordLiteral());
core::print([@vm.inferred-type.metadata=dart.core::_Smi] self::recordFieldAccess1((10, "hi")));
core::print([@vm.inferred-type.metadata=dart.core::_Smi (value: 10)] self::recordFieldAccess1((10, "hi")));
core::print([@vm.inferred-type.metadata=dart.core::_Smi (value: 20)] self::recordFieldAccess2(({a: 20, b: "bye"})));
core::print([@vm.inferred-type.metadata=dart.core::_OneByteString (value: "foo42")] self::recordDynamicFieldAccess([@vm.direct-call.metadata=dart.core::_GrowableList.[]??] [@vm.inferred-type.metadata=! (receiver not int)] [@vm.inferred-type.metadata=dart.core::_GrowableList?<dart.core::Object>] self::list{dynamic}.[](1)));
}

View file

@ -5,19 +5,19 @@ import "dart:core" as core;
[@vm.inferred-type.metadata=dart.core::_GrowableList?<dart.core::Object>]static field dynamic list = <core::Object>["abc", (42, {foo42: "foo42"})];
[@vm.unboxing-info.metadata=(i,i,i)->b]static method recordLiteral([@vm.inferred-type.metadata=dart.core::_BoxedInt] dynamic x, [@vm.inferred-type.metadata=dart.core::_BoxedInt] dynamic y, [@vm.inferred-type.metadata=dart.core::_BoxedInt] dynamic z) → dynamic
return (x, y, {bar: z});
[@vm.unboxing-info.metadata=(b)->i]static method recordFieldAccess1([@vm.inferred-type.metadata=!](core::int, core::String) rec) → dynamic
static method recordFieldAccess1([@vm.inferred-type.metadata=dart.core::Record_2](core::int, core::String) rec) → dynamic
return rec.$1{core::int};
[@vm.unboxing-info.metadata=(b)->i]static method recordFieldAccess2([@vm.inferred-type.metadata=!]({required a: core::int, required b: core::String}) rec) → dynamic
static method recordFieldAccess2([@vm.inferred-type.metadata=dart.core::Record_0_a_b]({required a: core::int, required b: core::String}) rec) → dynamic
return rec.a{core::int};
static method recordDynamicFieldAccess(dynamic x) → dynamic
return x{dynamic}.foo42;
static method main() → dynamic {
core::print(#C4);
core::print([@vm.inferred-type.metadata=!] self::recordLiteral(1, 2, 3));
core::print([@vm.inferred-type.metadata=dart.core::_BoxedInt] self::recordFieldAccess1((10, "hi")));
core::print([@vm.inferred-type.metadata=dart.core::_BoxedInt] self::recordFieldAccess2(({a: 20, b: "bye"})));
core::print([@vm.inferred-type.metadata=dart.core::Record_2_bar] self::recordLiteral(1, 2, 3));
core::print(self::recordFieldAccess1((10, "hi")));
core::print(self::recordFieldAccess2(({a: 20, b: "bye"})));
core::print(self::recordDynamicFieldAccess([@vm.direct-call.metadata=dart.core::_ListBase.[]??] [@vm.inferred-type.metadata=!? (receiver not int)] [@vm.inferred-type.metadata=dart.core::_GrowableList?<dart.core::Object>] self::list{dynamic}.[](1)));
core::print([@vm.inferred-type.metadata=! (skip check) (receiver not int)](1, 2).{core::Object::toString}(){() → core::String});
core::print([@vm.direct-call.metadata=dart.core::Record_2.toString] [@vm.inferred-type.metadata=! (skip check) (receiver not int)](1, 2).{core::Object::toString}(){() → core::String});
}
constants {
#C1 = 42