Skip to content

Commit

Permalink
[dart2wasm] Add ImmutableWasmArray to dart:_wasm
Browse files Browse the repository at this point in the history
Dart does not incorporate immutability into it's type system.
Though the WasmGC type system does have such a concept:

  * mutable wasm arrays: they are invariant in the element type
  * immutable wasm arrays: they are covariant in the element type

Currently we use the mutable wasm array types for e.g.

  const foo = const WasmArray<WasmI32>.literal([0]);

which will be compiled to

  (type $Array<WasmI32> () (array (field (mut i32))))
  (global $globalFoo
         (ref $Array<WasmI32>)
         (i32.const 0)
         (array.new_fixed $Array<WasmI32> 1
  )

=> Notice the **mut** in `array (field (mut i32))`

This CL introduces a `ImmutableWasmArray` which is analogous to
`WasmArray`. The array supports only reading methods since the
contents of the array cannot be modified.

Future CLs will make use of this new array type in various places.

=> This will allow binaryen to optimize things better as it knows the
array contents cannot change.
=> So loads from a (final) global with immutable contents can be
folded away at compile time if the index is known at compile-time.

NOTE: The `WasmArray`& `ImmutableArray` classes are not related to
each other. The reason is that in WasmGC a mutable array isn't a
subtype of a immutable array.

Change-Id: I3a764b5aaa3ac47827332f3064035133ab01f1b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392900
Reviewed-by: Ömer Ağacan <[email protected]>
Commit-Queue: Martin Kustermann <[email protected]>
  • Loading branch information
mkustermann authored and Commit Queue committed Oct 31, 2024
1 parent a975466 commit a96ad1b
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 40 deletions.
4 changes: 2 additions & 2 deletions pkg/dart2wasm/lib/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2742,8 +2742,8 @@ abstract class AstCodeGenerator
w.ValueType makeArrayFromExpressions(
List<Expression> expressions, InterfaceType elementType) {
return makeArray(
translator.arrayTypeForDartType(elementType), expressions.length,
(w.ValueType type, int i) {
translator.arrayTypeForDartType(elementType, mutable: true),
expressions.length, (w.ValueType type, int i) {
translateExpression(expressions[i], type);
});
}
Expand Down
39 changes: 28 additions & 11 deletions pkg/dart2wasm/lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,21 @@ class Constants {

/// Creates a `WasmArray<T>` with the given [Constant]s
InstanceConstant makeArrayOf(
InterfaceType elementType, List<Constant> entries) =>
InstanceConstant(translator.wasmArrayClass.reference, [
elementType,
], {
translator.wasmArrayValueField.fieldReference:
ListConstant(elementType, entries),
});
InterfaceType elementType, List<Constant> entries,
{bool mutable = true}) =>
InstanceConstant(
mutable
? translator.wasmArrayClass.reference
: translator.immutableWasmArrayClass.reference,
[
elementType,
],
{
mutable
? translator.wasmArrayValueField.fieldReference
: translator.immutableWasmArrayValueField.fieldReference:
ListConstant(elementType, entries),
});

/// Ensure that the constant has a Wasm global assigned.
///
Expand Down Expand Up @@ -580,7 +588,10 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
ConstantInfo? visitInstanceConstant(InstanceConstant constant) {
Class cls = constant.classNode;
if (cls == translator.wasmArrayClass) {
return _makeWasmArrayLiteral(constant);
return _makeWasmArrayLiteral(constant, mutable: true);
}
if (cls == translator.immutableWasmArrayClass) {
return _makeWasmArrayLiteral(constant, mutable: false);
}

ClassInfo info = translator.classInfo[cls]!;
Expand Down Expand Up @@ -630,9 +641,10 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
});
}

