[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>
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 306fd4b..224258e 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2921,31 +2921,33 @@
 
   @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 @@
 
   @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 da3b84a..e9061c7262 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 @@
           _uninitializedHashBaseIndexConstant,
 
       // _hashMask
-      translator.hashFieldBaseHashMaskField.fieldReference:
-          IntConstant(_computeHashMask(constant.entries.length)),
+      translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0),
 
       // _data
       translator.hashFieldBaseDataField.fieldReference:
@@ -553,8 +551,7 @@
           _uninitializedHashBaseIndexConstant,
 
       // _hashMask
-      translator.hashFieldBaseHashMaskField.fieldReference:
-          IntConstant(_computeHashMask(constant.entries.length)),
+      translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0),
 
       // _data
       translator.hashFieldBaseDataField.fieldReference:
@@ -576,17 +573,6 @@
     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 d9ac961..9f7ac52 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -174,20 +174,16 @@
   // 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 =
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 07378aa..d3a89ae 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -147,6 +147,9 @@
   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 @@
     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 @@
     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;
+    });
+  }
+}
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index 15aa86e..ba40432 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -287,23 +287,23 @@
     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 @@
   /// 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 @@
     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 @@
           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 @@
     // 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 e89480b..768ed52 100644
--- a/sdk/lib/_internal/wasm/lib/compact_hash.dart
+++ b/sdk/lib/_internal/wasm/lib/compact_hash.dart
@@ -211,6 +211,7 @@
         _OperatorEqualsAndCanonicalHashCode,
         _LinkedHashMapMixin<K, V>,
         _UnmodifiableMapMixin<K, V>,
+        _MapCreateIndexMixin<K, V>,
         _ImmutableLinkedHashMapMixin<K, V>
     implements LinkedHashMap<K, V> {
   factory _ConstMap._uninstantiable() {
@@ -219,28 +220,16 @@
   }
 }
 
-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 @@
       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 @@
     // 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 @@
         _OperatorEqualsAndCanonicalHashCode,
         _LinkedHashSetMixin<E>,
         _UnmodifiableSetMixin<E>,
+        _SetCreateIndexMixin<E>,
         _ImmutableLinkedHashSetMixin<E>
     implements LinkedHashSet<E> {
   factory _ConstSet._uninstantiable() {
@@ -857,63 +875,78 @@
   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);
 }
diff --git a/sdk/lib/_internal/wasm/lib/growable_list.dart b/sdk/lib/_internal/wasm/lib/growable_list.dart
index f824469..321f264 100644
--- a/sdk/lib/_internal/wasm/lib/growable_list.dart
+++ b/sdk/lib/_internal/wasm/lib/growable_list.dart
@@ -8,6 +8,7 @@
 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 @@
 
   // 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 0828ab4..a1c8b5a 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 @@
   }
 
   @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 @@
   }
 
   @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 @@
         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 @@
         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 @@
         _HashBase,
         _OperatorEqualsAndHashCode,
         _LinkedHashMapMixin<K, V>,
+        _MapCreateIndexMixin<K, V>,
         _UnmodifiableMapMixin<K, V>,
         _ImmutableLinkedHashMapMixin<K, V>
     implements LinkedHashMap<K, V> {}
@@ -110,13 +144,14 @@
         _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);
 }