[wasm-gc] Add 'none' type for nullref
This adds a new type 'none' as part of the WASM GC MVP. The type can only be used in combination with a nullable reference, e.g. 'ref.null none'. A 'nullref' is implicitly convertible to any nullable reference type. Bug: v8:7748 Change-Id: Ic5ab6cc27094b3c9103ce3584452daa34633612f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3755136 Auto-Submit: Matthias Liedtke <mliedtke@google.com> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/main@{#81670}
This commit is contained in:
parent
8eb07e4916
commit
2935b22fe2
@ -238,6 +238,7 @@ HeapType read_heap_type(Decoder* decoder, const byte* pc,
|
||||
case kDataRefCode:
|
||||
case kArrayRefCode:
|
||||
case kAnyRefCodeAlias:
|
||||
case kNoneCode:
|
||||
if (!VALIDATE(enabled.has_gc())) {
|
||||
DecodeError<validate>(
|
||||
decoder, pc,
|
||||
@ -317,6 +318,7 @@ ValueType read_value_type(Decoder* decoder, const byte* pc,
|
||||
case kDataRefCode:
|
||||
case kArrayRefCode:
|
||||
case kAnyRefCodeAlias:
|
||||
case kNoneCode:
|
||||
if (!VALIDATE(enabled.has_gc())) {
|
||||
DecodeError<validate>(
|
||||
decoder, pc,
|
||||
|
@ -69,6 +69,7 @@ class HeapType {
|
||||
kStringViewWtf8, // shorthand: x.
|
||||
kStringViewWtf16, // shorthand: y.
|
||||
kStringViewIter, // shorthand: z.
|
||||
kNone, //
|
||||
// This value is used to represent failures in the parsing of heap types and
|
||||
// does not correspond to a wasm heap type. It has to be last in this list.
|
||||
kBottom
|
||||
@ -97,6 +98,8 @@ class HeapType {
|
||||
return HeapType(kStringViewWtf16);
|
||||
case ValueTypeCode::kStringViewIterCode:
|
||||
return HeapType(kStringViewIter);
|
||||
case ValueTypeCode::kNoneCode:
|
||||
return HeapType(kNone);
|
||||
default:
|
||||
return HeapType(kBottom);
|
||||
}
|
||||
@ -159,6 +162,8 @@ class HeapType {
|
||||
return std::string("stringview_wtf16");
|
||||
case kStringViewIter:
|
||||
return std::string("stringview_iter");
|
||||
case kNone:
|
||||
return std::string("none");
|
||||
default:
|
||||
return std::to_string(representation_);
|
||||
}
|
||||
@ -190,6 +195,8 @@ class HeapType {
|
||||
return mask | kStringViewWtf16Code;
|
||||
case kStringViewIter:
|
||||
return mask | kStringViewIterCode;
|
||||
case kNone:
|
||||
return mask | kNoneCode;
|
||||
default:
|
||||
return static_cast<int32_t>(representation_);
|
||||
}
|
||||
@ -409,6 +416,10 @@ class ValueType {
|
||||
|
||||
// If {this} is (ref null $t), returns (ref $t). Otherwise, returns {this}.
|
||||
constexpr ValueType AsNonNull() const {
|
||||
if (is_reference_to(HeapType::kNone)) {
|
||||
// Non-null none type is not a valid type.
|
||||
return ValueType::Primitive(kBottom);
|
||||
}
|
||||
return is_nullable() ? Ref(heap_type()) : *this;
|
||||
}
|
||||
|
||||
@ -521,6 +532,8 @@ class ValueType {
|
||||
return kStringViewWtf16Code;
|
||||
case HeapType::kStringViewIter:
|
||||
return kStringViewIterCode;
|
||||
case HeapType::kNone:
|
||||
return kNoneCode;
|
||||
default:
|
||||
return kRefNullCode;
|
||||
}
|
||||
@ -674,6 +687,7 @@ constexpr ValueType kWasmEqRef = ValueType::RefNull(HeapType::kEq);
|
||||
constexpr ValueType kWasmI31Ref = ValueType::Ref(HeapType::kI31);
|
||||
constexpr ValueType kWasmDataRef = ValueType::Ref(HeapType::kData);
|
||||
constexpr ValueType kWasmArrayRef = ValueType::Ref(HeapType::kArray);
|
||||
constexpr ValueType kWasmNullRef = ValueType::RefNull(HeapType::kNone);
|
||||
constexpr ValueType kWasmStringRef = ValueType::RefNull(HeapType::kString);
|
||||
constexpr ValueType kWasmStringViewWtf8 =
|
||||
ValueType::RefNull(HeapType::kStringViewWtf8);
|
||||
|
@ -50,6 +50,7 @@ enum ValueTypeCode : uint8_t {
|
||||
kRttCode = 0x68,
|
||||
kDataRefCode = 0x67,
|
||||
kArrayRefCode = 0x66,
|
||||
kNoneCode = 0x65,
|
||||
kStringRefCode = 0x64,
|
||||
kStringViewWtf8Code = 0x63,
|
||||
kStringViewWtf16Code = 0x62,
|
||||
|
@ -190,6 +190,9 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl(
|
||||
(FLAG_experimental_wasm_gc && super_heap == HeapType::kAny);
|
||||
case HeapType::kBottom:
|
||||
UNREACHABLE();
|
||||
case HeapType::kNone:
|
||||
// none is a subtype of every compatible reference type under wasm-gc.
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -217,6 +220,9 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl(
|
||||
return false;
|
||||
case HeapType::kBottom:
|
||||
UNREACHABLE();
|
||||
case HeapType::kNone:
|
||||
// None is not a supertype for any index type.
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -325,6 +331,7 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
|
||||
case HeapType::kI31:
|
||||
switch (heap2.representation()) {
|
||||
case HeapType::kI31:
|
||||
case HeapType::kNone:
|
||||
return HeapType::kI31;
|
||||
case HeapType::kEq:
|
||||
case HeapType::kData:
|
||||
@ -341,6 +348,7 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
|
||||
switch (heap2.representation()) {
|
||||
case HeapType::kData:
|
||||
case HeapType::kArray:
|
||||
case HeapType::kNone:
|
||||
return HeapType::kData;
|
||||
case HeapType::kI31:
|
||||
case HeapType::kEq:
|
||||
@ -355,6 +363,7 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
|
||||
case HeapType::kArray:
|
||||
switch (heap2.representation()) {
|
||||
case HeapType::kArray:
|
||||
case HeapType::kNone:
|
||||
return HeapType::kArray;
|
||||
case HeapType::kData:
|
||||
return HeapType::kData;
|
||||
@ -373,6 +382,8 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
|
||||
return HeapType::kAny;
|
||||
case HeapType::kBottom:
|
||||
return HeapType::kBottom;
|
||||
case HeapType::kNone:
|
||||
return heap2.representation();
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@ -421,16 +432,21 @@ TypeInModule Intersection(ValueType type1, ValueType type2,
|
||||
}
|
||||
Nullability nullability =
|
||||
type1.is_nullable() && type2.is_nullable() ? kNullable : kNonNullable;
|
||||
return IsHeapSubtypeOf(type1.heap_type(), type2.heap_type(), module1, module2)
|
||||
? TypeInModule{ValueType::RefMaybeNull(type1.heap_type(),
|
||||
nullability),
|
||||
module1}
|
||||
: IsHeapSubtypeOf(type2.heap_type(), type1.heap_type(), module2,
|
||||
module1)
|
||||
? TypeInModule{ValueType::RefMaybeNull(type2.heap_type(),
|
||||
nullability),
|
||||
module2}
|
||||
: TypeInModule{kWasmBottom, module1};
|
||||
// non-nullable none is not a valid type.
|
||||
if (nullability == kNonNullable &&
|
||||
(type1 == kWasmNullRef || type2 == kWasmNullRef)) {
|
||||
return {kWasmBottom, module1};
|
||||
}
|
||||
if (IsHeapSubtypeOf(type1.heap_type(), type2.heap_type(), module1, module2)) {
|
||||
return TypeInModule{ValueType::RefMaybeNull(type1.heap_type(), nullability),
|
||||
module1};
|
||||
}
|
||||
if (IsHeapSubtypeOf(type2.heap_type(), type1.heap_type(), module2, module1)) {
|
||||
return TypeInModule{ValueType::RefMaybeNull(type2.heap_type(), nullability),
|
||||
module2};
|
||||
}
|
||||
ValueType type = nullability == kNullable ? kWasmNullRef : kWasmBottom;
|
||||
return TypeInModule{type, module1};
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
@ -1795,6 +1795,80 @@ WASM_COMPILED_EXEC_TEST(CallRef) {
|
||||
tester.CheckResult(caller, 47, 5);
|
||||
}
|
||||
|
||||
// Test that calling a function expecting any ref accepts nullref argument.
|
||||
WASM_COMPILED_EXEC_TEST(CallNullRefImplicitConversion) {
|
||||
const ValueType null_ref_types[] = {
|
||||
kWasmFuncRef,
|
||||
kWasmEqRef,
|
||||
kWasmI31Ref.AsNullable(),
|
||||
kWasmDataRef.AsNullable(),
|
||||
kWasmArrayRef.AsNullable(),
|
||||
kWasmAnyRef,
|
||||
refNull(0), // struct
|
||||
refNull(1), // array
|
||||
refNull(2), // signature
|
||||
};
|
||||
|
||||
for (ValueType ref_type : null_ref_types) {
|
||||
CHECK(ref_type.is_nullable());
|
||||
WasmGCTester tester(execution_tier);
|
||||
byte struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)});
|
||||
CHECK_EQ(struct_idx, 0);
|
||||
byte array_idx = tester.DefineArray(kWasmI32, true);
|
||||
CHECK_EQ(array_idx, 1);
|
||||
FunctionSig dummySig(1, 0, &kWasmI32);
|
||||
byte signature_idx = tester.DefineSignature(&dummySig);
|
||||
CHECK_EQ(signature_idx, 2);
|
||||
|
||||
ValueType ref_sig_types[] = {kWasmI32, ref_type};
|
||||
FunctionSig sig_ref(1, 1, ref_sig_types);
|
||||
byte callee = tester.DefineFunction(
|
||||
&sig_ref, {}, {WASM_REF_IS_NULL(WASM_LOCAL_GET(0)), kExprEnd});
|
||||
byte caller = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_CALL_FUNCTION(callee, WASM_REF_NULL(kNoneCode)), kExprEnd});
|
||||
|
||||
tester.CompileModule();
|
||||
tester.CheckResult(caller, 1);
|
||||
}
|
||||
}
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(CastNullRef) {
|
||||
WasmGCTester tester(execution_tier);
|
||||
byte to_func = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_AS_FUNC(WASM_REF_NULL(kNoneCode))), kExprEnd});
|
||||
byte to_non_null = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_AS_NON_NULL(WASM_REF_NULL(kNoneCode))),
|
||||
kExprEnd});
|
||||
byte to_array = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_REF_NULL(kNoneCode))),
|
||||
kExprEnd});
|
||||
byte to_data = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_AS_DATA(WASM_REF_NULL(kNoneCode))), kExprEnd});
|
||||
byte to_i31 = tester.DefineFunction(
|
||||
tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_AS_I31(WASM_REF_NULL(kNoneCode))), kExprEnd});
|
||||
byte struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)});
|
||||
byte to_struct =
|
||||
tester.DefineFunction(tester.sigs.i_v(), {},
|
||||
{WASM_REF_IS_NULL(WASM_REF_CAST_STATIC(
|
||||
WASM_REF_NULL(kNoneCode), struct_idx)),
|
||||
kExprEnd});
|
||||
tester.CompileModule();
|
||||
// Generic casts trap on null.
|
||||
tester.CheckHasThrown(to_func);
|
||||
tester.CheckHasThrown(to_non_null);
|
||||
tester.CheckHasThrown(to_array);
|
||||
tester.CheckHasThrown(to_data);
|
||||
tester.CheckHasThrown(to_i31);
|
||||
// Static ref.cast succeeds.
|
||||
tester.CheckResult(to_struct, 1);
|
||||
}
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(CallReftypeParameters) {
|
||||
WasmGCTester tester(execution_tier);
|
||||
byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
|
||||
|
@ -2150,6 +2150,15 @@ TEST_F(FunctionBodyDecoderTest, Float64Globals) {
|
||||
{WASM_GLOBAL_SET(0, WASM_LOCAL_GET(0)), WASM_LOCAL_GET(0)});
|
||||
}
|
||||
|
||||
TEST_F(FunctionBodyDecoderTest, NullRefGlobals) {
|
||||
ValueType nullRefs[] = {kWasmNullRef, kWasmNullRef, kWasmNullRef};
|
||||
FunctionSig sig(1, 2, nullRefs);
|
||||
builder.AddGlobal(kWasmNullRef);
|
||||
ExpectValidates(&sig, {WASM_GLOBAL_GET(0)});
|
||||
ExpectValidates(&sig,
|
||||
{WASM_GLOBAL_SET(0, WASM_LOCAL_GET(0)), WASM_LOCAL_GET(0)});
|
||||
}
|
||||
|
||||
TEST_F(FunctionBodyDecoderTest, AllGetGlobalCombinations) {
|
||||
for (size_t i = 0; i < arraysize(kValueTypes); i++) {
|
||||
ValueType local_type = kValueTypes[i];
|
||||
@ -3805,7 +3814,8 @@ TEST_F(FunctionBodyDecoderTest, RefNull) {
|
||||
byte struct_type_index = builder.AddStruct({F(kWasmI32, true)});
|
||||
byte array_type_index = builder.AddArray(kWasmI32, true);
|
||||
uint32_t type_reprs[] = {struct_type_index, array_type_index, HeapType::kFunc,
|
||||
HeapType::kEq, HeapType::kAny, HeapType::kI31};
|
||||
HeapType::kEq, HeapType::kAny, HeapType::kI31,
|
||||
HeapType::kNone};
|
||||
// It works with heap types.
|
||||
for (uint32_t type_repr : type_reprs) {
|
||||
const ValueType type = ValueType::RefNull(type_repr);
|
||||
|
@ -131,6 +131,7 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
constexpr ValueType ref_types[] = {
|
||||
kWasmFuncRef, kWasmEqRef, kWasmI31Ref, // --
|
||||
kWasmDataRef, kWasmArrayRef, kWasmAnyRef, // --
|
||||
kWasmNullRef, // --
|
||||
refNull(0), ref(0), // struct
|
||||
refNull(2), ref(2), // array
|
||||
refNull(11), ref(11) // signature
|
||||
@ -205,13 +206,17 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
// Functions are subtypes of funcref.
|
||||
SUBTYPE_IFF(ref_type, kWasmFuncRef,
|
||||
ref_type == kWasmFuncRef || ref_type == refNull(11) ||
|
||||
ref_type == ref(11));
|
||||
ref_type == ref(11) || ref_type == kWasmNullRef);
|
||||
// Each reference type is a subtype of itself.
|
||||
SUBTYPE(ref_type, ref_type);
|
||||
// Each reference type is a subtype of anyref.
|
||||
SUBTYPE(ref_type, kWasmAnyRef);
|
||||
// Only anyref is a subtype of anyref.
|
||||
SUBTYPE_IFF(kWasmAnyRef, ref_type, ref_type == kWasmAnyRef);
|
||||
// Each nullable reference type is a supertype of nullref.
|
||||
SUBTYPE_IFF(kWasmNullRef, ref_type, ref_type.is_nullable());
|
||||
// Only nullref is a subtype of nullref.
|
||||
SUBTYPE_IFF(ref_type, kWasmNullRef, ref_type == kWasmNullRef);
|
||||
|
||||
// Make sure symmetric relations are symmetric.
|
||||
for (ValueType ref_type2 : ref_types) {
|
||||
@ -331,7 +336,8 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
INTERSECTION(kWasmAnyRef, type, type);
|
||||
UNION(kWasmAnyRef.AsNonNull(), type,
|
||||
type.is_nullable() ? kWasmAnyRef : kWasmAnyRef.AsNonNull());
|
||||
INTERSECTION(kWasmAnyRef.AsNonNull(), type, type.AsNonNull());
|
||||
INTERSECTION(kWasmAnyRef.AsNonNull(), type,
|
||||
type != kWasmNullRef ? type.AsNonNull() : kWasmBottom);
|
||||
}
|
||||
|
||||
// Abstract types vs abstract types.
|
||||
@ -339,23 +345,34 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
UNION(kWasmFuncRef, kWasmDataRef, kWasmAnyRef);
|
||||
UNION(kWasmFuncRef, kWasmI31Ref, kWasmAnyRef);
|
||||
UNION(kWasmFuncRef, kWasmArrayRef, kWasmAnyRef);
|
||||
UNION(kWasmFuncRef, kWasmNullRef, kWasmFuncRef);
|
||||
UNION(kWasmEqRef, kWasmDataRef, kWasmEqRef);
|
||||
UNION(kWasmEqRef, kWasmI31Ref, kWasmEqRef);
|
||||
UNION(kWasmEqRef, kWasmArrayRef, kWasmEqRef);
|
||||
UNION(kWasmEqRef, kWasmNullRef, kWasmEqRef);
|
||||
UNION(kWasmDataRef, kWasmI31Ref, kWasmEqRef.AsNonNull());
|
||||
UNION(kWasmDataRef, kWasmArrayRef, kWasmDataRef);
|
||||
UNION(kWasmDataRef, kWasmNullRef, kWasmDataRef.AsNullable());
|
||||
UNION(kWasmI31Ref, kWasmArrayRef, kWasmEqRef.AsNonNull());
|
||||
UNION(kWasmI31Ref, kWasmNullRef, kWasmI31Ref.AsNullable());
|
||||
UNION(kWasmArrayRef, kWasmNullRef, kWasmArrayRef.AsNullable());
|
||||
|
||||
INTERSECTION(kWasmFuncRef, kWasmEqRef, kWasmBottom);
|
||||
INTERSECTION(kWasmFuncRef, kWasmEqRef, kWasmNullRef);
|
||||
INTERSECTION(kWasmFuncRef, kWasmDataRef, kWasmBottom);
|
||||
INTERSECTION(kWasmFuncRef, kWasmI31Ref, kWasmBottom);
|
||||
INTERSECTION(kWasmFuncRef, kWasmArrayRef, kWasmBottom);
|
||||
INTERSECTION(kWasmFuncRef, kWasmNullRef, kWasmNullRef);
|
||||
INTERSECTION(kWasmEqRef, kWasmDataRef, kWasmDataRef);
|
||||
INTERSECTION(kWasmEqRef, kWasmI31Ref, kWasmI31Ref);
|
||||
INTERSECTION(kWasmEqRef, kWasmArrayRef, kWasmArrayRef);
|
||||
INTERSECTION(kWasmEqRef, kWasmNullRef, kWasmNullRef);
|
||||
INTERSECTION(kWasmEqRef, kWasmFuncRef, kWasmNullRef);
|
||||
INTERSECTION(kWasmDataRef, kWasmI31Ref, kWasmBottom);
|
||||
INTERSECTION(kWasmDataRef, kWasmArrayRef, kWasmArrayRef);
|
||||
INTERSECTION(kWasmDataRef, kWasmNullRef, kWasmBottom);
|
||||
INTERSECTION(kWasmI31Ref, kWasmArrayRef, kWasmBottom);
|
||||
INTERSECTION(kWasmI31Ref, kWasmNullRef, kWasmBottom);
|
||||
INTERSECTION(kWasmArrayRef, kWasmNullRef, kWasmBottom);
|
||||
|
||||
ValueType struct_type = ref(0);
|
||||
ValueType array_type = ref(2);
|
||||
@ -397,6 +414,13 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
INTERSECTION(kWasmArrayRef, array_type, array_type);
|
||||
INTERSECTION(kWasmArrayRef, function_type, kWasmBottom);
|
||||
|
||||
UNION(kWasmNullRef, struct_type, struct_type.AsNullable());
|
||||
UNION(kWasmNullRef, array_type, array_type.AsNullable());
|
||||
UNION(kWasmNullRef, function_type, function_type.AsNullable());
|
||||
INTERSECTION(kWasmNullRef, struct_type, kWasmBottom);
|
||||
INTERSECTION(kWasmNullRef, array_type, kWasmBottom);
|
||||
INTERSECTION(kWasmNullRef, function_type, kWasmBottom);
|
||||
|
||||
// Indexed types of different kinds.
|
||||
UNION(struct_type, array_type, kWasmDataRef);
|
||||
UNION(struct_type, function_type, kWasmAnyRef.AsNonNull());
|
||||
|
Loading…
Reference in New Issue
Block a user