[dart2wasm] Add Wasm bottom heap types

- 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 <omersa@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen 2022-11-18 11:43:52 +00:00 committed by Commit Queue
parent e0874df618
commit 2846cb8da6
7 changed files with 236 additions and 63 deletions

View file

@ -547,7 +547,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
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.

View file

@ -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<w.ValueType> {
@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<ConstantInfo?> {
// 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");

View file

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

View file

@ -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)!);
}

View file

@ -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
}
}
}

View file

@ -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);

View file

@ -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<StorageType> 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) {