Commit 72dffedb authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[wasm-gc] Refactor wasm subtyping, extend it to struct/array types.

Changes:
- Remove subtyping checks from value-type.h and move them to dedicated
  files. Leave a limited version in value-type.h for testing.
- Implement subtyping for struct and array types, according to the
  wasm-gc proposal.
- Implement type equivalence checking.
- Introduce a subtyping relation cache in WasmModule.
- Rename IsSubTypeOf -> IsSubtypeOf.
- Fix v8 possible bug where iterator_range took two unused type
  parameters.
- Add unittests for subtyping.

Bug: v8:7748
Change-Id: I0ddbda4145e0412196dcf4fc63f3c5875fb3ab5a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2228497
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68192}
parent b8883649
...@@ -3201,6 +3201,8 @@ v8_source_set("v8_base_without_compiler") { ...@@ -3201,6 +3201,8 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/wasm-result.h", "src/wasm/wasm-result.h",
"src/wasm/wasm-serialization.cc", "src/wasm/wasm-serialization.cc",
"src/wasm/wasm-serialization.h", "src/wasm/wasm-serialization.h",
"src/wasm/wasm-subtyping.cc",
"src/wasm/wasm-subtyping.h",
"src/wasm/wasm-tier.h", "src/wasm/wasm-tier.h",
"src/wasm/wasm-value.h", "src/wasm/wasm-value.h",
"src/zone/accounting-allocator.cc", "src/zone/accounting-allocator.cc",
......
...@@ -36,8 +36,7 @@ class iterator_range { ...@@ -36,8 +36,7 @@ class iterator_range {
typename std::iterator_traits<iterator>::difference_type; typename std::iterator_traits<iterator>::difference_type;
iterator_range() : begin_(), end_() {} iterator_range() : begin_(), end_() {}
template <typename ForwardIterator1, typename ForwardIterator2> iterator_range(ForwardIterator begin, ForwardIterator end)
iterator_range(ForwardIterator1 begin, ForwardIterator2 end)
: begin_(begin), end_(end) {} : begin_(begin), end_(end) {}
iterator begin() { return begin_; } iterator begin() { return begin_; }
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h" #include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h" #include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-subtyping.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -1381,8 +1382,8 @@ class WasmDecoder : public Decoder { ...@@ -1381,8 +1382,8 @@ class WasmDecoder : public Decoder {
return false; return false;
} }
ValueType elem_type = module_->elem_segments[imm.elem_segment_index].type; ValueType elem_type = module_->elem_segments[imm.elem_segment_index].type;
if (!VALIDATE( if (!VALIDATE(IsSubtypeOf(elem_type, module_->tables[imm.table.index].type,
elem_type.IsSubTypeOf(module_->tables[imm.table.index].type))) { module_))) {
errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table.index, errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table.index,
elem_type.type_name()); elem_type.type_name());
return false; return false;
...@@ -1402,8 +1403,8 @@ class WasmDecoder : public Decoder { ...@@ -1402,8 +1403,8 @@ class WasmDecoder : public Decoder {
if (!Validate(pc_ + 1, imm.table_src)) return false; if (!Validate(pc_ + 1, imm.table_src)) return false;
if (!Validate(pc_ + 2, imm.table_dst)) return false; if (!Validate(pc_ + 2, imm.table_dst)) return false;
ValueType src_type = module_->tables[imm.table_src.index].type; ValueType src_type = module_->tables[imm.table_src.index].type;
if (!VALIDATE( if (!VALIDATE(IsSubtypeOf(
src_type.IsSubTypeOf(module_->tables[imm.table_dst.index].type))) { src_type, module_->tables[imm.table_dst.index].type, module_))) {
errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table_dst.index, errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table_dst.index,
src_type.type_name()); src_type.type_name());
return false; return false;
...@@ -3017,7 +3018,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3017,7 +3018,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// The expected type is the biggest common sub type of all targets. // The expected type is the biggest common sub type of all targets.
ValueType type = (*result_types)[i]; ValueType type = (*result_types)[i];
(*result_types)[i] = (*result_types)[i] =
ValueType::CommonSubType((*result_types)[i], (*merge)[i].type); CommonSubtype((*result_types)[i], (*merge)[i].type, this->module_);
if ((*result_types)[i] == kWasmBottom) { if ((*result_types)[i] == kWasmBottom) {
this->errorf(pos, this->errorf(pos,
"inconsistent type in br_table target %u (previous " "inconsistent type in br_table target %u (previous "
...@@ -3057,7 +3058,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3057,7 +3058,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// Type-check the topmost br_arity values on the stack. // Type-check the topmost br_arity values on the stack.
for (int i = 0; i < br_arity; ++i) { for (int i = 0; i < br_arity; ++i) {
Value& val = stack_values[i]; Value& val = stack_values[i];
if (!val.type.IsSubTypeOf(result_types[i])) { if (!IsSubtypeOf(val.type, result_types[i], this->module_)) {
this->errorf(this->pc_, this->errorf(this->pc_,
"type error in merge[%u] (expected %s, got %s)", i, "type error in merge[%u] (expected %s, got %s)", i,
result_types[i].type_name(), val.type.type_name()); result_types[i].type_name(), val.type.type_name());
...@@ -3543,8 +3544,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3543,8 +3544,8 @@ class WasmFullDecoder : public WasmDecoder<validate> {
V8_INLINE Value Pop(int index, ValueType expected) { V8_INLINE Value Pop(int index, ValueType expected) {
Value val = Pop(); Value val = Pop();
if (!VALIDATE(val.type.IsSubTypeOf(expected) || val.type == kWasmBottom || if (!VALIDATE(IsSubtypeOf(val.type, expected, this->module_) ||
expected == kWasmBottom)) { val.type == kWasmBottom || expected == kWasmBottom)) {
this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s", this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s",
SafeOpcodeNameAt(this->pc_), index, expected.type_name(), SafeOpcodeNameAt(this->pc_), index, expected.type_name(),
SafeOpcodeNameAt(val.pc), val.type.type_name()); SafeOpcodeNameAt(val.pc), val.type.type_name());
...@@ -3607,7 +3608,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3607,7 +3608,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
for (uint32_t i = 0; i < merge->arity; ++i) { for (uint32_t i = 0; i < merge->arity; ++i) {
Value& val = stack_values[i]; Value& val = stack_values[i];
Value& old = (*merge)[i]; Value& old = (*merge)[i];
if (!val.type.IsSubTypeOf(old.type)) { if (!IsSubtypeOf(val.type, old.type, this->module_)) {
this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)",
i, old.type.type_name(), val.type.type_name()); i, old.type.type_name(), val.type.type_name());
return false; return false;
...@@ -3624,7 +3625,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3624,7 +3625,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
for (uint32_t i = 0; i < c->start_merge.arity; ++i) { for (uint32_t i = 0; i < c->start_merge.arity; ++i) {
Value& start = c->start_merge[i]; Value& start = c->start_merge[i];
Value& end = c->end_merge[i]; Value& end = c->end_merge[i];
if (!start.type.IsSubTypeOf(end.type)) { if (!IsSubtypeOf(start.type, end.type, this->module_)) {
this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)",
i, end.type.type_name(), start.type.type_name()); i, end.type.type_name(), start.type.type_name());
return false; return false;
...@@ -3728,7 +3729,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -3728,7 +3729,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
for (int i = 0; i < num_returns; ++i) { for (int i = 0; i < num_returns; ++i) {
Value& val = stack_values[i]; Value& val = stack_values[i];
ValueType expected_type = this->sig_->GetReturn(i); ValueType expected_type = this->sig_->GetReturn(i);
if (!val.type.IsSubTypeOf(expected_type)) { if (!IsSubtypeOf(val.type, expected_type, this->module_)) {
this->errorf(this->pc_, this->errorf(this->pc_,
"type error in return[%u] (expected %s, got %s)", i, "type error in return[%u] (expected %s, got %s)", i,
expected_type.type_name(), val.type.type_name()); expected_type.type_name(), val.type.type_name());
......
...@@ -925,7 +925,8 @@ class ModuleDecoderImpl : public Decoder { ...@@ -925,7 +925,8 @@ class ModuleDecoderImpl : public Decoder {
errorf(pos, "out of bounds table index %u", table_index); errorf(pos, "out of bounds table index %u", table_index);
break; break;
} }
if (!type.IsSubTypeOf(module_->tables[table_index].type)) { if (!IsSubtypeOf(type, module_->tables[table_index].type,
this->module_.get())) {
errorf(pos, errorf(pos,
"Invalid element segment. Table %u is not a super-type of %s", "Invalid element segment. Table %u is not a super-type of %s",
table_index, type.type_name()); table_index, type.type_name());
...@@ -1671,7 +1672,8 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1671,7 +1672,8 @@ class ModuleDecoderImpl : public Decoder {
} }
expr.kind = WasmInitExpr::kRefNullConst; expr.kind = WasmInitExpr::kRefNullConst;
len = imm.length; len = imm.length;
if (expected != kWasmStmt && !imm.type.IsSubTypeOf(expected)) { if (expected != kWasmStmt &&
!IsSubtypeOf(imm.type, expected, module_.get())) {
errorf(pos, "type error in init expression, expected %s, got %s", errorf(pos, "type error in init expression, expected %s, got %s",
expected.type_name(), imm.type.type_name()); expected.type_name(), imm.type.type_name());
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "src/wasm/wasm-import-wrapper-cache.h" #include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-module.h" #include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-subtyping.h"
#define TRACE(...) \ #define TRACE(...) \
do { \ do { \
...@@ -1122,7 +1123,8 @@ bool InstanceBuilder::ProcessImportedWasmGlobalObject( ...@@ -1122,7 +1123,8 @@ bool InstanceBuilder::ProcessImportedWasmGlobalObject(
return false; return false;
} }
bool is_sub_type = global_object->type().IsSubTypeOf(global.type); bool is_sub_type =
IsSubtypeOf(global_object->type(), global.type, instance->module());
bool is_same_type = global_object->type() == global.type; bool is_same_type = global_object->type() == global.type;
bool valid_type = global.mutability ? is_same_type : is_sub_type; bool valid_type = global.mutability ? is_same_type : is_sub_type;
......
...@@ -20,20 +20,13 @@ namespace wasm { ...@@ -20,20 +20,13 @@ namespace wasm {
// Type for holding simd values, defined in wasm-value.h. // Type for holding simd values, defined in wasm-value.h.
class Simd128; class Simd128;
// Type lattice: Given a fixed struct type S, the following lattice // The subtyping between value types is described by the following rules:
// defines the subtyping relation among types: // - All types are a supertype of bottom.
// For every two types connected by a line, the top type is a // - All reference types, except funcref, are subtypes of eqref.
// (direct) subtype of the bottom type. // - optref(t1) <: optref(t2) iff t1 <: t2.
// - ref(t1) <: optref(t2) iff t1 <: t2.
// = ref(t1) <: ref(t2) iff t1 <: t2.
// //
// AnyRef
// / \
// / EqRef
// / / \
// FuncRef ExnRef OptRef(S)
// \ | / \
// I32 I64 F32 F64 NullRef Ref(S)
// \ \ \ \ | /
// ---------------------- Bottom ---------
// Format: kind, log2Size, code, machineType, shortName, typeName // Format: kind, log2Size, code, machineType, shortName, typeName
// //
// Some of these types are from proposals that are not standardized yet: // Some of these types are from proposals that are not standardized yet:
...@@ -120,12 +113,12 @@ class ValueType { ...@@ -120,12 +113,12 @@ class ValueType {
// TODO(7748): Extend this with struct and function subtyping. // TODO(7748): Extend this with struct and function subtyping.
// Keep up to date with funcref vs. anyref subtyping. // Keep up to date with funcref vs. anyref subtyping.
constexpr bool IsSubTypeOf(ValueType other) const { constexpr bool IsSubtypeOfNoImmediates(ValueType other) const {
#if V8_HAS_CXX14_CONSTEXPR
DCHECK(!has_immediate() && !other.has_immediate());
#endif
return (*this == other) || return (*this == other) ||
(other.kind() == kEqRef && (other.kind() == kEqRef && (kind() == kExnRef || kind() == kAnyRef));
(kind() == kExnRef || kind() == kOptRef || kind() == kRef)) ||
(kind() == kRef && other.kind() == kOptRef &&
ref_index() == other.ref_index());
} }
constexpr bool IsReferenceType() const { constexpr bool IsReferenceType() const {
...@@ -137,23 +130,6 @@ class ValueType { ...@@ -137,23 +130,6 @@ class ValueType {
kind() == kOptRef; kind() == kOptRef;
} }
// TODO(7748): Extend this with struct and function subtyping.
// Keep up to date with funcref vs. anyref subtyping.
static ValueType CommonSubType(ValueType a, ValueType b) {
if (a == b) return a;
// The only sub type of any value type is {bot}.
if (!a.IsReferenceType() || !b.IsReferenceType()) {
return ValueType(kBottom);
}
if (a.IsSubTypeOf(b)) return a;
if (b.IsSubTypeOf(a)) return b;
// {a} and {b} are not each other's subtype.
// If one of them is not nullable, their greatest subtype is bottom,
// otherwise null.
if (a.kind() == kRef || b.kind() == kRef) return ValueType(kBottom);
return ValueType(kNullRef);
}
constexpr ValueTypeCode value_type_code() const { constexpr ValueTypeCode value_type_code() const {
#if V8_HAS_CXX14_CONSTEXPR #if V8_HAS_CXX14_CONSTEXPR
DCHECK_NE(kBottom, kind()); DCHECK_NE(kBottom, kind());
......
...@@ -218,7 +218,16 @@ std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name) { ...@@ -218,7 +218,16 @@ std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name) {
} }
WasmModule::WasmModule(std::unique_ptr<Zone> signature_zone) WasmModule::WasmModule(std::unique_ptr<Zone> signature_zone)
: signature_zone(std::move(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())) {}
bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) { bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) {
// TODO(wasm): Once wasm has its own CSP policy, we should introduce a // TODO(wasm): Once wasm has its own CSP policy, we should introduce a
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "src/wasm/struct-types.h" #include "src/wasm/struct-types.h"
#include "src/wasm/wasm-constants.h" #include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-opcodes.h" #include "src/wasm/wasm-opcodes.h"
#include "src/zone/zone-containers.h"
namespace v8 { namespace v8 {
...@@ -330,6 +331,28 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -330,6 +331,28 @@ struct V8_EXPORT_PRIVATE WasmModule {
bool has_array(uint32_t index) const { bool has_array(uint32_t index) const {
return index < types.size() && type_kinds[index] == kWasmArrayTypeCode; return index < types.size() && type_kinds[index] == kWasmArrayTypeCode;
} }
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<WasmFunction> functions;
std::vector<WasmDataSegment> data_segments; std::vector<WasmDataSegment> data_segments;
std::vector<WasmTable> tables; std::vector<WasmTable> tables;
...@@ -350,6 +373,15 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -350,6 +373,15 @@ struct V8_EXPORT_PRIVATE WasmModule {
explicit WasmModule(std::unique_ptr<Zone> signature_zone = nullptr); 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;
DISALLOW_COPY_AND_ASSIGN(WasmModule); DISALLOW_COPY_AND_ASSIGN(WasmModule);
}; };
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-subtyping.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module);
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;
}
const ArrayType* sub_array = module->types[type_index_1].array_type;
const ArrayType* super_array = module->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)) {
return true;
} else {
module->uncache_type_equivalence(type_index_1, type_index_2);
// 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;
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);
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);
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;
if (module->is_cached_equivalent_type(type1.ref_index(), type2.ref_index())) {
return true;
}
return IsArrayTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
}
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;
if (sub_struct->field_count() < super_struct->field_count()) {
return false;
}
module->cache_subtype(subtype_index, supertype_index);
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);
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 sub_mut = sub_array->mutability();
bool super_mut = super_array->mutability();
module->cache_subtype(subtype_index, supertype_index);
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);
return false;
} else {
return true;
}
}
} // namespace
// TODO(7748): Extend this with function subtyping.
// Keep up to date with funcref vs. anyref subtyping.
V8_EXPORT_PRIVATE bool IsSubtypeOfRef(ValueType subtype, ValueType supertype,
const WasmModule* module) {
DCHECK(subtype != supertype && subtype.IsReferenceType() &&
supertype.IsReferenceType());
// eqref is a supertype of all reference types except funcref.
if (supertype == kWasmEqRef) {
return subtype != kWasmFuncRef;
}
// No other subtyping is possible except between ref and optref.
if (!((subtype.kind() == ValueType::kRef &&
supertype.kind() == ValueType::kRef) ||
(subtype.kind() == ValueType::kRef &&
supertype.kind() == ValueType::kOptRef) ||
(subtype.kind() == ValueType::kOptRef &&
supertype.kind() == ValueType::kOptRef))) {
return false;
}
if (subtype.ref_index() == supertype.ref_index()) {
return true;
}
if (module->is_cached_subtype(subtype.ref_index(), supertype.ref_index())) {
return true;
}
return IsStructSubtype(subtype.ref_index(), supertype.ref_index(), module) ||
IsArraySubtype(subtype.ref_index(), supertype.ref_index(), module);
}
// TODO(7748): Extend this with function subtyping.
// Keep up to date with funcref vs. anyref subtyping.
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module) {
if (a == b) return a;
if (IsSubtypeOf(a, b, module)) return a;
if (IsSubtypeOf(b, a, module)) return b;
return kWasmBottom;
}
} // namespace wasm
} // namespace internal
} // namespace v8
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WASM_WASM_SUBTYPING_H_
#define V8_WASM_WASM_SUBTYPING_H_
#include "src/wasm/value-type.h"
namespace v8 {
namespace internal {
namespace wasm {
struct WasmModule;
V8_EXPORT_PRIVATE bool IsSubtypeOfRef(ValueType subtype, ValueType supertype,
const WasmModule* module);
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
const WasmModule* module) {
if (subtype == supertype) return true;
bool both_reference_types =
subtype.IsReferenceType() && supertype.IsReferenceType();
if (!both_reference_types) return false;
return IsSubtypeOfRef(subtype, supertype, module);
}
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_SUBTYPING_H_
...@@ -310,6 +310,7 @@ v8_source_set("unittests_sources") { ...@@ -310,6 +310,7 @@ v8_source_set("unittests_sources") {
"wasm/loop-assignment-analysis-unittest.cc", "wasm/loop-assignment-analysis-unittest.cc",
"wasm/module-decoder-unittest.cc", "wasm/module-decoder-unittest.cc",
"wasm/streaming-decoder-unittest.cc", "wasm/streaming-decoder-unittest.cc",
"wasm/subtyping-unittest.cc",
"wasm/wasm-code-manager-unittest.cc", "wasm/wasm-code-manager-unittest.cc",
"wasm/wasm-compiler-unittest.cc", "wasm/wasm-compiler-unittest.cc",
"wasm/wasm-macro-gen-unittest.cc", "wasm/wasm-macro-gen-unittest.cc",
......
...@@ -1699,8 +1699,8 @@ TEST_F(FunctionBodyDecoderTest, MultiReturnType) { ...@@ -1699,8 +1699,8 @@ TEST_F(FunctionBodyDecoderTest, MultiReturnType) {
ExpectValidates(&sig_cd_v, {WASM_CALL_FUNCTION0(0)}); ExpectValidates(&sig_cd_v, {WASM_CALL_FUNCTION0(0)});
if (kValueTypes[c].IsSubTypeOf(kValueTypes[a]) && if (kValueTypes[c].IsSubtypeOfNoImmediates(kValueTypes[a]) &&
kValueTypes[d].IsSubTypeOf(kValueTypes[b])) { kValueTypes[d].IsSubtypeOfNoImmediates(kValueTypes[b])) {
ExpectValidates(&sig_ab_v, {WASM_CALL_FUNCTION0(0)}); ExpectValidates(&sig_ab_v, {WASM_CALL_FUNCTION0(0)});
} else { } else {
ExpectFailure(&sig_ab_v, {WASM_CALL_FUNCTION0(0)}); ExpectFailure(&sig_ab_v, {WASM_CALL_FUNCTION0(0)});
...@@ -1916,7 +1916,8 @@ TEST_F(FunctionBodyDecoderTest, AllGetGlobalCombinations) { ...@@ -1916,7 +1916,8 @@ TEST_F(FunctionBodyDecoderTest, AllGetGlobalCombinations) {
TestModuleBuilder builder; TestModuleBuilder builder;
module = builder.module(); module = builder.module();
builder.AddGlobal(global_type); builder.AddGlobal(global_type);
Validate(global_type.IsSubTypeOf(local_type), &sig, {WASM_GET_GLOBAL(0)}); Validate(global_type.IsSubtypeOfNoImmediates(local_type), &sig,
{WASM_GET_GLOBAL(0)});
} }
} }
} }
...@@ -1930,7 +1931,7 @@ TEST_F(FunctionBodyDecoderTest, AllSetGlobalCombinations) { ...@@ -1930,7 +1931,7 @@ TEST_F(FunctionBodyDecoderTest, AllSetGlobalCombinations) {
TestModuleBuilder builder; TestModuleBuilder builder;
module = builder.module(); module = builder.module();
builder.AddGlobal(global_type); builder.AddGlobal(global_type);
Validate(local_type.IsSubTypeOf(global_type), &sig, Validate(local_type.IsSubtypeOfNoImmediates(global_type), &sig,
{WASM_SET_GLOBAL(0, WASM_GET_LOCAL(0))}); {WASM_SET_GLOBAL(0, WASM_GET_LOCAL(0))});
} }
} }
...@@ -2284,7 +2285,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll1) { ...@@ -2284,7 +2285,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll1) {
sig.GetReturn(), WASM_IF(WASM_ZERO, WASM_BRV(0, WASM_GET_LOCAL(0))), sig.GetReturn(), WASM_IF(WASM_ZERO, WASM_BRV(0, WASM_GET_LOCAL(0))),
WASM_GET_LOCAL(1))}; WASM_GET_LOCAL(1))};
Validate(kValueTypes[j].IsSubTypeOf(kValueTypes[i]), &sig, code); Validate(kValueTypes[j].IsSubtypeOfNoImmediates(kValueTypes[i]), &sig,
code);
} }
} }
} }
...@@ -2299,7 +2301,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll2) { ...@@ -2299,7 +2301,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll2) {
WASM_BRV_IF_ZERO(0, WASM_GET_LOCAL(0)), WASM_BRV_IF_ZERO(0, WASM_GET_LOCAL(0)),
WASM_GET_LOCAL(1))}; WASM_GET_LOCAL(1))};
Validate(kValueTypes[j].IsSubTypeOf(kValueTypes[i]), &sig, code); Validate(kValueTypes[j].IsSubtypeOfNoImmediates(kValueTypes[i]), &sig,
code);
} }
} }
} }
...@@ -2314,7 +2317,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll3) { ...@@ -2314,7 +2317,8 @@ TEST_F(FunctionBodyDecoderTest, Break_TypeCheckAll3) {
WASM_GET_LOCAL(1), WASM_GET_LOCAL(1),
WASM_BRV_IF_ZERO(0, WASM_GET_LOCAL(0)))}; WASM_BRV_IF_ZERO(0, WASM_GET_LOCAL(0)))};
Validate(kValueTypes[j].IsSubTypeOf(kValueTypes[i]), &sig, code); Validate(kValueTypes[j].IsSubtypeOfNoImmediates(kValueTypes[i]), &sig,
code);
} }
} }
} }
...@@ -2360,7 +2364,8 @@ TEST_F(FunctionBodyDecoderTest, BreakIf_val_type) { ...@@ -2360,7 +2364,8 @@ TEST_F(FunctionBodyDecoderTest, BreakIf_val_type) {
types[1], WASM_BRV_IF(0, WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)), types[1], WASM_BRV_IF(0, WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
WASM_DROP, WASM_GET_LOCAL(0))}; WASM_DROP, WASM_GET_LOCAL(0))};
Validate(kValueTypes[j].IsSubTypeOf(kValueTypes[i]), &sig, code); Validate(kValueTypes[j].IsSubtypeOfNoImmediates(kValueTypes[i]), &sig,
code);
} }
} }
} }
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-subtyping.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace subtyping_unittest {
class WasmSubtypingTest : public TestWithZone {};
using FieldInit = std::pair<ValueType, bool>;
ValueType ref(uint32_t index) { return ValueType(ValueType::kRef, index); }
ValueType optRef(uint32_t index) {
return ValueType(ValueType::kOptRef, index);
}
FieldInit mut(ValueType type) { return FieldInit(type, true); }
FieldInit immut(ValueType type) { return FieldInit(type, false); }
void DefineStruct(WasmModule* module, std::initializer_list<FieldInit> fields) {
StructType::Builder builder(module->signature_zone.get(),
static_cast<uint32_t>(fields.size()));
for (FieldInit field : fields) {
builder.AddField(field.first, field.second);
}
return module->add_struct_type(builder.Build());
}
void DefineArray(WasmModule* module, FieldInit element_type) {
module->add_array_type(new (module->signature_zone.get()) ArrayType(
element_type.first, element_type.second));
}
TEST_F(WasmSubtypingTest, Subtyping) {
v8::internal::AccountingAllocator allocator;
WasmModule module_(std::make_unique<Zone>(*(zone())));
WasmModule* module = &module_;
/* 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};
ValueType ref_types[] = {kWasmAnyRef, kWasmFuncRef, kWasmExnRef, kWasmEqRef,
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);
}
}
// 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));
}
}
for (ValueType ref_type : ref_types) {
// Reference types are a subtype of eqref, except funcref.
CHECK_EQ(IsSubtypeOf(ref_type, kWasmEqRef, module),
ref_type != kWasmFuncRef);
// Each reference type is a subtype of itself.
CHECK(IsSubtypeOf(ref_type, ref_type, module));
}
for (ValueType type_1 : {kWasmAnyRef, kWasmFuncRef, kWasmExnRef}) {
for (ValueType type_2 : {kWasmAnyRef, kWasmFuncRef, kWasmExnRef}) {
CHECK_EQ(IsSubtypeOf(type_1, type_2, 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));
}
} // namespace subtyping_unittest
} // namespace wasm
} // namespace internal
} // namespace v8
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