mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 16:59:47 +00:00
[dart2wasm] Record type related cleanups
* avoid allocating _RecordType objects for `<obj> is/as <record-type>` => Introduce `Record._checkRecordType()` => Use `Record._recordRuntimeType` (which is now not masquerading) for other purposes (e.g. verification) * avoid using masqueraded types for `<obj> is/as <record-type>` => Introduce `Record._masqueradedRecordRuntimeType` Although we introduce 2 extra methods on `Record` that are overriden, it's O(record-shapes-in-program) and therefore not a big overhead. => This will enforce the invariant that the actual type check implementation (i.e. for `<obj> is/as <type>` or for `<type> <: <type>`) *never* calls back into masquerading functionality => This in return means we can make the masquerading functionality using `<obj> is <type>` checks. Change-Id: I3e3a0411022042a8e735aaeed396cc8f90d8c9c5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349800 Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
parent
105825dfc4
commit
32c22da2b6
|
@ -207,8 +207,6 @@ mixin KernelNodes {
|
|||
index.getProcedure("dart:core", "Object", "_nullToString");
|
||||
late final Procedure nullNoSuchMethod =
|
||||
index.getProcedure("dart:core", "Object", "_nullNoSuchMethod");
|
||||
late final Procedure recordGetRecordRuntimeType =
|
||||
index.getProcedure("dart:core", "Record", "_getRecordRuntimeType");
|
||||
late final Procedure stringEquals =
|
||||
index.getProcedure("dart:_string", "StringBase", "==");
|
||||
late final Procedure stringInterpolate =
|
||||
|
@ -257,8 +255,6 @@ mixin KernelNodes {
|
|||
// dart:core type procedures
|
||||
late final Procedure getClosureRuntimeType =
|
||||
index.getProcedure("dart:core", '_Closure', "_getClosureRuntimeType");
|
||||
late final Procedure getActualRuntimeType =
|
||||
index.getTopLevelProcedure("dart:core", "_getActualRuntimeType");
|
||||
late final Procedure getMasqueradedRuntimeType =
|
||||
index.getTopLevelProcedure("dart:core", "_getMasqueradedRuntimeType");
|
||||
late final Procedure isSubtype =
|
||||
|
|
|
@ -30,14 +30,36 @@ import 'package:dart2wasm/records.dart';
|
|||
/// Record_2_a(this.$1, this.$2, this.a);
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// _Type get _recordRuntimeType =>
|
||||
/// bool _checkRecordType(WasmArray<_Type> types, WasmArray<String> names) {
|
||||
/// if (types.length != 3) return false;
|
||||
/// if (!identical(names, const WasmArray(["a"]))) return false;
|
||||
///
|
||||
/// if (!_isSubtype($1, types[0])) return false;
|
||||
/// if (!_isSubtype($2, types[1])) return false;
|
||||
/// if (!_isSubtype($a, types[2])) return false;
|
||||
///
|
||||
/// return true;
|
||||
/// }
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// _Type get _masqueradedRecordRuntimeType =>
|
||||
/// _RecordType(
|
||||
/// const ["a"],
|
||||
/// [
|
||||
/// const WasmArray(["a"]),
|
||||
/// WasmArray.literal([
|
||||
/// _getMasqueradedRuntimeTypeNullable($1),
|
||||
/// _getMasqueradedRuntimeTypeNullable($2),
|
||||
/// _getMasqueradedRuntimeTypeNullable(a)
|
||||
/// ]);
|
||||
/// ]));
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// _Type get _recordRuntimeType =>
|
||||
/// _RecordType(
|
||||
/// const WasmArray(["a"]),
|
||||
/// WasmArray.literal([
|
||||
/// _getActualRuntimeTypeNullable($1),
|
||||
/// _getActualRuntimeTypeNullable($2),
|
||||
/// _getActualRuntimeTypeNullable(a)
|
||||
/// ]));
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// String toString() =>
|
||||
|
@ -87,25 +109,56 @@ class _RecordClassGenerator {
|
|||
late final Procedure objectToStringProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'Object', 'toString');
|
||||
|
||||
late final Procedure identical =
|
||||
coreTypes.index.getTopLevelProcedure('dart:core', 'identical');
|
||||
|
||||
late final Procedure objectEqualsProcedure = coreTypes.objectEquals;
|
||||
|
||||
late final FunctionType integerEqualsFunctionType =
|
||||
FunctionType([intType, intType], boolType, Nullability.nonNullable);
|
||||
|
||||
late final Procedure stringPlusProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'String', '+');
|
||||
|
||||
late final Procedure isSubtype =
|
||||
coreTypes.index.getTopLevelProcedure('dart:core', '_isSubtype');
|
||||
|
||||
late final Class wasmArrayClass =
|
||||
coreTypes.index.getClass('dart:_wasm', 'WasmArray');
|
||||
|
||||
late final Procedure wasmArrayRefLength =
|
||||
coreTypes.index.getProcedure('dart:_wasm', 'WasmArrayRef', 'get:length');
|
||||
|
||||
late final Procedure wasmArrayIndex = coreTypes.index
|
||||
.getLibrary('dart:_wasm')
|
||||
.extensions
|
||||
.singleWhere((e) => e.name == 'WasmArrayExt')
|
||||
.memberDescriptors
|
||||
.singleWhere((member) => member.name.text == '[]')
|
||||
.memberReference
|
||||
.node as Procedure;
|
||||
|
||||
late final Constructor wasmArrayLiteralConstructor =
|
||||
coreTypes.index.getConstructor('dart:_wasm', 'WasmArray', 'literal');
|
||||
|
||||
late final Field wasmArrayValueField =
|
||||
coreTypes.index.getField("dart:_wasm", "WasmArray", "_value");
|
||||
|
||||
late final InterfaceType wasmArrayOfType = InterfaceType(
|
||||
wasmArrayClass, Nullability.nonNullable, [nonNullableTypeType]);
|
||||
|
||||
late final InterfaceType wasmArrayOfString = InterfaceType(
|
||||
wasmArrayClass, Nullability.nonNullable, [nonNullableStringType]);
|
||||
|
||||
late final InterfaceType runtimeTypeType =
|
||||
InterfaceType(typeRuntimetypeTypeClass, Nullability.nonNullable);
|
||||
|
||||
late final InterfaceType nonNullableTypeType =
|
||||
InterfaceType(typeRuntimetypeTypeClass, Nullability.nonNullable);
|
||||
|
||||
late final Procedure getActualRuntimeTypeNullable = coreTypes.index
|
||||
.getTopLevelProcedure('dart:core', '_getActualRuntimeTypeNullable');
|
||||
|
||||
late final Procedure getMasqueradedRuntimeTypeNullableProcedure = coreTypes
|
||||
.index
|
||||
.getTopLevelProcedure('dart:core', '_getMasqueradedRuntimeTypeNullable');
|
||||
|
@ -159,7 +212,9 @@ class _RecordClassGenerator {
|
|||
));
|
||||
library.addClass(cls);
|
||||
cls.addProcedure(_generateEquals(shape, fields, cls));
|
||||
cls.addProcedure(_generateCheckRecordType(shape, fields));
|
||||
cls.addProcedure(_generateRecordRuntimeType(shape, fields));
|
||||
cls.addProcedure(_generateMasqueradedRecordRuntimeType(shape, fields));
|
||||
return cls;
|
||||
}
|
||||
|
||||
|
@ -355,27 +410,109 @@ class _RecordClassGenerator {
|
|||
));
|
||||
}
|
||||
|
||||
/// Generate `_Type get _runtimeType` member.
|
||||
/// Generate `_checkRecordType` member.
|
||||
Procedure _generateCheckRecordType(RecordShape shape, List<Field> fields) {
|
||||
final typesParameter = VariableDeclaration('types', type: wasmArrayOfType);
|
||||
final namesParameter =
|
||||
VariableDeclaration('names', type: wasmArrayOfString);
|
||||
|
||||
final List<Statement> statements = [];
|
||||
|
||||
// if (types.length != shape.numFields) return false;
|
||||
statements.add(IfStatement(
|
||||
Not(EqualsCall(
|
||||
InstanceGet(
|
||||
InstanceAccessKind.Instance,
|
||||
VariableGet(typesParameter),
|
||||
wasmArrayRefLength.name,
|
||||
interfaceTarget: wasmArrayRefLength,
|
||||
resultType: intType,
|
||||
),
|
||||
IntLiteral(shape.numFields),
|
||||
functionType: integerEqualsFunctionType,
|
||||
interfaceTarget: objectEqualsProcedure)),
|
||||
ReturnStatement(BoolLiteral(false)),
|
||||
null));
|
||||
|
||||
// if (!identical(names, _fieldNamesConstant(shape))) return false;
|
||||
statements.add(IfStatement(
|
||||
Not(StaticInvocation(
|
||||
identical,
|
||||
Arguments([
|
||||
VariableGet(namesParameter),
|
||||
ConstantExpression(_fieldNamesConstant(shape)),
|
||||
]))),
|
||||
ReturnStatement(BoolLiteral(false)),
|
||||
null));
|
||||
|
||||
// if (!_isSubtype($..., types[...])) return false;
|
||||
for (int i = 0; i < shape.numFields; ++i) {
|
||||
final field = fields[i];
|
||||
statements.add(IfStatement(
|
||||
Not(StaticInvocation(
|
||||
isSubtype,
|
||||
Arguments([
|
||||
InstanceGet(
|
||||
InstanceAccessKind.Instance, ThisExpression(), field.name,
|
||||
interfaceTarget: field, resultType: nullableObjectType),
|
||||
StaticInvocation(
|
||||
wasmArrayIndex,
|
||||
Arguments([
|
||||
VariableGet(typesParameter),
|
||||
IntLiteral(i),
|
||||
], types: [
|
||||
nonNullableTypeType
|
||||
])),
|
||||
]))),
|
||||
ReturnStatement(BoolLiteral(false)),
|
||||
null));
|
||||
}
|
||||
|
||||
// return true
|
||||
statements.add(ReturnStatement(BoolLiteral(true)));
|
||||
|
||||
final FunctionNode function = FunctionNode(
|
||||
Block(statements),
|
||||
positionalParameters: [typesParameter, namesParameter],
|
||||
returnType: boolType,
|
||||
);
|
||||
|
||||
return _addWasmEntryPointPragma(Procedure(
|
||||
Name('_checkRecordType', library),
|
||||
ProcedureKind.Method,
|
||||
function,
|
||||
fileUri: library.fileUri,
|
||||
));
|
||||
}
|
||||
|
||||
/// Generate `_Type get _recordRuntimeType` member.
|
||||
Procedure _generateRecordRuntimeType(RecordShape shape, List<Field> fields) {
|
||||
return _generateRecordRuntimeTypeHelper(
|
||||
'_recordRuntimeType', getActualRuntimeTypeNullable, shape, fields);
|
||||
}
|
||||
|
||||
/// Generate `_Type get _masqueradedRecordRuntimeType ` member.
|
||||
Procedure _generateMasqueradedRecordRuntimeType(
|
||||
RecordShape shape, List<Field> fields) {
|
||||
return _generateRecordRuntimeTypeHelper('_masqueradedRecordRuntimeType',
|
||||
getMasqueradedRuntimeTypeNullableProcedure, shape, fields);
|
||||
}
|
||||
|
||||
Procedure _generateRecordRuntimeTypeHelper(
|
||||
String name, Procedure target, RecordShape shape, List<Field> fields) {
|
||||
final List<Statement> statements = [];
|
||||
|
||||
// const WasmArray(["name1", "name2", ...])
|
||||
final fieldNamesList =
|
||||
ConstantExpression(InstanceConstant(wasmArrayClass.reference, [
|
||||
nonNullableStringType
|
||||
], {
|
||||
wasmArrayValueField.fieldReference: ListConstant(nonNullableStringType,
|
||||
shape.names.map((name) => StringConstant(name)).toList())
|
||||
}));
|
||||
final fieldNamesList = ConstantExpression(_fieldNamesConstant(shape));
|
||||
|
||||
Expression fieldRuntimeTypeExpr(Field field) => StaticInvocation(
|
||||
getMasqueradedRuntimeTypeNullableProcedure,
|
||||
target,
|
||||
Arguments([
|
||||
InstanceGet(InstanceAccessKind.Instance, ThisExpression(), field.name,
|
||||
interfaceTarget: field, resultType: nullableObjectType)
|
||||
]));
|
||||
|
||||
// WasmArray.literal([this.$1.runtimeType, this.x.runtimeType, ...])
|
||||
// WasmArray.literal([_get*RuntimeTypeNullable(this.$1), ...])
|
||||
final fieldTypesList = ConstructorInvocation(
|
||||
wasmArrayLiteralConstructor,
|
||||
Arguments([
|
||||
|
@ -403,12 +540,21 @@ class _RecordClassGenerator {
|
|||
);
|
||||
|
||||
return _addWasmEntryPointPragma(Procedure(
|
||||
Name('_recordRuntimeType', library),
|
||||
Name(name, library),
|
||||
ProcedureKind.Getter,
|
||||
function,
|
||||
fileUri: library.fileUri,
|
||||
));
|
||||
}
|
||||
|
||||
Constant _fieldNamesConstant(RecordShape shape) {
|
||||
return InstanceConstant(wasmArrayClass.reference, [
|
||||
nonNullableStringType
|
||||
], {
|
||||
wasmArrayValueField.fieldReference: ListConstant(nonNullableStringType,
|
||||
shape.names.map((name) => StringConstant(name)).toList())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _RecordVisitor extends RecursiveVisitor<void> {
|
||||
|
|
|
@ -9,12 +9,16 @@ part of "core_patch.dart";
|
|||
@pragma('wasm:entry-point')
|
||||
@patch
|
||||
abstract class Record {
|
||||
_RecordType get _masqueradedRecordRuntimeType;
|
||||
_RecordType get _recordRuntimeType;
|
||||
|
||||
// An instance member needs a call from Dart code to be properly included in
|
||||
// the dispatch table. Hence we use an inlined static wrapper as entry point.
|
||||
@pragma("wasm:entry-point")
|
||||
bool _checkRecordType(WasmArray<_Type> types, WasmArray<String> names);
|
||||
|
||||
@pragma("wasm:prefer-inline")
|
||||
static _RecordType _getRecordRuntimeType(Record record) =>
|
||||
record._recordRuntimeType;
|
||||
|
||||
@pragma("wasm:prefer-inline")
|
||||
static _RecordType _getMasqueradedRecordRuntimeType(Record record) =>
|
||||
record._masqueradedRecordRuntimeType;
|
||||
}
|
||||
|
|
|
@ -537,7 +537,8 @@ class _RecordType extends _Type {
|
|||
|
||||
@override
|
||||
bool _checkInstance(Object o) {
|
||||
return _typeUniverse.isSubtype(_getActualRuntimeType(o), null, this, null);
|
||||
if (!_isRecordInstance(o)) return false;
|
||||
return unsafeCast<Record>(o)._checkRecordType(fieldTypes, names);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1296,7 +1297,6 @@ void _checkClosureType(
|
|||
}
|
||||
}
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
_Type _getActualRuntimeType(Object object) {
|
||||
final classId = ClassID.getID(object);
|
||||
final category = _typeCategoryTable.readUnsigned(classId);
|
||||
|
@ -1332,7 +1332,7 @@ _Type _getMasqueradedRuntimeType(Object object) {
|
|||
return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
|
||||
}
|
||||
if (category == _typeCategoryRecord.toIntUnsigned()) {
|
||||
return Record._getRecordRuntimeType(unsafeCast<Record>(object));
|
||||
return Record._getMasqueradedRecordRuntimeType(unsafeCast<Record>(object));
|
||||
}
|
||||
if (category == _typeCategoryObject.toIntUnsigned()) {
|
||||
return _literal<Object>();
|
||||
|
|
Loading…
Reference in a new issue