From 2846cb8da6aac770fd0cdf3ace9a0f1130fb839c Mon Sep 17 00:00:00 2001 From: Aske Simon Christensen Date: Fri, 18 Nov 2022 11:43:52 +0000 Subject: [PATCH] [dart2wasm] Add Wasm bottom heap types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getters for the top and bottom types of the WasmGC hierarchy that a type belongs to. - Rename the internal common supertype from `top` to `common` to avoid confusion with the top type of each hierarchy. - Always use a bottom type when emitting a `ref.null` instruction. - Translate the `Null` and `Never` Dart types to WasmGC `nullref`. Change-Id: I43ba5da222a848214647980f7b4876940546242a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/268463 Reviewed-by: Ömer Ağacan Commit-Queue: Aske Simon Christensen --- pkg/dart2wasm/lib/code_generator.dart | 2 +- pkg/dart2wasm/lib/constants.dart | 11 +- pkg/dart2wasm/lib/dynamic_dispatch.dart | 2 +- pkg/dart2wasm/lib/globals.dart | 2 +- pkg/dart2wasm/lib/translator.dart | 58 +++--- pkg/wasm_builder/lib/src/instructions.dart | 10 +- pkg/wasm_builder/lib/src/types.dart | 214 +++++++++++++++++++-- 7 files changed, 236 insertions(+), 63 deletions(-) diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 4ec3f26c402..b6a7dbf0505 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -547,7 +547,7 @@ class CodeGenerator extends ExpressionVisitor1 w.ValueType returnType = function.type.outputs[0]; if (returnType is w.RefType && returnType.nullable) { // Dart body may have an implicit return null. - b.ref_null(returnType.heapType); + b.ref_null(returnType.heapType.bottomType); } else { // This point is unreachable, but the Wasm validator still expects the // stack to contain a value matching the Wasm function return type. diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index 9f6f75a2487..42bb3def79e 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -95,7 +95,7 @@ class Constants { w.Instructions ib = emptyTypeList.initializer; ib.i32_const(info.classId); ib.i32_const(initialIdentityHash); - ib.ref_null(typeInfo.struct); // Initialized later + ib.ref_null(w.HeapType.none); // Initialized later ib.i64_const(0); ib.array_new_fixed(arrayType, 0); ib.struct_new(info.struct); @@ -282,11 +282,8 @@ class ConstantInstantiator extends ConstantVisitor { @override w.ValueType visitNullConstant(NullConstant node) { - w.ValueType expectedType = this.expectedType; - w.HeapType heapType = - expectedType is w.RefType ? expectedType.heapType : w.HeapType.data; - b.ref_null(heapType); - return w.RefType(heapType, nullable: true); + b.ref_null(w.HeapType.none); + return const w.RefType.none(nullable: true); } w.ValueType _maybeBox(w.ValueType wasmType, void Function() pushValue) { @@ -352,7 +349,7 @@ class ConstantCreator extends ConstantVisitor { // Create uninitialized global and function to initialize it. w.DefinedGlobal global = m.addGlobal(w.GlobalType(type.withNullability(true))); - global.initializer.ref_null(type.heapType); + global.initializer.ref_null(w.HeapType.none); global.initializer.end(); w.FunctionType ftype = m.addFunctionType(const [], [type]); w.DefinedFunction function = m.addFunction(ftype, "$constant"); diff --git a/pkg/dart2wasm/lib/dynamic_dispatch.dart b/pkg/dart2wasm/lib/dynamic_dispatch.dart index d09a00ebbc2..7a070320ca0 100644 --- a/pkg/dart2wasm/lib/dynamic_dispatch.dart +++ b/pkg/dart2wasm/lib/dynamic_dispatch.dart @@ -507,7 +507,7 @@ class DynamicDispatcher { // `noSuchMethod` if the supplied number of arguments is less than the // number required for any given selector. for (int i = positionalArguments.length; i < maxParameterCount; i++) { - b.ref_null(translator.topInfo.nullableType.heapType); + b.ref_null(w.HeapType.none); } b.call(dynamicTrampolines[type]![name]!); return translator.topInfo.nullableType; diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index bb88c098e0b..b30ea55b627 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart @@ -112,7 +112,7 @@ class Globals { if (type is w.RefType) { w.HeapType heapType = type.heapType; if (type.nullable) { - b.ref_null(heapType); + b.ref_null(heapType.bottomType); } else { b.global_get(_prepareDummyValue(type)!); } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index b7b32db3c34..1a5ad32b940 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -606,10 +606,8 @@ class Translator { if (type is DynamicType || type is VoidType) { return topInfo.nullableType; } - // TODO(joshualitt): When we add support to `wasm_builder` for bottom heap - // types, we should return bottom heap type here. if (type is NullType || type is NeverType) { - return topInfo.nullableType; + return const w.RefType.none(nullable: true); } if (type is TypeParameterType) { return translateStorageType(type.isPotentiallyNullable @@ -828,7 +826,7 @@ class Translator { // This can happen when a void method has its return type overridden // to return a value, in which case the selector signature will have a // non-void return type to encompass all possible return values. - b.ref_null((to as w.RefType).heapType); + b.ref_null((to as w.RefType).heapType.bottomType); return; } } @@ -875,31 +873,35 @@ class Translator { var heapType = (to as w.RefType).heapType; if (heapType is w.FunctionType) { b.ref_cast(heapType); - return; - } - w.Label? nullLabel = null; - if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) { - if (from.nullable && to.nullable) { - // Nullable cast from above dataref. Since ref.as_data is not - // null-polymorphic, we need to check explicitly for null. - w.Local temp = function.addLocal(from); - b.local_set(temp); - nullLabel = b.block(const [], [to]); - w.Label nonNullLabel = - b.block(const [], [from.withNullability(false)]); - b.local_get(temp); - b.br_on_non_null(nonNullLabel); - b.ref_null(to.heapType); - b.br(nullLabel); - b.end(); // nonNullLabel + } else if (heapType == w.HeapType.none) { + assert(to.nullable); + b.drop(); + b.ref_null(w.HeapType.none); + } else { + w.Label? nullLabel = null; + if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) { + if (from.nullable && to.nullable) { + // Nullable cast from above dataref. Since ref.as_data is not + // null-polymorphic, we need to check explicitly for null. + w.Local temp = function.addLocal(from); + b.local_set(temp); + nullLabel = b.block(const [], [to]); + w.Label nonNullLabel = + b.block(const [], [from.withNullability(false)]); + b.local_get(temp); + b.br_on_non_null(nonNullLabel); + b.ref_null(w.HeapType.none); + b.br(nullLabel); + b.end(); // nonNullLabel + } + b.ref_as_data(); + } + if (heapType is w.DefType) { + b.ref_cast(heapType); + } + if (nullLabel != null) { + b.end(); // nullLabel } - b.ref_as_data(); - } - if (heapType is w.DefType) { - b.ref_cast(heapType); - } - if (nullLabel != null) { - b.end(); // nullLabel } } } diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart index e7608a26013..252280bc166 100644 --- a/pkg/wasm_builder/lib/src/instructions.dart +++ b/pkg/wasm_builder/lib/src/instructions.dart @@ -223,7 +223,7 @@ class Instructions with SerializerMixin { } ValueType get _topOfStack { - if (!reachable) return RefType.top(nullable: true); + if (!reachable) return RefType.common(nullable: true); if (_stackTypes.isEmpty) _reportError("Stack underflow"); return _stackTypes.last; } @@ -885,7 +885,7 @@ class Instructions with SerializerMixin { /// Emit a `ref.is_null` instruction. void ref_is_null() { assert(_verifyTypes( - const [RefType.top(nullable: true)], const [NumType.i32], + const [RefType.common(nullable: true)], const [NumType.i32], trace: const ['ref.is_null'])); writeByte(0xD1); } @@ -900,7 +900,7 @@ class Instructions with SerializerMixin { /// Emit a `ref.as_non_null` instruction. void ref_as_non_null() { - assert(_verifyTypes(const [RefType.top(nullable: true)], + assert(_verifyTypes(const [RefType.common(nullable: true)], [_topOfStack.withNullability(false)], trace: const ['ref.as_non_null'])); writeByte(0xD3); @@ -908,7 +908,7 @@ class Instructions with SerializerMixin { /// Emit a `br_on_null` instruction. void br_on_null(Label label) { - assert(_verifyTypes(const [RefType.top(nullable: true)], + assert(_verifyTypes(const [RefType.common(nullable: true)], [_topOfStack.withNullability(false)], trace: ['br_on_null', label])); assert(_verifyBranchTypes(label, 1)); @@ -928,7 +928,7 @@ class Instructions with SerializerMixin { /// Emit a `br_on_non_null` instruction. void br_on_non_null(Label label) { assert(_verifyBranchTypes(label, 1, [_topOfStack.withNullability(false)])); - assert(_verifyTypes(const [RefType.top(nullable: true)], const [], + assert(_verifyTypes(const [RefType.common(nullable: true)], const [], trace: ['br_on_non_null', label])); writeByte(0xD6); _writeLabel(label); diff --git a/pkg/wasm_builder/lib/src/types.dart b/pkg/wasm_builder/lib/src/types.dart index 5a3360ce3bf..2393a8fb259 100644 --- a/pkg/wasm_builder/lib/src/types.dart +++ b/pkg/wasm_builder/lib/src/types.dart @@ -143,8 +143,9 @@ class RefType extends ValueType { const RefType._(this.heapType, this.nullable); - /// Internal top type above any, func and extern. Not a real Wasm ref type. - const RefType.top({required bool nullable}) : this._(HeapType.top, nullable); + /// Internal supertype above any, func and extern. Not a real Wasm ref type. + const RefType.common({required bool nullable}) + : this._(HeapType.common, nullable); /// A (possibly nullable) reference to the `any` heap type. const RefType.extern({required bool nullable}) @@ -167,6 +168,18 @@ class RefType extends ValueType { /// A (possibly nullable) reference to the `i31` heap type. const RefType.i31({required bool nullable}) : this._(HeapType.i31, nullable); + /// A (possibly nullable) reference to the `none` heap type. + const RefType.none({required bool nullable}) + : this._(HeapType.none, nullable); + + /// A (possibly nullable) reference to the `noextern` heap type. + const RefType.noextern({required bool nullable}) + : this._(HeapType.noextern, nullable); + + /// A (possibly nullable) reference to the `nofunc` heap type. + const RefType.nofunc({required bool nullable}) + : this._(HeapType.nofunc, nullable); + /// A (possibly nullable) reference to a custom heap type. RefType.def(DefType defType, {required bool nullable}) : this(defType, nullable: nullable); @@ -195,7 +208,9 @@ class RefType extends ValueType { @override String toString() { - if (nullable == heapType.nullableByDefault) return "${heapType}ref"; + if (nullable == heapType.nullableByDefault) { + return "${heapType.shorthandName}ref"; + } return "ref${nullable ? " null " : " "}$heapType"; } @@ -213,8 +228,8 @@ class RefType extends ValueType { abstract class HeapType implements Serializable { const HeapType(); - /// Internal top type above any, func and extern. Not a real Wasm heap type. - static const top = TopHeapType._(); + /// Internal supertype above any, func and extern. Not a real Wasm heap type. + static const common = CommonHeapType._(); /// The `extern` heap type. static const extern = ExternHeapType._(); @@ -234,37 +249,61 @@ abstract class HeapType implements Serializable { /// The `i31` heap type. static const i31 = I31HeapType._(); + /// The `none` heap type. + static const none = NoneHeapType._(); + + /// The `noextern` heap type. + static const noextern = NoExternHeapType._(); + + /// The `nofunc` heap type. + static const nofunc = NoFuncHeapType._(); + /// Whether this heap type is nullable by default, i.e. when written with the /// -`ref` shorthand. A `null` value here means the heap type has no default /// nullability, so the nullability of a reference has to be specified /// explicitly. bool? get nullableByDefault; + /// The top type of the hierarchy containing this type. + HeapType get topType; + + /// The bottom type of the hierarchy containing this type. + HeapType get bottomType; + /// Whether this heap type is a declared subtype of the other heap type. bool isSubtypeOf(HeapType other); /// Whether this heap type is a structural subtype of the other heap type. bool isStructuralSubtypeOf(HeapType other) => isSubtypeOf(other); + + String get shorthandName => toString(); } -/// Internal top type above any, func and extern. This is only used to specify +/// Internal supertype above any, func and extern. This is only used to specify /// input constraints for instructions that are polymorphic across the three /// type hierarchies. It's not a real Wasm heap type. -class TopHeapType extends HeapType { - const TopHeapType._(); +class CommonHeapType extends HeapType { + const CommonHeapType._(); @override bool? get nullableByDefault => null; @override - bool isSubtypeOf(HeapType other) => other == HeapType.top; + HeapType get topType => throw "No top type of internal common supertype"; + + @override + HeapType get bottomType => + throw "No bottom type of internal common supertype"; + + @override + bool isSubtypeOf(HeapType other) => other == HeapType.common; @override void serialize(Serializer s) => - throw "Attempt to serialize internal top type"; + throw "Attempt to serialize internal common supertype"; @override - String toString() => "#top"; + String toString() => "#common"; } /// The `extern` heap type. @@ -276,9 +315,15 @@ class ExternHeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.extern; + + @override + HeapType get bottomType => HeapType.noextern; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || other == HeapType.extern; + other == HeapType.common || other == HeapType.extern; @override void serialize(Serializer s) => s.writeByte(0x6F); @@ -296,9 +341,15 @@ class AnyHeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || other == HeapType.any; + other == HeapType.common || other == HeapType.any; @override void serialize(Serializer s) => s.writeByte(0x6E); @@ -316,9 +367,15 @@ class EqHeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || other == HeapType.any || other == HeapType.eq; + other == HeapType.common || other == HeapType.any || other == HeapType.eq; @override void serialize(Serializer s) => s.writeByte(0x6D); @@ -336,9 +393,15 @@ class FuncHeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.func; + + @override + HeapType get bottomType => HeapType.nofunc; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || other == HeapType.func; + other == HeapType.common || other == HeapType.func; @override void serialize(Serializer s) => s.writeByte(0x70); @@ -356,9 +419,15 @@ class DataHeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || + other == HeapType.common || other == HeapType.any || other == HeapType.eq || other == HeapType.data; @@ -379,9 +448,15 @@ class I31HeapType extends HeapType { @override bool? get nullableByDefault => defaultNullability; + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + @override bool isSubtypeOf(HeapType other) => - other == HeapType.top || + other == HeapType.common || other == HeapType.any || other == HeapType.eq || other == HeapType.i31; @@ -393,6 +468,93 @@ class I31HeapType extends HeapType { String toString() => "i31"; } +/// The `none` heap type. +class NoneHeapType extends HeapType { + const NoneHeapType._(); + + static const defaultNullability = true; + + @override + bool? get nullableByDefault => defaultNullability; + + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + + @override + bool isSubtypeOf(HeapType other) => + other == HeapType.common || other.bottomType == HeapType.none; + + @override + void serialize(Serializer s) => s.writeByte(0x65); + + @override + String toString() => "none"; + + @override + String get shorthandName => "null"; +} + +/// The `noextern` heap type. +class NoExternHeapType extends HeapType { + const NoExternHeapType._(); + + static const defaultNullability = true; + + @override + bool? get nullableByDefault => defaultNullability; + + @override + HeapType get topType => HeapType.extern; + + @override + HeapType get bottomType => HeapType.noextern; + + @override + bool isSubtypeOf(HeapType other) => + other == HeapType.common || other.bottomType == HeapType.noextern; + + @override + void serialize(Serializer s) => s.writeByte(0x69); + + @override + String toString() => "extern"; + + @override + String get shorthandName => "nullextern"; +} + +/// The `nofunc` heap type. +class NoFuncHeapType extends HeapType { + const NoFuncHeapType._(); + + static const defaultNullability = true; + + @override + bool? get nullableByDefault => defaultNullability; + + @override + HeapType get topType => HeapType.func; + + @override + HeapType get bottomType => HeapType.nofunc; + + @override + bool isSubtypeOf(HeapType other) => + other == HeapType.common || other.bottomType == HeapType.nofunc; + + @override + void serialize(Serializer s) => s.writeByte(0x68); + + @override + String toString() => "nofunc"; + + @override + String get shorthandName => "nullfunc"; +} + /// A custom heap type. abstract class DefType extends HeapType { int? _index; @@ -453,12 +615,18 @@ class FunctionType extends DefType { @override HeapType get abstractSuperType => HeapType.func; + @override + HeapType get topType => HeapType.func; + + @override + HeapType get bottomType => HeapType.nofunc; + @override Iterable get constituentTypes => [...inputs, ...outputs]; @override bool isStructuralSubtypeOf(HeapType other) { - if (other == HeapType.top || other == HeapType.func) return true; + if (other == HeapType.common || other == HeapType.func) return true; if (other is! FunctionType) return false; if (inputs.length != other.inputs.length) return false; if (outputs.length != other.outputs.length) return false; @@ -490,6 +658,12 @@ abstract class DataType extends DefType { DataType(this.name, {super.superType}); + @override + HeapType get topType => HeapType.any; + + @override + HeapType get bottomType => HeapType.none; + @override String toString() => name; } @@ -512,7 +686,7 @@ class StructType extends DataType { @override bool isStructuralSubtypeOf(HeapType other) { - if (other == HeapType.top || + if (other == HeapType.common || other == HeapType.any || other == HeapType.eq || other == HeapType.data) { @@ -550,7 +724,7 @@ class ArrayType extends DataType { @override bool isStructuralSubtypeOf(HeapType other) { - if (other == HeapType.top || + if (other == HeapType.common || other == HeapType.any || other == HeapType.eq || other == HeapType.data) {