[wasm-gc] Fix and extend type union

Bug: v8:7748
Change-Id: Ia0486dd543bdb2c9eb42899fd57aae22297f8cd2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4177095
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85367}
This commit is contained in:
Manos Koukoutos 2023-01-18 14:37:33 +01:00 committed by V8 LUCI CQ
parent e3c6e32ec0
commit e9024ad26c
3 changed files with 194 additions and 67 deletions

View File

@ -343,12 +343,17 @@ HeapType::Representation CommonAncestor(uint32_t type_index1,
}
switch (kind1) {
case TypeDefinition::kFunction:
DCHECK_EQ(kind2, kind1);
return HeapType::kFunc;
switch (kind2) {
case TypeDefinition::kFunction:
return HeapType::kFunc;
case TypeDefinition::kStruct:
case TypeDefinition::kArray:
return HeapType::kBottom;
}
case TypeDefinition::kStruct:
switch (kind2) {
case TypeDefinition::kFunction:
UNREACHABLE();
return HeapType::kBottom;
case TypeDefinition::kStruct:
return HeapType::kStruct;
case TypeDefinition::kArray:
@ -357,7 +362,7 @@ HeapType::Representation CommonAncestor(uint32_t type_index1,
case TypeDefinition::kArray:
switch (kind2) {
case TypeDefinition::kFunction:
UNREACHABLE();
return HeapType::kBottom;
case TypeDefinition::kStruct:
return HeapType::kEq;
case TypeDefinition::kArray:
@ -368,21 +373,67 @@ HeapType::Representation CommonAncestor(uint32_t type_index1,
// Returns the least common ancestor of a generic HeapType {heap1}, and
// another HeapType {heap2}.
// TODO(7748): This function sometimes assumes that incompatible types cannot be
// compared, in some cases explicitly and in others implicitly. Make it
// consistent.
HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
HeapType heap2,
const WasmModule* module2) {
DCHECK(heap1.is_generic());
switch (heap1.representation()) {
case HeapType::kFunc:
DCHECK(IsHeapSubtypeOf(heap2, heap1, module2, module2));
return HeapType::kFunc;
case HeapType::kFunc: {
if (heap2 == HeapType::kFunc || heap2 == HeapType::kNoFunc ||
(heap2.is_index() && module2->has_signature(heap2.ref_index()))) {
return HeapType::kFunc;
} else {
return HeapType::kBottom;
}
}
case HeapType::kAny: {
switch (heap2.representation()) {
case HeapType::kI31:
case HeapType::kNone:
case HeapType::kEq:
case HeapType::kStruct:
case HeapType::kArray:
case HeapType::kAny:
case HeapType::kString:
return HeapType::kAny;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kAny;
}
}
case HeapType::kEq: {
return IsHeapSubtypeOf(heap2, heap1, module2, module2)
? heap1.representation()
: HeapType::kAny;
switch (heap2.representation()) {
case HeapType::kI31:
case HeapType::kNone:
case HeapType::kEq:
case HeapType::kStruct:
case HeapType::kArray:
return HeapType::kEq;
case HeapType::kAny:
case HeapType::kString:
return HeapType::kAny;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kEq;
}
}
case HeapType::kI31:
switch (heap2.representation()) {
@ -394,12 +445,17 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
case HeapType::kArray:
return HeapType::kEq;
case HeapType::kAny:
case HeapType::kString:
return HeapType::kAny;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
UNREACHABLE();
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kEq;
@ -410,21 +466,25 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
case HeapType::kNone:
return HeapType::kStruct;
case HeapType::kArray:
return HeapType::kEq;
case HeapType::kI31:
case HeapType::kEq:
return HeapType::kEq;
case HeapType::kAny:
case HeapType::kString:
return HeapType::kAny;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
UNREACHABLE();
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: module2->has_struct(heap2.ref_index()) ? HeapType::kStruct
: HeapType::kEq;
return module2->has_struct(heap2.ref_index()) ? HeapType::kStruct
: module2->has_array(heap2.ref_index()) ? HeapType::kEq
: HeapType::kBottom;
}
case HeapType::kArray:
switch (heap2.representation()) {
@ -432,29 +492,29 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
case HeapType::kNone:
return HeapType::kArray;
case HeapType::kStruct:
return HeapType::kEq;
case HeapType::kI31:
case HeapType::kEq:
return HeapType::kEq;
case HeapType::kAny:
case HeapType::kString:
return HeapType::kAny;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
UNREACHABLE();
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_array(heap2.ref_index()) ? HeapType::kArray
: module2->has_struct(heap2.ref_index()) ? HeapType::kEq
: HeapType::kBottom;
}
case HeapType::kAny:
return HeapType::kAny;
case HeapType::kBottom:
return HeapType::kBottom;
case HeapType::kNone:
return heap2.representation();
case HeapType::kNoFunc:
switch (heap2.representation()) {
case HeapType::kArray:
case HeapType::kNone:
@ -462,25 +522,63 @@ HeapType::Representation CommonAncestorWithGeneric(HeapType heap1,
case HeapType::kI31:
case HeapType::kEq:
case HeapType::kAny:
case HeapType::kString:
return heap2.representation();
case HeapType::kExtern:
case HeapType::kNoExtern:
UNREACHABLE();
case HeapType::kNoFunc:
return HeapType::kNoFunc;
case HeapType::kFunc:
return HeapType::kFunc;
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index())
? heap2.representation()
: HeapType::kBottom;
? HeapType::kBottom
: heap2.representation();
}
case HeapType::kNoFunc:
return (heap2 == HeapType::kNoFunc || heap2 == HeapType::kFunc ||
module2->has_signature(heap2.ref_index()))
? heap2.representation()
: HeapType::kBottom;
case HeapType::kNoExtern:
return heap2.representation() == HeapType::kExtern ? HeapType::kExtern
: HeapType::kNoExtern;
return heap2 == HeapType::kExtern || heap2 == HeapType::kNoExtern
? heap2.representation()
: HeapType::kBottom;
case HeapType::kExtern:
return HeapType::kExtern;
case HeapType::kString:
return heap2 == HeapType::kExtern || heap2 == HeapType::kNoExtern
? HeapType::kExtern
: HeapType::kBottom;
case HeapType::kString: {
switch (heap2.representation()) {
case HeapType::kI31:
case HeapType::kEq:
case HeapType::kStruct:
case HeapType::kArray:
case HeapType::kAny:
return HeapType::kAny;
case HeapType::kNone:
case HeapType::kString:
return HeapType::kString;
case HeapType::kFunc:
case HeapType::kExtern:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf8:
case HeapType::kStringViewWtf16:
case HeapType::kBottom:
return HeapType::kBottom;
default:
return module2->has_signature(heap2.ref_index()) ? HeapType::kBottom
: HeapType::kAny;
}
}
case HeapType::kStringViewIter:
case HeapType::kStringViewWtf16:
case HeapType::kStringViewWtf8:
return heap1 == heap2 ? heap1.representation() : HeapType::kBottom;
default:
UNREACHABLE();
@ -503,21 +601,23 @@ V8_EXPORT_PRIVATE TypeInModule Union(ValueType type1, ValueType type2,
if (heap1 == heap2 && module1 == module2) {
return {ValueType::RefMaybeNull(heap1, nullability), module1};
}
HeapType::Representation result_repr;
const WasmModule* result_module;
if (heap1.is_generic()) {
return {ValueType::RefMaybeNull(
CommonAncestorWithGeneric(heap1, heap2, module2), nullability),
module1};
result_repr = CommonAncestorWithGeneric(heap1, heap2, module2);
result_module = module2;
} else if (heap2.is_generic()) {
return {ValueType::RefMaybeNull(
CommonAncestorWithGeneric(heap2, heap1, module1), nullability),
module1};
result_repr = CommonAncestorWithGeneric(heap2, heap1, module1);
result_module = module1;
} else {
return {ValueType::RefMaybeNull(
CommonAncestor(heap1.ref_index(), heap2.ref_index(), module1,
module2),
nullability),
module1};
result_repr =
CommonAncestor(heap1.ref_index(), heap2.ref_index(), module1, module2);
result_module = module1;
}
return {result_repr == HeapType::kBottom
? kWasmBottom
: ValueType::RefMaybeNull(result_repr, nullability),
result_module};
}
TypeInModule Intersection(ValueType type1, ValueType type2,

View File

@ -49,14 +49,14 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool EquivalentTypes(ValueType type1,
// For heap types, the following subtyping rules hold:
// - The abstract heap types form the following type hierarchies:
//
// any func extern
// | | |
// eq nofunc noextern
// / | \
// i31 array struct
// \ | /
// \ | /
// none
// any func extern
// / \ | |
// eq \ nofunc noextern
// / | \ \
// i31 array struct string
// \___|______|_____/
// |
// none
//
// - All functions are subtypes of func.
// - All structs are subtypes of struct.
@ -147,6 +147,7 @@ inline std::ostream& operator<<(std::ostream& oss, TypeInModule type) {
<< reinterpret_cast<intptr_t>(type.module);
}
// Returns {kWasmBottom} if the union of {type1} and {type2} is not defined.
V8_EXPORT_PRIVATE TypeInModule Union(ValueType type1, ValueType type2,
const WasmModule* module1,
const WasmModule* module2);

View File

@ -170,12 +170,13 @@ TEST_F(WasmSubtypingTest, Subtyping) {
#define DISTINCT(index1, index2) \
EXPECT_FALSE(EquivalentTypes(ValueType::RefNull(index1), \
ValueType::RefNull(index2), module1, module));
// Union always expresses the result in terms of module1.
#define UNION(type1, type2, type_result) \
EXPECT_EQ(Union(type1, type2, module1, module), \
TypeInModule(type_result, module1))
// Intersection might return either module, so we have a version which checks
// the module and one which deos not.
// For union and intersection, we have a version that also checks the module,
// and one that does not.
#define UNION(type1, type2, type_result) \
EXPECT_EQ(Union(type1, type2, module1, module).type, type_result)
#define UNION_M(type1, type2, type_result, module_result) \
EXPECT_EQ(Union(type1, type2, module1, module), \
TypeInModule(type_result, module_result))
#define INTERSECTION(type1, type2, type_result) \
EXPECT_EQ(Intersection(type1, type2, module1, module).type, type_result)
#define INTERSECTION_M(type1, type2, type_result, module_result) \
@ -409,6 +410,20 @@ TEST_F(WasmSubtypingTest, Subtyping) {
UNION(kWasmAnyRef, kWasmNullRef, kWasmAnyRef);
UNION(kWasmExternRef, kWasmNullExternRef, kWasmExternRef);
UNION(kWasmFuncRef, kWasmNullFuncRef, kWasmFuncRef);
UNION(kWasmFuncRef, kWasmStructRef, kWasmBottom);
UNION(kWasmFuncRef, kWasmArrayRef, kWasmBottom);
UNION(kWasmFuncRef, kWasmAnyRef, kWasmBottom);
UNION(kWasmFuncRef, kWasmEqRef, kWasmBottom);
UNION(kWasmStringRef, kWasmAnyRef, kWasmAnyRef);
UNION(kWasmStringRef, kWasmStructRef, kWasmAnyRef);
UNION(kWasmStringRef, kWasmArrayRef, kWasmAnyRef);
UNION(kWasmStringRef, kWasmFuncRef, kWasmBottom);
UNION(kWasmStringViewIter, kWasmStringRef, kWasmBottom);
UNION(kWasmStringViewWtf8, kWasmStringRef, kWasmBottom);
UNION(kWasmStringViewWtf16, kWasmStringRef, kWasmBottom);
UNION(kWasmStringViewIter, kWasmAnyRef, kWasmBottom);
UNION(kWasmStringViewWtf8, kWasmAnyRef, kWasmBottom);
UNION(kWasmStringViewWtf16, kWasmAnyRef, kWasmBottom);
INTERSECTION(kWasmExternRef, kWasmEqRef, kWasmBottom);
INTERSECTION(kWasmExternRef, kWasmStructRef, kWasmBottom);
@ -457,11 +472,15 @@ TEST_F(WasmSubtypingTest, Subtyping) {
// Abstract vs indexed types.
UNION(kWasmFuncRef, function_type, kWasmFuncRef);
UNION(kWasmFuncRef, struct_type, kWasmBottom);
UNION(kWasmFuncRef, array_type, kWasmBottom);
INTERSECTION(kWasmFuncRef, struct_type, kWasmBottom);
INTERSECTION(kWasmFuncRef, array_type, kWasmBottom);
INTERSECTION(kWasmFuncRef, function_type, function_type);
INTERSECTION_M(kWasmFuncRef, function_type, function_type, module);
UNION(kWasmNullFuncRef, function_type, function_type.AsNullable());
UNION(kWasmNullFuncRef, struct_type, kWasmBottom);
UNION(kWasmNullFuncRef, array_type, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, struct_type, kWasmBottom);
INTERSECTION(kWasmNullFuncRef, struct_type.AsNullable(), kWasmBottom);
INTERSECTION(kWasmNullFuncRef, array_type, kWasmBottom);
@ -478,7 +497,8 @@ TEST_F(WasmSubtypingTest, Subtyping) {
UNION(kWasmStructRef, struct_type, kWasmStructRef);
UNION(kWasmStructRef, array_type, kWasmEqRef);
INTERSECTION(kWasmStructRef, struct_type, struct_type);
UNION(kWasmStructRef, function_type, kWasmBottom);
INTERSECTION_M(kWasmStructRef, struct_type, struct_type, module);
INTERSECTION(kWasmStructRef, array_type, kWasmBottom);
INTERSECTION(kWasmStructRef, function_type, kWasmBottom);
@ -490,17 +510,22 @@ TEST_F(WasmSubtypingTest, Subtyping) {
UNION(kWasmArrayRef, struct_type, kWasmEqRef);
UNION(kWasmArrayRef, array_type, kWasmArrayRef);
UNION(kWasmArrayRef, function_type, kWasmBottom);
INTERSECTION(kWasmArrayRef, struct_type, kWasmBottom);
INTERSECTION(kWasmArrayRef, array_type, array_type);
INTERSECTION_M(kWasmArrayRef, array_type, array_type, module);
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());
UNION_M(kWasmNullRef, struct_type, struct_type.AsNullable(), module);
UNION_M(kWasmNullRef, array_type, array_type.AsNullable(), module);
UNION(kWasmNullRef, function_type, kWasmBottom);
INTERSECTION(kWasmNullRef, struct_type, kWasmBottom);
INTERSECTION(kWasmNullRef, array_type, kWasmBottom);
INTERSECTION(kWasmNullRef, function_type, kWasmBottom);
UNION(struct_type, kWasmStringRef, kWasmAnyRef);
UNION(array_type, kWasmStringRef, kWasmAnyRef);
UNION(function_type, kWasmStringRef, kWasmBottom);
// Indexed types of different kinds.
UNION(struct_type, array_type, kWasmEqRef.AsNonNull());
INTERSECTION(struct_type, array_type, kWasmBottom);
@ -516,11 +541,11 @@ TEST_F(WasmSubtypingTest, Subtyping) {
// Concrete types of the same kind.
// Subtyping relation.
UNION(refNull(4), ref(1), refNull(1));
UNION_M(refNull(4), ref(1), refNull(1), module1);
INTERSECTION_M(refNull(4), ref(1), ref(4), module1);
INTERSECTION_M(refNull(1), refNull(4), refNull(4), module);
// Common ancestor.
UNION(ref(4), ref(31), ref(1));
UNION_M(ref(4), ref(31), ref(1), module1);
INTERSECTION(ref(4), ref(31), kWasmBottom);
// No common ancestor.
UNION(ref(6), refNull(2), kWasmArrayRef.AsNullable());
@ -538,6 +563,7 @@ TEST_F(WasmSubtypingTest, Subtyping) {
#undef IDENTICAL
#undef DISTINCT
#undef UNION
#undef UNION_M
#undef INTERSECTION
#undef INTERSECTION_M
}