Commit a61aaed9 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[wasm-gc] Allow reference types to function signatures

Changes:
- Remove restriction that function types cannot be used as ref types.
- Introduce WasmModule::has_type().
- Remove deferred signature checks in module-decoder. Instead, check if
  type indices are out of bounds in consume_value_type (was bugged
  before).
- Remove obsolete GetCanonicalRttIndex.
- Refine type of ref.func.
- Statically check immediate type against table type for call_indirect.
- Dynamic check for call_indirect should only happen when for funcref
  (currently the only function supertype).
- Allocate a different map per function signature (with Map::Copy).
- Introduce function type equivalence and (trivial) subtyping.
- Add a few elementary tests.

Bug: v8:7748
Change-Id: If57d0bfd856c9eb3784191f3de423f53dfd26ef1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2335190
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69250}
parent 408e7240
......@@ -2890,11 +2890,16 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
key = graph()->NewNode(machine->Word32And(), key, mask);
}
// Load signature from the table and check.
int32_t expected_sig_id = env_->module->signature_ids[sig_index];
Node* int32_scaled_key = Uint32ToUintptr(
graph()->NewNode(machine->Word32Shl(), key, Int32Constant(2)));
// Check that the dynamic type of the function is a subtype of its static
// (table) type. Currently, the only subtyping between function types is
// (ref null $t) <: funcref for all $t: function_type.
// TODO(7748): Expand this with function subtyping.
if (env_->module->tables[table_index].type == wasm::kWasmFuncRef) {
int32_t expected_sig_id = env_->module->signature_ids[sig_index];
Node* loaded_sig = SetEffect(
graph()->NewNode(machine->Load(MachineType::Int32()), ift_sig_ids,
int32_scaled_key, effect(), control()));
......@@ -2902,6 +2907,7 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
}
Node* tagged_scaled_key;
if (kTaggedSize == kInt32Size) {
......@@ -5358,10 +5364,9 @@ Node* WasmGraphBuilder::RttCanon(wasm::HeapType type) {
UNREACHABLE();
}
}
int map_index = wasm::GetCanonicalRttIndex(env_->module, type.ref_index());
Node* maps_list =
LOAD_INSTANCE_FIELD(ManagedObjectMaps, MachineType::TaggedPointer());
return LOAD_FIXED_ARRAY_SLOT_PTR(maps_list, map_index);
return LOAD_FIXED_ARRAY_SLOT_PTR(maps_list, type.ref_index());
}
Node* WasmGraphBuilder::RttSub(wasm::HeapType type, Node* parent_rtt) {
......
......@@ -1922,14 +1922,6 @@ Handle<JSFunction> Factory::NewFunction(const NewFunctionArgs& args) {
// bootstrapping.
(args.maybe_builtin_id_ == Builtins::kEmptyFunction ||
args.maybe_builtin_id_ == Builtins::kProxyConstructor));
} else {
DCHECK(
(*map == *isolate()->sloppy_function_map()) ||
(*map == *isolate()->sloppy_function_without_prototype_map()) ||
(*map == *isolate()->sloppy_function_with_readonly_prototype_map()) ||
(*map == *isolate()->strict_function_map()) ||
(*map == *isolate()->strict_function_without_prototype_map()) ||
(*map == *isolate()->wasm_exported_function_map()));
}
#endif
......
......@@ -1231,14 +1231,25 @@ class WasmDecoder : public Decoder {
error("function table has to exist to execute call_indirect");
return false;
}
if (!VALIDATE(module_->tables[imm.table_index].type == kWasmFuncRef)) {
error("table of call_indirect must be of type funcref");
if (!VALIDATE(IsSubtypeOf(module_->tables[imm.table_index].type,
kWasmFuncRef, module_))) {
error("table of call_indirect must be of a function type");
return false;
}
if (!Complete(imm)) {
errorf(pc, "invalid signature index: #%u", imm.sig_index);
return false;
}
// Check that the dynamic signature for this call is a subtype of the static
// type of the table the function is defined in.
if (!VALIDATE(IsSubtypeOf(ValueType::Ref(imm.sig_index, kNonNullable),
module_->tables[imm.table_index].type,
module_))) {
errorf(pc,
"call_indirect: Signature of function %u is not a subtype of "
"table %u",
imm.sig_index, imm.table_index);
}
return true;
}
......@@ -1435,12 +1446,8 @@ class WasmDecoder : public Decoder {
return false;
}
if (!VALIDATE(imm.type.is_generic() ||
module_->has_array(imm.type.ref_index()) ||
module_->has_struct(imm.type.ref_index()))) {
errorf(
pc,
"Type index %u does not refer to a struct or array type definition",
imm.type.ref_index());
module_->has_type(imm.type.ref_index()))) {
errorf(pc, "Type index %u is out of bounds", imm.type.ref_index());
return false;
}
return true;
......@@ -2638,7 +2645,10 @@ class WasmFullDecoder : public WasmDecoder<validate> {
CHECK_PROTOTYPE_OPCODE(reftypes);
FunctionIndexImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_ + 1, imm)) return 0;
Value* value = Push(ValueType::Ref(HeapType::kFunc, kNonNullable));
HeapType heap_type(this->enabled_.has_typed_funcref()
? this->module_->functions[imm.index].sig_index
: HeapType::kFunc);
Value* value = Push(ValueType::Ref(heap_type, kNonNullable));
CALL_INTERFACE_IF_REACHABLE(RefFunc, imm.index, value);
return 1 + imm.length;
}
......
......@@ -106,44 +106,6 @@ bool validate_utf8(Decoder* decoder, WireBytesRef string) {
string.length());
}
ValueType TypeOf(const WasmModule* module, const WasmInitExpr& expr) {
switch (expr.kind()) {
case WasmInitExpr::kNone:
return kWasmStmt;
case WasmInitExpr::kGlobalGet:
return expr.immediate().index < module->globals.size()
? module->globals[expr.immediate().index].type
: kWasmStmt;
case WasmInitExpr::kI32Const:
return kWasmI32;
case WasmInitExpr::kI64Const:
return kWasmI64;
case WasmInitExpr::kF32Const:
return kWasmF32;
case WasmInitExpr::kF64Const:
return kWasmF64;
case WasmInitExpr::kS128Const:
return kWasmS128;
case WasmInitExpr::kRefFuncConst:
return ValueType::Ref(HeapType::kFunc, kNonNullable);
case WasmInitExpr::kRefNullConst:
return ValueType::Ref(expr.immediate().heap_type, kNullable);
case WasmInitExpr::kRttCanon:
// TODO(7748): If heaptype is "anyref" (not introduced yet),
// then this should be uint8_t{0}.
return ValueType::Rtt(expr.immediate().heap_type, uint8_t{1});
case WasmInitExpr::kRttSub: {
ValueType operand_type = TypeOf(module, *expr.operand());
if (operand_type.is_rtt()) {
return ValueType::Rtt(expr.immediate().heap_type,
operand_type.depth() + 1);
} else {
return kWasmStmt;
}
}
}
}
// Reads a length-prefixed string, checking that it is within bounds. Returns
// the offset of the string, and the length as an out parameter.
WireBytesRef consume_string(Decoder* decoder, bool validate_utf8,
......@@ -571,8 +533,7 @@ class ModuleDecoderImpl : public Decoder {
uint8_t kind = consume_u8("type kind");
switch (kind) {
case kWasmFunctionTypeCode: {
const FunctionSig* s = consume_sig(module_->signature_zone.get(),
DeferIndexCheckMode::kDeferCheck);
const FunctionSig* s = consume_sig(module_->signature_zone.get());
module_->add_signature(s);
break;
}
......@@ -602,27 +563,6 @@ class ModuleDecoderImpl : public Decoder {
}
}
module_->signature_map.Freeze();
VerifyDeferredTypeOffsets();
}
// TODO(7748): When typed function references are allowed, this should be
// deleted altogether and replaced by an inline in-bounds check.
void VerifyDeferredTypeOffsets() {
for (auto& type_offset : deferred_check_type_index_) {
uint32_t type_index = type_offset.first;
uint32_t code_offset = type_offset.second;
if (type_index >= module_->type_kinds.size()) {
errorf(code_offset, "reference to undeclared struct/array #%u",
type_index);
break;
}
uint8_t type = module_->type_kinds[type_index];
if (type == kWasmFunctionTypeCode) {
errorf(code_offset, "cannot build reference to function type index #%u",
type_index);
break;
}
}
}
void DecodeImportSection() {
......@@ -1305,7 +1245,7 @@ class ModuleDecoderImpl : public Decoder {
pc_ = start_;
expect_u8("type form", kWasmFunctionTypeCode);
if (!ok()) return FunctionResult{std::move(intermediate_error_)};
function->sig = consume_sig(zone, DeferIndexCheckMode::kNoCheck);
function->sig = consume_sig(zone);
function->code = {off(pc_), static_cast<uint32_t>(end_ - pc_)};
if (ok())
......@@ -1323,8 +1263,7 @@ class ModuleDecoderImpl : public Decoder {
const FunctionSig* DecodeFunctionSignature(Zone* zone, const byte* start) {
pc_ = start;
if (!expect_u8("type form", kWasmFunctionTypeCode)) return nullptr;
const FunctionSig* result =
consume_sig(zone, DeferIndexCheckMode::kNoCheck);
const FunctionSig* result = consume_sig(zone);
return ok() ? result : nullptr;
}
......@@ -1371,6 +1310,49 @@ class ModuleDecoderImpl : public Decoder {
std::unordered_map<uint32_t, int> deferred_check_type_index_;
ModuleOrigin origin_;
ValueType TypeOf(const WasmInitExpr& expr) {
switch (expr.kind()) {
case WasmInitExpr::kNone:
return kWasmStmt;
case WasmInitExpr::kGlobalGet:
return expr.immediate().index < module_->globals.size()
? module_->globals[expr.immediate().index].type
: kWasmStmt;
case WasmInitExpr::kI32Const:
return kWasmI32;
case WasmInitExpr::kI64Const:
return kWasmI64;
case WasmInitExpr::kF32Const:
return kWasmF32;
case WasmInitExpr::kF64Const:
return kWasmF64;
case WasmInitExpr::kS128Const:
return kWasmS128;
case WasmInitExpr::kRefFuncConst: {
uint32_t heap_type =
enabled_features_.has_typed_funcref()
? module_->functions[expr.immediate().index].sig_index
: HeapType::kFunc;
return ValueType::Ref(heap_type, kNonNullable);
}
case WasmInitExpr::kRefNullConst:
return ValueType::Ref(expr.immediate().heap_type, kNullable);
case WasmInitExpr::kRttCanon:
// TODO(7748): If heaptype is "anyref" (not introduced yet),
// then this should be uint8_t{0}.
return ValueType::Rtt(expr.immediate().heap_type, uint8_t{1});
case WasmInitExpr::kRttSub: {
ValueType operand_type = TypeOf(*expr.operand());
if (operand_type.is_rtt()) {
return ValueType::Rtt(expr.immediate().heap_type,
operand_type.depth() + 1);
} else {
return kWasmStmt;
}
}
}
}
bool has_seen_unordered_section(SectionCode section_code) {
return seen_unordered_sections_ & (1 << section_code);
}
......@@ -1628,12 +1610,8 @@ class ModuleDecoderImpl : public Decoder {
return false;
}
if (V8_UNLIKELY(!(imm.type.is_generic() ||
module_->has_array(imm.type.ref_index()) ||
module_->has_struct(imm.type.ref_index())))) {
errorf(
pc,
"Type index %u does not refer to a struct or array type definition",
imm.type.ref_index());
module_->has_type(imm.type.ref_index())))) {
errorf(pc, "Type index %u is out of bounds", imm.type.ref_index());
return false;
}
return true;
......@@ -1765,7 +1743,7 @@ class ModuleDecoderImpl : public Decoder {
}
WasmInitExpr parent = std::move(stack.back());
stack.pop_back();
ValueType parent_type = TypeOf(module_.get(), parent);
ValueType parent_type = TypeOf(parent);
if (V8_UNLIKELY(
parent_type.kind() != ValueType::kRtt ||
!IsSubtypeOf(
......@@ -1811,10 +1789,9 @@ class ModuleDecoderImpl : public Decoder {
DCHECK_EQ(stack.size(), 1);
WasmInitExpr expr = std::move(stack.back());
if (expected != kWasmStmt &&
!IsSubtypeOf(TypeOf(module, expr), expected, module)) {
if (expected != kWasmStmt && !IsSubtypeOf(TypeOf(expr), expected, module)) {
errorf(pc(), "type error in init expression, expected %s, got %s",
expected.name().c_str(), TypeOf(module, expr).name().c_str());
expected.name().c_str(), TypeOf(expr).name().c_str());
}
return expr;
}
......@@ -1832,6 +1809,11 @@ class ModuleDecoderImpl : public Decoder {
this, this->pc(), &type_length,
origin_ == kWasmOrigin ? enabled_features_ : WasmFeatures::None());
if (result == kWasmBottom) error(pc_, "invalid value type");
// We use capacity() over size() so this function works
// mid-DecodeTypeSection.
if (result.has_index() && result.ref_index() >= module_->types.capacity()) {
errorf(pc(), "Type index %u is out of bounds", result.ref_index());
}
consume_bytes(type_length, "value type");
return result;
}
......@@ -1872,28 +1854,17 @@ class ModuleDecoderImpl : public Decoder {
}
}
enum DeferIndexCheckMode { kNoCheck, kDeferCheck };
void defer_index_check(ValueType type) {
if (type.has_index()) {
deferred_check_type_index_.emplace(type.ref_index(), pc_offset());
}
}
const FunctionSig* consume_sig(Zone* zone, DeferIndexCheckMode defer_check) {
const FunctionSig* consume_sig(Zone* zone) {
// Parse parameter types.
uint32_t param_count =
consume_count("param count", kV8MaxWasmFunctionParams);
if (failed()) return nullptr;
std::vector<ValueType> params;
for (uint32_t i = 0; ok() && i < param_count; ++i) {
ValueType param = consume_value_type();
if (defer_check == DeferIndexCheckMode::kDeferCheck) {
defer_index_check(param);
}
params.push_back(param);
params.push_back(consume_value_type());
}
std::vector<ValueType> returns;
// Parse return types.
const size_t max_return_count = enabled_features_.has_mv()
? kV8MaxWasmFunctionMultiReturns
......@@ -1901,13 +1872,8 @@ class ModuleDecoderImpl : public Decoder {
uint32_t return_count = consume_count("return count", max_return_count);
if (failed()) return nullptr;
for (uint32_t i = 0; ok() && i < return_count; ++i) {
ValueType ret = consume_value_type();
if (defer_check == DeferIndexCheckMode::kDeferCheck) {
defer_index_check(ret);
returns.push_back(consume_value_type());
}
returns.push_back(ret);
}
if (failed()) return nullptr;
// FunctionSig stores the return types first.
......@@ -1927,7 +1893,6 @@ class ModuleDecoderImpl : public Decoder {
bool* mutabilities = zone->NewArray<bool>(field_count);
for (uint32_t i = 0; ok() && i < field_count; ++i) {
ValueType field = consume_storage_type();
defer_index_check(field);
fields[i] = field;
bool mutability = consume_mutability();
mutabilities[i] = mutability;
......@@ -1940,7 +1905,6 @@ class ModuleDecoderImpl : public Decoder {
const ArrayType* consume_array(Zone* zone) {
ValueType field = consume_storage_type();
if (failed()) return nullptr;
defer_index_check(field);
bool mutability = consume_mutability();
if (!mutability) {
error(this->pc() - 1, "immutable arrays are not supported yet");
......
......@@ -185,7 +185,14 @@ Handle<Map> AllocateSubRtt(Isolate* isolate,
} else if (module->has_array(type)) {
rtt = wasm::CreateArrayMap(isolate, module, type, parent);
} else {
UNREACHABLE();
DCHECK(module->has_signature(type));
// Currently, parent rtts for functions are meaningless,
// since (rtt.test func rtt) iff (func.map == rtt).
// Therefore, we simply create a fresh function map here.
// TODO(7748): Canonicalize rtts to make them work for identical function
// types.
rtt = Map::Copy(isolate, isolate->wasm_exported_function_map(),
"fresh function map");
}
cache = RttSubtypes::Insert(isolate, cache, type, rtt);
parent->wasm_type_info().set_subtypes(*cache);
......@@ -567,27 +574,32 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Must happen before {InitGlobals} because globals can refer to these maps.
//--------------------------------------------------------------------------
if (enabled_.has_gc()) {
int count = GetCanonicalRttIndex(
module_, static_cast<uint32_t>(module_->type_kinds.size()));
Handle<FixedArray> maps =
isolate_->factory()->NewUninitializedFixedArray(count);
Handle<FixedArray> maps = isolate_->factory()->NewUninitializedFixedArray(
static_cast<int>(module_->type_kinds.size()));
// TODO(7748): Do we want a different sentinel here?
Handle<Map> anyref_sentinel_map = isolate_->factory()->null_map();
int map_index = 0;
for (int i = 0; i < static_cast<int>(module_->type_kinds.size()); i++) {
if (module_->type_kinds[i] == kWasmStructTypeCode) {
Handle<Map> map =
CreateStructMap(isolate_, module_, i, anyref_sentinel_map);
maps->set(map_index++, *map);
}
if (module_->type_kinds[i] == kWasmArrayTypeCode) {
Handle<Map> map =
CreateArrayMap(isolate_, module_, i, anyref_sentinel_map);
maps->set(map_index++, *map);
for (int map_index = 0;
map_index < static_cast<int>(module_->type_kinds.size());
map_index++) {
Handle<Map> map;
switch (module_->type_kinds[map_index]) {
case kWasmStructTypeCode:
map = CreateStructMap(isolate_, module_, map_index,
anyref_sentinel_map);
break;
case kWasmArrayTypeCode:
map =
CreateArrayMap(isolate_, module_, map_index, anyref_sentinel_map);
break;
case kWasmFunctionTypeCode:
// TODO(7748): Canonicalize rtts to make them work for identical
// function types.
map = Map::Copy(isolate_, isolate_->wasm_exported_function_map(),
"fresh function map");
break;
}
maps->set(map_index, *map);
}
// {GetCanonicalRttIndex} must be in sync with the for-loop above.
DCHECK_EQ(count, map_index);
instance->set_managed_object_maps(*maps);
}
......@@ -1516,8 +1528,7 @@ Handle<Object> InstanceBuilder::RecursivelyEvaluateGlobalInitializer(
UNREACHABLE();
}
// Non-generic types fall through.
int map_index = GetCanonicalRttIndex(
module_, static_cast<uint32_t>(init.immediate().heap_type));
int map_index = init.immediate().heap_type;
return handle(instance->managed_object_maps().get(map_index), isolate_);
}
case WasmInitExpr::kRttSub: {
......
......@@ -83,21 +83,6 @@ int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
return result;
}
// static
int GetCanonicalRttIndex(const WasmModule* module, uint32_t type_index) {
int rtt_index = 0;
const std::vector<uint8_t>& type_kinds = module->type_kinds;
// This logic must be in sync with the code in module-instantiate.cc that
// initializes the "managed_object_maps" list on the instance.
for (uint32_t i = 0; i < type_index; i++) {
if (type_kinds[i] == wasm::kWasmStructTypeCode ||
type_kinds[i] == wasm::kWasmArrayTypeCode) {
rtt_index++;
}
}
return rtt_index;
}
// static
int GetWasmFunctionOffset(const WasmModule* module, uint32_t func_index) {
const std::vector<WasmFunction>& functions = module->functions;
......
......@@ -296,6 +296,9 @@ struct V8_EXPORT_PRIVATE WasmModule {
std::vector<TypeDefinition> types; // by type index
std::vector<uint8_t> type_kinds; // by type index
std::vector<uint32_t> signature_ids; // by signature index
bool has_type(uint32_t index) const { return index < types.size(); }
void add_signature(const FunctionSig* sig) {
types.push_back(TypeDefinition(sig));
type_kinds.push_back(kWasmFunctionTypeCode);
......@@ -403,10 +406,6 @@ V8_EXPORT_PRIVATE int MaxNumExportWrappers(const WasmModule* module);
int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
bool is_import);
// Returns the index of the canonical RTT of the given struct/array type
// in the instance's list of canonical RTTs.
int GetCanonicalRttIndex(const WasmModule* module, uint32_t type_index);
// Return the byte offset of the function identified by the given index.
// The offset will be relative to the start of the module bytes.
// Returns -1 if the function index is invalid.
......
......@@ -1844,7 +1844,17 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
Handle<Map> function_map;
switch (instance->module()->origin) {
case wasm::kWasmOrigin:
if (instance->module_object()
.native_module()
->enabled_features()
.has_gc()) {
uint32_t sig_index =
instance->module()->functions[func_index].sig_index;
function_map = handle(
Map::cast(instance->managed_object_maps().get(sig_index)), isolate);
} else {
function_map = isolate->wasm_exported_function_map();
}
break;
case wasm::kAsmJsSloppyOrigin:
function_map = isolate->sloppy_function_map();
......
......@@ -62,6 +62,33 @@ bool IsStructTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
}
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;
if (sig1->parameter_count() != sig2->parameter_count() ||
sig1->return_count() != sig2->return_count()) {
return false;
}
auto iter1 = sig1->all();
auto iter2 = sig2->all();
// Temporarily cache type equivalence for the recursive call.
module->cache_type_equivalence(type_index_1, type_index_2);
for (int i = 0; i < iter1.size(); i++) {
if (iter1[i] != iter2[i]) {
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;
......@@ -76,7 +103,8 @@ bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module) {
return true;
}
return IsArrayTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
IsFunctionTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
}
bool IsStructSubtype(uint32_t subtype_index, uint32_t supertype_index,
......@@ -130,9 +158,16 @@ bool IsArraySubtype(uint32_t subtype_index, uint32_t supertype_index,
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);
}
} // namespace
// TODO(7748): Extend this with function and any-heap subtyping.
// TODO(7748): Extend this with any-heap subtyping.
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
ValueType supertype,
const WasmModule* module) {
......@@ -145,16 +180,22 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
HeapType sub_heap = subtype.heap_type();
HeapType super_heap = supertype.heap_type();
DCHECK(!module->has_signature(sub_heap.representation()) &&
!module->has_signature(super_heap.representation()));
if (sub_heap == super_heap) {
return true;
}
// eqref is a supertype of i31ref and all custom types.
// eqref is a supertype of i31ref, array, and struct types.
if (super_heap.representation() == HeapType::kEq) {
return (sub_heap.is_index() || sub_heap.representation() == HeapType::kI31);
return (sub_heap.is_index() &&
!module->has_signature(sub_heap.ref_index())) ||
sub_heap.representation() == HeapType::kI31;
}
// 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());
}
// At the moment, generic heap types are not subtyping-related otherwise.
if (sub_heap.is_generic() || super_heap.is_generic()) {
return false;
......@@ -168,10 +209,11 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
}
return IsStructSubtype(sub_heap.ref_index(), super_heap.ref_index(),
module) ||
IsArraySubtype(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);
}
// TODO(7748): Extend this with function subtyping.
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module) {
if (a == b) return a;
if (IsSubtypeOf(a, b, module)) return a;
......
......@@ -86,6 +86,8 @@ class WasmGCTester {
return builder_.AddArrayType(zone.New<ArrayType>(element_type, mutability));
}
byte DefineSignature(FunctionSig* sig) { return builder_.AddSignature(sig); }
void CompileModule() {
ZoneBuffer buffer(&zone);
builder_.WriteTo(&buffer);
......@@ -793,6 +795,78 @@ TEST(ArrayNewMap) {
CHECK_EQ(Handle<WasmArray>::cast(result)->map(), *map);
}
TEST(FunctionRefs) {
WasmGCTester tester;
const byte func_index =
tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_I32V(42), kExprEnd});
const byte sig_index = 0;
const byte other_sig_index = tester.DefineSignature(tester.sigs.d_d());
// This is just so func_index counts as "declared".
tester.AddGlobal(ValueType::Ref(sig_index, kNullable), false,
WasmInitExpr::RefFuncConst(func_index));
ValueType func_type = ValueType::Ref(sig_index, kNonNullable);
FunctionSig sig_func(1, 0, &func_type);
ValueType rtt1 = ValueType::Rtt(sig_index, 1);
FunctionSig sig_rtt1(1, 0, &rtt1);
const byte rtt_canon = tester.DefineFunction(
&sig_rtt1, {}, {WASM_RTT_CANON(sig_index), kExprEnd});
ValueType rtt2 = ValueType::Rtt(sig_index, 2);
FunctionSig sig_rtt2(1, 0, &rtt2);
const byte rtt_sub = tester.DefineFunction(
&sig_rtt2, {},
{WASM_RTT_SUB(sig_index, WASM_RTT_CANON(kLocalFuncRef)), kExprEnd});
const byte cast = tester.DefineFunction(
&sig_func, {kWasmFuncRef},
{WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)),
WASM_REF_CAST(kLocalFuncRef, sig_index, WASM_GET_LOCAL(0),
WASM_RTT_CANON(sig_index)),
kExprEnd});
const byte cast_reference = tester.DefineFunction(
&sig_func, {}, {WASM_REF_FUNC(sig_index), kExprEnd});
const byte test = tester.DefineFunction(
tester.sigs.i_v(), {kWasmFuncRef},
{WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)),
WASM_REF_TEST(kLocalFuncRef, other_sig_index, WASM_GET_LOCAL(0),
WASM_RTT_CANON(other_sig_index)),
kExprEnd});
tester.CompileModule();
Handle<Object> result_canon =
tester.GetResultObject(rtt_canon).ToHandleChecked();
CHECK(result_canon->IsMap());
Handle<Map> map_canon = Handle<Map>::cast(result_canon);
CHECK(map_canon->IsJSFunctionMap());
Handle<Object> result_sub = tester.GetResultObject(rtt_sub).ToHandleChecked();
CHECK(result_sub->IsMap());
Handle<Map> map_sub = Handle<Map>::cast(result_sub);
CHECK(map_sub->IsJSFunctionMap());
Handle<Object> result_cast = tester.GetResultObject(cast).ToHandleChecked();
CHECK(result_cast->IsJSFunction());
Handle<JSFunction> cast_function = Handle<JSFunction>::cast(result_cast);
Handle<Object> result_cast_reference =
tester.GetResultObject(cast_reference).ToHandleChecked();
CHECK(result_cast_reference->IsJSFunction());
Handle<JSFunction> cast_function_reference =
Handle<JSFunction>::cast(result_cast_reference);
CHECK_EQ(cast_function->code().raw_instruction_start(),
cast_function_reference->code().raw_instruction_start());
tester.CheckResult(test, 0);
}
TEST(RefTestCastNull) {
WasmGCTester tester;
byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
......
......@@ -3516,7 +3516,6 @@ TEST_F(FunctionBodyDecoderTest, RefNull) {
module = builder.module();
byte struct_type_index = builder.AddStruct({F(kWasmI32, true)});
byte array_type_index = builder.AddArray(kWasmI32, true);
byte func_index = builder.AddSignature(sigs.i_i());
uint32_t type_reprs[] = {
struct_type_index, array_type_index, HeapType::kExn, HeapType::kFunc,
HeapType::kEq, HeapType::kExtern, HeapType::kI31};
......@@ -3527,14 +3526,8 @@ TEST_F(FunctionBodyDecoderTest, RefNull) {
ExpectValidates(&sig, {WASM_REF_NULL(WASM_HEAP_TYPE(HeapType(type_repr)))});
}
// It fails for undeclared types.
ExpectFailure(
sigs.v_v(), {WASM_REF_NULL(42), kExprDrop}, kAppendEnd,
"Type index 42 does not refer to a struct or array type definition");
// It fails for user-defined function types.
// TODO(7748): This will go once function types are fully implemented.
ExpectFailure(
sigs.v_v(), {WASM_REF_NULL(func_index), kExprDrop}, kAppendEnd,
"Type index 2 does not refer to a struct or array type definition");
ExpectFailure(sigs.v_v(), {WASM_REF_NULL(42), kExprDrop}, kAppendEnd,
"Type index 42 is out of bounds");
}
TEST_F(FunctionBodyDecoderTest, RefIsNull) {
......
......@@ -628,9 +628,7 @@ TEST_F(WasmModuleVerifyTest, RefNullGlobalInvalid1) {
static const byte data[] = {SECTION(Global, ENTRY_COUNT(1), kLocalOptRef, 0,
1, WASM_REF_NULL(0), kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(
result,
"Type index 0 does not refer to a struct or array type definition");
EXPECT_NOT_OK(result, "Type index 0 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, RefNullGlobalInvalid2) {
......@@ -2865,42 +2863,24 @@ TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
EXPECT_OK(result);
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsUndefinedIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) {
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(1),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(WASM_OPT_REF(1), true)))};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "reference to undeclared struct/array");
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsIllegalIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(2),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(WASM_OPT_REF(1), true)),
WASM_FUNCTION_DEF(ENTRY_COUNT(1), kLocalI32, ENTRY_COUNT(1), kLocalI32))};
static const byte data[] = {SECTION(Global, ENTRY_COUNT(1), kLocalRef, 0,
WASM_REF_NULL(0), kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "cannot build reference to function type index");
EXPECT_NOT_OK(result, "Type index 0 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsFunSigIllegalIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInType) {
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(1),
WASM_FUNCTION_DEF(U32V_1(1), kLocalI32, U32V_1(1), WASM_OPT_REF(0)))};
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
static const byte data[] = {
SECTION(Type, ENTRY_COUNT(1),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kLocalRef, true)))};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "cannot build reference to function type index");
EXPECT_NOT_OK(result, "Type index 1 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, IllegalPackedFields) {
......
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