From fd954d426fa793e4bce91c2000db55cfa20d3ef4 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Mon, 11 Mar 2024 13:07:47 +0000 Subject: [PATCH] [dart2wasm] Better handling of list/map/set literals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the literals are * empty: call function with zero arguments * non-empty: call function taking `WasmArray` => This avoids making a call for each element we add. Reorganize the map/set mixins: * pull out the `createIndex` into it's own mixin * make mutable set/map implementations use that mixin * remove compiler-knowledge about how to create hash mask (instead set the hashmask field in `_createIndex()`) => Function creating map/set from `WasmArray` can create index => Allows the above Outline functions (for now only for list/map/set literal creation functions): * Often many call sites use literals with instantiated types * Make outlined functions that will populate those instantiated types and forward the arguments This turns e.g. `{}` (if `A`/`B` are instantiated, e.g. `int`) global.get A global.get B call _WasmDefaultMap._default into call createEmpty createEmpty: global.get A global.get B call _WasmDefaultMap._default All together this * reduces flute size by a bit more than 0.5% * makes some benchmarks faster Change-Id: I13c7e6060470d74769a3d49816671aceb2cd3aab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/356500 Reviewed-by: Ömer Ağacan Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 146 +++++++++------- pkg/dart2wasm/lib/constants.dart | 18 +- pkg/dart2wasm/lib/kernel_nodes.dart | 20 +-- pkg/dart2wasm/lib/translator.dart | 116 ++++++++++--- pkg/dart2wasm/lib/types.dart | 28 ++-- sdk/lib/_internal/wasm/lib/compact_hash.dart | 157 +++++++++++------- sdk/lib/_internal/wasm/lib/growable_list.dart | 2 + .../_internal/wasm/lib/hash_factories.dart | 47 +++++- 8 files changed, 337 insertions(+), 197 deletions(-) diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 306fd4b63e3..224258e8588 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -2921,31 +2921,33 @@ class CodeGenerator extends ExpressionVisitor1 @override w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) { - return makeListFromExpressions(node.expressions, node.typeArgument, - isGrowable: true); - } + final useSharedCreator = types.isTypeConstant(node.typeArgument); - /// Allocate a Dart `List` with element type [typeArg], length [length] and - /// push the list to the stack. - /// - /// [generateItem] will be called [length] times to initialize list elements. - /// - /// Concrete type of the list will be `_GrowableList` if [isGrowable] is - /// true, `_List` otherwise. - w.ValueType makeList(DartType typeArg, int length, - void Function(w.ValueType, int) generateItem, - {bool isGrowable = false}) { - return translator.makeList( - function, (b) => types.makeType(this, typeArg), length, generateItem, - isGrowable: isGrowable); - } + final passType = !useSharedCreator; + final passArray = node.expressions.isNotEmpty; - w.ValueType makeListFromExpressions( - List expressions, DartType typeArg, - {bool isGrowable = false}) => - makeList(typeArg, expressions.length, - (w.ValueType elementType, int i) => wrap(expressions[i], elementType), - isGrowable: isGrowable); + final targetReference = passArray + ? translator.growableListFromWasmArray.reference + : translator.growableListEmpty.reference; + + final w.BaseFunction target = useSharedCreator + ? translator.partialInstantiator.getOneTypeArgumentForwarder( + targetReference, + node.typeArgument, + 'create${passArray ? '' : 'Empty'}List<${node.typeArgument}>') + : translator.functions.getFunction(targetReference); + + if (passType) { + types.makeType(this, node.typeArgument); + } + if (passArray) { + makeArrayFromExpressions(node.expressions, + translator.coreTypes.objectRawType(Nullability.nullable)); + } + + b.call(target); + return target.type.outputs.single; + } w.ValueType makeArrayFromExpressions( List expressions, InterfaceType elementType) { @@ -2963,55 +2965,71 @@ class CodeGenerator extends ExpressionVisitor1 @override w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) { - types.makeType(this, node.keyType); - types.makeType(this, node.valueType); - w.ValueType factoryReturnType = - call(translator.mapFactory.reference).single; - if (node.entries.isEmpty) { - return factoryReturnType; + final useSharedCreator = types.isTypeConstant(node.keyType) && + types.isTypeConstant(node.valueType); + + final passTypes = !useSharedCreator; + final passArray = node.entries.isNotEmpty; + + final targetReference = passArray + ? translator.mapFromWasmArray.reference + : translator.mapFactory.reference; + + final w.BaseFunction target = useSharedCreator + ? translator.partialInstantiator.getTwoTypeArgumentForwarder( + targetReference, + node.keyType, + node.valueType, + 'create${passArray ? '' : 'Empty'}Map<${node.keyType}, ${node.valueType}>') + : translator.functions.getFunction(targetReference); + + if (passTypes) { + types.makeType(this, node.keyType); + types.makeType(this, node.valueType); } - w.FunctionType mapPutType = - translator.functions.getFunctionType(translator.mapPut.reference); - w.ValueType putReceiverType = mapPutType.inputs[0]; - w.ValueType putKeyType = mapPutType.inputs[1]; - w.ValueType putValueType = mapPutType.inputs[2]; - w.Local mapLocal = addLocal(putReceiverType); - translator.convertType(function, factoryReturnType, mapLocal.type); - b.local_set(mapLocal); - for (MapLiteralEntry entry in node.entries) { - b.local_get(mapLocal); - wrap(entry.key, putKeyType); - wrap(entry.value, putValueType); - call(translator.mapPut.reference); - b.drop(); + if (passArray) { + makeArray(translator.nullableObjectArrayType, 2 * node.entries.length, + (elementType, elementIndex) { + final index = elementIndex ~/ 2; + final entry = node.entries[index]; + if (elementIndex % 2 == 0) { + wrap(entry.key, elementType); + } else { + wrap(entry.value, elementType); + } + }); } - b.local_get(mapLocal); - return mapLocal.type; + b.call(target); + return target.type.outputs.single; } @override w.ValueType visitSetLiteral(SetLiteral node, w.ValueType expectedType) { - types.makeType(this, node.typeArgument); - w.ValueType factoryReturnType = - call(translator.setFactory.reference).single; - if (node.expressions.isEmpty) { - return factoryReturnType; + final useSharedCreator = types.isTypeConstant(node.typeArgument); + + final passType = !useSharedCreator; + final passArray = node.expressions.isNotEmpty; + + final targetReference = passArray + ? translator.setFromWasmArray.reference + : translator.setFactory.reference; + + final w.BaseFunction target = useSharedCreator + ? translator.partialInstantiator.getOneTypeArgumentForwarder( + targetReference, + node.typeArgument, + 'create${passArray ? '' : 'Empty'}Set<${node.typeArgument}>') + : translator.functions.getFunction(targetReference); + + if (passType) { + types.makeType(this, node.typeArgument); } - w.FunctionType setAddType = - translator.functions.getFunctionType(translator.setAdd.reference); - w.ValueType addReceiverType = setAddType.inputs[0]; - w.ValueType addKeyType = setAddType.inputs[1]; - w.Local setLocal = addLocal(addReceiverType); - translator.convertType(function, factoryReturnType, setLocal.type); - b.local_set(setLocal); - for (Expression element in node.expressions) { - b.local_get(setLocal); - wrap(element, addKeyType); - call(translator.setAdd.reference); - b.drop(); + if (passArray) { + makeArrayFromExpressions(node.expressions, + translator.coreTypes.objectRawType(Nullability.nullable)); } - b.local_get(setLocal); - return setLocal.type; + b.call(target); + return target.type.outputs.single; } @override diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index da3b84a40ed..e9061c7262d 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -2,7 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:math'; import 'dart:typed_data'; import 'package:kernel/ast.dart'; @@ -519,8 +518,7 @@ class ConstantCreator extends ConstantVisitor _uninitializedHashBaseIndexConstant, // _hashMask - translator.hashFieldBaseHashMaskField.fieldReference: - IntConstant(_computeHashMask(constant.entries.length)), + translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0), // _data translator.hashFieldBaseDataField.fieldReference: @@ -553,8 +551,7 @@ class ConstantCreator extends ConstantVisitor _uninitializedHashBaseIndexConstant, // _hashMask - translator.hashFieldBaseHashMaskField.fieldReference: - IntConstant(_computeHashMask(constant.entries.length)), + translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0), // _data translator.hashFieldBaseDataField.fieldReference: @@ -576,17 +573,6 @@ class ConstantCreator extends ConstantVisitor return ensureConstant(instanceConstant); } - int _computeHashMask(int entries) { - // This computation of the hash mask follows the computations in - // [_ImmutableLinkedHashMapMixin._createIndex], - // [_ImmutableLinkedHashSetMixin._createIndex] and - // [_HashBase._indexSizeToHashMask]. - const int initialIndexSize = 8; - final int indexSize = max(entries * 2, initialIndexSize); - final int hashMask = (1 << (31 - (indexSize - 1).bitLength)) - 1; - return hashMask; - } - @override ConstantInfo? visitStaticTearOffConstant(StaticTearOffConstant constant) { Procedure member = constant.targetReference.asProcedure; diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index d9ac961c3c4..9f7ac527f91 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -174,20 +174,16 @@ mixin KernelNodes { // dart:collection procedures and fields late final Procedure mapFactory = index.getProcedure("dart:collection", "LinkedHashMap", "_default"); - late final Procedure mapPut = index - .getClass("dart:collection", "_WasmDefaultMap") - .superclass! // _LinkedHashMapMixin - .procedures - .firstWhere((p) => p.name.text == "[]="); + late final Procedure mapFromWasmArray = + index.getProcedure("dart:collection", "_WasmDefaultMap", "fromWasmArray"); late final Procedure setFactory = index.getProcedure("dart:collection", "LinkedHashSet", "_default"); - late final Procedure setAdd = index - .getClass("dart:collection", "_WasmDefaultSet") - .superclass! // _LinkedHashSetMixin - .procedures - .firstWhere((p) => p.name.text == "add"); - late final Procedure growableListAdd = - index.getProcedure("dart:core", "_GrowableList", "add"); + late final Procedure setFromWasmArray = + index.getProcedure("dart:collection", "_WasmDefaultSet", "fromWasmArray"); + late final Procedure growableListEmpty = + index.getProcedure("dart:core", "_GrowableList", "empty"); + late final Constructor growableListFromWasmArray = + index.getConstructor("dart:core", "_GrowableList", "_withData"); late final Procedure hashImmutableIndexNullable = index.getProcedure( "dart:collection", "_HashAbstractImmutableBase", "get:_indexNullable"); late final Field hashFieldBaseIndexField = diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 07378aa7491..d3a89ae32fe 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -147,6 +147,9 @@ class Translator with KernelNodes { late final w.RefType nullableObjectArrayTypeRef = w.RefType.def(nullableObjectArrayType, nullable: false); + late final PartialInstantiator partialInstantiator = + PartialInstantiator(this); + /// Dart types that have specialized Wasm representations. late final Map builtinTypes = { coreTypes.boolClass: w.NumType.i32, @@ -994,29 +997,6 @@ class Translator with KernelNodes { return null; } - w.ValueType makeList( - w.FunctionBuilder function, - void Function(w.InstructionsBuilder b) generateType, - int length, - void Function(w.ValueType, int) generateItem, - {bool isGrowable = false}) { - final b = function.body; - - final Class cls = isGrowable ? growableListClass : fixedLengthListClass; - final ClassInfo info = classInfo[cls]!; - functions.recordClassAllocation(info.classId); - final w.ArrayType arrayType = listArrayType; - - b.i32_const(info.classId); - b.i32_const(initialIdentityHash); - generateType(b); - b.i64_const(length); - makeArray(function, arrayType, length, generateItem); - b.struct_new(info.struct); - - return info.nonNullableType; - } - w.ValueType makeArray(w.FunctionBuilder function, w.ArrayType arrayType, int length, void Function(w.ValueType, int) generateItem) { final b = function.body; @@ -1356,3 +1336,93 @@ class NodeCounter extends VisitorDefault with VisitorVoidMixin { node.visitChildren(this); } } + +/// Creates forwarders for generic functions where the caller passes a constant +/// type argument. +/// +/// Let's say we have +/// +/// foo(args) => ...; +/// +/// and 3 call sites +/// +/// foo(args) +/// foo(args) +/// foo(args) +/// +/// the callsites can instead call a forwarder +/// +/// fooInt(args) +/// fooInt(args) +/// fooDouble(args) +/// +/// fooInt(args) => foo(args) +/// fooDouble(args) => foo(args) +/// +/// This saves code size on the call site. +class PartialInstantiator { + final Translator translator; + + final Map<(Reference, DartType), w.BaseFunction> _oneTypeArgument = {}; + final Map<(Reference, DartType, DartType), w.BaseFunction> _twoTypeArguments = + {}; + + PartialInstantiator(this.translator); + + w.BaseFunction getOneTypeArgumentForwarder( + Reference target, DartType type, String name) { + assert(translator.types.isTypeConstant(type)); + + return _oneTypeArgument.putIfAbsent((target, type), () { + final wasmTarget = translator.functions.getFunction(target); + + final function = translator.m.functions.define( + translator.m.types.defineFunction( + [...wasmTarget.type.inputs.skip(1)], + wasmTarget.type.outputs, + ), + name); + final b = function.body; + translator.constants.instantiateConstant(function, b, + TypeLiteralConstant(type), translator.types.nonNullableTypeType); + for (int i = 1; i < wasmTarget.type.inputs.length; ++i) { + b.local_get(b.locals[i - 1]); + } + b.call(wasmTarget); + b.return_(); + b.end(); + + return function; + }); + } + + w.BaseFunction getTwoTypeArgumentForwarder( + Reference target, DartType type1, DartType type2, String name) { + assert(translator.types.isTypeConstant(type1)); + assert(translator.types.isTypeConstant(type2)); + + return _twoTypeArguments.putIfAbsent((target, type1, type2), () { + final wasmTarget = translator.functions.getFunction(target); + + final function = translator.m.functions.define( + translator.m.types.defineFunction( + [...wasmTarget.type.inputs.skip(2)], + wasmTarget.type.outputs, + ), + name); + final b = function.body; + translator.constants.instantiateConstant(function, b, + TypeLiteralConstant(type1), translator.types.nonNullableTypeType); + translator.constants.instantiateConstant(function, b, + TypeLiteralConstant(type2), translator.types.nonNullableTypeType); + for (int i = 2; i < wasmTarget.type.inputs.length; ++i) { + b.local_get(b.locals[i - 2]); + } + b.call(wasmTarget); + b.return_(); + b.end(); + + return function; + }); + } +} diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 15aa86edf11..ba404325310 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -287,23 +287,23 @@ class Types { return typeNamesType; } - bool _isTypeConstant(DartType type) { + bool isTypeConstant(DartType type) { return type is DynamicType || type is VoidType || type is NeverType || type is NullType || - type is FutureOrType && _isTypeConstant(type.typeArgument) || + type is FutureOrType && isTypeConstant(type.typeArgument) || (type is FunctionType && - type.typeParameters.every((p) => _isTypeConstant(p.bound)) && - _isTypeConstant(type.returnType) && - type.positionalParameters.every(_isTypeConstant) && - type.namedParameters.every((n) => _isTypeConstant(n.type))) || - type is InterfaceType && type.typeArguments.every(_isTypeConstant) || + type.typeParameters.every((p) => isTypeConstant(p.bound)) && + isTypeConstant(type.returnType) && + type.positionalParameters.every(isTypeConstant) && + type.namedParameters.every((n) => isTypeConstant(n.type))) || + type is InterfaceType && type.typeArguments.every(isTypeConstant) || (type is RecordType && - type.positional.every(_isTypeConstant) && - type.named.every((n) => _isTypeConstant(n.type))) || + type.positional.every(isTypeConstant) && + type.named.every((n) => isTypeConstant(n.type))) || type is StructuralParameterType || - type is ExtensionType && _isTypeConstant(type.extensionTypeErasure); + type is ExtensionType && isTypeConstant(type.extensionTypeErasure); } Class classForType(DartType type) { @@ -359,7 +359,7 @@ class Types { /// Allocates a `WasmArray<_Type>` from [types] and pushes it to the /// stack. void _makeTypeArray(CodeGenerator codeGen, Iterable types) { - if (types.every(_isTypeConstant)) { + if (types.every(isTypeConstant)) { translator.constants.instantiateConstant(codeGen.function, codeGen.b, translator.constants.makeTypeArray(types), typeArrayExpectedType); } else { @@ -457,7 +457,7 @@ class Types { b.i64_const(type.requiredParameterCount); // WasmArray<_NamedParameter> namedParameters - if (type.namedParameters.every((n) => _isTypeConstant(n.type))) { + if (type.namedParameters.every((n) => isTypeConstant(n.type))) { translator.constants.instantiateConstant( codeGen.function, b, @@ -469,7 +469,7 @@ class Types { namedParameterClass.constructors.single; List expressions = []; for (NamedType n in type.namedParameters) { - expressions.add(_isTypeConstant(n.type) + expressions.add(isTypeConstant(n.type) ? ConstantExpression( translator.constants.makeNamedParameterConstant(n), namedParameterType) @@ -495,7 +495,7 @@ class Types { // Always ensure type is normalized before making a type. type = normalize(type); final b = codeGen.b; - if (_isTypeConstant(type)) { + if (isTypeConstant(type)) { translator.constants.instantiateConstant( codeGen.function, b, TypeLiteralConstant(type), nonNullableTypeType); return nonNullableTypeType; diff --git a/sdk/lib/_internal/wasm/lib/compact_hash.dart b/sdk/lib/_internal/wasm/lib/compact_hash.dart index e89480b53ad..768ed527169 100644 --- a/sdk/lib/_internal/wasm/lib/compact_hash.dart +++ b/sdk/lib/_internal/wasm/lib/compact_hash.dart @@ -211,6 +211,7 @@ base class _ConstMap extends _HashFieldBase _OperatorEqualsAndCanonicalHashCode, _LinkedHashMapMixin, _UnmodifiableMapMixin, + _MapCreateIndexMixin, _ImmutableLinkedHashMapMixin implements LinkedHashMap { factory _ConstMap._uninstantiable() { @@ -219,28 +220,16 @@ base class _ConstMap extends _HashFieldBase } } -mixin _ImmutableLinkedHashMapMixin - on _LinkedHashMapMixin, _HashFieldBase { - bool containsKey(Object? key) { - if (identical(_index, _uninitializedHashBaseIndex)) { - _createIndex(); - } - return super.containsKey(key); - } +mixin _MapCreateIndexMixin on _LinkedHashMapMixin, _HashFieldBase { + void _createIndex(bool canContainDuplicates) { + assert(_index == _uninitializedHashBaseIndex); + assert(_hashMask == _HashBase._UNINITIALIZED_HASH_MASK); + assert(_deletedKeys == 0); - V? operator [](Object? key) { - if (identical(_index, _uninitializedHashBaseIndex)) { - _createIndex(); - } - return super[key]; - } - - void _createIndex() { final size = _roundUpToPowerOfTwo(max(_data.length, _HashBase._INITIAL_INDEX_SIZE)); final newIndex = WasmArray.filled(size, const WasmI32(0)); - final hashMask = _HashBase._indexSizeToHashMask(size); - assert(_hashMask == hashMask); + final hashMask = _hashMask = _HashBase._indexSizeToHashMask(size); for (int j = 0; j < _usedData; j += 2) { final key = _data[j] as K; @@ -249,6 +238,18 @@ mixin _ImmutableLinkedHashMapMixin final hashPattern = _HashBase._hashPattern(fullHash, hashMask, size); final d = _findValueOrInsertPoint(key, fullHash, hashPattern, size, newIndex); + + if (d > 0 && canContainDuplicates) { + // Replace the existing entry. + _data[d] = _data[j + 1]; + + // Mark this as a free slot. + _HashBase._setDeletedAt(_data, j); + _HashBase._setDeletedAt(_data, j + 1); + _deletedKeys++; + continue; + } + // We just allocated the index, so we should not find this key in it yet. assert(d <= 0); @@ -263,6 +264,22 @@ mixin _ImmutableLinkedHashMapMixin // Publish new index, uses store release semantics. _index = newIndex; } +} + +mixin _ImmutableLinkedHashMapMixin on _MapCreateIndexMixin { + bool containsKey(Object? key) { + if (identical(_index, _uninitializedHashBaseIndex)) { + _createIndex(false); + } + return super.containsKey(key); + } + + V? operator [](Object? key) { + if (identical(_index, _uninitializedHashBaseIndex)) { + _createIndex(false); + } + return super[key]; + } Iterable get keys => _CompactIterableImmutable(this, _data, _usedData, -2, 2); @@ -842,6 +859,7 @@ base class _ConstSet extends _HashFieldBase _OperatorEqualsAndCanonicalHashCode, _LinkedHashSetMixin, _UnmodifiableSetMixin, + _SetCreateIndexMixin, _ImmutableLinkedHashSetMixin implements LinkedHashSet { factory _ConstSet._uninstantiable() { @@ -857,63 +875,78 @@ base class _ConstSet extends _HashFieldBase Set toSet() => _Set()..addAll(this); } -mixin _ImmutableLinkedHashSetMixin +mixin _SetCreateIndexMixin on Set, _LinkedHashSetMixin, _HashFieldBase { + void _createIndex(bool canContainDuplicates) { + assert(_index == _uninitializedHashBaseIndex); + assert(_hashMask == _HashBase._UNINITIALIZED_HASH_MASK); + assert(_deletedKeys == 0); + + final size = _roundUpToPowerOfTwo( + max(_data.length * 2, _HashBase._INITIAL_INDEX_SIZE)); + final index = WasmArray.filled(size, const WasmI32(0)); + final hashMask = _hashMask = _HashBase._indexSizeToHashMask(size); + + final sizeMask = size - 1; + final maxEntries = size >> 1; + + for (int j = 0; j < _usedData; j++) { + next: + { + final key = _data[j]; + + final fullHash = _hashCode(key); + final hashPattern = _HashBase._hashPattern(fullHash, hashMask, size); + + int i = _HashBase._firstProbe(fullHash, sizeMask); + int pair = index.readUnsigned(i); + while (pair != _HashBase._UNUSED_PAIR) { + assert(pair != _HashBase._DELETED_PAIR); + + final int d = hashPattern ^ pair; + if (d < maxEntries) { + // We should not already find an entry in the index. + if (canContainDuplicates && _equals(key, _data[d])) { + // Exists already, skip this entry. + _HashBase._setDeletedAt(_data, j); + _deletedKeys++; + break next; + } else { + assert(!_equals(key, _data[d])); + } + } + + i = _HashBase._nextProbe(i, sizeMask); + pair = index.readUnsigned(i); + } + + final int insertionPoint = i; + assert(1 <= hashPattern && hashPattern < (1 << 32)); + assert((hashPattern & j) == 0); + index[insertionPoint] = WasmI32.fromInt(hashPattern | j); + } + } + + // Publish new index, uses store release semantics. + _index = index; + } +} + +mixin _ImmutableLinkedHashSetMixin on _SetCreateIndexMixin { E? lookup(Object? key) { if (identical(_index, _uninitializedHashBaseIndex)) { - _createIndex(); + _createIndex(false); } return super.lookup(key); } bool contains(Object? key) { if (identical(_index, _uninitializedHashBaseIndex)) { - _createIndex(); + _createIndex(false); } return super.contains(key); } - void _createIndex() { - final size = _roundUpToPowerOfTwo( - max(_data.length * 2, _HashBase._INITIAL_INDEX_SIZE)); - final index = WasmArray.filled(size, const WasmI32(0)); - final hashMask = _HashBase._indexSizeToHashMask(size); - assert(_hashMask == hashMask); - - final sizeMask = size - 1; - final maxEntries = size >> 1; - - for (int j = 0; j < _usedData; j++) { - final key = _data[j]; - - final fullHash = _hashCode(key); - final hashPattern = _HashBase._hashPattern(fullHash, hashMask, size); - - int i = _HashBase._firstProbe(fullHash, sizeMask); - int pair = index.readUnsigned(i); - while (pair != _HashBase._UNUSED_PAIR) { - assert(pair != _HashBase._DELETED_PAIR); - - final int d = hashPattern ^ pair; - if (d < maxEntries) { - // We should not already find an entry in the index. - assert(!_equals(key, _data[d])); - } - - i = _HashBase._nextProbe(i, sizeMask); - pair = index.readUnsigned(i); - } - - final int insertionPoint = i; - assert(1 <= hashPattern && hashPattern < (1 << 32)); - assert((hashPattern & j) == 0); - index[insertionPoint] = WasmI32.fromInt(hashPattern | j); - } - - // Publish new index, uses store release semantics. - _index = index; - } - Iterator get iterator => _CompactIteratorImmutable(this, _data, _usedData, -1, 1); } diff --git a/sdk/lib/_internal/wasm/lib/growable_list.dart b/sdk/lib/_internal/wasm/lib/growable_list.dart index f824469b0d2..321f2649902 100644 --- a/sdk/lib/_internal/wasm/lib/growable_list.dart +++ b/sdk/lib/_internal/wasm/lib/growable_list.dart @@ -8,6 +8,7 @@ part of "core_patch.dart"; class _GrowableList extends _ModifiableList { _GrowableList._(int length, int capacity) : super(length, capacity); + @pragma("wasm:entry-point") _GrowableList._withData(WasmArray data) : super._withData(data.length, data); @@ -21,6 +22,7 @@ class _GrowableList extends _ModifiableList { // Specialization of List.empty constructor for growable == true. // Used by pkg/dart2wasm/lib/list_factory_specializer.dart. + @pragma("wasm:entry-point") factory _GrowableList.empty() => _GrowableList(0); // Specialization of List.filled constructor for growable == true. diff --git a/sdk/lib/_internal/wasm/lib/hash_factories.dart b/sdk/lib/_internal/wasm/lib/hash_factories.dart index 0828ab4587f..a1c8b5a107d 100644 --- a/sdk/lib/_internal/wasm/lib/hash_factories.dart +++ b/sdk/lib/_internal/wasm/lib/hash_factories.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import "dart:_internal" show patch; +import "dart:_wasm"; import "dart:typed_data" show Uint32List; @@ -28,7 +29,7 @@ class LinkedHashMap { } @pragma("wasm:entry-point") - factory LinkedHashMap._default() => _WasmDefaultMap(); + static _WasmDefaultMap _default() => _WasmDefaultMap(); @patch factory LinkedHashMap.identity() => _CompactLinkedIdentityHashMap(); @@ -56,7 +57,7 @@ class LinkedHashSet { } @pragma("wasm:entry-point") - factory LinkedHashSet._default() => _WasmDefaultSet(); + static _WasmDefaultSet _default() => _WasmDefaultSet(); @patch factory LinkedHashSet.identity() => _CompactLinkedIdentityHashSet(); @@ -68,9 +69,25 @@ base class _WasmDefaultMap extends _HashFieldBase MapMixin, _HashBase, _OperatorEqualsAndHashCode, - _LinkedHashMapMixin + _LinkedHashMapMixin, + _MapCreateIndexMixin implements LinkedHashMap { @pragma("wasm:entry-point") + static _WasmDefaultMap fromWasmArray(WasmArray data) { + final map = _WasmDefaultMap(); + assert(map._index == _uninitializedHashBaseIndex); + assert(map._hashMask == _HashBase._UNINITIALIZED_HASH_MASK); + assert(map._data == _uninitializedHashBaseData); + assert(map._usedData == 0); + assert(map._deletedKeys == 0); + + map._data = data; + map._usedData = data.length; + map._createIndex(true); + + return map; + } + void operator []=(K key, V value); } @@ -80,9 +97,25 @@ base class _WasmDefaultSet extends _HashFieldBase SetMixin, _HashBase, _OperatorEqualsAndHashCode, - _LinkedHashSetMixin + _LinkedHashSetMixin, + _SetCreateIndexMixin implements LinkedHashSet { @pragma("wasm:entry-point") + static _WasmDefaultSet fromWasmArray(WasmArray data) { + final map = _WasmDefaultSet(); + assert(map._index == _uninitializedHashBaseIndex); + assert(map._hashMask == _HashBase._UNINITIALIZED_HASH_MASK); + assert(map._data == _uninitializedHashBaseData); + assert(map._usedData == 0); + assert(map._deletedKeys == 0); + + map._data = data; + map._usedData = data.length; + map._createIndex(true); + + return map; + } + bool add(E key); Set cast() => Set.castFrom(this, newSet: _newEmpty); @@ -99,6 +132,7 @@ base class _WasmImmutableMap extends _HashFieldBase _HashBase, _OperatorEqualsAndHashCode, _LinkedHashMapMixin, + _MapCreateIndexMixin, _UnmodifiableMapMixin, _ImmutableLinkedHashMapMixin implements LinkedHashMap {} @@ -110,13 +144,14 @@ base class _WasmImmutableSet extends _HashFieldBase _HashBase, _OperatorEqualsAndHashCode, _LinkedHashSetMixin, + _SetCreateIndexMixin, _UnmodifiableSetMixin, _ImmutableLinkedHashSetMixin implements LinkedHashSet { Set cast() => Set.castFrom(this, newSet: _newEmpty); - static Set _newEmpty() => LinkedHashSet._default(); + static Set _newEmpty() => LinkedHashSet._default(); // Returns a mutable set. - Set toSet() => LinkedHashSet._default()..addAll(this); + Set toSet() => LinkedHashSet._default()..addAll(this); }