mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 18:19:44 +00:00
[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:
parent
03101837d9
commit
fd954d426f
|
@ -2921,32 +2921,34 @@ 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);
|
||||
|
||||
final passType = !useSharedCreator;
|
||||
final passArray = node.expressions.isNotEmpty;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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);
|
||||
b.call(target);
|
||||
return target.type.outputs.single;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
w.ValueType makeArrayFromExpressions(
|
||||
List<Expression> expressions, InterfaceType elementType) {
|
||||
return makeArray(
|
||||
|
@ -2963,55 +2965,71 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
|
||||
@override
|
||||
w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) {
|
||||
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.ValueType factoryReturnType =
|
||||
call(translator.mapFactory.reference).single;
|
||||
if (node.entries.isEmpty) {
|
||||
return factoryReturnType;
|
||||
}
|
||||
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) {
|
||||
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.ValueType factoryReturnType =
|
||||
call(translator.setFactory.reference).single;
|
||||
if (node.expressions.isEmpty) {
|
||||
return factoryReturnType;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,33 +875,24 @@ 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 {
|
||||
E? lookup(Object? key) {
|
||||
if (identical(_index, _uninitializedHashBaseIndex)) {
|
||||
_createIndex();
|
||||
}
|
||||
return super.lookup(key);
|
||||
}
|
||||
void _createIndex(bool canContainDuplicates) {
|
||||
assert(_index == _uninitializedHashBaseIndex);
|
||||
assert(_hashMask == _HashBase._UNINITIALIZED_HASH_MASK);
|
||||
assert(_deletedKeys == 0);
|
||||
|
||||
bool contains(Object? key) {
|
||||
if (identical(_index, _uninitializedHashBaseIndex)) {
|
||||
_createIndex();
|
||||
}
|
||||
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 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);
|
||||
|
@ -897,8 +906,15 @@ mixin _ImmutableLinkedHashSetMixin<E>
|
|||
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);
|
||||
|
@ -909,10 +925,27 @@ mixin _ImmutableLinkedHashSetMixin<E>
|
|||
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(false);
|
||||
}
|
||||
return super.lookup(key);
|
||||
}
|
||||
|
||||
bool contains(Object? key) {
|
||||
if (identical(_index, _uninitializedHashBaseIndex)) {
|
||||
_createIndex(false);
|
||||
}
|
||||
return super.contains(key);
|
||||
}
|
||||
|
||||
Iterator<E> get iterator =>
|
||||
_CompactIteratorImmutable<E>(this, _data, _usedData, -1, 1);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue