Commit 4cbc5a4d authored by Andreas Haas's avatar Andreas Haas Committed by Commit Bot

[wasm][anyref] Implement WebAssembly.Table.[get|set]

This Cl adds a type to {WasmTableObject}, and extends
{WasmTableObject::Set} and {WasmTableObject::Get} to support anyref
tables. I did it in one CL so that I can write tests.

R=mstarzinger@chromium.org

Bug: v8:7581
Change-Id: I6c6d78f84715a7805f7bb881a63d3c1174f6a6ab
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1511332Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60206}
parent de7ab39a
......@@ -393,7 +393,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
for (int i = module_->num_imported_tables; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
Handle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_, table.initial_size, table.maximum_size, nullptr);
isolate_, table.type, table.initial_size, table.maximum_size, nullptr);
tables->set(i, *table_obj);
}
instance->set_tables(*tables);
......@@ -864,34 +864,26 @@ bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance,
// Initialize the dispatch table with the (foreign) JS functions
// that are already in the table.
for (int i = 0; i < imported_table_size; ++i) {
// TODO(ahaas): Extract this code here into a function on WasmTableObject.
Handle<Object> val(table_object->elements()->get(i), isolate_);
Handle<WasmInstanceObject> target_instance;
bool is_valid;
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
FunctionSig* sig;
if (val->IsNull(isolate_)) {
continue;
} else if (WasmExportedFunction::IsWasmExportedFunction(*val)) {
auto target_func = Handle<WasmExportedFunction>::cast(val);
target_instance = handle(target_func->instance(), isolate_);
sig = target_func->sig();
function_index = target_func->function_index();
} else if (val->IsTuple2()) {
// {val} can be a {Tuple2} if no WasmExportedFunction has been
// constructed for the function yet, but the function exists.
auto tuple = Handle<Tuple2>::cast(val);
target_instance =
handle(WasmInstanceObject::cast(tuple->value1()), isolate_);
function_index = Smi::cast(tuple->value2()).value();
sig = target_instance->module_object()
->module()
->functions[function_index]
.sig;
} else {
WasmTableObject::GetFunctionTableEntry(isolate_, table_object, i, &is_valid,
&is_null, &maybe_target_instance,
&function_index);
if (!is_valid) {
thrower_->LinkError("table import %d[%d] is not a wasm function",
import_index, i);
return false;
}
if (is_null) continue;
Handle<WasmInstanceObject> target_instance =
maybe_target_instance.ToHandleChecked();
FunctionSig* sig = target_instance->module_object()
->module()
->functions[function_index]
.sig;
// Look up the signature's canonical id. If there is no canonical
// id, then the signature does not appear at all in this module,
......@@ -1518,7 +1510,7 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance,
if (func_index == WasmElemSegment::kNullIndex) {
IndirectFunctionTableEntry(instance, entry_index).clear();
WasmTableObject::Set(isolate, table_object, entry_index,
Handle<JSFunction>::null());
isolate->factory()->null_value());
continue;
}
......@@ -1543,12 +1535,8 @@ bool LoadElemSegmentImpl(Isolate* isolate, Handle<WasmInstanceObject> instance,
func_index, js_to_wasm_cache);
table_object->elements()->set(entry_index, *function);
} else {
// Put (instance, func_index) as a placeholder into the table_index.
// The {WasmExportedFunction} will be created lazily.
Handle<Tuple2> tuple = isolate->factory()->NewTuple2(
instance, Handle<Smi>(Smi::FromInt(func_index), isolate),
AllocationType::kYoung);
table_object->elements()->set(entry_index, *tuple);
WasmTableObject::SetFunctionTablePlaceholder(
isolate, table_object, entry_index, instance, func_index);
}
} else {
table_object->elements()->set(entry_index,
......
......@@ -1027,6 +1027,7 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
i::wasm::ValueType type;
// The descriptor's 'element'.
{
v8::MaybeLocal<v8::Value> maybe =
......@@ -1035,7 +1036,13 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (!maybe.ToLocal(&value)) return;
v8::Local<v8::String> string;
if (!value->ToString(context).ToLocal(&string)) return;
if (!string->StringEquals(v8_str(isolate, "anyfunc"))) {
auto enabled_features = i::wasm::WasmFeaturesFromFlags();
if (string->StringEquals(v8_str(isolate, "anyfunc"))) {
type = i::wasm::kWasmAnyFunc;
} else if (enabled_features.anyref &&
string->StringEquals(v8_str(isolate, "anyref"))) {
type = i::wasm::kWasmAnyRef;
} else {
thrower.TypeError("Descriptor property 'element' must be 'anyfunc'");
return;
}
......@@ -1057,7 +1064,7 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::FixedArray> fixed_array;
i::Handle<i::JSObject> table_obj =
i::WasmTableObject::New(i_isolate, static_cast<uint32_t>(initial),
i::WasmTableObject::New(i_isolate, type, static_cast<uint32_t>(initial),
static_cast<uint32_t>(maximum), &fixed_array);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(table_obj));
......@@ -1403,31 +1410,24 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.set()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
EXTRACT_THIS(table_object, WasmTableObject);
// Parameter 0.
uint32_t index;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &index)) {
return;
}
// Parameter 1.
i::Handle<i::Object> value = Utils::OpenHandle(*args[1]);
if (!value->IsNull(i_isolate) &&
!i::WasmExportedFunction::IsWasmExportedFunction(*value)) {
thrower.TypeError("Argument 1 must be null or a WebAssembly function");
if (!i::WasmTableObject::IsInBounds(i_isolate, table_object, index)) {
thrower.RangeError("invalid index %u into function table", index);
return;
}
if (index >= static_cast<uint64_t>(receiver->elements()->length())) {
thrower.RangeError("index out of bounds");
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");
return;
}
i::WasmTableObject::Set(i_isolate, receiver, index,
value->IsNull(i_isolate)
? i::Handle<i::JSFunction>::null()
: i::Handle<i::JSFunction>::cast(value));
i::WasmTableObject::Set(i_isolate, table_object, index, element);
}
// WebAssembly.Memory.grow(num) -> num
......
......@@ -108,6 +108,7 @@ bool WasmModuleObject::is_asm_js() {
ACCESSORS(WasmTableObject, elements, FixedArray, kElementsOffset)
ACCESSORS(WasmTableObject, maximum_length, Object, kMaximumLengthOffset)
ACCESSORS(WasmTableObject, dispatch_tables, FixedArray, kDispatchTablesOffset)
SMI_ACCESSORS(WasmTableObject, raw_type, kRawTypeOffset)
// WasmMemoryObject
ACCESSORS(WasmMemoryObject, array_buffer, JSArrayBuffer, kArrayBufferOffset)
......@@ -301,6 +302,10 @@ OPTIONAL_ACCESSORS(WasmDebugInfo, c_wasm_entry_map, Managed<wasm::SignatureMap>,
uint32_t WasmTableObject::current_length() { return elements()->length(); }
wasm::ValueType WasmTableObject::type() {
return static_cast<wasm::ValueType>(raw_type());
}
bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; }
// WasmExceptionTag
......
......@@ -21,6 +21,7 @@
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
......@@ -777,9 +778,10 @@ bool WasmModuleObject::GetPositionInfo(uint32_t position,
return true;
}
Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
uint32_t maximum,
Handle<FixedArray>* js_functions) {
Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate,
wasm::ValueType type,
uint32_t initial, uint32_t maximum,
Handle<FixedArray>* elements) {
Handle<JSFunction> table_ctor(
isolate->native_context()->wasm_table_constructor(), isolate);
auto table_obj = Handle<WasmTableObject>::cast(
......@@ -790,13 +792,15 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
for (int i = 0; i < static_cast<int>(initial); ++i) {
backing_store->set(i, null);
}
table_obj->set_raw_type(static_cast<int>(type));
table_obj->set_elements(*backing_store);
Handle<Object> max = isolate->factory()->NewNumberFromUint(maximum);
table_obj->set_maximum_length(*max);
table_obj->set_dispatch_tables(ReadOnlyRoots(isolate).empty_fixed_array());
if (js_functions != nullptr) {
*js_functions = backing_store;
if (elements != nullptr) {
*elements = backing_store;
}
return Handle<WasmTableObject>::cast(table_obj);
}
......@@ -857,16 +861,38 @@ bool WasmTableObject::IsInBounds(Isolate* isolate,
static_cast<int>(entry_index) < table->elements()->length());
}
bool WasmTableObject::IsValidElement(Isolate* isolate,
Handle<WasmTableObject> table,
Handle<Object> element) {
// Anyref tables take everything.
if (table->type() == wasm::kWasmAnyRef) return true;
// Anyfunc tables can store {null} or {WasmExportedFunction} objects.
if (element->IsNull(isolate)) return true;
return WasmExportedFunction::IsWasmExportedFunction(*element);
}
void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t table_index, Handle<JSFunction> function) {
Handle<FixedArray> array(table->elements(), isolate);
if (function.is_null()) {
uint32_t index, Handle<Object> element) {
// Callers need to perform bounds checks, type check, and error handling.
DCHECK(IsInBounds(isolate, table, index));
DCHECK(IsValidElement(isolate, table, element));
Handle<FixedArray> elements(table->elements(), isolate);
// The FixedArray is addressed with int's.
int table_index = static_cast<int>(index);
if (table->type() == wasm::kWasmAnyRef) {
elements->set(table_index, *element);
return;
}
if (element->IsNull(isolate)) {
ClearDispatchTables(isolate, table, table_index); // Degenerate case.
array->set(table_index, ReadOnlyRoots(isolate).null_value());
elements->set(table_index, ReadOnlyRoots(isolate).null_value());
return;
}
auto exported_function = Handle<WasmExportedFunction>::cast(function);
DCHECK(WasmExportedFunction::IsWasmExportedFunction(*element));
auto exported_function = Handle<WasmExportedFunction>::cast(element);
Handle<WasmInstanceObject> target_instance(exported_function->instance(),
isolate);
int func_index = exported_function->function_index();
......@@ -876,19 +902,25 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
UpdateDispatchTables(isolate, table, table_index, wasm_function->sig,
handle(exported_function->instance(), isolate),
func_index);
array->set(table_index, *function);
elements->set(table_index, *element);
}
Handle<Object> WasmTableObject::Get(Isolate* isolate,
Handle<WasmTableObject> table,
uint32_t index) {
Handle<FixedArray> elements(table->elements(), isolate);
// Callers need to perform bounds checks and error handling.
DCHECK(IsInBounds(isolate, table, index));
// The FixedArray is addressed with int's.
int entry_index = static_cast<int>(index);
Handle<Object> element(elements->get(entry_index), isolate);
// First we handle the easy anyref table case.
if (table->type() == wasm::kWasmAnyRef) return element;
// Now we handle the anyfunc case.
if (WasmExportedFunction::IsWasmExportedFunction(*element)) {
return element;
}
......@@ -973,6 +1005,44 @@ void WasmTableObject::ClearDispatchTables(Isolate* isolate,
}
}
void WasmTableObject::SetFunctionTablePlaceholder(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
Handle<WasmInstanceObject> instance, int func_index) {
// Put (instance, func_index) as a Tuple2 into the table_index.
// The {WasmExportedFunction} will be created lazily.
Handle<Tuple2> tuple = isolate->factory()->NewTuple2(
instance, Handle<Smi>(Smi::FromInt(func_index), isolate),
AllocationType::kYoung);
table->elements()->set(entry_index, *tuple);
}
void WasmTableObject::GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index) {
DCHECK_EQ(table->type(), wasm::kWasmAnyFunc);
DCHECK_LT(entry_index, table->elements()->length());
// We initialize {is_valid} with {true}. We may change it later.
*is_valid = true;
Handle<Object> element(table->elements()->get(entry_index), isolate);
*is_null = element->IsNull(isolate);
if (*is_null) return;
if (WasmExportedFunction::IsWasmExportedFunction(*element)) {
auto target_func = Handle<WasmExportedFunction>::cast(element);
*instance = handle(target_func->instance(), isolate);
*function_index = target_func->function_index();
return;
} else if (element->IsTuple2()) {
auto tuple = Handle<Tuple2>::cast(element);
*instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate);
*function_index = Smi::cast(tuple->value2()).value();
return;
}
*is_valid = false;
}
namespace {
bool AdjustBufferPermissions(Isolate* isolate, Handle<JSArrayBuffer> old_buffer,
size_t new_size) {
......
......@@ -258,23 +258,26 @@ class WasmTableObject : public JSObject {
// TODO(titzer): introduce DECL_I64_ACCESSORS macro
DECL_ACCESSORS(maximum_length, Object)
DECL_ACCESSORS(dispatch_tables, FixedArray)
DECL_INT_ACCESSORS(raw_type)
// Layout description.
#define WASM_TABLE_OBJECT_FIELDS(V) \
V(kElementsOffset, kTaggedSize) \
V(kMaximumLengthOffset, kTaggedSize) \
V(kDispatchTablesOffset, kTaggedSize) \
V(kRawTypeOffset, kTaggedSize) \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize, WASM_TABLE_OBJECT_FIELDS)
#undef WASM_TABLE_OBJECT_FIELDS
inline uint32_t current_length();
inline wasm::ValueType type();
void Grow(Isolate* isolate, uint32_t count);
static Handle<WasmTableObject> New(Isolate* isolate, uint32_t initial,
uint32_t maximum,
Handle<FixedArray>* js_functions);
static Handle<WasmTableObject> New(Isolate* isolate, wasm::ValueType type,
uint32_t initial, uint32_t maximum,
Handle<FixedArray>* elements);
static void AddDispatchTable(Isolate* isolate, Handle<WasmTableObject> table,
Handle<WasmInstanceObject> instance,
int table_index);
......@@ -282,8 +285,11 @@ class WasmTableObject : public JSObject {
static bool IsInBounds(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t entry_index);
static bool IsValidElement(Isolate* isolate, Handle<WasmTableObject> table,
Handle<Object> entry);
static void Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index, Handle<JSFunction> function);
uint32_t index, Handle<Object> element);
static Handle<Object> Get(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index);
......@@ -297,6 +303,22 @@ class WasmTableObject : public JSObject {
static void ClearDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table, int index);
static void SetFunctionTablePlaceholder(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index,
Handle<WasmInstanceObject> instance,
int func_index);
// This function reads the content of a function table entry and returns it
// through the out parameters {is_valid}, {is_null}, {instance}, and
// {function_index}.
static void GetFunctionTableEntry(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index, bool* is_valid,
bool* is_null,
MaybeHandle<WasmInstanceObject>* instance,
int* function_index);
OBJECT_CONSTRUCTORS(WasmTableObject, JSObject);
};
......
// Copyright 2019 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-anyref
load("test/mjsunit/wasm/wasm-module-builder.js");
(function TestAnyRefTableSetWithMultipleTypes() {
print(arguments.callee.name);
let table = new WebAssembly.Table({element: "anyref", initial: 10});
// Table should be initialized with null.
assertEquals(null, table.get(1));
let obj = {'hello' : 'world'};
table.set(2, obj);
assertSame(obj, table.get(2));
table.set(3, 1234);
assertEquals(1234, table.get(3));
table.set(4, 123.5);
assertEquals(123.5, table.get(4));
table.set(5, undefined);
assertEquals(undefined, table.get(5));
// Overwrite entry 4, because null would otherwise be the default value.
table.set(4, null);
assertEquals(null, table.get(4));
table.set(7, print);
assertEquals(print, table.get(7));
assertThrows(() => table.set(12), RangeError);
})();
......@@ -641,9 +641,9 @@ assertEq(get.call(tbl1, 0), null);
assertEq(get.call(tbl1, 0, Infinity), null);
assertEq(get.call(tbl1, 1), null);
assertEq(get.call(tbl1, 1.5), null);
assertThrows(() => get.call(tbl1, 2), RangeError, /invalid index \d* into function table/);
assertThrows(() => get.call(tbl1, 2), RangeError, /invalid index \d+ into function table/);
assertThrows(
() => get.call(tbl1, 2.5), RangeError, /invalid index \d* into function table/);
() => get.call(tbl1, 2.5), RangeError, /invalid index \d+ into function table/);
assertThrows(() => get.call(tbl1, -1), TypeError, /must be non-negative/);
assertThrows(
() => get.call(tbl1, Math.pow(2, 33)), TypeError,
......@@ -670,7 +670,7 @@ assertThrows(
() => set.call(tbl1, undefined), TypeError,
/must be convertible to a valid number/);
assertThrows(
() => set.call(tbl1, 2, null), RangeError, /index out of bounds/);
() => set.call(tbl1, 2, null), RangeError, /invalid index \d+ into function table/);
assertThrows(
() => set.call(tbl1, -1, null), TypeError, /must be non-negative/);
assertThrows(
......
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