[wasm-gc] Implement cross-module subtyping
Additional changes: - Add tests. - Rename some subtyping functions. Bug: v8:7748 Change-Id: I3635e93ea6bbab1942f927a8e414afc8efd31f69 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2389983 Commit-Queue: Manos Koukoutos <manoskouk@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#69784}
This commit is contained in:
parent
914b231753
commit
698f8caca0
@ -218,16 +218,7 @@ std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name) {
|
||||
}
|
||||
|
||||
WasmModule::WasmModule(std::unique_ptr<Zone> signature_zone)
|
||||
: signature_zone(std::move(signature_zone)),
|
||||
subtyping_cache(this->signature_zone.get() == nullptr
|
||||
? nullptr
|
||||
: new ZoneUnorderedSet<std::pair<uint32_t, uint32_t>>(
|
||||
this->signature_zone.get())),
|
||||
type_equivalence_cache(
|
||||
this->signature_zone.get() == nullptr
|
||||
? nullptr
|
||||
: new ZoneUnorderedSet<std::pair<uint32_t, uint32_t>>(
|
||||
this->signature_zone.get())) {}
|
||||
: signature_zone(std::move(signature_zone)) {}
|
||||
|
||||
bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) {
|
||||
// TODO(wasm): Once wasm has its own CSP policy, we should introduce a
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "src/wasm/struct-types.h"
|
||||
#include "src/wasm/wasm-constants.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
#include "src/zone/zone-containers.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
@ -334,28 +333,6 @@ struct V8_EXPORT_PRIVATE WasmModule {
|
||||
bool has_array(uint32_t index) const {
|
||||
return index < types.size() && type_kinds[index] == kWasmArrayTypeCode;
|
||||
}
|
||||
base::RecursiveMutex* type_cache_mutex() const { return &type_cache_mutex_; }
|
||||
bool is_cached_subtype(uint32_t subtype, uint32_t supertype) const {
|
||||
return subtyping_cache->count(std::make_pair(subtype, supertype)) == 1;
|
||||
}
|
||||
void cache_subtype(uint32_t subtype, uint32_t supertype) const {
|
||||
subtyping_cache->emplace(subtype, supertype);
|
||||
}
|
||||
void uncache_subtype(uint32_t subtype, uint32_t supertype) const {
|
||||
subtyping_cache->erase(std::make_pair(subtype, supertype));
|
||||
}
|
||||
bool is_cached_equivalent_type(uint32_t type1, uint32_t type2) const {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
return type_equivalence_cache->count(std::make_pair(type1, type2)) == 1;
|
||||
}
|
||||
void cache_type_equivalence(uint32_t type1, uint32_t type2) const {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
type_equivalence_cache->emplace(type1, type2);
|
||||
}
|
||||
void uncache_type_equivalence(uint32_t type1, uint32_t type2) const {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
type_equivalence_cache->erase(std::make_pair(type1, type2));
|
||||
}
|
||||
|
||||
std::vector<WasmFunction> functions;
|
||||
std::vector<WasmDataSegment> data_segments;
|
||||
@ -378,17 +355,6 @@ struct V8_EXPORT_PRIVATE WasmModule {
|
||||
explicit WasmModule(std::unique_ptr<Zone> signature_zone = nullptr);
|
||||
|
||||
private:
|
||||
// Cache for discovered subtyping pairs.
|
||||
std::unique_ptr<ZoneUnorderedSet<std::pair<uint32_t, uint32_t>>>
|
||||
subtyping_cache;
|
||||
// Cache for discovered equivalent type pairs.
|
||||
// Indexes are stored in increasing order.
|
||||
std::unique_ptr<ZoneUnorderedSet<std::pair<uint32_t, uint32_t>>>
|
||||
type_equivalence_cache;
|
||||
// The above two caches are used from background compile jobs, so they
|
||||
// must be protected from concurrent modifications:
|
||||
mutable base::RecursiveMutex type_cache_mutex_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WasmModule);
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "src/base/platform/mutex.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/zone/zone-containers.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -13,63 +14,148 @@ namespace wasm {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module);
|
||||
using CacheKey =
|
||||
std::tuple<uint32_t, uint32_t, const WasmModule*, const WasmModule*>;
|
||||
|
||||
bool IsArrayTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module) {
|
||||
if (module->type_kinds[type_index_1] != kWasmArrayTypeCode ||
|
||||
module->type_kinds[type_index_2] != kWasmArrayTypeCode) {
|
||||
return false;
|
||||
struct CacheKeyHasher {
|
||||
size_t operator()(CacheKey key) const {
|
||||
static constexpr size_t large_prime = 14887;
|
||||
return std::get<0>(key) + (std::get<1>(key) * large_prime) +
|
||||
(reinterpret_cast<size_t>(std::get<2>(key)) * large_prime *
|
||||
large_prime) +
|
||||
(reinterpret_cast<size_t>(std::get<3>(key)) * large_prime *
|
||||
large_prime * large_prime);
|
||||
}
|
||||
};
|
||||
|
||||
class TypeJudgementCache {
|
||||
public:
|
||||
TypeJudgementCache()
|
||||
: zone_(new AccountingAllocator(), "type judgement zone"),
|
||||
subtyping_cache_(&zone_),
|
||||
type_equivalence_cache_(&zone_) {}
|
||||
|
||||
static TypeJudgementCache* instance() {
|
||||
static base::LazyInstance<TypeJudgementCache>::type instance_ =
|
||||
LAZY_INSTANCE_INITIALIZER;
|
||||
return instance_.Pointer();
|
||||
}
|
||||
|
||||
const ArrayType* sub_array = module->types[type_index_1].array_type;
|
||||
const ArrayType* super_array = module->types[type_index_2].array_type;
|
||||
base::RecursiveMutex* type_cache_mutex() { return &type_cache_mutex_; }
|
||||
bool is_cached_subtype(uint32_t subtype, uint32_t supertype,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) const {
|
||||
return subtyping_cache_.count(std::make_tuple(
|
||||
subtype, supertype, sub_module, super_module)) == 1;
|
||||
}
|
||||
void cache_subtype(uint32_t subtype, uint32_t supertype,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
subtyping_cache_.emplace(subtype, supertype, sub_module, super_module);
|
||||
}
|
||||
void uncache_subtype(uint32_t subtype, uint32_t supertype,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
subtyping_cache_.erase(
|
||||
std::make_tuple(subtype, supertype, sub_module, super_module));
|
||||
}
|
||||
bool is_cached_equivalent_type(uint32_t type1, uint32_t type2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) const {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
if (reinterpret_cast<uintptr_t>(module1) >
|
||||
reinterpret_cast<uintptr_t>(module2)) {
|
||||
std::swap(module1, module2);
|
||||
}
|
||||
return type_equivalence_cache_.count(
|
||||
std::make_tuple(type1, type2, module1, module2)) == 1;
|
||||
}
|
||||
void cache_type_equivalence(uint32_t type1, uint32_t type2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
if (reinterpret_cast<uintptr_t>(module1) >
|
||||
reinterpret_cast<uintptr_t>(module2)) {
|
||||
std::swap(module1, module2);
|
||||
}
|
||||
type_equivalence_cache_.emplace(type1, type2, module1, module2);
|
||||
}
|
||||
void uncache_type_equivalence(uint32_t type1, uint32_t type2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
if (type1 > type2) std::swap(type1, type2);
|
||||
if (reinterpret_cast<uintptr_t>(module1) >
|
||||
reinterpret_cast<uintptr_t>(module2)) {
|
||||
std::swap(module1, module2);
|
||||
}
|
||||
type_equivalence_cache_.erase(
|
||||
std::make_tuple(type1, type2, module1, module2));
|
||||
}
|
||||
|
||||
private:
|
||||
Zone zone_;
|
||||
ZoneUnorderedSet<CacheKey, CacheKeyHasher>
|
||||
// Cache for discovered subtyping pairs.
|
||||
subtyping_cache_,
|
||||
// Cache for discovered equivalent type pairs.
|
||||
// Indexes and modules are stored in increasing order.
|
||||
type_equivalence_cache_;
|
||||
// The above two caches are used from background compile jobs, so they
|
||||
// must be protected from concurrent modifications:
|
||||
base::RecursiveMutex type_cache_mutex_;
|
||||
};
|
||||
|
||||
bool ArrayEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
const ArrayType* sub_array = module1->types[type_index_1].array_type;
|
||||
const ArrayType* super_array = module2->types[type_index_2].array_type;
|
||||
if (sub_array->mutability() != super_array->mutability()) return false;
|
||||
|
||||
// Temporarily cache type equivalence for the recursive call.
|
||||
module->cache_type_equivalence(type_index_1, type_index_2);
|
||||
if (IsEquivalent(sub_array->element_type(), super_array->element_type(),
|
||||
module)) {
|
||||
TypeJudgementCache::instance()->cache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
if (EquivalentTypes(sub_array->element_type(), super_array->element_type(),
|
||||
module1, module2)) {
|
||||
return true;
|
||||
} else {
|
||||
module->uncache_type_equivalence(type_index_1, type_index_2);
|
||||
TypeJudgementCache::instance()->uncache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
// TODO(7748): Consider caching negative results as well.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsStructTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module) {
|
||||
if (module->type_kinds[type_index_1] != kWasmStructTypeCode ||
|
||||
module->type_kinds[type_index_2] != kWasmStructTypeCode) {
|
||||
return false;
|
||||
}
|
||||
const StructType* sub_struct = module->types[type_index_1].struct_type;
|
||||
const StructType* super_struct = module->types[type_index_2].struct_type;
|
||||
bool StructEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
const StructType* sub_struct = module1->types[type_index_1].struct_type;
|
||||
const StructType* super_struct = module2->types[type_index_2].struct_type;
|
||||
|
||||
if (sub_struct->field_count() != super_struct->field_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Temporarily cache type equivalence for the recursive call.
|
||||
module->cache_type_equivalence(type_index_1, type_index_2);
|
||||
TypeJudgementCache::instance()->cache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
for (uint32_t i = 0; i < sub_struct->field_count(); i++) {
|
||||
if (sub_struct->mutability(i) != super_struct->mutability(i) ||
|
||||
!IsEquivalent(sub_struct->field(i), super_struct->field(i), module)) {
|
||||
module->uncache_type_equivalence(type_index_1, type_index_2);
|
||||
!EquivalentTypes(sub_struct->field(i), super_struct->field(i), module1,
|
||||
module2)) {
|
||||
TypeJudgementCache::instance()->uncache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool IsFunctionTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module) {
|
||||
if (module->type_kinds[type_index_1] != kWasmFunctionTypeCode ||
|
||||
module->type_kinds[type_index_2] != kWasmFunctionTypeCode) {
|
||||
return false;
|
||||
}
|
||||
const FunctionSig* sig1 = module->types[type_index_1].function_sig;
|
||||
const FunctionSig* sig2 = module->types[type_index_2].function_sig;
|
||||
|
||||
bool FunctionEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
const FunctionSig* sig1 = module1->types[type_index_1].function_sig;
|
||||
const FunctionSig* sig2 = module2->types[type_index_2].function_sig;
|
||||
|
||||
if (sig1->parameter_count() != sig2->parameter_count() ||
|
||||
sig1->return_count() != sig2->return_count()) {
|
||||
@ -80,138 +166,206 @@ bool IsFunctionTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
|
||||
auto iter2 = sig2->all();
|
||||
|
||||
// Temporarily cache type equivalence for the recursive call.
|
||||
module->cache_type_equivalence(type_index_1, type_index_2);
|
||||
TypeJudgementCache::instance()->cache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
for (int i = 0; i < iter1.size(); i++) {
|
||||
if (iter1[i] != iter2[i]) {
|
||||
module->uncache_type_equivalence(type_index_1, type_index_2);
|
||||
if (!EquivalentTypes(iter1[i], iter2[i], module1, module2)) {
|
||||
TypeJudgementCache::instance()->uncache_type_equivalence(
|
||||
type_index_1, type_index_2, module1, module2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module) {
|
||||
if (type1 == type2) return true;
|
||||
if (type1.kind() != type2.kind()) return false;
|
||||
// At this point, the types can only be both rtts, refs, or optrefs,
|
||||
// but with different indexed types.
|
||||
V8_INLINE bool EquivalentIndices(uint32_t index1, uint32_t index2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2) {
|
||||
DCHECK(index1 != index2 || module1 != module2);
|
||||
uint8_t kind1 = module1->type_kinds[index1];
|
||||
|
||||
// Rtts need to have the same depth.
|
||||
if (type1.has_depth() && type1.depth() != type2.depth()) return false;
|
||||
// In all three cases, the indexed types have to be equivalent.
|
||||
if (module->is_cached_equivalent_type(type1.ref_index(), type2.ref_index())) {
|
||||
if (kind1 != module2->type_kinds[index2]) return false;
|
||||
|
||||
base::RecursiveMutexGuard type_cache_access(
|
||||
TypeJudgementCache::instance()->type_cache_mutex());
|
||||
if (TypeJudgementCache::instance()->is_cached_equivalent_type(
|
||||
index1, index2, module1, module2)) {
|
||||
return true;
|
||||
}
|
||||
return IsArrayTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
|
||||
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
|
||||
IsFunctionTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
|
||||
|
||||
if (kind1 == kWasmStructTypeCode) {
|
||||
return StructEquivalentIndices(index1, index2, module1, module2);
|
||||
} else if (kind1 == kWasmArrayTypeCode) {
|
||||
return ArrayEquivalentIndices(index1, index2, module1, module2);
|
||||
} else {
|
||||
DCHECK_EQ(kind1, kWasmFunctionTypeCode);
|
||||
return FunctionEquivalentIndices(index1, index2, module1, module2);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsStructSubtype(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* module) {
|
||||
if (module->type_kinds[subtype_index] != kWasmStructTypeCode ||
|
||||
module->type_kinds[supertype_index] != kWasmStructTypeCode) {
|
||||
return false;
|
||||
}
|
||||
const StructType* sub_struct = module->types[subtype_index].struct_type;
|
||||
const StructType* super_struct = module->types[supertype_index].struct_type;
|
||||
bool StructIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
const StructType* sub_struct = sub_module->types[subtype_index].struct_type;
|
||||
const StructType* super_struct =
|
||||
super_module->types[supertype_index].struct_type;
|
||||
|
||||
if (sub_struct->field_count() < super_struct->field_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
module->cache_subtype(subtype_index, supertype_index);
|
||||
TypeJudgementCache::instance()->cache_subtype(subtype_index, supertype_index,
|
||||
sub_module, super_module);
|
||||
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 &&
|
||||
!IsEquivalent(sub_struct->field(i), super_struct->field(i), module)) ||
|
||||
(!sub_mut &&
|
||||
!IsSubtypeOf(sub_struct->field(i), super_struct->field(i), module))) {
|
||||
module->uncache_subtype(subtype_index, supertype_index);
|
||||
!EquivalentTypes(sub_struct->field(i), super_struct->field(i),
|
||||
sub_module, super_module)) ||
|
||||
(!sub_mut && !IsSubtypeOf(sub_struct->field(i), super_struct->field(i),
|
||||
sub_module, super_module))) {
|
||||
TypeJudgementCache::instance()->uncache_subtype(
|
||||
subtype_index, supertype_index, sub_module, super_module);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsArraySubtype(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* module) {
|
||||
if (module->type_kinds[subtype_index] != kWasmArrayTypeCode ||
|
||||
module->type_kinds[supertype_index] != kWasmArrayTypeCode) {
|
||||
return false;
|
||||
}
|
||||
const ArrayType* sub_array = module->types[subtype_index].array_type;
|
||||
const ArrayType* super_array = module->types[supertype_index].array_type;
|
||||
bool ArrayIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
const ArrayType* sub_array = sub_module->types[subtype_index].array_type;
|
||||
const ArrayType* super_array =
|
||||
super_module->types[supertype_index].array_type;
|
||||
bool sub_mut = sub_array->mutability();
|
||||
bool super_mut = super_array->mutability();
|
||||
module->cache_subtype(subtype_index, supertype_index);
|
||||
TypeJudgementCache::instance()->cache_subtype(subtype_index, supertype_index,
|
||||
sub_module, super_module);
|
||||
if (sub_mut != super_mut ||
|
||||
(sub_mut && !IsEquivalent(sub_array->element_type(),
|
||||
super_array->element_type(), module)) ||
|
||||
(!sub_mut && !IsSubtypeOf(sub_array->element_type(),
|
||||
super_array->element_type(), module))) {
|
||||
module->uncache_subtype(subtype_index, supertype_index);
|
||||
(sub_mut &&
|
||||
!EquivalentTypes(sub_array->element_type(), super_array->element_type(),
|
||||
sub_module, super_module)) ||
|
||||
(!sub_mut &&
|
||||
!IsSubtypeOf(sub_array->element_type(), super_array->element_type(),
|
||||
sub_module, super_module))) {
|
||||
TypeJudgementCache::instance()->uncache_subtype(
|
||||
subtype_index, supertype_index, sub_module, super_module);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(7748): Expand this with function subtyping.
|
||||
bool IsFunctionSubtype(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* module) {
|
||||
return IsFunctionTypeEquivalent(subtype_index, supertype_index, module);
|
||||
// TODO(7748): Expand this with function subtyping once the hiccups
|
||||
// with 'exact types' have been cleared.
|
||||
bool FunctionIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
return FunctionEquivalentIndices(subtype_index, supertype_index, sub_module,
|
||||
super_module);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(7748): Extend this with any-heap subtyping.
|
||||
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
|
||||
ValueType supertype,
|
||||
const WasmModule* module) {
|
||||
bool compatible_references = (subtype.kind() == ValueType::kOptRef &&
|
||||
supertype.kind() == ValueType::kOptRef) ||
|
||||
(subtype.kind() == ValueType::kRef &&
|
||||
(supertype.kind() == ValueType::kOptRef ||
|
||||
supertype.kind() == ValueType::kRef));
|
||||
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
|
||||
ValueType subtype, ValueType supertype, const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
DCHECK(subtype != supertype || sub_module != super_module);
|
||||
|
||||
// This function checks for subtyping based on the kind of subtype.
|
||||
|
||||
if (!subtype.is_reference_type()) return subtype == supertype;
|
||||
|
||||
if (subtype.kind() == ValueType::kRtt) {
|
||||
return subtype.heap_type().is_generic()
|
||||
? subtype == supertype
|
||||
: (supertype.kind() == ValueType::kRtt &&
|
||||
subtype.depth() == supertype.depth() &&
|
||||
EquivalentIndices(subtype.ref_index(), supertype.ref_index(),
|
||||
sub_module, super_module));
|
||||
}
|
||||
|
||||
DCHECK(subtype.is_object_reference_type());
|
||||
|
||||
bool compatible_references = subtype.kind() == ValueType::kOptRef
|
||||
? supertype.kind() == ValueType::kOptRef
|
||||
: supertype.is_object_reference_type();
|
||||
if (!compatible_references) return false;
|
||||
|
||||
DCHECK(supertype.is_object_reference_type());
|
||||
|
||||
// Now check that sub_heap and super_heap are subtype-related.
|
||||
|
||||
HeapType sub_heap = subtype.heap_type();
|
||||
HeapType super_heap = supertype.heap_type();
|
||||
|
||||
if (sub_heap == super_heap) {
|
||||
if (sub_heap.representation() == HeapType::kI31 &&
|
||||
super_heap.representation() == HeapType::kEq) {
|
||||
return true;
|
||||
}
|
||||
// eqref is a supertype of i31ref, array, and struct types.
|
||||
if (sub_heap.is_generic()) return sub_heap == super_heap;
|
||||
|
||||
DCHECK(sub_heap.is_index());
|
||||
uint32_t sub_index = sub_heap.ref_index();
|
||||
DCHECK(sub_module->has_type(sub_index));
|
||||
|
||||
if (super_heap.representation() == HeapType::kEq) {
|
||||
return (sub_heap.is_index() &&
|
||||
!module->has_signature(sub_heap.ref_index())) ||
|
||||
sub_heap.representation() == HeapType::kI31;
|
||||
return !sub_module->has_signature(sub_heap.ref_index());
|
||||
}
|
||||
|
||||
// funcref is a supertype of all function types.
|
||||
if (super_heap.representation() == HeapType::kFunc) {
|
||||
return sub_heap.is_index() && module->has_signature(sub_heap.ref_index());
|
||||
return sub_module->has_signature(sub_heap.ref_index());
|
||||
}
|
||||
if (super_heap.is_generic()) return false;
|
||||
|
||||
// At the moment, generic heap types are not subtyping-related otherwise.
|
||||
if (sub_heap.is_generic() || super_heap.is_generic()) {
|
||||
return false;
|
||||
}
|
||||
DCHECK(super_heap.is_index());
|
||||
uint32_t super_index = super_heap.ref_index();
|
||||
DCHECK(super_module->has_type(super_index));
|
||||
|
||||
uint8_t sub_kind = sub_module->type_kinds[sub_index];
|
||||
|
||||
if (sub_kind != super_module->type_kinds[super_index]) return false;
|
||||
|
||||
// Accessing the caches for subtyping and equivalence from multiple background
|
||||
// threads is protected by a lock.
|
||||
base::RecursiveMutexGuard type_cache_access(module->type_cache_mutex());
|
||||
if (module->is_cached_subtype(sub_heap.ref_index(), super_heap.ref_index())) {
|
||||
base::RecursiveMutexGuard type_cache_access(
|
||||
TypeJudgementCache::instance()->type_cache_mutex());
|
||||
if (TypeJudgementCache::instance()->is_cached_subtype(
|
||||
sub_index, super_index, sub_module, super_module)) {
|
||||
return true;
|
||||
}
|
||||
return IsStructSubtype(sub_heap.ref_index(), super_heap.ref_index(),
|
||||
module) ||
|
||||
IsArraySubtype(sub_heap.ref_index(), super_heap.ref_index(), module) ||
|
||||
IsFunctionSubtype(sub_heap.ref_index(), super_heap.ref_index(),
|
||||
module);
|
||||
|
||||
if (sub_kind == kWasmStructTypeCode) {
|
||||
return StructIsSubtypeOf(sub_index, super_index, sub_module, super_module);
|
||||
} else if (sub_kind == kWasmArrayTypeCode) {
|
||||
return ArrayIsSubtypeOf(sub_index, super_index, sub_module, super_module);
|
||||
} else {
|
||||
DCHECK_EQ(sub_kind, kWasmFunctionTypeCode);
|
||||
return FunctionIsSubtypeOf(sub_index, super_index, sub_module,
|
||||
super_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()) return type1 == type2;
|
||||
if (type1.kind() != type2.kind()) return false;
|
||||
|
||||
DCHECK(type1.has_index() && type2.has_index() &&
|
||||
(type1 != type2 || module1 != module2));
|
||||
|
||||
DCHECK_IMPLIES(type1.has_depth(), type2.has_depth()); // Due to 'if' above
|
||||
if (type1.has_depth() && type1.depth() != type2.depth()) return false;
|
||||
|
||||
DCHECK(type1.has_index() && module1->has_type(type1.ref_index()) &&
|
||||
type2.has_index() && module2->has_type(type2.ref_index()));
|
||||
|
||||
return EquivalentIndices(type1.ref_index(), type2.ref_index(), module1,
|
||||
module2);
|
||||
}
|
||||
|
||||
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module) {
|
||||
|
@ -12,19 +12,32 @@ namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
struct WasmModule;
|
||||
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
|
||||
ValueType supertype,
|
||||
const WasmModule* module);
|
||||
|
||||
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
|
||||
ValueType subtype, ValueType supertype, const WasmModule* sub_module,
|
||||
const WasmModule* super_module);
|
||||
|
||||
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
|
||||
const WasmModule* module1,
|
||||
const WasmModule* module2);
|
||||
|
||||
// The subtyping between value types is described by the following rules:
|
||||
// - All types are a supertype of bottom.
|
||||
// - All reference types, except funcref, are subtypes of eqref.
|
||||
// - optref(ht1) <: optref(ht2) iff ht1 <: ht2.
|
||||
// - ref(ht1) <: ref/optref(ht2) iff ht1 <: ht2.
|
||||
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
|
||||
const WasmModule* sub_module,
|
||||
const WasmModule* super_module) {
|
||||
if (subtype == supertype && sub_module == super_module) return true;
|
||||
return IsSubtypeOfImpl(subtype, supertype, sub_module, super_module);
|
||||
}
|
||||
|
||||
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
|
||||
const WasmModule* module) {
|
||||
// If the types are trivially identical, exit early.
|
||||
if (V8_LIKELY(subtype == supertype)) return true;
|
||||
return IsSubtypeOfImpl(subtype, supertype, module);
|
||||
return IsSubtypeOfImpl(subtype, supertype, module, module);
|
||||
}
|
||||
|
||||
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module);
|
||||
|
@ -35,20 +35,25 @@ void DefineArray(WasmModule* module, FieldInit element_type) {
|
||||
|
||||
TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
v8::internal::AccountingAllocator allocator;
|
||||
WasmModule module_(std::make_unique<Zone>(&allocator, ZONE_NAME));
|
||||
WasmModule module1_(std::make_unique<Zone>(&allocator, ZONE_NAME));
|
||||
WasmModule module2_(std::make_unique<Zone>(&allocator, ZONE_NAME));
|
||||
|
||||
WasmModule* module = &module_;
|
||||
WasmModule* module1 = &module1_;
|
||||
WasmModule* module2 = &module2_;
|
||||
|
||||
/* 0 */ DefineStruct(module, {mut(ref(2)), immut(optRef(2))});
|
||||
/* 1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))});
|
||||
/* 2 */ DefineArray(module, immut(ref(0)));
|
||||
/* 3 */ DefineArray(module, immut(ref(1)));
|
||||
/* 4 */ DefineStruct(module, {mut(ref(2)), immut(ref(3)), immut(kWasmF64)});
|
||||
/* 5 */ DefineStruct(module, {mut(optRef(2)), immut(ref(2))});
|
||||
/* 6 */ DefineArray(module, mut(kWasmI32));
|
||||
/* 7 */ DefineArray(module, immut(kWasmI32));
|
||||
/* 8 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
|
||||
/* 9 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
|
||||
// Set up two identical modules.
|
||||
for (WasmModule* module : {module1, module2}) {
|
||||
/* 0 */ DefineStruct(module, {mut(ref(2)), immut(optRef(2))});
|
||||
/* 1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))});
|
||||
/* 2 */ DefineArray(module, immut(ref(0)));
|
||||
/* 3 */ DefineArray(module, immut(ref(1)));
|
||||
/* 4 */ DefineStruct(module, {mut(ref(2)), immut(ref(3)), immut(kWasmF64)});
|
||||
/* 5 */ DefineStruct(module, {mut(optRef(2)), immut(ref(2))});
|
||||
/* 6 */ DefineArray(module, mut(kWasmI32));
|
||||
/* 7 */ DefineArray(module, immut(kWasmI32));
|
||||
/* 8 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
|
||||
/* 9 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
|
||||
}
|
||||
|
||||
ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
|
||||
kWasmS128};
|
||||
@ -56,65 +61,87 @@ TEST_F(WasmSubtypingTest, Subtyping) {
|
||||
kWasmEqRef, kWasmI31Ref, optRef(0),
|
||||
ref(0), optRef(2), ref(2)};
|
||||
|
||||
// Value types are unrelated, except if they are equal.
|
||||
for (ValueType subtype : numeric_types) {
|
||||
for (ValueType supertype : numeric_types) {
|
||||
CHECK_EQ(IsSubtypeOf(subtype, supertype, module), subtype == supertype);
|
||||
// Type judgements across modules should work the same as within one module.
|
||||
for (WasmModule* module : {module1, module2}) {
|
||||
// Value types are unrelated, except if they are equal.
|
||||
for (ValueType subtype : numeric_types) {
|
||||
for (ValueType supertype : numeric_types) {
|
||||
CHECK_EQ(IsSubtypeOf(subtype, supertype, module1, module),
|
||||
subtype == supertype);
|
||||
}
|
||||
}
|
||||
|
||||
// Value types are unrelated with reference types.
|
||||
for (ValueType value_type : numeric_types) {
|
||||
for (ValueType ref_type : ref_types) {
|
||||
CHECK(!IsSubtypeOf(value_type, ref_type, module1, module));
|
||||
CHECK(!IsSubtypeOf(ref_type, value_type, module1, module));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value types are unrelated with reference types.
|
||||
for (ValueType value_type : numeric_types) {
|
||||
for (ValueType ref_type : ref_types) {
|
||||
CHECK(!IsSubtypeOf(value_type, ref_type, module));
|
||||
CHECK(!IsSubtypeOf(ref_type, value_type, module));
|
||||
// Concrete reference types and i31ref are subtypes of eqref,
|
||||
// exnref/externref/funcref are not.
|
||||
CHECK_EQ(IsSubtypeOf(ref_type, kWasmEqRef, module1, module),
|
||||
ref_type != kWasmFuncRef && ref_type != kWasmExternRef &&
|
||||
ref_type != kWasmExnRef);
|
||||
// Each reference type is a subtype of itself.
|
||||
CHECK(IsSubtypeOf(ref_type, ref_type, module1, module));
|
||||
}
|
||||
}
|
||||
|
||||
for (ValueType ref_type : ref_types) {
|
||||
// Concrete reference types and i31ref are subtypes of eqref,
|
||||
// exnref/externref/funcref are not.
|
||||
CHECK_EQ(IsSubtypeOf(ref_type, kWasmEqRef, module),
|
||||
ref_type != kWasmFuncRef && ref_type != kWasmExternRef &&
|
||||
ref_type != kWasmExnRef);
|
||||
// Each reference type is a subtype of itself.
|
||||
CHECK(IsSubtypeOf(ref_type, ref_type, module));
|
||||
}
|
||||
|
||||
// The rest of ref. types are unrelated.
|
||||
for (ValueType type_1 :
|
||||
{kWasmExternRef, kWasmFuncRef, kWasmExnRef, kWasmI31Ref}) {
|
||||
for (ValueType type_2 :
|
||||
// The rest of ref. types are unrelated.
|
||||
for (ValueType type_1 :
|
||||
{kWasmExternRef, kWasmFuncRef, kWasmExnRef, kWasmI31Ref}) {
|
||||
CHECK_EQ(IsSubtypeOf(type_1, type_2, module), type_1 == type_2);
|
||||
for (ValueType type_2 :
|
||||
{kWasmExternRef, kWasmFuncRef, kWasmExnRef, kWasmI31Ref}) {
|
||||
CHECK_EQ(IsSubtypeOf(type_1, type_2, module1, module),
|
||||
type_1 == type_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unrelated refs are unrelated.
|
||||
CHECK(!IsSubtypeOf(ref(0), ref(2), module));
|
||||
CHECK(!IsSubtypeOf(optRef(3), optRef(1), module));
|
||||
// ref is a subtype of optref for the same struct/array.
|
||||
CHECK(IsSubtypeOf(ref(0), optRef(0), module));
|
||||
CHECK(IsSubtypeOf(ref(2), optRef(2), module));
|
||||
// optref is not a subtype of ref for the same struct/array.
|
||||
CHECK(!IsSubtypeOf(optRef(0), ref(0), module));
|
||||
CHECK(!IsSubtypeOf(optRef(2), ref(2), module));
|
||||
// ref is a subtype of optref if the same is true for the underlying
|
||||
// structs/arrays.
|
||||
CHECK(IsSubtypeOf(ref(3), optRef(2), module));
|
||||
// Prefix subtyping for structs.
|
||||
CHECK(IsSubtypeOf(optRef(4), optRef(0), module));
|
||||
// Mutable fields are invariant.
|
||||
CHECK(!IsSubtypeOf(ref(0), ref(5), module));
|
||||
// Immutable fields are covariant.
|
||||
CHECK(IsSubtypeOf(ref(1), ref(0), module));
|
||||
// Prefix subtyping + immutable field covariance for structs.
|
||||
CHECK(IsSubtypeOf(optRef(4), optRef(1), module));
|
||||
// No subtyping between mutable/immutable fields.
|
||||
CHECK(!IsSubtypeOf(ref(7), ref(6), module));
|
||||
CHECK(!IsSubtypeOf(ref(6), ref(7), module));
|
||||
// Recursive types.
|
||||
CHECK(IsSubtypeOf(ref(9), ref(8), module));
|
||||
// Unrelated refs are unrelated.
|
||||
CHECK(!IsSubtypeOf(ref(0), ref(2), module1, module));
|
||||
CHECK(!IsSubtypeOf(optRef(3), optRef(1), module1, module));
|
||||
// ref is a subtype of optref for the same struct/array.
|
||||
CHECK(IsSubtypeOf(ref(0), optRef(0), module1, module));
|
||||
CHECK(IsSubtypeOf(ref(2), optRef(2), module1, module));
|
||||
// optref is not a subtype of ref for the same struct/array.
|
||||
CHECK(!IsSubtypeOf(optRef(0), ref(0), module1, module));
|
||||
CHECK(!IsSubtypeOf(optRef(2), ref(2), module1, module));
|
||||
// ref is a subtype of optref if the same is true for the underlying
|
||||
// structs/arrays.
|
||||
CHECK(IsSubtypeOf(ref(3), optRef(2), module1, module));
|
||||
// Prefix subtyping for structs.
|
||||
CHECK(IsSubtypeOf(optRef(4), optRef(0), module1, module));
|
||||
// Mutable fields are invariant.
|
||||
CHECK(!IsSubtypeOf(ref(0), ref(5), module1, module));
|
||||
// Immutable fields are covariant.
|
||||
CHECK(IsSubtypeOf(ref(1), ref(0), module1, module));
|
||||
// Prefix subtyping + immutable field covariance for structs.
|
||||
CHECK(IsSubtypeOf(optRef(4), optRef(1), module1, module));
|
||||
// No subtyping between mutable/immutable fields.
|
||||
CHECK(!IsSubtypeOf(ref(7), ref(6), module1, module));
|
||||
CHECK(!IsSubtypeOf(ref(6), ref(7), module1, module));
|
||||
// Recursive types.
|
||||
CHECK(IsSubtypeOf(ref(9), ref(8), module1, module));
|
||||
|
||||
// Identical rtts are subtypes of each other.
|
||||
CHECK(IsSubtypeOf(ValueType::Rtt(5, 3), ValueType::Rtt(5, 3), module1,
|
||||
module2));
|
||||
CHECK(IsSubtypeOf(ValueType::Rtt(HeapType::kExn, 3),
|
||||
ValueType::Rtt(HeapType::kExn, 3), module1, module2));
|
||||
// Rtts of different depth are unrelated.
|
||||
CHECK(!IsSubtypeOf(ValueType::Rtt(5, 1), ValueType::Rtt(5, 3), module1,
|
||||
module2));
|
||||
CHECK(!IsSubtypeOf(ValueType::Rtt(5, 8), ValueType::Rtt(5, 3), module1,
|
||||
module2));
|
||||
// Rtts of identical types are subtype-related.
|
||||
CHECK(IsSubtypeOf(ValueType::Rtt(8, 1), ValueType::Rtt(9, 1), module1,
|
||||
module));
|
||||
// Rtts of subtypes are not related.
|
||||
CHECK(!IsSubtypeOf(ValueType::Rtt(1, 1), ValueType::Rtt(0, 1), module1,
|
||||
module));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace subtyping_unittest
|
||||
|
Loading…
Reference in New Issue
Block a user