Commit 69ca751b authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[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: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70032}
parent 42db3676
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "src/wasm/wasm-debug.h" #include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/wasm/wasm-value.h" #include "src/wasm/wasm-value.h"
namespace v8 { namespace v8 {
...@@ -334,7 +335,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) { ...@@ -334,7 +335,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) {
auto table = handle( auto table = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate); WasmTableObject::cast(instance->tables().get(table_index)), isolate);
// We only use the runtime call for lazily initialized function references. // 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)) { if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
...@@ -357,7 +362,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) { ...@@ -357,7 +362,11 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) {
auto table = handle( auto table = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate); WasmTableObject::cast(instance->tables().get(table_index)), isolate);
// We only use the runtime call for function references. // 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)) { if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds); return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
......
...@@ -575,7 +575,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() { ...@@ -575,7 +575,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// iteration below. // iteration below.
for (int i = 1; i < table_count; ++i) { for (int i = 1; i < table_count; ++i) {
const WasmTable& table = module_->tables[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 = Handle<WasmIndirectFunctionTable> table_obj =
WasmIndirectFunctionTable::New(isolate_, table.initial_size); WasmIndirectFunctionTable::New(isolate_, table.initial_size);
tables->set(i, *table_obj); tables->set(i, *table_obj);
...@@ -1091,9 +1091,9 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable( ...@@ -1091,9 +1091,9 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable(
MaybeHandle<WasmInstanceObject> maybe_target_instance; MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index; int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function; MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(isolate_, table_object, i, &is_valid, WasmTableObject::GetFunctionTableEntry(
&is_null, &maybe_target_instance, isolate_, module_, table_object, i, &is_valid, &is_null,
&function_index, &maybe_js_function); &maybe_target_instance, &function_index, &maybe_js_function);
if (!is_valid) { if (!is_valid) {
thrower_->LinkError("table import %d[%d] is not a wasm function", thrower_->LinkError("table import %d[%d] is not a wasm function",
import_index, i); import_index, i);
...@@ -1167,13 +1167,19 @@ bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance, ...@@ -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", ReportLinkError("imported table does not match the expected type",
import_index, module_name, import_name); import_index, module_name, import_name);
return false; return false;
} }
if (table.type.is_reference_to(HeapType::kFunc) && if (IsSubtypeOf(table.type, kWasmFuncRef, module_) &&
!InitializeImportedIndirectFunctionTable(instance, table_index, !InitializeImportedIndirectFunctionTable(instance, table_index,
import_index, table_object)) { import_index, table_object)) {
return false; return false;
...@@ -1874,7 +1880,7 @@ void InstanceBuilder::InitializeIndirectFunctionTables( ...@@ -1874,7 +1880,7 @@ void InstanceBuilder::InitializeIndirectFunctionTables(
for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) { for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) {
const WasmTable& table = module_->tables[i]; const WasmTable& table = module_->tables[i];
if (table.type.is_reference_to(HeapType::kFunc)) { if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, i, table.initial_size); instance, i, table.initial_size);
} }
...@@ -1905,7 +1911,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance, ...@@ -1905,7 +1911,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance,
int entry_index = static_cast<int>(dst + i); int entry_index = static_cast<int>(dst + i);
if (func_index == WasmElemSegment::kNullIndex) { 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(); IndirectFunctionTableEntry(instance, table_index, entry_index).clear();
} }
WasmTableObject::Set(isolate, table_object, entry_index, WasmTableObject::Set(isolate, table_object, entry_index,
...@@ -1916,8 +1922,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance, ...@@ -1916,8 +1922,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance,
const WasmFunction* function = &module->functions[func_index]; const WasmFunction* function = &module->functions[func_index];
// Update the local dispatch table first if necessary. // Update the local dispatch table first if necessary.
// TODO(9495): Make sure tables work with all function types. if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) {
if (table_object->type().is_reference_to(HeapType::kFunc)) {
uint32_t sig_id = module->signature_ids[function->sig_index]; uint32_t sig_id = module->signature_ids[function->sig_index];
IndirectFunctionTableEntry(instance, table_index, entry_index) IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, instance, func_index); .Set(sig_id, instance, func_index);
...@@ -1992,7 +1997,7 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) { ...@@ -1992,7 +1997,7 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
int table_count = static_cast<int>(module_->tables.size()); int table_count = static_cast<int>(module_->tables.size());
for (int index = 0; index < table_count; ++index) { 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( auto table_object = handle(
WasmTableObject::cast(instance->tables().get(index)), isolate_); WasmTableObject::cast(instance->tables().get(index)), isolate_);
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "src/wasm/wasm-module.h" #include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes-inl.h" #include "src/wasm/wasm-opcodes-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/wasm/wasm-value.h" #include "src/wasm/wasm-value.h"
#include "src/zone/accounting-allocator.h" #include "src/zone/accounting-allocator.h"
...@@ -60,7 +61,8 @@ MaybeHandle<JSObject> CreateFunctionTablesObject( ...@@ -60,7 +61,8 @@ MaybeHandle<JSObject> CreateFunctionTablesObject(
for (int table_index = 0; table_index < tables->length(); ++table_index) { for (int table_index = 0; table_index < tables->length(); ++table_index) {
auto func_table = auto func_table =
handle(WasmTableObject::cast(tables->get(table_index)), isolate); 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; Handle<String> table_name;
if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index) if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index)
......
...@@ -1686,7 +1686,9 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -1686,7 +1686,9 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::Object> element = Utils::OpenHandle(*args[1]); i::Handle<i::Object> element = Utils::OpenHandle(*args[1]);
if (!i::WasmTableObject::IsValidElement(i_isolate, table_object, element)) { 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; return;
} }
i::WasmTableObject::Set(i_isolate, table_object, index, element); i::WasmTableObject::Set(i_isolate, table_object, index, element);
......
...@@ -399,23 +399,13 @@ bool WasmTableObject::IsValidElement(Isolate* isolate, ...@@ -399,23 +399,13 @@ bool WasmTableObject::IsValidElement(Isolate* isolate,
: nullptr; : nullptr;
return wasm::TypecheckJSObject(isolate, module, entry, table->type(), return wasm::TypecheckJSObject(isolate, module, entry, table->type(),
&error_message); &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)) { if (entry->IsNull(isolate)) {
ClearDispatchTables(isolate, table, entry_index); // Degenerate case. ClearDispatchTables(isolate, table, entry_index); // Degenerate case.
entries->set(entry_index, ReadOnlyRoots(isolate).null_value()); entries->set(entry_index, ReadOnlyRoots(isolate).null_value());
...@@ -443,6 +433,44 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table, ...@@ -443,6 +433,44 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
entries->set(entry_index, *entry); 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<Object> WasmTableObject::Get(Isolate* isolate,
Handle<WasmTableObject> table, Handle<WasmTableObject> table,
uint32_t index) { uint32_t index) {
...@@ -455,21 +483,42 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate, ...@@ -455,21 +483,42 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate,
Handle<Object> entry(entries->get(entry_index), isolate); Handle<Object> entry(entries->get(entry_index), isolate);
// First we handle the easy externref and exnref table case. if (entry->IsNull(isolate)) {
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; return entry;
} }
if (entry->IsNull(isolate)) { switch (table->type().heap_representation()) {
return entry; 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 // {entry} is not a valid entry in the table. It has to be a placeholder
...@@ -632,10 +681,11 @@ void WasmTableObject::SetFunctionTablePlaceholder( ...@@ -632,10 +681,11 @@ void WasmTableObject::SetFunctionTablePlaceholder(
} }
void WasmTableObject::GetFunctionTableEntry( void WasmTableObject::GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index, Isolate* isolate, const WasmModule* module, Handle<WasmTableObject> table,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance, int entry_index, bool* is_valid, bool* is_null,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function) { MaybeHandle<WasmInstanceObject>* instance, int* function_index,
DCHECK(table->type().is_reference_to(wasm::HeapType::kFunc)); MaybeHandle<WasmJSFunction>* maybe_js_function) {
DCHECK(wasm::IsSubtypeOf(table->type(), wasm::kWasmFuncRef, module));
DCHECK_LT(entry_index, table->current_length()); DCHECK_LT(entry_index, table->current_length());
// We initialize {is_valid} with {true}. We may change it later. // We initialize {is_valid} with {true}. We may change it later.
*is_valid = true; *is_valid = true;
......
...@@ -271,10 +271,17 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { ...@@ -271,10 +271,17 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
// through the out parameters {is_valid}, {is_null}, {instance}, // through the out parameters {is_valid}, {is_null}, {instance},
// {function_index}, and {maybe_js_function}. // {function_index}, and {maybe_js_function}.
static void GetFunctionTableEntry( static void GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index, Isolate* isolate, const wasm::WasmModule* module,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance, Handle<WasmTableObject> table, int entry_index, bool* is_valid,
bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function); 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); OBJECT_CONSTRUCTORS(WasmTableObject, JSObject);
}; };
......
// 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; ...@@ -105,7 +105,9 @@ let kWasmExternRef = 0x6f;
function wasmOptRefType(index) { return {opcode: 0x6c, index: index}; } function wasmOptRefType(index) { return {opcode: 0x6c, index: index}; }
function wasmRefType(index) { return {opcode: 0x6b, index: index}; } function wasmRefType(index) { return {opcode: 0x6b, index: index}; }
let kWasmI31Ref = 0x6a; 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 kWasmExnRef = 0x68;
let kExternalFunction = 0; let kExternalFunction = 0;
...@@ -1077,8 +1079,10 @@ class WasmModuleBuilder { ...@@ -1077,8 +1079,10 @@ class WasmModuleBuilder {
} }
addExportOfKind(name, kind, index) { addExportOfKind(name, kind, index) {
if (index == undefined && kind != kExternalTable && kind != kExternalMemory) { if (index == undefined && kind != kExternalTable &&
throw new Error('Index for exports other than tables/memories must be provided'); kind != kExternalMemory) {
throw new Error(
'Index for exports other than tables/memories must be provided');
} }
if (index !== undefined && (typeof index) != 'number') { if (index !== undefined && (typeof index) != 'number') {
throw new Error('Index for exports must be a number') throw new Error('Index for exports must be a number')
......
...@@ -1763,7 +1763,7 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsOutOfBounds) { ...@@ -1763,7 +1763,7 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsOutOfBounds) {
ExpectFailure(sig, {WASM_CALL_INDIRECT(2, WASM_I32V_1(27), WASM_ZERO)}); 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(); const FunctionSig* sig = sigs.i_i();
builder.InitializeTable(wasm::kWasmStmt); builder.InitializeTable(wasm::kWasmStmt);
...@@ -1784,6 +1784,34 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs3) { ...@@ -1784,6 +1784,34 @@ TEST_F(FunctionBodyDecoderTest, IndirectCallsWithMismatchedSigs3) {
ExpectFailure(sig, {WASM_CALL_INDIRECT(sig1, WASM_F32(17.6), WASM_ZERO)}); 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) { TEST_F(FunctionBodyDecoderTest, IndirectCallsWithoutTableCrash) {
const FunctionSig* sig = sigs.i_i(); const FunctionSig* sig = sigs.i_i();
...@@ -1962,15 +1990,24 @@ TEST_F(FunctionBodyDecoderTest, AllSetGlobalCombinations) { ...@@ -1962,15 +1990,24 @@ TEST_F(FunctionBodyDecoderTest, AllSetGlobalCombinations) {
TEST_F(FunctionBodyDecoderTest, TableSet) { TEST_F(FunctionBodyDecoderTest, TableSet) {
WASM_FEATURE_SCOPE(reftypes); 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_ref1 = builder.AddTable(kWasmExternRef, 10, true, 20);
byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30); byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30);
byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20); byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20);
byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20); byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20);
ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32}; byte tab_typed_func =
FunctionSig sig(0, 3, sig_types); 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_ref = 0;
byte local_func = 1; byte local_func = 1;
byte local_int = 2; byte local_int = 2;
byte local_typed_func = 3;
ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(6), ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(6),
WASM_GET_LOCAL(local_ref))}); WASM_GET_LOCAL(local_ref))});
ExpectValidates(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(5), ExpectValidates(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(5),
...@@ -1979,6 +2016,10 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { ...@@ -1979,6 +2016,10 @@ TEST_F(FunctionBodyDecoderTest, TableSet) {
WASM_GET_LOCAL(local_func))}); WASM_GET_LOCAL(local_func))});
ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref2, WASM_I32V(8), ExpectValidates(&sig, {WASM_TABLE_SET(tab_ref2, WASM_I32V(8),
WASM_GET_LOCAL(local_ref))}); 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. // Only values of the correct type can be set to a table.
ExpectFailure(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(4), ExpectFailure(&sig, {WASM_TABLE_SET(tab_ref1, WASM_I32V(4),
...@@ -1993,6 +2034,8 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { ...@@ -1993,6 +2034,8 @@ TEST_F(FunctionBodyDecoderTest, TableSet) {
WASM_GET_LOCAL(local_int))}); WASM_GET_LOCAL(local_int))});
ExpectFailure(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(3), ExpectFailure(&sig, {WASM_TABLE_SET(tab_func1, WASM_I32V(3),
WASM_GET_LOCAL(local_int))}); 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. // Out-of-bounds table index should fail.
byte oob_tab = 37; byte oob_tab = 37;
...@@ -2004,15 +2047,24 @@ TEST_F(FunctionBodyDecoderTest, TableSet) { ...@@ -2004,15 +2047,24 @@ TEST_F(FunctionBodyDecoderTest, TableSet) {
TEST_F(FunctionBodyDecoderTest, TableGet) { TEST_F(FunctionBodyDecoderTest, TableGet) {
WASM_FEATURE_SCOPE(reftypes); 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_ref1 = builder.AddTable(kWasmExternRef, 10, true, 20);
byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30); byte tab_func1 = builder.AddTable(kWasmFuncRef, 20, true, 30);
byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20); byte tab_func2 = builder.AddTable(kWasmFuncRef, 10, false, 20);
byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20); byte tab_ref2 = builder.AddTable(kWasmExternRef, 10, false, 20);
ValueType sig_types[]{kWasmExternRef, kWasmFuncRef, kWasmI32}; byte tab_typed_func =
FunctionSig sig(0, 3, sig_types); 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_ref = 0;
byte local_func = 1; byte local_func = 1;
byte local_int = 2; byte local_int = 2;
byte local_typed_func = 3;
ExpectValidates( ExpectValidates(
&sig, &sig,
{WASM_SET_LOCAL(local_ref, WASM_TABLE_GET(tab_ref1, WASM_I32V(6)))}); {WASM_SET_LOCAL(local_ref, WASM_TABLE_GET(tab_ref1, WASM_I32V(6)))});
...@@ -2028,6 +2080,12 @@ TEST_F(FunctionBodyDecoderTest, TableGet) { ...@@ -2028,6 +2080,12 @@ TEST_F(FunctionBodyDecoderTest, TableGet) {
ExpectValidates( ExpectValidates(
&sig, {WASM_SET_LOCAL(local_ref, WASM_SEQ(WASM_I32V(6), kExprTableGet, &sig, {WASM_SET_LOCAL(local_ref, WASM_SEQ(WASM_I32V(6), kExprTableGet,
U32V_2(tab_ref1)))}); 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. // We cannot store references as any other type.
ExpectFailure(&sig, {WASM_SET_LOCAL(local_func, ExpectFailure(&sig, {WASM_SET_LOCAL(local_func,
...@@ -2043,6 +2101,10 @@ TEST_F(FunctionBodyDecoderTest, TableGet) { ...@@ -2043,6 +2101,10 @@ TEST_F(FunctionBodyDecoderTest, TableGet) {
WASM_TABLE_GET(tab_ref1, WASM_I32V(9)))}); WASM_TABLE_GET(tab_ref1, WASM_I32V(9)))});
ExpectFailure(&sig, {WASM_SET_LOCAL( ExpectFailure(&sig, {WASM_SET_LOCAL(
local_int, WASM_TABLE_GET(tab_func1, WASM_I32V(3)))}); 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. // Out-of-bounds table index should fail.
byte oob_tab = 37; byte oob_tab = 37;
ExpectFailure( ExpectFailure(
......
...@@ -1827,6 +1827,56 @@ TEST_F(WasmModuleVerifyTest, MultipleTablesWithFlag) { ...@@ -1827,6 +1827,56 @@ TEST_F(WasmModuleVerifyTest, MultipleTablesWithFlag) {
EXPECT_EQ(kWasmExternRef, result.value()->tables[1].type); 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) { TEST_F(WasmModuleVerifyTest, TieringCompilationHints) {
WASM_FEATURE_SCOPE(compilation_hints); WASM_FEATURE_SCOPE(compilation_hints);
static const byte data[] = { static const byte data[] = {
......
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