From 32c22da2b62d1a6b9d494e38eecc4e4ba3fab2c7 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 2 Feb 2024 11:52:23 +0000 Subject: [PATCH] [dart2wasm] Record type related cleanups * avoid allocating _RecordType objects for ` is/as ` => Introduce `Record._checkRecordType()` => Use `Record._recordRuntimeType` (which is now not masquerading) for other purposes (e.g. verification) * avoid using masqueraded types for ` is/as ` => 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 ` is/as ` or for ` <: `) *never* calls back into masquerading functionality => This in return means we can make the masquerading functionality using ` is ` checks. Change-Id: I3e3a0411022042a8e735aaeed396cc8f90d8c9c5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349800 Reviewed-by: Slava Egorov --- pkg/dart2wasm/lib/kernel_nodes.dart | 4 - pkg/dart2wasm/lib/record_class_generator.dart | 176 ++++++++++++++++-- sdk/lib/_internal/wasm/lib/record_patch.dart | 10 +- sdk/lib/_internal/wasm/lib/type.dart | 6 +- 4 files changed, 171 insertions(+), 25 deletions(-) diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 4688c3975a0..c519a8a3fd0 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -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 = diff --git a/pkg/dart2wasm/lib/record_class_generator.dart b/pkg/dart2wasm/lib/record_class_generator.dart index 19feaea12e4..5ee2d7d034e 100644 --- a/pkg/dart2wasm/lib/record_class_generator.dart +++ b/pkg/dart2wasm/lib/record_class_generator.dart @@ -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 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 fields) { + final typesParameter = VariableDeclaration('types', type: wasmArrayOfType); + final namesParameter = + VariableDeclaration('names', type: wasmArrayOfString); + + final List 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 fields) { + return _generateRecordRuntimeTypeHelper( + '_recordRuntimeType', getActualRuntimeTypeNullable, shape, fields); + } + + /// Generate `_Type get _masqueradedRecordRuntimeType ` member. + Procedure _generateMasqueradedRecordRuntimeType( + RecordShape shape, List fields) { + return _generateRecordRuntimeTypeHelper('_masqueradedRecordRuntimeType', + getMasqueradedRuntimeTypeNullableProcedure, shape, fields); + } + + Procedure _generateRecordRuntimeTypeHelper( + String name, Procedure target, RecordShape shape, List fields) { final List 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 { diff --git a/sdk/lib/_internal/wasm/lib/record_patch.dart b/sdk/lib/_internal/wasm/lib/record_patch.dart index 99abaa621d6..2c7c85fa55e 100644 --- a/sdk/lib/_internal/wasm/lib/record_patch.dart +++ b/sdk/lib/_internal/wasm/lib/record_patch.dart @@ -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 names); + @pragma("wasm:prefer-inline") static _RecordType _getRecordRuntimeType(Record record) => record._recordRuntimeType; + + @pragma("wasm:prefer-inline") + static _RecordType _getMasqueradedRecordRuntimeType(Record record) => + record._masqueradedRecordRuntimeType; } diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index 26c29c94a22..4827242910e 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -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(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(object)); + return Record._getMasqueradedRecordRuntimeType(unsafeCast(object)); } if (category == _typeCategoryObject.toIntUnsigned()) { return _literal();