[dart2wasm] Better handling of list/map/set literals

If the literals are

* empty: call function with zero arguments
* non-empty: call function taking `WasmArray<Object?>`

=> 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<Object?>` 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. `<A, B>{}` (if `A`/`B` are instantiated, e.g. `int`)

  global.get A
  global.get B
  call _WasmDefaultMap._default

into

  call createEmpty<A, B>

  createEmpty<A, B>:
    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 <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann 2024-03-11 13:07:47 +00:00 committed by Commit Queue
parent 03101837d9
commit fd954d426f
8 changed files with 337 additions and 197 deletions

View file

@ -2921,31 +2921,33 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@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<Expression> 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<Expression> expressions, InterfaceType elementType) {
@ -2963,55 +2965,71 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@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

View file

@ -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<ConstantInfo?>
_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<ConstantInfo?>
_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<ConstantInfo?>
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;

View file

@ -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<K, V>
.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<K, V>
.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 =

View file

@ -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<Class, w.StorageType> 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<void> with VisitorVoidMixin {
node.visitChildren(this);
}
}
/// Creates forwarders for generic functions where the caller passes a constant
/// type argument.
///
/// Let's say we have
///
/// foo<T>(args) => ...;
///
/// and 3 call sites
///
/// foo<int>(args)
/// foo<int>(args)
/// foo<double>(args)
///
/// the callsites can instead call a forwarder
///
/// fooInt(args)
/// fooInt(args)
/// fooDouble(args)
///
/// fooInt(args) => foo<int>(args)
/// fooDouble(args) => foo<double>(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;
});
}
}

View file

@ -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<DartType> 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<Expression> 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;

View file

@ -211,6 +211,7 @@ base class _ConstMap<K, V> extends _HashFieldBase
_OperatorEqualsAndCanonicalHashCode,
_LinkedHashMapMixin<K, V>,
_UnmodifiableMapMixin<K, V>,
_MapCreateIndexMixin<K, V>,
_ImmutableLinkedHashMapMixin<K, V>
implements LinkedHashMap<K, V> {
factory _ConstMap._uninstantiable() {
@ -219,28 +220,16 @@ base class _ConstMap<K, V> extends _HashFieldBase
}
}
mixin _ImmutableLinkedHashMapMixin<K, V>
on _LinkedHashMapMixin<K, V>, _HashFieldBase {
bool containsKey(Object? key) {
if (identical(_index, _uninitializedHashBaseIndex)) {
_createIndex();
}
return super.containsKey(key);
}
mixin _MapCreateIndexMixin<K, V> on _LinkedHashMapMixin<K, V>, _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<WasmI32>.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<K, V>
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<K, V>
// Publish new index, uses store release semantics.
_index = newIndex;
}
}
mixin _ImmutableLinkedHashMapMixin<K, V> on _MapCreateIndexMixin<K, V> {
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<K> get keys =>
_CompactIterableImmutable<K>(this, _data, _usedData, -2, 2);
@ -842,6 +859,7 @@ base class _ConstSet<E> extends _HashFieldBase
_OperatorEqualsAndCanonicalHashCode,
_LinkedHashSetMixin<E>,
_UnmodifiableSetMixin<E>,
_SetCreateIndexMixin<E>,
_ImmutableLinkedHashSetMixin<E>
implements LinkedHashSet<E> {
factory _ConstSet._uninstantiable() {
@ -857,63 +875,78 @@ base class _ConstSet<E> extends _HashFieldBase
Set<E> toSet() => _Set<E>()..addAll(this);
}
mixin _ImmutableLinkedHashSetMixin<E>
mixin _SetCreateIndexMixin<E>
on Set<E>, _LinkedHashSetMixin<E>, _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<WasmI32>.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<E> on _SetCreateIndexMixin<E> {
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<WasmI32>.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<E> get iterator =>
_CompactIteratorImmutable<E>(this, _data, _usedData, -1, 1);
}

View file

@ -8,6 +8,7 @@ part of "core_patch.dart";
class _GrowableList<E> extends _ModifiableList<E> {
_GrowableList._(int length, int capacity) : super(length, capacity);
@pragma("wasm:entry-point")
_GrowableList._withData(WasmArray<Object?> data)
: super._withData(data.length, data);
@ -21,6 +22,7 @@ class _GrowableList<E> extends _ModifiableList<E> {
// 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.

View file

@ -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<K, V> {
}
@pragma("wasm:entry-point")
factory LinkedHashMap._default() => _WasmDefaultMap<K, V>();
static _WasmDefaultMap<K, V> _default<K, V>() => _WasmDefaultMap<K, V>();
@patch
factory LinkedHashMap.identity() => _CompactLinkedIdentityHashMap<K, V>();
@ -56,7 +57,7 @@ class LinkedHashSet<E> {
}
@pragma("wasm:entry-point")
factory LinkedHashSet._default() => _WasmDefaultSet<E>();
static _WasmDefaultSet<E> _default<E>() => _WasmDefaultSet<E>();
@patch
factory LinkedHashSet.identity() => _CompactLinkedIdentityHashSet<E>();
@ -68,9 +69,25 @@ base class _WasmDefaultMap<K, V> extends _HashFieldBase
MapMixin<K, V>,
_HashBase,
_OperatorEqualsAndHashCode,
_LinkedHashMapMixin<K, V>
_LinkedHashMapMixin<K, V>,
_MapCreateIndexMixin<K, V>
implements LinkedHashMap<K, V> {
@pragma("wasm:entry-point")
static _WasmDefaultMap<K, V> fromWasmArray<K, V>(WasmArray<Object?> data) {
final map = _WasmDefaultMap<K, V>();
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<E> extends _HashFieldBase
SetMixin<E>,
_HashBase,
_OperatorEqualsAndHashCode,
_LinkedHashSetMixin<E>
_LinkedHashSetMixin<E>,
_SetCreateIndexMixin<E>
implements LinkedHashSet<E> {
@pragma("wasm:entry-point")
static _WasmDefaultSet<E> fromWasmArray<E>(WasmArray<Object?> data) {
final map = _WasmDefaultSet<E>();
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<R> cast<R>() => Set.castFrom<E, R>(this, newSet: _newEmpty);
@ -99,6 +132,7 @@ base class _WasmImmutableMap<K, V> extends _HashFieldBase
_HashBase,
_OperatorEqualsAndHashCode,
_LinkedHashMapMixin<K, V>,
_MapCreateIndexMixin<K, V>,
_UnmodifiableMapMixin<K, V>,
_ImmutableLinkedHashMapMixin<K, V>
implements LinkedHashMap<K, V> {}
@ -110,13 +144,14 @@ base class _WasmImmutableSet<E> extends _HashFieldBase
_HashBase,
_OperatorEqualsAndHashCode,
_LinkedHashSetMixin<E>,
_SetCreateIndexMixin<E>,
_UnmodifiableSetMixin<E>,
_ImmutableLinkedHashSetMixin<E>
implements LinkedHashSet<E> {
Set<R> cast<R>() => Set.castFrom<E, R>(this, newSet: _newEmpty);
static Set<R> _newEmpty<R>() => LinkedHashSet<R>._default();
static Set<R> _newEmpty<R>() => LinkedHashSet._default<R>();
// Returns a mutable set.
Set<E> toSet() => LinkedHashSet<E>._default()..addAll(this);
Set<E> toSet() => LinkedHashSet._default<E>()..addAll(this);
}