ConstantInfo? _makeWasmArrayLiteral(InstanceConstant constant) {
w.ArrayType arrayType =
translator.arrayTypeForDartType(constant.typeArguments.single);
ConstantInfo? _makeWasmArrayLiteral(InstanceConstant constant,
{required bool mutable}) {
w.ArrayType arrayType = translator
.arrayTypeForDartType(constant.typeArguments.single, mutable: mutable);
w.ValueType elementType = arrayType.elementType.type.unpacked;

List<Constant> elements =
Expand All @@ -643,6 +655,11 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
lazy |= ensureConstant(element)?.isLazy ?? false;
}

if (tooLargeForArrayNewFixed && !mutable) {
throw Exception('Cannot allocate immutable wasm array of size '
'$tooLargeForArrayNewFixed');
}

return createConstant(constant, w.RefType.def(arrayType, nullable: false),
lazy: lazy, (b) {
if (tooLargeForArrayNewFixed) {
Expand Down
52 changes: 35 additions & 17 deletions pkg/dart2wasm/lib/intrinsics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -647,14 +647,16 @@ class Intrinsifier {
final (ext, extDescriptor) = translator.extensionOfMember(target);
final memberName = extDescriptor.name.text;

// extension WasmArrayExt on WasmArray<T>
if (ext.name == 'WasmArrayExt') {
// extension {,Immutable}WasmArrayExt on {,Immutable}WasmArray<T>
if (ext.name.endsWith('WasmArrayExt')) {
final dartWasmArrayType = dartTypeOf(node.arguments.positional.first);
final dartElementType =
(dartWasmArrayType as InterfaceType).typeArguments.single;
w.ArrayType arrayType =
translator.arrayTypeForDartType(dartElementType);
w.StorageType wasmType = arrayType.elementType.type;
final w.ArrayType arrayType =
(translator.translateType(dartWasmArrayType) as w.RefType).heapType
as w.ArrayType;
final w.FieldType fieldType = arrayType.elementType;
final w.StorageType wasmType = fieldType.type;

switch (memberName) {
case '[]':
Expand All @@ -671,6 +673,7 @@ class Intrinsifier {
}
return wasmType.unpacked;
case '[]=':
assert(fieldType.mutable);
final array = node.arguments.positional[0];
final index = node.arguments.positional[1];
final value = node.arguments.positional[2];
Expand All @@ -682,6 +685,7 @@ class Intrinsifier {
b.array_set(arrayType);
return codeGen.voidMarker;
case 'copy':
assert(fieldType.mutable);
final destArray = node.arguments.positional[0];
final destOffset = node.arguments.positional[1];
final sourceArray = node.arguments.positional[2];
Expand All @@ -701,6 +705,7 @@ class Intrinsifier {
b.array_copy(arrayType, arrayType);
return codeGen.voidMarker;
case 'fill':
assert(fieldType.mutable);
final array = node.arguments.positional[0];
final offset = node.arguments.positional[1];
final value = node.arguments.positional[2];
Expand All @@ -717,6 +722,7 @@ class Intrinsifier {
b.array_fill(arrayType);
return codeGen.voidMarker;
case 'clone':
assert(fieldType.mutable);
// Until `array.new_copy` we need a special case for empty arrays.
// https://github.com/WebAssembly/gc/issues/367
final sourceArray = node.arguments.positional[0];
Expand Down Expand Up @@ -764,14 +770,14 @@ class Intrinsifier {
}
}

// extension (I8|I16|I32|I64|F32|F64)ArrayExt on WasmArray<...>
// extension {,Immutable}(I8|I16|I32|I64|F32|F64)ArrayExt on {,Immutable}WasmArray<...>
if (ext.name.endsWith('ArrayExt')) {
final dartWasmArrayType = dartTypeOf(node.arguments.positional.first);
final dartElementType =
(dartWasmArrayType as InterfaceType).typeArguments.single;
w.ArrayType arrayType =
translator.arrayTypeForDartType(dartElementType);
w.StorageType wasmType = arrayType.elementType.type;
final w.ArrayType arrayType =
(translator.translateType(dartWasmArrayType) as w.RefType).heapType
as w.ArrayType;
final w.FieldType fieldType = arrayType.elementType;
final w.StorageType wasmType = fieldType.type;

final innerExtend =
wasmType == w.PackedType.i8 || wasmType == w.PackedType.i16;
Expand Down Expand Up @@ -815,6 +821,7 @@ class Intrinsifier {
}
return wasmType.unpacked;
case 'write':
assert(fieldType.mutable);
final array = node.arguments.positional[0];
final index = node.arguments.positional[1];
final value = node.arguments.positional[2];
Expand Down Expand Up @@ -1082,10 +1089,15 @@ class Intrinsifier {

if (cls != null && translator.isWasmType(cls)) {
// WasmArray constructors
if (cls == translator.wasmArrayClass) {
if (cls == translator.wasmArrayClass ||
cls == translator.immutableWasmArrayClass) {
final dartWasmArrayType =
InterfaceType(cls, Nullability.nonNullable, node.arguments.types);
final dartElementType = node.arguments.types.single;
w.ArrayType arrayType =
translator.arrayTypeForDartType(dartElementType);
final w.ArrayType arrayType =
(translator.translateType(dartWasmArrayType) as w.RefType).heapType
as w.ArrayType;

final elementType = arrayType.elementType.type;
final isDefaultable = elementType is! w.RefType || elementType.nullable;
if (!isDefaultable && node.arguments.positional.length == 1) {
Expand Down Expand Up @@ -1252,9 +1264,15 @@ class Intrinsifier {

// WasmArray.literal
final klass = node.target.enclosingClass;
if (klass == translator.wasmArrayClass && name == "literal") {
w.ArrayType arrayType =
translator.arrayTypeForDartType(node.arguments.types.single);
if ((klass == translator.wasmArrayClass ||
klass == translator.immutableWasmArrayClass) &&
name == "literal") {
final dartWasmArrayType = InterfaceType(node.target.enclosingClass,
Nullability.nonNullable, node.arguments.types);
final w.ArrayType arrayType =
(translator.translateType(dartWasmArrayType) as w.RefType).heapType
as w.ArrayType;

w.ValueType elementType = arrayType.elementType.type.unpacked;
Expression value = node.arguments.positional[0];
List<Expression> elements = value is ListLiteral
Expand Down
4 changes: 4 additions & 0 deletions pkg/dart2wasm/lib/kernel_nodes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,12 @@ mixin KernelNodes {
late final Class wasmVoidClass = index.getClass("dart:_wasm", "WasmVoid");
late final Class wasmTableClass = index.getClass("dart:_wasm", "WasmTable");
late final Class wasmArrayClass = index.getClass("dart:_wasm", "WasmArray");
late final Class immutableWasmArrayClass =
index.getClass("dart:_wasm", "ImmutableWasmArray");
late final Field wasmArrayValueField =
index.getField("dart:_wasm", "WasmArray", "_value");
late final Field immutableWasmArrayValueField =
index.getField("dart:_wasm", "ImmutableWasmArray", "_value");
late final Field uninitializedHashBaseIndex = index.getTopLevelField(
"dart:_compact_hash", "_uninitializedHashBaseIndex");
late final Field wasmI64ValueField =
Expand Down
30 changes: 21 additions & 9 deletions pkg/dart2wasm/lib/translator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,17 @@ class Translator with KernelNodes {
late final ClassInfo closureInfo = classInfo[closureClass]!;
late final ClassInfo stackTraceInfo = classInfo[stackTraceClass]!;
late final ClassInfo recordInfo = classInfo[coreTypes.recordClass]!;
late final w.ArrayType typeArrayType =
arrayTypeForDartType(InterfaceType(typeClass, Nullability.nonNullable));
late final w.ArrayType typeArrayType = arrayTypeForDartType(
InterfaceType(typeClass, Nullability.nonNullable),
mutable: true);
late final w.ArrayType listArrayType = (classInfo[listBaseClass]!
.struct
.fields[FieldIndex.listArray]
.type as w.RefType)
.heapType as w.ArrayType;
late final w.ArrayType nullableObjectArrayType =
arrayTypeForDartType(coreTypes.objectRawType(Nullability.nullable));
late final w.ArrayType nullableObjectArrayType = arrayTypeForDartType(
coreTypes.objectRawType(Nullability.nullable),
mutable: true);
late final w.RefType typeArrayTypeRef =
w.RefType.def(typeArrayType, nullable: false);
late final w.RefType nullableObjectArrayTypeRef =
Expand Down Expand Up @@ -555,9 +557,16 @@ class Translator with KernelNodes {
}

// Wasm array?
if (cls.superclass == wasmArrayRefClass) {
if (cls == wasmArrayClass) {
DartType elementType = type.typeArguments.single;
return w.RefType.def(arrayTypeForDartType(elementType),
return w.RefType.def(arrayTypeForDartType(elementType, mutable: true),
nullable: nullable);
}

// Immutable Wasm array?
if (cls == immutableWasmArrayClass) {
DartType elementType = type.typeArguments.single;
return w.RefType.def(arrayTypeForDartType(elementType, mutable: false),
nullable: nullable);
}

Expand Down Expand Up @@ -644,20 +653,22 @@ class Translator with KernelNodes {
throw "Unsupported type ${type.runtimeType}";
}

w.ArrayType arrayTypeForDartType(DartType type) {
w.ArrayType arrayTypeForDartType(DartType type, {required bool mutable}) {
while (type is TypeParameterType) {
type = type.bound;
}
return wasmArrayType(
translateStorageType(type), type.toText(defaultAstTextStrategy));
translateStorageType(type), type.toText(defaultAstTextStrategy),
mutable: mutable);
}

w.ArrayType wasmArrayType(w.StorageType type, String name,
{bool mutable = true}) {
final cache = mutable ? mutableArrayTypeCache : immutableArrayTypeCache;
return cache.putIfAbsent(
type,
() => typesBuilder.defineArray("Array<$name>",
() => typesBuilder.defineArray(
"${mutable ? '' : 'Immutable'}Array<$name>",
elementType: w.FieldType(type, mutable: mutable)));
}

Expand Down Expand Up @@ -1213,6 +1224,7 @@ class Translator with KernelNodes {
final arrayTypeRef = w.RefType.def(arrayType, nullable: false);

if (length > maxArrayNewFixedLength) {
assert(arrayType.elementType.mutable);
// Too long for `array.new_fixed`. Set elements individually.
b.i32_const(length);
b.array_new_default(arrayType);
Expand Down
2 changes: 1 addition & 1 deletion pkg/dart2wasm/lib/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Types {

/// Wasm array type of `WasmArray<_Type>`
late final w.ArrayType typeArrayArrayType =
translator.arrayTypeForDartType(typeType);
translator.arrayTypeForDartType(typeType, mutable: true);

/// Wasm value type of `WasmArray<_Type>`
late final w.ValueType typeArrayExpectedType =
Expand Down
48 changes: 48 additions & 0 deletions sdk/lib/_wasm/wasm_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ class WasmF64 extends _WasmBase {
}

/// A Wasm array.
///
/// NOTE: `T` is invariant.
@pragma("wasm:entry-point")
class WasmArray<T> extends WasmArrayRef {
/// Dummy value field to contain the value for constant instances.
Expand Down Expand Up @@ -280,6 +282,52 @@ extension F64ArrayExt on WasmArray<WasmF64> {
external void write(int index, double value);
}

/// A immutable Wasm array.
///
/// NOTE: `T` is covariant.
@pragma("wasm:entry-point")
class ImmutableWasmArray<T> extends WasmArrayRef {
/// Dummy value field to contain the value for constant instances.
@pragma("wasm:entry-point")
final List<Object?> _value;

external factory ImmutableWasmArray(int length);
external factory ImmutableWasmArray.filled(int length, T value);

const ImmutableWasmArray.literal(this._value) : super._();
}

extension ImmutableWasmArrayExt<T> on ImmutableWasmArray<T> {
external T operator [](int index);
}

extension ImmutableI8ArrayExt on ImmutableWasmArray<WasmI8> {
external int readSigned(int index);
external int readUnsigned(int index);
}

extension ImmutableI16ArrayExt on ImmutableWasmArray<WasmI16> {
external int readSigned(int index);
external int readUnsigned(int index);
}

extension ImmutableI32ArrayExt on ImmutableWasmArray<WasmI32> {
external int readSigned(int index);
external int readUnsigned(int index);
}

extension ImmutableI64ArrayExt on ImmutableWasmArray<WasmI64> {
external int read(int index);
}

extension ImmutableF32ArrayExt on ImmutableWasmArray<WasmF32> {
external double read(int index);
}

extension ImmutableF64ArrayExt on ImmutableWasmArray<WasmF64> {
external double read(int index);
}

/// Wasm typed function reference.
@pragma("wasm:entry-point")
class WasmFunction<F extends Function> extends WasmFuncRef {
Expand Down
Loading

0 comments on commit a96ad1b

Please sign in to comment.