#include "src/wasm/wasm-subtyping.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/wasm-module.h"
namespace v8::internal::wasm {
namespace {
V8_INLINE bool EquivalentIndices(ModuleTypeIndex index1, ModuleTypeIndex index2,
const WasmModule* module) {
if (index1 == index2) return true;
return module->canonical_type_id(index1) == module->canonical_type_id(index2);
}
bool ValidStructSubtypeDefinition(ModuleTypeIndex subtype_index,
ModuleTypeIndex supertype_index,
const WasmModule* module) {
const TypeDefinition& sub_def = module->type(subtype_index);
const TypeDefinition& super_def = module->type(supertype_index);
const StructType* sub_struct = sub_def.struct_type;
const StructType* super_struct = super_def.struct_type;
if (sub_struct->field_count() < super_struct->field_count()) {
return false;
}
for (uint32_t i = 0; i < super_struct->field_count(); i++) {
bool sub_mut = sub_struct->mutability(i);
bool super_mut = super_struct->mutability(i);
if (sub_mut != super_mut ||
(sub_mut && !EquivalentTypes(sub_struct->field(i),
super_struct->field(i), module)) ||
(!sub_mut &&
!IsSubtypeOf(sub_struct->field(i), super_struct->field(i), module))) {
return false;
}
}
if (sub_def.descriptor.valid()) {
if (super_def.descriptor.valid() &&
module->supertype(sub_def.descriptor) != super_def.descriptor) {
return false;
}
} else {
if (super_def.descriptor.valid()) return false;
}
if (sub_def.describes.valid()) {
if (!super_def.describes.valid()) return false;
if (module->supertype(sub_def.describes) != super_def.describes) {
return false;
}
} else {
if (super_def.describes.valid()) return false;
}
return true;
}
bool ValidArraySubtypeDefinition(ModuleTypeIndex subtype_index,
ModuleTypeIndex supertype_index,
const WasmModule* module) {
const ArrayType* sub_array = module->type(subtype_index).array_type;
const ArrayType* super_array = module->type(supertype_index).array_type;
bool sub_mut = sub_array->mutability();
bool super_mut = super_array->mutability();
return (sub_mut && super_mut &&
EquivalentTypes(sub_array->element_type(),
super_array->element_type(), module)) ||
(!sub_mut && !super_mut &&
IsSubtypeOf(sub_array->element_type(), super_array->element_type(),
module));
}
bool ValidFunctionSubtypeDefinition(ModuleTypeIndex subtype_index,
ModuleTypeIndex supertype_index,
const WasmModule* module) {
const FunctionSig* sub_func = module->type(subtype_index).function_sig;
const FunctionSig* super_func = module->type(supertype_index).function_sig;
if (sub_func->parameter_count() != super_func->parameter_count() ||
sub_func->return_count() != super_func->return_count()) {
return false;
}
for (uint32_t i = 0; i < sub_func->parameter_count(); i++) {
if (!IsSubtypeOf(super_func->parameters()[i], sub_func->parameters()[i],
module)) {
return false;
}
}
for (uint32_t i = 0; i < sub_func->return_count(); i++) {
if (!IsSubtypeOf(sub_func->returns()[i], super_func->returns()[i],
module)) {
return false;
}
}
return true;
}
bool ValidContinuationSubtypeDefinition(ModuleTypeIndex subtype_index,
ModuleTypeIndex supertype_index,
const WasmModule* module) {
const ContType* sub_cont = module->type(subtype_index).cont_type;
const ContType* super_cont = module->type(supertype_index).cont_type;
return IsHeapSubtypeOf(module->heap_type(sub_cont->contfun_typeindex()),
module->heap_type(super_cont->contfun_typeindex()),
module);
}
StandardType UpcastToStandardType(ValueTypeBase type) {
if (!type.has_index()) return type.standard_type();
switch (type.ref_type_kind()) {
case RefTypeKind::kStruct:
return StandardType::kStruct;
case RefTypeKind::kArray:
return StandardType::kArray;
case RefTypeKind::kFunction:
return StandardType::kFunc;
case RefTypeKind::kCont:
return StandardType::kCont;
case RefTypeKind::kOther:
break;
}
UNREACHABLE();
}
#define FOREACH_SUBTYPING(V) \
\
V(Eq, Any) \
V(Struct, Eq) \
V(None, Struct) \
V(Array, Eq) \
V(None, Array) \
V(I31, Eq) \
V(None, I31) \
V(String, Any) \
V(None, String) \
\
V(NoFunc, Func) \
\
V(ExternString, Extern) \
V(NoExtern, ExternString) \
\
V(NoExn, Exn) \
\
V(NoCont, Cont)
static constexpr uint32_t kNumStandardTypes =
value_type_impl::kNumberOfStandardTypes;
static constexpr std::array<GenericKind, kNumStandardTypes>
kValueTypeLookupMap = base::make_array<kNumStandardTypes>([](size_t i) {
#define ENTRY(name, ...) \
if (static_cast<size_t>(StandardType::k##name) == i) { \
return GenericKind::k##name; \
}
FOREACH_GENERIC_TYPE(ENTRY)
#undef ENTRY
return GenericKind::kVoid;
});
constexpr GenericKind ToGenericKind(StandardType type) {
DCHECK_LT(static_cast<int>(type), static_cast<int>(StandardType::kI32));
return kValueTypeLookupMap[static_cast<size_t>(type)];
}
constexpr int NotInSubtypeRelation(StandardType type) {
#define CASE(sub, super) \
if (type == StandardType::k##sub || type == StandardType::k##super) return 0;
FOREACH_SUBTYPING(CASE)
#undef CASE
return 1;
}
static constexpr uint32_t kNumNonTrivial =
#define CASE(name, ...) (NotInSubtypeRelation(StandardType::k##name) ? 0 : 1) +
FOREACH_GENERIC_TYPE(CASE)
#undef CASE
0;
enum class CondensedIndices : int {
#define ENTRY(name, ...) \
k##name, \
k##name##AdjustNext = \
k##name - NotInSubtypeRelation(StandardType::k##name),
FOREACH_GENERIC_TYPE(ENTRY)
#undef ENTRY
};
static constexpr uint8_t kNotRelatedSentinel = 0xFF;
static_assert(kNumStandardTypes < kNotRelatedSentinel);
constexpr uint8_t ComputeCondensedIndex(StandardType type) {
switch (type) {
#define BAILOUT(name, ...) case StandardType::k##name:
FOREACH_NUMERIC_VALUE_TYPE(BAILOUT)
return kNotRelatedSentinel;
#undef BAILOUT
#define CASE(name, ...) \
case StandardType::k##name: \
if (NotInSubtypeRelation(type)) return kNotRelatedSentinel; \
return static_cast<uint8_t>(CondensedIndices::k##name);
FOREACH_GENERIC_TYPE(CASE)
#undef CASE
}
}
constexpr StandardType ComputeStandardType(uint8_t condensed_index) {
#define CASE(name, ...) \
if (ComputeCondensedIndex(StandardType::k##name) == condensed_index) \
return StandardType::k##name;
FOREACH_GENERIC_TYPE(CASE)
#undef CASE
UNREACHABLE();
}
static constexpr std::array<uint8_t, kNumStandardTypes>
kCondensedIndexLookupMap =
base::make_array<kNumStandardTypes>([](size_t i) {
return ComputeCondensedIndex(static_cast<StandardType>(i));
});
static constexpr std::array<uint8_t, kNumNonTrivial> kCondensedToStandardMap =
base::make_array<kNumNonTrivial>(
[](size_t i) { return static_cast<uint8_t>(ComputeStandardType(i)); });
constexpr uint8_t CondensedIndex(StandardType type) {
return kCondensedIndexLookupMap[static_cast<size_t>(type)];
}
constexpr StandardType CondensedToStandard(uint8_t condensed) {
return static_cast<StandardType>(kCondensedToStandardMap[condensed]);
}
constexpr bool ComputeIsSubtype(size_t sub, size_t super) {
if (sub == super) return true;
#define CASE(a, b) \
{ \
size_t raw_a = static_cast<size_t>(CondensedIndices::k##a); \
size_t raw_b = static_cast<size_t>(CondensedIndices::k##b); \
if (sub == raw_a) { \
if (super == raw_b) return true; \
if (ComputeIsSubtype(raw_b, super)) return true; \
} \
}
FOREACH_SUBTYPING(CASE)
#undef CASE
return false;
}
static constexpr std::array<std::array<bool, kNumNonTrivial>, kNumNonTrivial>
kSubtypeLookupMap2 = base::make_array<kNumNonTrivial>([](size_t sub) {
return base::make_array<kNumNonTrivial>(
[sub](size_t super) { return ComputeIsSubtype(sub, super); });
});
constexpr bool SubtypeLookup(StandardType sub, StandardType super) {
if (sub == StandardType::kBottom) return true;
if (super == StandardType::kTop) return true;
uint8_t sub_condensed = CondensedIndex(sub);
if (sub_condensed == kNotRelatedSentinel) return sub == super;
uint8_t super_condensed = CondensedIndex(super);
if (super_condensed == kNotRelatedSentinel) return false;
return kSubtypeLookupMap2[sub_condensed][super_condensed];
}
constexpr StandardType ComputeCommonAncestor(size_t t1, size_t t2) {
if (kSubtypeLookupMap2[t1][t2]) return CondensedToStandard(t2);
if (kSubtypeLookupMap2[t2][t1]) return CondensedToStandard(t1);
#define CASE(a, b) \
if (t1 == static_cast<size_t>(CondensedIndices::k##a)) { \
return ComputeCommonAncestor(static_cast<size_t>(CondensedIndices::k##b), \
t2); \
}
FOREACH_SUBTYPING(CASE)
#undef CASE
return StandardType::kTop;
}
static constexpr std::array<std::array<StandardType, kNumNonTrivial>,
kNumNonTrivial>
kCommonAncestorLookupMap = base::make_array<kNumNonTrivial>([](size_t sub) {
return base::make_array<kNumNonTrivial>(
[sub](size_t super) { return ComputeCommonAncestor(sub, super); });
});
constexpr StandardType CommonAncestorLookup(StandardType t1, StandardType t2) {
if (t1 == StandardType::kBottom) return t2;
if (t2 == StandardType::kBottom) return t1;
if (t1 == StandardType::kTop) return t1;
if (t2 == StandardType::kTop) return t2;
uint8_t t1_condensed = CondensedIndex(t1);
if (t1_condensed == kNotRelatedSentinel) {
return t2 == t1 ? t1 : StandardType::kTop;
}
uint8_t t2_condensed = CondensedIndex(t2);
if (t2_condensed == kNotRelatedSentinel) return StandardType::kTop;
return kCommonAncestorLookupMap[t1_condensed][t2_condensed];
}
HeapType NullSentinelImpl(HeapType type) {
StandardType standard = UpcastToStandardType(type);
constexpr StandardType candidates[] = {
#define NULLTYPE(name, ...) StandardType::k##name,
FOREACH_NONE_TYPE(NULLTYPE)
#undef NULLTYPE
};
for (StandardType candidate : candidates) {
if (SubtypeLookup(candidate, standard)) {
return HeapType::Generic(ToGenericKind(candidate), type.is_shared());
}
}
if (type.is_string_view()) {
return HeapType::Generic(GenericKind::kNone, type.is_shared());
}
UNREACHABLE();
}
bool IsGenericSubtypeOfIndexedTypes(ValueTypeBase type) {
DCHECK(type.is_generic());
GenericKind kind = type.generic_kind();
return IsNullKind(kind) || kind == GenericKind::kBottom;
}
}
bool ValidSubtypeDefinition(ModuleTypeIndex subtype_index,
ModuleTypeIndex supertype_index,
const WasmModule* module) {
const TypeDefinition& subtype = module->type(subtype_index);
const TypeDefinition& supertype = module->type(supertype_index);
if (subtype.kind != supertype.kind) return false;
if (supertype.is_final) return false;
if (subtype.is_shared != supertype.is_shared) return false;
switch (subtype.kind) {
case TypeDefinition::kFunction:
return ValidFunctionSubtypeDefinition(subtype_index, supertype_index,
module);
case TypeDefinition::kStruct:
return ValidStructSubtypeDefinition(subtype_index, supertype_index,
module);
case TypeDefinition::kArray:
return ValidArraySubtypeDefinition(subtype_index, supertype_index,
module);
case TypeDefinition::kCont:
return ValidContinuationSubtypeDefinition(subtype_index, supertype_index,
module);
}
}
namespace {
std::optional<bool> IsSubtypeOf_CommonImpl(ValueTypeBase subtype,
ValueTypeBase supertype) {
DCHECK(!subtype.is_numeric() && !supertype.is_numeric());
if (subtype.is_shared() != supertype.is_shared()) return false;
if (subtype.is_bottom()) return true;
if (supertype.has_index()) {
if (subtype.has_index()) {
if (supertype.is_exact() && !subtype.is_exact()) return false;
return {};
}
if (!IsGenericSubtypeOfIndexedTypes(subtype)) return false;
}
return SubtypeLookup(UpcastToStandardType(subtype),
UpcastToStandardType(supertype));
}
}
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
HeapType subtype, HeapType supertype, const WasmModule* sub_module,
const WasmModule* super_module) {
DCHECK(subtype != supertype || sub_module != super_module);
std::optional<bool> result = IsSubtypeOf_CommonImpl(subtype, supertype);
if (result.has_value()) return result.value();
DCHECK(subtype.has_index() && supertype.has_index());
ModuleTypeIndex sub_index = subtype.ref_index();
CanonicalTypeIndex super_canon =
super_module->canonical_type_id(supertype.ref_index());
if (supertype.is_exact()) {
return sub_module->canonical_type_id(sub_index) == super_canon;
}
do {
if (sub_module->canonical_type_id(sub_index) == super_canon) return true;
sub_index = sub_module->supertype(sub_index);
} while (sub_index.valid());
return false;
}
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
ValueType subtype, ValueType supertype, const WasmModule* sub_module,
const WasmModule* super_module) {
if (supertype.is_top()) return true;
if (subtype.is_numeric()) return subtype == supertype;
if (supertype.is_numeric()) return subtype.is_bottom();
if (subtype.is_nullable() && !supertype.is_nullable()) return false;
HeapType sub_heap = subtype.heap_type();
HeapType super_heap = supertype.heap_type();
if (sub_heap == super_heap && sub_module == super_module) return true;
return IsSubtypeOfImpl(sub_heap, super_heap, sub_module, super_module);
}
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
CanonicalValueType subtype, CanonicalValueType supertype) {
DCHECK_NE(subtype, supertype);
if (supertype.is_top()) return true;
if (subtype.is_numeric()) return false;
if (supertype.is_numeric()) return subtype.is_bottom();
if (subtype.is_nullable() && !supertype.is_nullable()) return false;
std::optional<bool> result = IsSubtypeOf_CommonImpl(subtype, supertype);
if (result.has_value()) return result.value();
DCHECK(subtype.has_index() && supertype.has_index());
CanonicalTypeIndex sub_index = subtype.ref_index();
CanonicalTypeIndex super_index = supertype.ref_index();
if (sub_index == super_index) return true;
if (supertype.is_exact()) return false;
return GetTypeCanonicalizer()->IsHeapSubtype(sub_index, super_index);
}
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
const WasmModule* module) {
if (type1 == type2) return true;
if (!type1.has_index() || !type2.has_index()) return type1 == type2;
if (type1.nullability() != type2.nullability()) return false;
if (type1.is_exact() != type2.is_exact()) return false;
DCHECK(type1.has_index() && module->has_type(type1.ref_index()) &&
type2.has_index() && module->has_type(type2.ref_index()));
return EquivalentIndices(type1.ref_index(), type2.ref_index(), module);
}
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
const WasmModule* module1,
const WasmModule* module2) {
if (type1 == type2 && module1 == module2) return true;
if (!type1.has_index() || !type2.has_index()) return type1 == type2;
if (type1.nullability() != type2.nullability()) return false;
if (type1.is_exact() != type2.is_exact()) return false;
DCHECK(type1.has_index() && module1->has_type(type1.ref_index()) &&
type2.has_index() && module2->has_type(type2.ref_index()));
return module1->canonical_type_id(type1.ref_index()) ==
module2->canonical_type_id(type2.ref_index());
}
namespace {
HeapType CommonAncestor(HeapType type1, HeapType type2,
const WasmModule* module) {
DCHECK(type1.has_index() && type2.has_index());
bool both_shared = type1.is_shared();
if (both_shared != type2.is_shared()) return HeapType{kWasmTop};
ModuleTypeIndex type_index1 = type1.ref_index();
ModuleTypeIndex type_index2 = type2.ref_index();
{
int depth1 = GetSubtypingDepth(module, type_index1);
int depth2 = GetSubtypingDepth(module, type_index2);
while (depth1 > depth2) {
type_index1 = module->supertype(type_index1);
depth1--;
}
while (depth2 > depth1) {
type_index2 = module->supertype(type_index2);
depth2--;
}
}
DCHECK_NE(type_index1, kNoSuperType);
DCHECK_NE(type_index2, kNoSuperType);
while (type_index1 != kNoSuperType &&
!EquivalentIndices(type_index1, type_index2, module)) {
type_index1 = module->supertype(type_index1);
type_index2 = module->supertype(type_index2);
}
DCHECK_EQ(type_index1 == kNoSuperType, type_index2 == kNoSuperType);
RefTypeKind kind1 = type1.ref_type_kind();
if (type_index1 != kNoSuperType) {
DCHECK_EQ(kind1, type2.ref_type_kind());
return HeapType::Index(type_index1, both_shared, kind1);
}
StandardType generic_ancestor = CommonAncestorLookup(
UpcastToStandardType(type1), UpcastToStandardType(type2));
return HeapType::Generic(ToGenericKind(generic_ancestor), both_shared);
}
HeapType CommonAncestorWithAbstract(HeapType heap1, HeapType heap2) {
DCHECK(heap1.is_abstract_ref());
bool is_shared = heap1.is_shared();
if (is_shared != heap2.is_shared()) return HeapType{kWasmTop};
std::optional<bool> is_sub = IsSubtypeOf_CommonImpl(heap1, heap2);
DCHECK(is_sub.has_value());
if (is_sub.value()) return heap2;
StandardType generic_ancestor = CommonAncestorLookup(
UpcastToStandardType(heap1), UpcastToStandardType(heap2));
return HeapType::Generic(ToGenericKind(generic_ancestor), is_shared);
}
Exactness UnionExactness(ValueType type1, ValueType type2,
const WasmModule* module) {
if (type1.is_exact()) {
if (type2.is_exact()) {
bool same =
EquivalentIndices(type1.ref_index(), type2.ref_index(), module);
return same ? Exactness::kExact : Exactness::kAnySubtype;
}
if (type2.is_none_type()) return Exactness::kExact;
} else if (type2.is_exact()) {
if (type1.is_none_type()) return Exactness::kExact;
}
return Exactness::kAnySubtype;
}
}
V8_EXPORT_PRIVATE TypeInModule Union(ValueType type1, ValueType type2,
const WasmModule* module) {
if (type1 == kWasmTop || type2 == kWasmTop) return {kWasmTop, module};
if (type1 == kWasmBottom) return {type2, module};
if (type2 == kWasmBottom) return {type1, module};
if (!type1.is_ref() || !type2.is_ref()) {
return {type1 == type2 ? type1 : kWasmTop, module};
}
Nullability nullability =
type1.is_nullable() || type2.is_nullable() ? kNullable : kNonNullable;
Exactness exactness = UnionExactness(type1, type2, module);
HeapType heap1 = type1.heap_type();
HeapType heap2 = type2.heap_type();
if (heap1 == heap2) {
return {type1.AsNullable(nullability).AsExactIfIndexed(exactness), module};
}
HeapType result_type = kWasmBottom;
if (heap1.is_abstract_ref()) {
result_type = CommonAncestorWithAbstract(heap1, heap2);
} else if (heap2.is_abstract_ref()) {
result_type = CommonAncestorWithAbstract(heap2, heap1);
} else {
result_type = CommonAncestor(heap1, heap2, module);
}
DCHECK_NE(result_type, kWasmBottom);
if (result_type.is_top()) return {kWasmTop, module};
return {ValueType::RefMaybeNull(result_type, nullability).AsExact(exactness),
module};
}
TypeInModule Intersection(ValueType type1, ValueType type2,
const WasmModule* module) {
if (type1 == kWasmTop) return {type2, module};
if (type2 == kWasmTop) return {type1, module};
if (!type1.is_ref() || !type2.is_ref()) {
return {type1 == type2 ? type1 : kWasmBottom, module};
}
Nullability nullability =
type1.is_nullable() && type2.is_nullable() ? kNullable : kNonNullable;
if (nullability == kNonNullable &&
(type1.is_none_type() || type2.is_none_type())) {
return {kWasmBottom, module};
}
if (IsHeapSubtypeOf(type1.heap_type(), type2.heap_type(), module)) {
return TypeInModule{type1.AsNullable(nullability), module};
}
if (IsHeapSubtypeOf(type2.heap_type(), type1.heap_type(), module)) {
return TypeInModule{type2.AsNullable(nullability), module};
}
if (nullability == kNonNullable) {
return {kWasmBottom, module};
}
ValueType null_type1 = ToNullSentinel({type1, module});
if (null_type1 == ToNullSentinel({type2, module})) {
return {null_type1, module};
}
return {kWasmBottom, module};
}
ValueType ToNullSentinel(TypeInModule type) {
HeapType null_heap = NullSentinelImpl(type.type.heap_type());
DCHECK(IsHeapSubtypeOf(null_heap, type.type.heap_type(), type.module));
return ValueType::RefNull(null_heap);
}
ValueType ToTopType(ValueType type) {
StandardType standard = UpcastToStandardType(type);
constexpr StandardType candidates[] = {
#define TOPTYPE(name, ...) StandardType::k##name,
FOREACH_TOP_TYPE(TOPTYPE)
#undef TOPTYPE
};
for (StandardType candidate : candidates) {
if (SubtypeLookup(standard, candidate)) {
return ValueType::Generic(ToGenericKind(candidate), kNullable,
type.is_shared());
}
}
if (type.is_string_view()) {
return kWasmTop;
}
UNREACHABLE();
}
bool IsSameTypeHierarchy(HeapType type1, HeapType type2,
const WasmModule* module) {
return NullSentinelImpl(type1) == NullSentinelImpl(type2);
}
}