Commit 18105c72 authored by Matthias Liedtke's avatar Matthias Liedtke Committed by V8 LUCI CQ

[wasm-gc] Introduce function null type nofunc

nofunc is the abstract null type, the equivalent of none but for the
function type hierarchy.
none and nofunc (and later on noextern) all can only represent a null
value, however their nulls are distinct (as there isn't any subtype
relationship between them).

Bug: v8:7748
Change-Id: Ic5ae502cc21a581ca2e0f5abc46139435d950af9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3805884Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82274}
parent e5524920
...@@ -6025,12 +6025,6 @@ class LiftoffCompiler { ...@@ -6025,12 +6025,6 @@ class LiftoffCompiler {
WASM_ARRAY_TYPE, frozen); WASM_ARRAY_TYPE, frozen);
} }
void FuncCheck(TypeCheck& check, const FreezeCacheState& frozen) {
LoadInstanceType(check, frozen);
__ emit_i32_cond_jumpi(kUnequal, check.no_match, check.instance_type(),
WASM_INTERNAL_FUNCTION_TYPE, frozen);
}
void I31Check(TypeCheck& check, const FreezeCacheState& frozen) { void I31Check(TypeCheck& check, const FreezeCacheState& frozen) {
__ emit_smi_check(check.obj_reg, check.no_match, __ emit_smi_check(check.obj_reg, check.no_match,
LiftoffAssembler::kJumpOnNotSmi, frozen); LiftoffAssembler::kJumpOnNotSmi, frozen);
......
...@@ -239,6 +239,7 @@ HeapType read_heap_type(Decoder* decoder, const byte* pc, ...@@ -239,6 +239,7 @@ HeapType read_heap_type(Decoder* decoder, const byte* pc,
case kArrayRefCode: case kArrayRefCode:
case kAnyRefCode: case kAnyRefCode:
case kNoneCode: case kNoneCode:
case kNoFuncCode:
if (!VALIDATE(enabled.has_gc())) { if (!VALIDATE(enabled.has_gc())) {
DecodeError<validate>( DecodeError<validate>(
decoder, pc, decoder, pc,
...@@ -316,6 +317,7 @@ ValueType read_value_type(Decoder* decoder, const byte* pc, ...@@ -316,6 +317,7 @@ ValueType read_value_type(Decoder* decoder, const byte* pc,
case kArrayRefCode: case kArrayRefCode:
case kAnyRefCode: case kAnyRefCode:
case kNoneCode: case kNoneCode:
case kNoFuncCode:
if (!VALIDATE(enabled.has_gc())) { if (!VALIDATE(enabled.has_gc())) {
DecodeError<validate>( DecodeError<validate>(
decoder, pc, decoder, pc,
......
...@@ -71,6 +71,7 @@ class HeapType { ...@@ -71,6 +71,7 @@ class HeapType {
kStringViewWtf16, // shorthand: y. kStringViewWtf16, // shorthand: y.
kStringViewIter, // shorthand: z. kStringViewIter, // shorthand: z.
kNone, // kNone, //
kNoFunc, //
// This value is used to represent failures in the parsing of heap types and // 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. // does not correspond to a wasm heap type. It has to be last in this list.
kBottom kBottom
...@@ -102,6 +103,8 @@ class HeapType { ...@@ -102,6 +103,8 @@ class HeapType {
return HeapType(kStringViewIter); return HeapType(kStringViewIter);
case ValueTypeCode::kNoneCode: case ValueTypeCode::kNoneCode:
return HeapType(kNone); return HeapType(kNone);
case ValueTypeCode::kNoFuncCode:
return HeapType(kNoFunc);
default: default:
return HeapType(kBottom); return HeapType(kBottom);
} }
...@@ -168,6 +171,8 @@ class HeapType { ...@@ -168,6 +171,8 @@ class HeapType {
return std::string("stringview_iter"); return std::string("stringview_iter");
case kNone: case kNone:
return std::string("none"); return std::string("none");
case kNoFunc:
return std::string("nofunc");
default: default:
return std::to_string(representation_); return std::to_string(representation_);
} }
...@@ -203,6 +208,8 @@ class HeapType { ...@@ -203,6 +208,8 @@ class HeapType {
return mask | kStringViewIterCode; return mask | kStringViewIterCode;
case kNone: case kNone:
return mask | kNoneCode; return mask | kNoneCode;
case kNoFunc:
return mask | kNoFuncCode;
default: default:
return static_cast<int32_t>(representation_); return static_cast<int32_t>(representation_);
} }
...@@ -422,7 +429,8 @@ class ValueType { ...@@ -422,7 +429,8 @@ class ValueType {
// If {this} is (ref null $t), returns (ref $t). Otherwise, returns {this}. // If {this} is (ref null $t), returns (ref $t). Otherwise, returns {this}.
constexpr ValueType AsNonNull() const { constexpr ValueType AsNonNull() const {
if (is_reference_to(HeapType::kNone)) { if (is_reference_to(HeapType::kNone) ||
is_reference_to(HeapType::kNoFunc)) {
// Non-null none type is not a valid type. // Non-null none type is not a valid type.
return ValueType::Primitive(kBottom); return ValueType::Primitive(kBottom);
} }
...@@ -548,6 +556,8 @@ class ValueType { ...@@ -548,6 +556,8 @@ class ValueType {
return kStringViewIterCode; return kStringViewIterCode;
case HeapType::kNone: case HeapType::kNone:
return kNoneCode; return kNoneCode;
case HeapType::kNoFunc:
return kNoFuncCode;
default: default:
return kRefNullCode; return kRefNullCode;
} }
...@@ -585,6 +595,10 @@ class ValueType { ...@@ -585,6 +595,10 @@ class ValueType {
break; break;
case kRefNull: case kRefNull:
if (heap_type().is_generic()) { if (heap_type().is_generic()) {
// TODO(mliedtke): Adapt short names:
// noneref -> nullref
// nofuncref -> nullfuncref
// noexternref -> nullexternref
buf << heap_type().name() << "ref"; buf << heap_type().name() << "ref";
} else { } else {
buf << "(ref null " << heap_type().name() << ")"; buf << "(ref null " << heap_type().name() << ")";
...@@ -687,6 +701,7 @@ constexpr ValueType kWasmStringViewWtf16 = ...@@ -687,6 +701,7 @@ constexpr ValueType kWasmStringViewWtf16 =
constexpr ValueType kWasmStringViewIter = constexpr ValueType kWasmStringViewIter =
ValueType::RefNull(HeapType::kStringViewIter); ValueType::RefNull(HeapType::kStringViewIter);
constexpr ValueType kWasmNullRef = ValueType::RefNull(HeapType::kNone); constexpr ValueType kWasmNullRef = ValueType::RefNull(HeapType::kNone);
constexpr ValueType kWasmNullFuncRef = ValueType::RefNull(HeapType::kNoFunc);
// Constants used by the generic js-to-wasm wrapper. // Constants used by the generic js-to-wasm wrapper.
constexpr int kWasmValueKindBitsMask = (1u << ValueType::kKindBits) - 1; constexpr int kWasmValueKindBitsMask = (1u << ValueType::kKindBits) - 1;
......
...@@ -37,6 +37,9 @@ enum ValueTypeCode : uint8_t { ...@@ -37,6 +37,9 @@ enum ValueTypeCode : uint8_t {
kI16Code = 0x79, kI16Code = 0x79,
// Current reference types // Current reference types
kFuncRefCode = 0x70, kFuncRefCode = 0x70,
// TODO(7784): Switch to official opcodes once they are aligned with the
// stringref proposal for nofunc and noextern.
kNoFuncCode = 0x68,
kExternRefCode = 0x6f, kExternRefCode = 0x6f,
// typed-funcref and GC proposal types // typed-funcref and GC proposal types
kAnyRefCode = 0x6e, kAnyRefCode = 0x6e,
......
...@@ -99,6 +99,41 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index, ...@@ -99,6 +99,41 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index,
return true; return true;
} }
HeapType::Representation NullSentinel(HeapType type, const WasmModule* module) {
switch (type.representation()) {
case HeapType::kI31:
case HeapType::kNone:
case HeapType::kEq:
case HeapType::kData:
case HeapType::kArray:
case HeapType::kAny:
case HeapType::kExtern: // TODO(mliedtke): Add noextern.
case HeapType::kString:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kStringViewIter:
return HeapType::kNone;
case HeapType::kFunc:
case HeapType::kNoFunc:
return HeapType::kNoFunc;
default:
// TODO(mliedtke): Add noextern.
return module->has_signature(type.ref_index()) ? HeapType::kNoFunc
: HeapType::kNone;
}
}
bool IsNullSentinel(HeapType type) {
switch (type.representation()) {
case HeapType::kNone:
case HeapType::kNoFunc:
// TODO(mliedtke): Add noextern.
return true;
default:
return false;
}
}
} // namespace } // namespace
bool ValidSubtypeDefinition(uint32_t subtype_index, uint32_t supertype_index, bool ValidSubtypeDefinition(uint32_t subtype_index, uint32_t supertype_index,
...@@ -191,8 +226,19 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl( ...@@ -191,8 +226,19 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl(
case HeapType::kBottom: case HeapType::kBottom:
UNREACHABLE(); UNREACHABLE();
case HeapType::kNone: case HeapType::kNone:
// none is a subtype of every compatible reference type under wasm-gc. // none is a subtype of every non-func, non-extern reference type under
return true; // wasm-gc.
// TODO(mliedtke): Break-up extern and nullref subtyping.
if (super_heap.is_index()) {
return !super_module->has_signature(super_heap.ref_index());
}
return super_heap != HeapType::kFunc && super_heap != HeapType::kNoFunc;
case HeapType::kNoFunc:
// nofunc is a subtype of every funcref type under wasm-gc.
if (super_heap.is_index()) {
return super_module->has_signature(super_heap.ref_index());
}
return super_heap == HeapType::kNoFunc || super_heap == HeapType::kFunc;
default: default:
break; break;
} }
...@@ -206,13 +252,12 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl( ...@@ -206,13 +252,12 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl(
return sub_module->has_signature(sub_index); return sub_module->has_signature(sub_index);
case HeapType::kEq: case HeapType::kEq:
case HeapType::kData: case HeapType::kData:
case HeapType::kAny:
return !sub_module->has_signature(sub_index); return !sub_module->has_signature(sub_index);
case HeapType::kArray: case HeapType::kArray:
return sub_module->has_array(sub_index); return sub_module->has_array(sub_index);
case HeapType::kI31: case HeapType::kI31:
return false; return false;
case HeapType::kAny:
return true;
case HeapType::kExtern: case HeapType::kExtern:
return false; return false;
case HeapType::kString: case HeapType::kString:
...@@ -223,7 +268,8 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl( ...@@ -223,7 +268,8 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsHeapSubtypeOfImpl(
case HeapType::kBottom: case HeapType::kBottom:
UNREACHABLE(); UNREACHABLE();
case HeapType::kNone: case HeapType::kNone:
// None is not a supertype for any index type. case HeapType::kNoFunc:
// Abstract null types are not supertypes for any index type.
return false; return false;
default: default:
break; break;
...@@ -345,9 +391,10 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1, ...@@ -345,9 +391,10 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
return HeapType::kAny; return HeapType::kAny;
case HeapType::kFunc: case HeapType::kFunc:
case HeapType::kExtern: case HeapType::kExtern:
case HeapType::kNoFunc:
UNREACHABLE(); UNREACHABLE();
default: default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kAny return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kEq; : HeapType::kEq;
} }
case HeapType::kData: case HeapType::kData:
...@@ -363,9 +410,10 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1, ...@@ -363,9 +410,10 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
return HeapType::kAny; return HeapType::kAny;
case HeapType::kFunc: case HeapType::kFunc:
case HeapType::kExtern: case HeapType::kExtern:
case HeapType::kNoFunc:
UNREACHABLE(); UNREACHABLE();
default: default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kAny return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kData; : HeapType::kData;
} }
case HeapType::kArray: case HeapType::kArray:
...@@ -382,11 +430,12 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1, ...@@ -382,11 +430,12 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
return HeapType::kAny; return HeapType::kAny;
case HeapType::kFunc: case HeapType::kFunc:
case HeapType::kExtern: case HeapType::kExtern:
case HeapType::kNoFunc:
UNREACHABLE(); UNREACHABLE();
default: default:
return module2->has_array(heap2.ref_index()) ? HeapType::kArray return module2->has_array(heap2.ref_index()) ? HeapType::kArray
: module2->has_struct(heap2.ref_index()) ? HeapType::kData : module2->has_struct(heap2.ref_index()) ? HeapType::kData
: HeapType::kAny; : HeapType::kBottom;
} }
case HeapType::kAny: case HeapType::kAny:
return HeapType::kAny; return HeapType::kAny;
...@@ -394,6 +443,25 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1, ...@@ -394,6 +443,25 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
return HeapType::kBottom; return HeapType::kBottom;
case HeapType::kNone: case HeapType::kNone:
return heap2.representation(); return heap2.representation();
case HeapType::kNoFunc:
switch (heap2.representation()) {
case HeapType::kArray:
case HeapType::kNone:
case HeapType::kData:
case HeapType::kI31:
case HeapType::kEq:
case HeapType::kAny:
case HeapType::kExtern:
UNREACHABLE();
case HeapType::kNoFunc:
return HeapType::kNoFunc;
case HeapType::kFunc:
return HeapType::kFunc;
default:
return module2->has_signature(heap2.ref_index())
? heap2.representation()
: HeapType::kBottom;
}
default: default:
UNREACHABLE(); UNREACHABLE();
} }
...@@ -442,9 +510,9 @@ TypeInModule Intersection(ValueType type1, ValueType type2, ...@@ -442,9 +510,9 @@ TypeInModule Intersection(ValueType type1, ValueType type2,
} }
Nullability nullability = Nullability nullability =
type1.is_nullable() && type2.is_nullable() ? kNullable : kNonNullable; type1.is_nullable() && type2.is_nullable() ? kNullable : kNonNullable;
// non-nullable none is not a valid type. // non-nullable null type is not a valid type.
if (nullability == kNonNullable && if (nullability == kNonNullable && (IsNullSentinel(type1.heap_type()) ||
(type1 == kWasmNullRef || type2 == kWasmNullRef)) { IsNullSentinel(type2.heap_type()))) {
return {kWasmBottom, module1}; return {kWasmBottom, module1};
} }
if (IsHeapSubtypeOf(type1.heap_type(), type2.heap_type(), module1, module2)) { if (IsHeapSubtypeOf(type1.heap_type(), type2.heap_type(), module1, module2)) {
...@@ -455,8 +523,16 @@ TypeInModule Intersection(ValueType type1, ValueType type2, ...@@ -455,8 +523,16 @@ TypeInModule Intersection(ValueType type1, ValueType type2,
return TypeInModule{ValueType::RefMaybeNull(type2.heap_type(), nullability), return TypeInModule{ValueType::RefMaybeNull(type2.heap_type(), nullability),
module2}; module2};
} }
ValueType type = nullability == kNullable ? kWasmNullRef : kWasmBottom; if (nullability == kNonNullable) {
return TypeInModule{type, module1}; return {kWasmBottom, module1};
}
// Check for common null representation.
HeapType::Representation null_type1 =
NullSentinel(type1.heap_type(), module1);
if (null_type1 == NullSentinel(type2.heap_type(), module2)) {
return {ValueType::RefNull(HeapType(null_type1)), module1};
}
return {kWasmBottom, module1};
} }
} // namespace wasm } // namespace wasm
......
...@@ -1533,22 +1533,27 @@ WASM_COMPILED_EXEC_TEST(CallRef) { ...@@ -1533,22 +1533,27 @@ WASM_COMPILED_EXEC_TEST(CallRef) {
tester.CheckResult(caller, 47, 5); tester.CheckResult(caller, 47, 5);
} }
// Test that calling a function expecting any ref accepts nullref argument. // Test that calling a function expecting any ref accepts the abstract null
WASM_COMPILED_EXEC_TEST(CallNullRefImplicitConversion) { // type argument (nullref, nullfuncref, nullexternref).
const ValueType null_ref_types[] = { WASM_COMPILED_EXEC_TEST(CallAbstractNullTypeImplicitConversion) {
kWasmFuncRef, const struct {
kWasmEqRef, ValueType super_type;
kWasmI31Ref.AsNullable(), ValueTypeCode sub_type_code;
kWasmDataRef.AsNullable(), } null_ref_types[] = {
kWasmArrayRef.AsNullable(), {kWasmFuncRef, kNoFuncCode},
kWasmAnyRef, {kWasmEqRef, kNoneCode},
refNull(0), // struct {kWasmI31Ref.AsNullable(), kNoneCode},
refNull(1), // array {kWasmDataRef.AsNullable(), kNoneCode},
refNull(2), // signature {kWasmArrayRef.AsNullable(), kNoneCode},
{kWasmAnyRef, kNoneCode},
{kWasmExternRef, kNoneCode},
{refNull(0), kNoneCode}, // struct
{refNull(1), kNoneCode}, // array
{refNull(2), kNoFuncCode}, // signature
}; };
for (ValueType ref_type : null_ref_types) { for (auto [super_type, sub_type_code] : null_ref_types) {
CHECK(ref_type.is_nullable()); CHECK(super_type.is_nullable());
WasmGCTester tester(execution_tier); WasmGCTester tester(execution_tier);
byte struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)}); byte struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)});
CHECK_EQ(struct_idx, 0); CHECK_EQ(struct_idx, 0);
...@@ -1558,13 +1563,13 @@ WASM_COMPILED_EXEC_TEST(CallNullRefImplicitConversion) { ...@@ -1558,13 +1563,13 @@ WASM_COMPILED_EXEC_TEST(CallNullRefImplicitConversion) {
byte signature_idx = tester.DefineSignature(&dummySig); byte signature_idx = tester.DefineSignature(&dummySig);
CHECK_EQ(signature_idx, 2); CHECK_EQ(signature_idx, 2);
ValueType ref_sig_types[] = {kWasmI32, ref_type}; ValueType ref_sig_types[] = {kWasmI32, super_type};
FunctionSig sig_ref(1, 1, ref_sig_types); FunctionSig sig_ref(1, 1, ref_sig_types);
byte callee = tester.DefineFunction( byte callee = tester.DefineFunction(
&sig_ref, {}, {WASM_REF_IS_NULL(WASM_LOCAL_GET(0)), kExprEnd}); &sig_ref, {}, {WASM_REF_IS_NULL(WASM_LOCAL_GET(0)), kExprEnd});
byte caller = tester.DefineFunction( byte caller = tester.DefineFunction(
tester.sigs.i_v(), {}, tester.sigs.i_v(), {},
{WASM_CALL_FUNCTION(callee, WASM_REF_NULL(kNoneCode)), kExprEnd}); {WASM_CALL_FUNCTION(callee, WASM_REF_NULL(sub_type_code)), kExprEnd});
tester.CompileModule(); tester.CompileModule();
tester.CheckResult(caller, 1); tester.CheckResult(caller, 1);
...@@ -1723,7 +1728,7 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) { ...@@ -1723,7 +1728,7 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
byte kBrOnDataTaken = byte kBrOnDataTaken =
BR_ON(DATA, Data, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); BR_ON(DATA, Data, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10)));
byte kBrOnDataNotTaken = BR_ON(DATA, Data, WASM_REF_FUNC(function_index)); byte kBrOnDataNotTaken = BR_ON(DATA, Data, WASM_REF_NULL(kNoneCode));
byte kBrOnArrayTaken = byte kBrOnArrayTaken =
BR_ON(ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); BR_ON(ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10)));
byte kBrOnArrayNotTaken = BR_ON(ARRAY, Array, WASM_I31_NEW(WASM_I32V(42))); byte kBrOnArrayNotTaken = BR_ON(ARRAY, Array, WASM_I31_NEW(WASM_I32V(42)));
...@@ -1745,7 +1750,7 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) { ...@@ -1745,7 +1750,7 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
byte kBrOnNonDataNotTaken = byte kBrOnNonDataNotTaken =
BR_ON_NON(DATA, Data, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); BR_ON_NON(DATA, Data, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10)));
byte kBrOnNonDataTaken = BR_ON_NON(DATA, Data, WASM_REF_FUNC(function_index)); byte kBrOnNonDataTaken = BR_ON_NON(DATA, Data, WASM_REF_NULL(kNoneCode));
byte kBrOnNonArrayNotTaken = BR_ON_NON( byte kBrOnNonArrayNotTaken = BR_ON_NON(
ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10)));
byte kBrOnNonArrayTaken = byte kBrOnNonArrayTaken =
......
...@@ -9,7 +9,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); ...@@ -9,7 +9,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
const builder = new WasmModuleBuilder(); const builder = new WasmModuleBuilder();
builder.addStruct([]); builder.addStruct([]);
builder.addType(makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32])); builder.addType(makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]));
builder.addType(makeSig([kWasmAnyRef, kWasmFuncRef, kWasmExternRef], builder.addType(makeSig([kWasmFuncRef, kWasmFuncRef, kWasmExternRef],
[wasmRefType(0)])); [wasmRefType(0)]));
builder.addType( builder.addType(
makeSig([kWasmI64, kWasmF32, kWasmS128, kWasmI32], makeSig([kWasmI64, kWasmF32, kWasmS128, kWasmI32],
...@@ -23,7 +23,7 @@ builder.addType( ...@@ -23,7 +23,7 @@ builder.addType(
wasmRefNullType(2)])); wasmRefNullType(2)]));
builder.addType(makeSig([], [])); builder.addType(makeSig([], []));
builder.addType( builder.addType(
makeSig([wasmRefType(kWasmAnyRef)], makeSig([wasmRefType(kWasmFuncRef)],
[kWasmI32, kWasmI32, wasmRefType(1), wasmRefType(kWasmAnyRef), [kWasmI32, kWasmI32, wasmRefType(1), wasmRefType(kWasmAnyRef),
kWasmI32, wasmRefType(1), kWasmI64, wasmRefNullType(4), kWasmI32, kWasmI32, wasmRefType(1), kWasmI64, wasmRefNullType(4), kWasmI32,
wasmRefType(kWasmAnyRef), wasmRefNullType(4), kWasmI64, kWasmI64, wasmRefType(kWasmAnyRef), wasmRefNullType(4), kWasmI64, kWasmI64,
......
...@@ -2131,12 +2131,28 @@ TEST_F(FunctionBodyDecoderTest, Float64Globals) { ...@@ -2131,12 +2131,28 @@ TEST_F(FunctionBodyDecoderTest, Float64Globals) {
} }
TEST_F(FunctionBodyDecoderTest, NullRefGlobals) { TEST_F(FunctionBodyDecoderTest, NullRefGlobals) {
WASM_FEATURE_SCOPE(gc);
ValueType nullRefs[] = {kWasmNullRef, kWasmNullRef, kWasmNullRef}; ValueType nullRefs[] = {kWasmNullRef, kWasmNullRef, kWasmNullRef};
FunctionSig sig(1, 2, nullRefs); FunctionSig sig(1, 2, nullRefs);
builder.AddGlobal(kWasmNullRef); builder.AddGlobal(kWasmNullRef);
ExpectValidates(&sig, {WASM_GLOBAL_GET(0)}); ExpectValidates(&sig, {WASM_GLOBAL_GET(0)});
ExpectValidates(&sig, ExpectValidates(&sig,
{WASM_GLOBAL_SET(0, WASM_LOCAL_GET(0)), WASM_LOCAL_GET(0)}); {WASM_GLOBAL_SET(0, WASM_LOCAL_GET(0)), WASM_LOCAL_GET(0)});
ExpectValidates(
&sig, {WASM_GLOBAL_SET(0, WASM_REF_NULL(kNoneCode)), WASM_LOCAL_GET(0)});
}
TEST_F(FunctionBodyDecoderTest, NullFuncRefGlobals) {
WASM_FEATURE_SCOPE(gc);
ValueType nullFuncRefs[] = {kWasmNullFuncRef, kWasmNullFuncRef,
kWasmNullFuncRef};
FunctionSig sig(1, 2, nullFuncRefs);
builder.AddGlobal(kWasmNullFuncRef);
ExpectValidates(&sig, {WASM_GLOBAL_GET(0)});
ExpectValidates(&sig,
{WASM_GLOBAL_SET(0, WASM_LOCAL_GET(0)), WASM_LOCAL_GET(0)});
ExpectValidates(&sig, {WASM_GLOBAL_SET(0, WASM_REF_NULL(kNoFuncCode)),
WASM_LOCAL_GET(0)});
} }
TEST_F(FunctionBodyDecoderTest, AllGetGlobalCombinations) { TEST_F(FunctionBodyDecoderTest, AllGetGlobalCombinations) {
......
...@@ -187,6 +187,7 @@ struct ValueTypePair { ...@@ -187,6 +187,7 @@ struct ValueTypePair {
{kF32Code, kWasmF32}, // -- {kF32Code, kWasmF32}, // --
{kF64Code, kWasmF64}, // -- {kF64Code, kWasmF64}, // --
{kFuncRefCode, kWasmFuncRef}, // -- {kFuncRefCode, kWasmFuncRef}, // --
{kNoFuncCode, kWasmNullFuncRef}, // --
{kExternRefCode, kWasmExternRef}, // -- {kExternRefCode, kWasmExternRef}, // --
{kAnyRefCode, kWasmAnyRef}, // -- {kAnyRefCode, kWasmAnyRef}, // --
{kEqRefCode, kWasmEqRef}, // -- {kEqRefCode, kWasmEqRef}, // --
......
...@@ -129,12 +129,13 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -129,12 +129,13 @@ TEST_F(WasmSubtypingTest, Subtyping) {
constexpr ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64, constexpr ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
kWasmS128}; kWasmS128};
constexpr ValueType ref_types[] = { constexpr ValueType ref_types[] = {
kWasmFuncRef, kWasmEqRef, kWasmI31Ref, // -- // TODO(mliedtke): Add externref
kWasmDataRef, kWasmArrayRef, kWasmAnyRef, // -- kWasmFuncRef, kWasmEqRef, kWasmI31Ref, // --
kWasmNullRef, // -- kWasmDataRef, kWasmArrayRef, kWasmAnyRef, // --
refNull(0), ref(0), // struct kWasmNullRef, kWasmNullFuncRef, // --
refNull(2), ref(2), // array refNull(0), ref(0), // struct
refNull(11), ref(11) // signature refNull(2), ref(2), // array
refNull(11), ref(11) // signature
}; };
// Some macros to help managing types and modules. // Some macros to help managing types and modules.
...@@ -191,11 +192,14 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -191,11 +192,14 @@ TEST_F(WasmSubtypingTest, Subtyping) {
} }
for (ValueType ref_type : ref_types) { for (ValueType ref_type : ref_types) {
const bool is_any_func = ref_type == kWasmFuncRef ||
ref_type == kWasmNullFuncRef ||
ref_type == refNull(11) || ref_type == ref(11);
SCOPED_TRACE("ref_type: " + ref_type.name());
// Concrete reference types, i31ref and dataref are subtypes of eqref, // Concrete reference types, i31ref and dataref are subtypes of eqref,
// externref/funcref/anyref/functions are not. // externref/funcref/anyref/functions are not.
SUBTYPE_IFF(ref_type, kWasmEqRef, SUBTYPE_IFF(ref_type, kWasmEqRef,
ref_type != kWasmFuncRef && ref_type != kWasmAnyRef && ref_type != kWasmAnyRef && !is_any_func);
ref_type != refNull(11) && ref_type != ref(11));
// Struct/array types are subtypes of dataref. // Struct/array types are subtypes of dataref.
SUBTYPE_IFF(ref_type, kWasmDataRef, SUBTYPE_IFF(ref_type, kWasmDataRef,
ref_type == kWasmDataRef || ref_type == kWasmArrayRef || ref_type == kWasmDataRef || ref_type == kWasmArrayRef ||
...@@ -207,22 +211,27 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -207,22 +211,27 @@ TEST_F(WasmSubtypingTest, Subtyping) {
ref_type == kWasmArrayRef || ref_type == ref(2) || ref_type == kWasmArrayRef || ref_type == ref(2) ||
ref_type == kWasmNullRef || ref_type == refNull(2)); ref_type == kWasmNullRef || ref_type == refNull(2));
// Functions are subtypes of funcref. // Functions are subtypes of funcref.
SUBTYPE_IFF(ref_type, kWasmFuncRef, SUBTYPE_IFF(ref_type, kWasmFuncRef, is_any_func);
ref_type == kWasmFuncRef || ref_type == refNull(11) ||
ref_type == ref(11) || ref_type == kWasmNullRef);
// Each reference type is a subtype of itself. // Each reference type is a subtype of itself.
SUBTYPE(ref_type, ref_type); SUBTYPE(ref_type, ref_type);
// Each reference type excluding funcref is a subtype of anyref. // Each non-func, non-extern reference type is a subtype of anyref.
SUBTYPE_IFF(ref_type, kWasmAnyRef, ref_type != kWasmFuncRef); SUBTYPE_IFF(ref_type, kWasmAnyRef, !is_any_func);
// Only anyref is a subtype of anyref. // Only anyref is a subtype of anyref.
SUBTYPE_IFF(kWasmAnyRef, ref_type, ref_type == kWasmAnyRef); SUBTYPE_IFF(kWasmAnyRef, ref_type, ref_type == kWasmAnyRef);
// TODO(mliedtke): Improve test coverage for externref. // TODO(mliedtke): Improve test coverage for externref.
// Only externref is a subtype of externref. // Only externref is a subtype of externref.
NOT_SUBTYPE(kWasmExternRef, ref_type); NOT_SUBTYPE(kWasmExternRef, ref_type);
// Each nullable reference type is a supertype of nullref. // Each nullable non-func, non-extern reference type is a supertype of
SUBTYPE_IFF(kWasmNullRef, ref_type, ref_type.is_nullable()); // nullref.
SUBTYPE_IFF(kWasmNullRef, ref_type,
ref_type.is_nullable() && !is_any_func);
// Only nullref is a subtype of nullref. // Only nullref is a subtype of nullref.
SUBTYPE_IFF(ref_type, kWasmNullRef, ref_type == kWasmNullRef); SUBTYPE_IFF(ref_type, kWasmNullRef, ref_type == kWasmNullRef);
// Only nullable funcs are supertypes of nofunc.
SUBTYPE_IFF(kWasmNullFuncRef, ref_type,
ref_type.is_nullable() && is_any_func);
// Only nullfuncref is a subtype of nullfuncref.
SUBTYPE_IFF(ref_type, kWasmNullFuncRef, ref_type == kWasmNullFuncRef);
// Make sure symmetric relations are symmetric. // Make sure symmetric relations are symmetric.
for (ValueType ref_type2 : ref_types) { for (ValueType ref_type2 : ref_types) {
...@@ -338,8 +347,11 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -338,8 +347,11 @@ TEST_F(WasmSubtypingTest, Subtyping) {
for (ValueType type : ref_types) { for (ValueType type : ref_types) {
UNION(type, type, type); UNION(type, type, type);
INTERSECTION(type, type, type); INTERSECTION(type, type, type);
if (type == kWasmFuncRef) { if (type == kWasmFuncRef || type == kWasmNullFuncRef || type == ref(11) ||
continue; // funcref and anyref don't share the same type hierarchy. type == refNull(11)) {
// functypes don't share the same type hierarchy as anyref.
INTERSECTION(type, kWasmAnyRef, kWasmBottom);
continue;
} }
UNION(kWasmAnyRef, type, kWasmAnyRef); UNION(kWasmAnyRef, type, kWasmAnyRef);
INTERSECTION(kWasmAnyRef, type, type); INTERSECTION(kWasmAnyRef, type, type);
...@@ -350,7 +362,6 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -350,7 +362,6 @@ TEST_F(WasmSubtypingTest, Subtyping) {
} }
// Abstract types vs abstract types. // Abstract types vs abstract types.
UNION(kWasmFuncRef, kWasmNullRef, kWasmFuncRef);
UNION(kWasmEqRef, kWasmDataRef, kWasmEqRef); UNION(kWasmEqRef, kWasmDataRef, kWasmEqRef);
UNION(kWasmEqRef, kWasmI31Ref, kWasmEqRef); UNION(kWasmEqRef, kWasmI31Ref, kWasmEqRef);
UNION(kWasmEqRef, kWasmArrayRef, kWasmEqRef); UNION(kWasmEqRef, kWasmArrayRef, kWasmEqRef);
...@@ -367,16 +378,23 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -367,16 +378,23 @@ TEST_F(WasmSubtypingTest, Subtyping) {
UNION(kWasmDataRef, kWasmArrayRef, kWasmDataRef); UNION(kWasmDataRef, kWasmArrayRef, kWasmDataRef);
UNION(kWasmI31Ref.AsNonNull(), kWasmArrayRef, kWasmEqRef); UNION(kWasmI31Ref.AsNonNull(), kWasmArrayRef, kWasmEqRef);
INTERSECTION(kWasmFuncRef, kWasmEqRef, kWasmNullRef); INTERSECTION(kWasmFuncRef, kWasmEqRef, kWasmBottom);
INTERSECTION(kWasmFuncRef, kWasmDataRef, kWasmNullRef); INTERSECTION(kWasmFuncRef, kWasmDataRef, kWasmBottom);
INTERSECTION(kWasmFuncRef, kWasmI31Ref.AsNonNull(), kWasmBottom); INTERSECTION(kWasmFuncRef, kWasmI31Ref.AsNonNull(), kWasmBottom);
INTERSECTION(kWasmFuncRef, kWasmArrayRef, kWasmNullRef); INTERSECTION(kWasmFuncRef, kWasmArrayRef, kWasmBottom);
INTERSECTION(kWasmFuncRef, kWasmNullRef, kWasmNullRef); INTERSECTION(kWasmFuncRef, kWasmNullRef, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmEqRef, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmDataRef, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmI31Ref, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmArrayRef, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmNullRef, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, kWasmFuncRef, kWasmNullFuncRef);
INTERSECTION(kWasmNullFuncRef, kWasmFuncRef.AsNonNull(), kWasmBottom);
INTERSECTION(kWasmEqRef, kWasmDataRef, kWasmDataRef); INTERSECTION(kWasmEqRef, kWasmDataRef, kWasmDataRef);
INTERSECTION(kWasmEqRef, kWasmI31Ref, kWasmI31Ref); INTERSECTION(kWasmEqRef, kWasmI31Ref, kWasmI31Ref);
INTERSECTION(kWasmEqRef, kWasmArrayRef, kWasmArrayRef); INTERSECTION(kWasmEqRef, kWasmArrayRef, kWasmArrayRef);
INTERSECTION(kWasmEqRef, kWasmNullRef, kWasmNullRef); INTERSECTION(kWasmEqRef, kWasmNullRef, kWasmNullRef);
INTERSECTION(kWasmEqRef, kWasmFuncRef, kWasmNullRef); INTERSECTION(kWasmEqRef, kWasmFuncRef, kWasmBottom);
INTERSECTION(kWasmDataRef, kWasmI31Ref, kWasmNullRef); INTERSECTION(kWasmDataRef, kWasmI31Ref, kWasmNullRef);
INTERSECTION(kWasmDataRef, kWasmArrayRef, kWasmArrayRef); INTERSECTION(kWasmDataRef, kWasmArrayRef, kWasmArrayRef);
INTERSECTION(kWasmDataRef, kWasmNullRef, kWasmNullRef); INTERSECTION(kWasmDataRef, kWasmNullRef, kWasmNullRef);
...@@ -394,6 +412,15 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -394,6 +412,15 @@ TEST_F(WasmSubtypingTest, Subtyping) {
INTERSECTION(kWasmFuncRef, array_type, kWasmBottom); INTERSECTION(kWasmFuncRef, array_type, kWasmBottom);
INTERSECTION(kWasmFuncRef, function_type, function_type); INTERSECTION(kWasmFuncRef, function_type, function_type);
UNION(kWasmNullFuncRef, function_type, function_type.AsNullable());
INTERSECTION(kWasmNullFuncRef, struct_type, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, struct_type.AsNullable(), kWasmBottom);
INTERSECTION(kWasmNullFuncRef, array_type, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, array_type.AsNullable(), kWasmBottom);
INTERSECTION(kWasmNullFuncRef, function_type, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, function_type.AsNullable(),
kWasmNullFuncRef);
UNION(kWasmEqRef, struct_type, kWasmEqRef); UNION(kWasmEqRef, struct_type, kWasmEqRef);
UNION(kWasmEqRef, array_type, kWasmEqRef); UNION(kWasmEqRef, array_type, kWasmEqRef);
INTERSECTION(kWasmEqRef, struct_type, struct_type); INTERSECTION(kWasmEqRef, struct_type, struct_type);
......
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