mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 14:49:43 +00:00
[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:
parent
61775822db
commit
110b0f0eba
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)';
|
||||
|
|
|
@ -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)]!;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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`).
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue