From 69ca751bc8aace8c15a6a2d2e22d1658914e4145 Mon Sep 17 00:00:00 2001 From: Manos Koukoutos Date: Mon, 21 Sep 2020 14:38:49 +0000 Subject: [PATCH] [wasm-gc] Implement typed function tables Changes: - When checking if a table is a function table, check for subtyping to funcref instead of equality. - Add WasmModuleObject argument to GetFunctionTableEntry. - Implement WasmTableObject::Get/Set for all legal table types. - Factor out SetFunctionTableEntry from WasmTableObject::Set. - Write unittests and JS tests. Bug: v8:9495 Change-Id: I4f0c7a7013f17c561afb3039c5e0811634a4d313 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2416387 Commit-Queue: Manos Koukoutos Reviewed-by: Jakob Kummerow Cr-Commit-Position: refs/heads/master@{#70032} --- src/runtime/runtime-wasm.cc | 13 +- src/wasm/module-instantiate.cc | 27 ++-- src/wasm/wasm-debug.cc | 4 +- src/wasm/wasm-js.cc | 4 +- src/wasm/wasm-objects.cc | 116 +++++++++++++----- src/wasm/wasm-objects.h | 11 +- test/mjsunit/wasm/reference-tables.js | 91 ++++++++++++++ test/mjsunit/wasm/wasm-module-builder.js | 10 +- .../wasm/function-body-decoder-unittest.cc | 72 ++++++++++- .../unittests/wasm/module-decoder-unittest.cc | 50 ++++++++ 10 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 test/mjsunit/wasm/reference-tables.js diff --git a/src/runtime/runtime-wasm.cc b/src/runtime/runtime-wasm.cc index 9417b75868..04cb59393f 100644 --- a/src/runtime/runtime-wasm.cc +++ b/src/runtime/runtime-wasm.cc @@ -23,6 +23,7 @@ #include "src/wasm/wasm-debug.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-objects.h" +#include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-value.h" namespace v8 { @@ -334,7 +335,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) { auto table = handle( WasmTableObject::cast(instance->tables().get(table_index)), isolate); // We only use the runtime call for lazily initialized function references. - DCHECK_EQ(table->type(), wasm::kWasmFuncRef); + DCHECK( + table->instance().IsUndefined() + ? table->type() == wasm::kWasmFuncRef + : IsSubtypeOf(table->type(), wasm::kWasmFuncRef, + WasmInstanceObject::cast(table->instance()).module())); if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) { return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); @@ -357,7 +362,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) { auto table = handle( WasmTableObject::cast(instance->tables().get(table_index)), isolate); // We only use the runtime call for function references. - DCHECK_EQ(table->type(), wasm::kWasmFuncRef); + DCHECK( + table->instance().IsUndefined() + ? table->type() == wasm::kWasmFuncRef + : IsSubtypeOf(table->type(), wasm::kWasmFuncRef, + WasmInstanceObject::cast(table->instance()).module())); if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) { return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc index 3566b010d0..8bf29cb3fd 100644 --- a/src/wasm/module-instantiate.cc +++ b/src/wasm/module-instantiate.cc @@ -575,7 +575,7 @@ MaybeHandle InstanceBuilder::Build() { // iteration below. for (int i = 1; i < table_count; ++i) { const WasmTable& table = module_->tables[i]; - if (table.type.is_reference_to(HeapType::kFunc)) { + if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) { Handle table_obj = WasmIndirectFunctionTable::New(isolate_, table.initial_size); tables->set(i, *table_obj); @@ -1091,9 +1091,9 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable( MaybeHandle maybe_target_instance; int function_index; MaybeHandle maybe_js_function; - WasmTableObject::GetFunctionTableEntry(isolate_, table_object, i, &is_valid, - &is_null, &maybe_target_instance, - &function_index, &maybe_js_function); + WasmTableObject::GetFunctionTableEntry( + isolate_, module_, table_object, i, &is_valid, &is_null, + &maybe_target_instance, &function_index, &maybe_js_function); if (!is_valid) { thrower_->LinkError("table import %d[%d] is not a wasm function", import_index, i); @@ -1167,13 +1167,19 @@ bool InstanceBuilder::ProcessImportedTable(Handle instance, } } - if (table.type != table_object->type()) { + const WasmModule* table_type_module = + !table_object->instance().IsUndefined() + ? WasmInstanceObject::cast(table_object->instance()).module() + : instance->module(); + + if (!EquivalentTypes(table.type, table_object->type(), module_, + table_type_module)) { ReportLinkError("imported table does not match the expected type", import_index, module_name, import_name); return false; } - if (table.type.is_reference_to(HeapType::kFunc) && + if (IsSubtypeOf(table.type, kWasmFuncRef, module_) && !InitializeImportedIndirectFunctionTable(instance, table_index, import_index, table_object)) { return false; @@ -1874,7 +1880,7 @@ void InstanceBuilder::InitializeIndirectFunctionTables( for (int i = 0; i < static_cast(module_->tables.size()); ++i) { const WasmTable& table = module_->tables[i]; - if (table.type.is_reference_to(HeapType::kFunc)) { + if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) { WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( instance, i, table.initial_size); } @@ -1905,7 +1911,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle instance, int entry_index = static_cast(dst + i); if (func_index == WasmElemSegment::kNullIndex) { - if (table_object->type().is_reference_to(HeapType::kFunc)) { + if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) { IndirectFunctionTableEntry(instance, table_index, entry_index).clear(); } WasmTableObject::Set(isolate, table_object, entry_index, @@ -1916,8 +1922,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle instance, const WasmFunction* function = &module->functions[func_index]; // Update the local dispatch table first if necessary. - // TODO(9495): Make sure tables work with all function types. - if (table_object->type().is_reference_to(HeapType::kFunc)) { + if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) { uint32_t sig_id = module->signature_ids[function->sig_index]; IndirectFunctionTableEntry(instance, table_index, entry_index) .Set(sig_id, instance, func_index); @@ -1992,7 +1997,7 @@ void InstanceBuilder::LoadTableSegments(Handle instance) { int table_count = static_cast(module_->tables.size()); for (int index = 0; index < table_count; ++index) { - if (module_->tables[index].type.is_reference_to(HeapType::kFunc)) { + if (IsSubtypeOf(module_->tables[index].type, kWasmFuncRef, module_)) { auto table_object = handle( WasmTableObject::cast(instance->tables().get(index)), isolate_); diff --git a/src/wasm/wasm-debug.cc b/src/wasm/wasm-debug.cc index 9910601958..886a7000cd 100644 --- a/src/wasm/wasm-debug.cc +++ b/src/wasm/wasm-debug.cc @@ -24,6 +24,7 @@ #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-opcodes-inl.h" +#include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-value.h" #include "src/zone/accounting-allocator.h" @@ -60,7 +61,8 @@ MaybeHandle CreateFunctionTablesObject( for (int table_index = 0; table_index < tables->length(); ++table_index) { auto func_table = handle(WasmTableObject::cast(tables->get(table_index)), isolate); - if (!func_table->type().is_reference_to(HeapType::kFunc)) continue; + if (!IsSubtypeOf(func_table->type(), kWasmFuncRef, instance->module())) + continue; Handle table_name; if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index) diff --git a/src/wasm/wasm-js.cc b/src/wasm/wasm-js.cc index 67e00c32a0..62632b0d4a 100644 --- a/src/wasm/wasm-js.cc +++ b/src/wasm/wasm-js.cc @@ -1686,7 +1686,9 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo& args) { i::Handle element = Utils::OpenHandle(*args[1]); if (!i::WasmTableObject::IsValidElement(i_isolate, table_object, element)) { - thrower.TypeError("Argument 1 must be null or a WebAssembly function"); + thrower.TypeError( + "Argument 1 must be null or a WebAssembly function of type compatible " + "to 'this'"); return; } i::WasmTableObject::Set(i_isolate, table_object, index, element); diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc index 1a1a26364f..e0202b98d1 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -399,23 +399,13 @@ bool WasmTableObject::IsValidElement(Isolate* isolate, : nullptr; return wasm::TypecheckJSObject(isolate, module, entry, table->type(), &error_message); - } - -void WasmTableObject::Set(Isolate* isolate, Handle table, - uint32_t index, Handle entry) { - // Callers need to perform bounds checks, type check, and error handling. - DCHECK(IsInBounds(isolate, table, index)); - DCHECK(IsValidElement(isolate, table, entry)); - - Handle entries(table->entries(), isolate); - // The FixedArray is addressed with int's. - int entry_index = static_cast(index); - if (table->type().is_reference_to(wasm::HeapType::kExtern) || - table->type().is_reference_to(wasm::HeapType::kExn)) { - entries->set(entry_index, *entry); - return; - } +} +void WasmTableObject::SetFunctionTableEntry(Isolate* isolate, + Handle table, + Handle entries, + int entry_index, + Handle entry) { if (entry->IsNull(isolate)) { ClearDispatchTables(isolate, table, entry_index); // Degenerate case. entries->set(entry_index, ReadOnlyRoots(isolate).null_value()); @@ -443,6 +433,44 @@ void WasmTableObject::Set(Isolate* isolate, Handle table, entries->set(entry_index, *entry); } +void WasmTableObject::Set(Isolate* isolate, Handle table, + uint32_t index, Handle entry) { + // Callers need to perform bounds checks, type check, and error handling. + DCHECK(IsInBounds(isolate, table, index)); + DCHECK(IsValidElement(isolate, table, entry)); + + Handle entries(table->entries(), isolate); + // The FixedArray is addressed with int's. + int entry_index = static_cast(index); + + switch (table->type().heap_representation()) { + case wasm::HeapType::kExtern: + case wasm::HeapType::kExn: + entries->set(entry_index, *entry); + return; + case wasm::HeapType::kFunc: + SetFunctionTableEntry(isolate, table, entries, entry_index, entry); + return; + case wasm::HeapType::kEq: + case wasm::HeapType::kI31: + // TODO(7748): Implement once we have a story for struct/arrays/i31ref in + // JS. + UNIMPLEMENTED(); + case wasm::HeapType::kBottom: + UNREACHABLE(); + default: + DCHECK(!table->instance().IsUndefined()); + if (WasmInstanceObject::cast(table->instance()) + .module() + ->has_signature(entry_index)) { + SetFunctionTableEntry(isolate, table, entries, entry_index, entry); + return; + } + // TODO(7748): Implement once we have a story for struct/arrays in JS. + UNIMPLEMENTED(); + } +} + Handle WasmTableObject::Get(Isolate* isolate, Handle table, uint32_t index) { @@ -455,23 +483,44 @@ Handle WasmTableObject::Get(Isolate* isolate, Handle entry(entries->get(entry_index), isolate); - // First we handle the easy externref and exnref table case. - if (table->type().is_reference_to(wasm::HeapType::kExtern) || - table->type().is_reference_to(wasm::HeapType::kExn)) { - return entry; - } - - // Now we handle the funcref case. - if (WasmExportedFunction::IsWasmExportedFunction(*entry) || - WasmJSFunction::IsWasmJSFunction(*entry) || - WasmCapiFunction::IsWasmCapiFunction(*entry)) { - return entry; - } - if (entry->IsNull(isolate)) { return entry; } + switch (table->type().heap_representation()) { + case wasm::HeapType::kExtern: + case wasm::HeapType::kExn: + return entry; + case wasm::HeapType::kFunc: + if (WasmExportedFunction::IsWasmExportedFunction(*entry) || + WasmJSFunction::IsWasmJSFunction(*entry) || + WasmCapiFunction::IsWasmCapiFunction(*entry)) { + return entry; + } + break; + case wasm::HeapType::kEq: + case wasm::HeapType::kI31: + // TODO(7748): Implement once we have a story for struct/arrays/i31ref in + // JS. + UNIMPLEMENTED(); + case wasm::HeapType::kBottom: + UNREACHABLE(); + default: + DCHECK(!table->instance().IsUndefined()); + if (WasmInstanceObject::cast(table->instance()) + .module() + ->has_signature(entry_index)) { + if (WasmExportedFunction::IsWasmExportedFunction(*entry) || + WasmJSFunction::IsWasmJSFunction(*entry) || + WasmCapiFunction::IsWasmCapiFunction(*entry)) { + return entry; + } + break; + } + // TODO(7748): Implement once we have a story for struct/arrays in JS. + UNIMPLEMENTED(); + } + // {entry} is not a valid entry in the table. It has to be a placeholder // for lazy initialization. Handle tuple = Handle::cast(entry); @@ -632,10 +681,11 @@ void WasmTableObject::SetFunctionTablePlaceholder( } void WasmTableObject::GetFunctionTableEntry( - Isolate* isolate, Handle table, int entry_index, - bool* is_valid, bool* is_null, MaybeHandle* instance, - int* function_index, MaybeHandle* maybe_js_function) { - DCHECK(table->type().is_reference_to(wasm::HeapType::kFunc)); + Isolate* isolate, const WasmModule* module, Handle table, + int entry_index, bool* is_valid, bool* is_null, + MaybeHandle* instance, int* function_index, + MaybeHandle* maybe_js_function) { + DCHECK(wasm::IsSubtypeOf(table->type(), wasm::kWasmFuncRef, module)); DCHECK_LT(entry_index, table->current_length()); // We initialize {is_valid} with {true}. We may change it later. *is_valid = true; diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h index 64000e34c9..2e31797e67 100644 --- a/src/wasm/wasm-objects.h +++ b/src/wasm/wasm-objects.h @@ -271,10 +271,17 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { // through the out parameters {is_valid}, {is_null}, {instance}, // {function_index}, and {maybe_js_function}. static void GetFunctionTableEntry( - Isolate* isolate, Handle table, int entry_index, - bool* is_valid, bool* is_null, MaybeHandle* instance, + Isolate* isolate, const wasm::WasmModule* module, + Handle table, int entry_index, bool* is_valid, + bool* is_null, MaybeHandle* instance, int* function_index, MaybeHandle* maybe_js_function); + private: + static void SetFunctionTableEntry(Isolate* isolate, + Handle table, + Handle entries, int entry_index, + Handle entry); + OBJECT_CONSTRUCTORS(WasmTableObject, JSObject); }; diff --git a/test/mjsunit/wasm/reference-tables.js b/test/mjsunit/wasm/reference-tables.js new file mode 100644 index 0000000000..756ec04d44 --- /dev/null +++ b/test/mjsunit/wasm/reference-tables.js @@ -0,0 +1,91 @@ +// 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. + +// Flags: --experimental-wasm-typed-funcref + +load("test/mjsunit/wasm/wasm-module-builder.js"); + +(function Test1() { + var exporting_instance = (function () { + var builder = new WasmModuleBuilder(); + var binary_type = builder.addType(kSig_i_ii); + + builder.addFunction("addition", kSig_i_ii) + .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add]) + .exportFunc(); + + builder.addFunction("id", kSig_i_i) + .addBody([kExprLocalGet, 0]) + .exportFunc(); + + builder.addTable(wasmOptRefType(binary_type), 1, 100).exportAs("table"); + + return builder.instantiate({}); + })(); + + // Wrong type for imported table. + assertThrows( + () => { + var builder = new WasmModuleBuilder(); + var unary_type = builder.addType(kSig_i_i); + builder.addImportedTable("imports", "table", 1, 100, + wasmOptRefType(unary_type)); + builder.instantiate({imports: {table: exporting_instance.exports.table}}) + }, + WebAssembly.LinkError, + /imported table does not match the expected type/ + ) + + // Type for imported table must match exactly. + assertThrows( + () => { + var builder = new WasmModuleBuilder(); + builder.addImportedTable("imports", "table", 1, 100, kWasmFuncRef); + builder.instantiate({imports: {table: exporting_instance.exports.table}}) + }, + WebAssembly.LinkError, + /imported table does not match the expected type/ + ) + + var instance = (function () { + var builder = new WasmModuleBuilder(); + + var unary_type = builder.addType(kSig_i_i); + var binary_type = builder.addType(kSig_i_ii); + + builder.addImportedTable("imports", "table", 1, 100, + wasmOptRefType(binary_type)); + + var table = builder.addTable(wasmOptRefType(unary_type), 1) + .exportAs("table"); + builder.addTable(kWasmFuncRef, 1).exportAs("generic_table"); + + builder.addFunction("table_test", makeSig([wasmRefType(unary_type)], + [kWasmI32])) + // Set table[0] to input function, then retrieve it and call it. + .addBody([kExprI32Const, 0, kExprLocalGet, 0, kExprTableSet, table.index, + kExprI32Const, 42, kExprI32Const, 0, kExprTableGet, table.index, + kExprCallRef]) + .exportFunc(); + + // Instantiate with a table of the correct type. + return builder.instantiate( + {imports: {table: exporting_instance.exports.table}}); + })(); + + // This module is valid. + assertTrue(!!instance); + + // The correct function reference is preserved when setting it to and getting + // it back from a table. + assertEquals(42, instance.exports.table_test(exporting_instance.exports.id)); + + // Setting from JS API respects types. + instance.exports.generic_table.set(0, exporting_instance.exports.id); + instance.exports.table.set(0, exporting_instance.exports.id); + assertThrows( + () => instance.exports.table.set(0, exporting_instance.exports.addition), + TypeError, + /Argument 1 must be null or a WebAssembly function of type compatible to 'this'/); +})(); diff --git a/test/mjsunit/wasm/wasm-module-builder.js b/test/mjsunit/wasm/wasm-module-builder.js index 2c5d16a7ec..177550a270 100644 --- a/test/mjsunit/wasm/wasm-module-builder.js +++ b/test/mjsunit/wasm/wasm-module-builder.js @@ -105,7 +105,9 @@ let kWasmExternRef = 0x6f; function wasmOptRefType(index) { return {opcode: 0x6c, index: index}; } function wasmRefType(index) { return {opcode: 0x6b, index: index}; } let kWasmI31Ref = 0x6a; -function wasmRtt(index, depth) { return {opcode: 0x69, index: index, depth: depth}; } +function wasmRtt(index, depth) { + return {opcode: 0x69, index: index, depth: depth}; +} let kWasmExnRef = 0x68; let kExternalFunction = 0; @@ -1077,8 +1079,10 @@ class WasmModuleBuilder { } addExportOfKind(name, kind, index) { - if (index == undefined && kind != kExternalTable && kind != kExternalMemory) { - throw new Error('Index for exports other than tables/memories must be provided'); + if (index == undefined && kind != kExternalTable && + kind != kExternalMemory) { + throw new Error( + 'Index for exports other than tables/memories must be provided'); } if (index !== undefined && (typeof index) != 'number') { throw new Error('Index for exports must be a number') diff --git a/test/unittests/wasm/function-body-decoder-unittest.cc b/test/unittests/wasm/function-body-decoder-unittest.cc index 8e9944e50d..c6858114f0 100644 --- a/test/unittests/wasm/function-body-decoder-unittest.cc +++ b/test/unittests/wasm/function-body-decoder-unittest.cc @@ -1763,7 +1763,7 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsOutOfBounds) { ExpectFailure(sig, {WASM_CALL_INDIRECT(2, WASM_I32V_1(27), WASM_ZERO)}); } -TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs3) { +TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs1) { const FunctionSig* sig = sigs.i_i(); builder.InitializeTable(wasm::kWasmStmt); @@ -1784,6 +1784,34 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs3) { ExpectFailure(sig, {WASM_CALL_INDIRECT(sig1, WASM_F32(17.6), WASM_ZERO)}); } +TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs2) { + WASM_FEATURE_SCOPE(reftypes); + WASM_FEATURE_SCOPE(typed_funcref); + byte table_type_index = builder.AddSignature(sigs.i_i()); + byte table_index = + builder.InitializeTable(ValueType::Ref(table_type_index, kNullable)); + + ExpectValidates(sigs.i_v(), + {WASM_CALL_INDIRECT_TABLE(table_index, table_type_index, + WASM_I32V_1(42), WASM_ZERO)}); + + byte wrong_type_index = builder.AddSignature(sigs.i_ii()); + ExpectFailure(sigs.i_v(), + {WASM_CALL_INDIRECT_TABLE(table_index, wrong_type_index, + WASM_I32V_1(42), WASM_ZERO)}, + kAppendEnd, + "call_indirect: Immediate signature #1 is not a subtype of " + "immediate table #0"); + + byte non_function_table_index = builder.InitializeTable(kWasmExternRef); + ExpectFailure( + sigs.i_v(), + {WASM_CALL_INDIRECT_TABLE(non_function_table_index, table_type_index, + WASM_I32V_1(42), WASM_ZERO)}, + kAppendEnd, + "call_indirect: immediate table #1 is not of a function type"); +} + TEST_F(FunctionBodyDecoderTest, IndirectCallsWithoutTableCrash) { const FunctionSig* sig = sigs.i_i(); @@ -1962,15 +1990,24 @@ TEST_F(FunctionBodyDecoderTest, AllSetGlobalCombinations) { TEST_F(FunctionBodyDecoderTest, TableSet) { WASM_FEATURE_SCOPE(reftypes); + WASM_FEATURE_SCOPE(typed_funcref); + + byte tab_type = builder.AddSignature(sigs.i_i()); byte tab_ref1 = builder.AddTable(kWasmExternRef, 10, true, 20); byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30); byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20); byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20); - ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32}; - FunctionSig sig(0, 3, sig_types); + byte tab_typed_func = + builder.AddTable(ValueType::Ref(tab_type, kNullable), 10, false, 20); + + ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32, + ValueType::Ref(tab_type, kNonNullable)}; + FunctionSig sig(0, 4, sig_types); byte local_ref = 0; byte local_func = 1; byte local_int = 2; + byte local_typed_func = 3; + ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(6), WASM_GET_LOCAL(local_ref))}); ExpectValidates(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(5), @@ -1979,6 +2016,10 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { WASM_GET_LOCAL(local_func))}); ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref2, WASM_I32V(8), WASM_GET_LOCAL(local_ref))}); + ExpectValidates(&sig, {WASM_TABLE_SET(tab_typed_func, WASM_I32V(8), + WASM_GET_LOCAL(local_typed_func))}); + ExpectValidates(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(8), + WASM_GET_LOCAL(local_typed_func))}); // Only values of the correct type can be set to a table. ExpectFailure(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(4), @@ -1993,6 +2034,8 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { WASM_GET_LOCAL(local_int))}); ExpectFailure(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(3), WASM_GET_LOCAL(local_int))}); + ExpectFailure(&sig, {WASM_TABLE_SET(tab_typed_func, WASM_I32V(3), + WASM_GET_LOCAL(local_func))}); // Out-of-bounds table index should fail. byte oob_tab = 37; @@ -2004,15 +2047,24 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { TEST_F(FunctionBodyDecoderTest, TableGet) { WASM_FEATURE_SCOPE(reftypes); + WASM_FEATURE_SCOPE(typed_funcref); + + byte tab_type = builder.AddSignature(sigs.i_i()); byte tab_ref1 = builder.AddTable(kWasmExternRef, 10, true, 20); byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30); byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20); byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20); - ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32}; - FunctionSig sig(0, 3, sig_types); + byte tab_typed_func = + builder.AddTable(ValueType::Ref(tab_type, kNullable), 10, false, 20); + + ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32, + ValueType::Ref(tab_type, kNullable)}; + FunctionSig sig(0, 4, sig_types); byte local_ref = 0; byte local_func = 1; byte local_int = 2; + byte local_typed_func = 3; + ExpectValidates( &sig, {WASM_SET_LOCAL(local_ref, WASM_TABLE_GET(tab_ref1, WASM_I32V(6)))}); @@ -2028,6 +2080,12 @@ TEST_F(FunctionBodyDecoderTest, TableGet) { ExpectValidates( &sig, {WASM_SET_LOCAL(local_ref, WASM_SEQ(WASM_I32V(6), kExprTableGet, U32V_2(tab_ref1)))}); + ExpectValidates( + &sig, {WASM_SET_LOCAL(local_func, + WASM_TABLE_GET(tab_typed_func, WASM_I32V(7)))}); + ExpectValidates( + &sig, {WASM_SET_LOCAL(local_typed_func, + WASM_TABLE_GET(tab_typed_func, WASM_I32V(7)))}); // We cannot store references as any other type. ExpectFailure(&sig, {WASM_SET_LOCAL(local_func, @@ -2043,6 +2101,10 @@ TEST_F(FunctionBodyDecoderTest, TableGet) { WASM_TABLE_GET(tab_ref1, WASM_I32V(9)))}); ExpectFailure(&sig, {WASM_SET_LOCAL( local_int, WASM_TABLE_GET(tab_func1, WASM_I32V(3)))}); + ExpectFailure(&sig, + {WASM_SET_LOCAL(local_typed_func, + WASM_TABLE_GET(tab_func1, WASM_I32V(3)))}); + // Out-of-bounds table index should fail. byte oob_tab = 37; ExpectFailure( diff --git a/test/unittests/wasm/module-decoder-unittest.cc b/test/unittests/wasm/module-decoder-unittest.cc index 5120a2a865..9a7570236a 100644 --- a/test/unittests/wasm/module-decoder-unittest.cc +++ b/test/unittests/wasm/module-decoder-unittest.cc @@ -1827,6 +1827,56 @@ TEST_F(WasmModuleVerifyTest, MultipleTablesWithFlag) { EXPECT_EQ(kWasmExternRef, result.value()->tables[1].type); } +TEST_F(WasmModuleVerifyTest, TypedFunctionTable) { + WASM_FEATURE_SCOPE(reftypes); + WASM_FEATURE_SCOPE(typed_funcref); + + static const byte data[] = { + SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_x(kLocalI32)), + SECTION(Table, // table section + ENTRY_COUNT(1), // 1 table + kLocalOptRef, 0, // table 0: type + 0, 10)}; // table 0: limits + + ModuleResult result = DecodeModule(data, data + sizeof(data)); + EXPECT_OK(result); + EXPECT_EQ(ValueType::Ref(0, kNullable), result.value()->tables[0].type); +} + +TEST_F(WasmModuleVerifyTest, IllegalTableTypes) { + WASM_FEATURE_SCOPE(reftypes); + WASM_FEATURE_SCOPE(typed_funcref); + WASM_FEATURE_SCOPE(gc); + + using Vec = std::vector; + + static Vec table_types[] = {{kLocalOptRef, 0}, + {kLocalOptRef, 1}, + {kLocalOptRef, kLocalI31Ref}, + {kLocalI31Ref}, + {kLocalRtt, 2, kLocalFuncRef}}; + + for (Vec type : table_types) { + Vec data = { + SECTION(Type, ENTRY_COUNT(2), + WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kLocalI32, true)), + WASM_ARRAY_DEF(kLocalI32, true)), + kTableSectionCode, static_cast(type.size() + 3), byte{1}}; + // Last elements are section size and entry count + + // Add table type + data.insert(data.end(), type.begin(), type.end()); + // Add table limits + data.insert(data.end(), {byte{0}, byte{10}}); + + auto result = DecodeModule(data.data(), data.data() + data.size()); + + EXPECT_NOT_OK(result, + "Currently, only nullable exnref, externref, and " + "function references are allowed as table types"); + } +} + TEST_F(WasmModuleVerifyTest, TieringCompilationHints) { WASM_FEATURE_SCOPE(compilation_hints); static const byte data[] = {