[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:
parent
42db3676ff
commit
69ca751bc8
@ -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);
|
||||
|
@ -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_);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
91
test/mjsunit/wasm/reference-tables.js
Normal file
91
test/mjsunit/wasm/reference-tables.js
Normal 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'/);
|
||||
})();
|
@ -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')
|
||||
|
@ -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(
|
||||
|
@ -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[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user