mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
[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:
parent
e0874df618
commit
2846cb8da6
7 changed files with 236 additions and 63 deletions
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)!);
|
||||
}
|
||||
|
|
|
@ -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,8 +873,11 @@ class Translator {
|
|||
var heapType = (to as w.RefType).heapType;
|
||||
if (heapType is w.FunctionType) {
|
||||
b.ref_cast(heapType);
|
||||
return;
|
||||
}
|
||||
} 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) {
|
||||
|
@ -889,7 +890,7 @@ class Translator {
|
|||
b.block(const [], [from.withNullability(false)]);
|
||||
b.local_get(temp);
|
||||
b.br_on_non_null(nonNullLabel);
|
||||
b.ref_null(to.heapType);
|
||||
b.ref_null(w.HeapType.none);
|
||||
b.br(nullLabel);
|
||||
b.end(); // nonNullLabel
|
||||
}
|
||||
|
@ -903,6 +904,7 @@ class Translator {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fromIsExtern && toIsExtern) {
|
||||
b.extern_externalize();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue