[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 <manoskouk@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70032}
This commit is contained in:
Manos Koukoutos 2020-09-21 14:38:49 +00:00 committed by Commit Bot
parent 42db3676ff
commit 69ca751bc8
10 changed files with 340 additions and 58 deletions

View File

@ -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);

View File

@ -575,7 +575,7 @@ MaybeHandle<WasmInstanceObject> 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<WasmIndirectFunctionTable> table_obj =
WasmIndirectFunctionTable::New(isolate_, table.initial_size);
tables->set(i, *table_obj);
@ -1091,9 +1091,9 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable(
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> 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<WasmInstanceObject> 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<int>(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<WasmInstanceObject> instance,
int entry_index = static_cast<int>(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<WasmInstanceObject> 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<WasmInstanceObject> instance) {
int table_count = static_cast<int>(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_);

View File

@ -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<JSObject> 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<String> table_name;
if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index)

View File

@ -1686,7 +1686,9 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::Object> 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);

View File

@ -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<WasmTableObject> table,
uint32_t index, Handle<Object> entry) {
// Callers need to perform bounds checks, type check, and error handling.
DCHECK(IsInBounds(isolate, table, index));
DCHECK(IsValidElement(isolate, table, entry));
Handle<FixedArray> entries(table->entries(), isolate);
// The FixedArray is addressed with int's.
int entry_index = static_cast<int>(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<WasmTableObject> table,
Handle<FixedArray> entries,
int entry_index,
Handle<Object> 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<WasmTableObject> table,
entries->set(entry_index, *entry);
}
void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index, Handle<Object> entry) {
// Callers need to perform bounds checks, type check, and error handling.
DCHECK(IsInBounds(isolate, table, index));
DCHECK(IsValidElement(isolate, table, entry));
Handle<FixedArray> entries(table->entries(), isolate);
// The FixedArray is addressed with int's.
int entry_index = static_cast<int>(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<Object> WasmTableObject::Get(Isolate* isolate,
Handle<WasmTableObject> table,
uint32_t index) {
@ -455,23 +483,44 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate,
Handle<Object> 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<Tuple2> tuple = Handle<Tuple2>::cast(entry);
@ -632,10 +681,11 @@ void WasmTableObject::SetFunctionTablePlaceholder(
}
void WasmTableObject::GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function) {
DCHECK(table->type().is_reference_to(wasm::HeapType::kFunc));
Isolate* isolate, const WasmModule* module, Handle<WasmTableObject> table,
int entry_index, bool* is_valid, bool* is_null,
MaybeHandle<WasmInstanceObject>* instance, int* function_index,
MaybeHandle<WasmJSFunction>* 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;

View File

@ -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<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
Isolate* isolate, const wasm::WasmModule* module,
Handle<WasmTableObject> table, int entry_index, bool* is_valid,
bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function);
private:
static void SetFunctionTableEntry(Isolate* isolate,
Handle<WasmTableObject> table,
Handle<FixedArray> entries, int entry_index,
Handle<Object> entry);
OBJECT_CONSTRUCTORS(WasmTableObject, JSObject);
};

View File

@ -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'/);
})();

View File

@ -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')

View File

@ -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(

View File

@ -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<byte>;
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<byte>(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[] = {