Commit 2935b22f authored by Matthias Liedtke's avatar Matthias Liedtke Committed by V8 LUCI CQ

[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: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81670}
parent 8eb07e49
......@@ -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());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment