Commit 01dc5707 authored by Andreas Haas's avatar Andreas Haas Committed by Commit Bot

[wasm] Generate code for the table.get and table.set instructions

This CL contains the following changes:
(1) Allocate memory for WasmTables in the WasmInstance.
    - We extend the WasmInstance by a FixedArray which stores
      references to the WasmTables.
(2) Rename the name of the backing store of WasmTables from `functions`
    to `elements`.
    - The name `functions` just does not fit anyref tables.
(3) Generate code with TurboFan for table.get and table.set.
(4) Extend wasm-module-builder.js to be able to generate modules with
    multiple tables.
(5) Add  mjsunit tests to test table.get and table.set.

R=mstarzinger@chromium.org

Bug: v8:7581
Change-Id: I44af4838ee7a37b394841a2f673ecae5734a4d1c
Reviewed-on: https://chromium-review.googlesource.com/c/1463519
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59529}
parent f269293a
......@@ -3215,12 +3215,67 @@ Node* WasmGraphBuilder::SetGlobal(uint32_t index, Node* val) {
graph()->NewNode(op, base, offset, val, Effect(), Control()));
}
Node* WasmGraphBuilder::GetTable(uint32_t table_index, Node* index) {
UNIMPLEMENTED();
void WasmGraphBuilder::GetTableBaseAndOffset(uint32_t table_index, Node* index,
wasm::WasmCodePosition position,
Node** base_node,
Node** offset_node) {
Node* tables = LOAD_INSTANCE_FIELD(Tables, MachineType::TaggedPointer());
Node* table = LOAD_FIXED_ARRAY_SLOT_ANY(tables, table_index);
int storage_field_size = WasmTableObject::kElementsOffsetEnd -
WasmTableObject::kElementsOffset + 1;
Node* storage = LOAD_RAW(
table, wasm::ObjectAccess::ToTagged(WasmTableObject::kElementsOffset),
assert_size(storage_field_size, MachineType::TaggedPointer()));
int length_field_size =
FixedArray::kLengthOffsetEnd - FixedArray::kLengthOffset + 1;
Node* storage_size =
LOAD_RAW(storage, wasm::ObjectAccess::ToTagged(FixedArray::kLengthOffset),
assert_size(length_field_size, MachineType::TaggedSigned()));
storage_size = BuildChangeSmiToInt32(storage_size);
// Bounds check against the table size.
Node* in_bounds = graph()->NewNode(mcgraph()->machine()->Uint32LessThan(),
index, storage_size);
TrapIfFalse(wasm::kTrapTableOutOfBounds, in_bounds, position);
// From the index, calculate the actual offset in the FixeArray. This
// is kHeaderSize + (index * kTaggedSize). kHeaderSize can be acquired with
// wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(0).
Node* index_times_tagged_size =
graph()->NewNode(mcgraph()->machine()->IntMul(), Uint32ToUintptr(index),
mcgraph()->Int32Constant(kTaggedSize));
*offset_node = graph()->NewNode(
mcgraph()->machine()->IntAdd(), index_times_tagged_size,
mcgraph()->IntPtrConstant(
wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(0)));
*base_node = storage;
}
Node* WasmGraphBuilder::GetTable(uint32_t table_index, Node* index,
wasm::WasmCodePosition position) {
Node* base = nullptr;
Node* offset = nullptr;
GetTableBaseAndOffset(table_index, index, position, &base, &offset);
return SetEffect(
graph()->NewNode(mcgraph()->machine()->Load(MachineType::AnyTagged()),
base, offset, Effect(), Control()));
}
Node* WasmGraphBuilder::SetTable(uint32_t table_index, Node* index, Node* val) {
UNIMPLEMENTED();
Node* WasmGraphBuilder::SetTable(uint32_t table_index, Node* index, Node* val,
wasm::WasmCodePosition position) {
Node* base = nullptr;
Node* offset = nullptr;
GetTableBaseAndOffset(table_index, index, position, &base, &offset);
const Operator* op = mcgraph()->machine()->Store(
StoreRepresentation(MachineRepresentation::kTagged, kFullWriteBarrier));
Node* store = graph()->NewNode(op, base, offset, val, Effect(), Control());
return SetEffect(store);
}
Node* WasmGraphBuilder::CheckBoundsAndAlignment(
......
......@@ -274,8 +274,10 @@ class WasmGraphBuilder {
Node* GetGlobal(uint32_t index);
Node* SetGlobal(uint32_t index, Node* val);
Node* GetTable(uint32_t table_index, Node* index);
Node* SetTable(uint32_t table_index, Node* index, Node* val);
Node* GetTable(uint32_t table_index, Node* index,
wasm::WasmCodePosition position);
Node* SetTable(uint32_t table_index, Node* index, Node* val,
wasm::WasmCodePosition position);
//-----------------------------------------------------------------------
// Operations that concern the linear memory.
//-----------------------------------------------------------------------
......@@ -323,6 +325,10 @@ class WasmGraphBuilder {
void GetBaseAndOffsetForImportedMutableAnyRefGlobal(
const wasm::WasmGlobal& global, Node** base, Node** offset);
void GetTableBaseAndOffset(uint32_t table_index, Node* index,
wasm::WasmCodePosition position, Node** base_node,
Node** offset_node);
// Utilities to manipulate sets of instance cache nodes.
void InitInstanceCache(WasmInstanceCacheNodes* instance_cache);
void PrepareInstanceCacheForLoop(WasmInstanceCacheNodes* instance_cache,
......
......@@ -289,12 +289,12 @@ class WasmGraphBuildingInterface {
void GetTable(FullDecoder* decoder, const Value& index, Value* result,
const TableIndexImmediate<validate>& imm) {
result->node = BUILD(GetTable, imm.index, index.node);
result->node = BUILD(GetTable, imm.index, index.node, decoder->position());
}
void SetTable(FullDecoder* decoder, const Value& index, const Value& value,
const TableIndexImmediate<validate>& imm) {
BUILD(SetTable, imm.index, index.node, value.node);
BUILD(SetTable, imm.index, index.node, value.node, decoder->position());
}
void Unreachable(FullDecoder* decoder) {
......
......@@ -524,6 +524,7 @@ class ModuleDecoderImpl : public Decoder {
// ===== Imported table ==========================================
if (!AddTable(module_.get())) break;
import->index = static_cast<uint32_t>(module_->tables.size());
module_->num_imported_tables++;
module_->tables.emplace_back();
WasmTable* table = &module_->tables.back();
table->imported = true;
......@@ -1573,13 +1574,10 @@ class ModuleDecoderImpl : public Decoder {
flags = consume_u32v("flags");
if (failed()) return;
} else {
flags = consume_u32v(name);
if (failed()) return;
if (flags != 0) {
errorf(pos, "illegal %s %u != 0", name, flags);
return;
}
// Without the bulk memory proposal, we should still read the table index.
// This is the same as reading the `ActiveWithIndex` flag with the bulk
// memory proposal.
flags = SegmentFlags::kActiveWithIndex;
}
bool read_index;
......
......@@ -383,9 +383,18 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
}
//--------------------------------------------------------------------------
// Reserve the metadata for indirect function tables.
// Set up table storage space.
//--------------------------------------------------------------------------
int table_count = static_cast<int>(module_->tables.size());
Handle<FixedArray> tables = isolate_->factory()->NewFixedArray(table_count);
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);
tables->set(i, *table_obj);
}
instance->set_tables(*tables);
table_instances_.resize(table_count);
//--------------------------------------------------------------------------
......@@ -821,7 +830,7 @@ bool InstanceBuilder::ProcessImportedTable(Handle<WasmInstanceObject> instance,
table_instance.table_object = Handle<WasmTableObject>::cast(value);
instance->set_table_object(*table_instance.table_object);
table_instance.js_functions =
Handle<FixedArray>(table_instance.table_object->functions(), isolate_);
Handle<FixedArray>(table_instance.table_object->elements(), isolate_);
int imported_table_size = table_instance.js_functions->length();
if (imported_table_size < static_cast<int>(table.initial_size)) {
......@@ -1445,13 +1454,12 @@ void InstanceBuilder::InitializeTables(Handle<WasmInstanceObject> instance) {
size_t table_count = module_->tables.size();
for (size_t index = 0; index < table_count; ++index) {
const WasmTable& table = module_->tables[index];
TableInstance& table_instance = table_instances_[index];
if (!instance->has_indirect_function_table() &&
table.type == kWasmAnyFunc) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table.initial_size);
table_instance.table_size = table.initial_size;
table_instances_[index].table_size = table.initial_size;
}
}
}
......@@ -1567,7 +1575,7 @@ bool LoadElemSegment(Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<FixedArray> js_functions;
if (instance->has_table_object()) {
table_object = Handle<WasmTableObject>(instance->table_object(), isolate);
js_functions = Handle<FixedArray>(table_object->functions(), isolate);
js_functions = Handle<FixedArray>(table_object->elements(), isolate);
}
TableInstance table_instance = {table_object, js_functions,
......
......@@ -1328,7 +1328,7 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
i::Handle<i::FixedArray> old_array(receiver->functions(), i_isolate);
i::Handle<i::FixedArray> old_array(receiver->elements(), i_isolate);
uint32_t old_size = static_cast<uint32_t>(old_array->length());
uint64_t max_size64 = receiver->maximum_length()->Number();
......@@ -1356,7 +1356,7 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
i::Object null = i::ReadOnlyRoots(i_isolate).null_value();
for (uint32_t i = old_size; i < new_size; ++i) new_array->set(i, null);
receiver->set_functions(*new_array);
receiver->set_elements(*new_array);
}
// TODO(gdeepti): use weak links for instances
......@@ -1372,7 +1372,7 @@ void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) {
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.get()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
i::Handle<i::FixedArray> array(receiver->functions(), i_isolate);
i::Handle<i::FixedArray> array(receiver->elements(), i_isolate);
uint32_t index;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &index)) {
......@@ -1412,7 +1412,7 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
if (index >= static_cast<uint64_t>(receiver->functions()->length())) {
if (index >= static_cast<uint64_t>(receiver->elements()->length())) {
thrower.RangeError("index out of bounds");
return;
}
......
......@@ -169,6 +169,7 @@ struct V8_EXPORT_PRIVATE WasmModule {
uint32_t tagged_globals_buffer_size = 0;
uint32_t num_imported_mutable_globals = 0;
uint32_t num_imported_functions = 0;
uint32_t num_imported_tables = 0;
uint32_t num_declared_functions = 0; // excluding imported
uint32_t num_exported_functions = 0;
uint32_t num_declared_data_segments = 0; // From the DataCount section.
......
......@@ -102,7 +102,7 @@ bool WasmModuleObject::is_asm_js() {
}
// WasmTableObject
ACCESSORS(WasmTableObject, functions, FixedArray, kFunctionsOffset)
ACCESSORS(WasmTableObject, elements, FixedArray, kElementsOffset)
ACCESSORS(WasmTableObject, maximum_length, Object, kMaximumLengthOffset)
ACCESSORS(WasmTableObject, dispatch_tables, FixedArray, kDispatchTablesOffset)
......@@ -222,6 +222,7 @@ OPTIONAL_ACCESSORS(WasmInstanceObject, debug_info, WasmDebugInfo,
kDebugInfoOffset)
OPTIONAL_ACCESSORS(WasmInstanceObject, table_object, WasmTableObject,
kTableObjectOffset)
OPTIONAL_ACCESSORS(WasmInstanceObject, tables, FixedArray, kTablesOffset)
ACCESSORS(WasmInstanceObject, imported_function_refs, FixedArray,
kImportedFunctionRefsOffset)
OPTIONAL_ACCESSORS(WasmInstanceObject, indirect_function_table_refs, FixedArray,
......@@ -297,7 +298,7 @@ OPTIONAL_ACCESSORS(WasmDebugInfo, c_wasm_entry_map, Managed<wasm::SignatureMap>,
#undef WRITE_PRIMITIVE_FIELD
#undef PRIMITIVE_ACCESSORS
uint32_t WasmTableObject::current_length() { return functions()->length(); }
uint32_t WasmTableObject::current_length() { return elements()->length(); }
bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; }
......
......@@ -783,16 +783,19 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
auto table_obj = Handle<WasmTableObject>::cast(
isolate->factory()->NewJSObject(table_ctor));
*js_functions = isolate->factory()->NewFixedArray(initial);
Handle<FixedArray> backing_store = isolate->factory()->NewFixedArray(initial);
Object null = ReadOnlyRoots(isolate).null_value();
for (int i = 0; i < static_cast<int>(initial); ++i) {
(*js_functions)->set(i, null);
backing_store->set(i, null);
}
table_obj->set_functions(**js_functions);
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;
}
return Handle<WasmTableObject>::cast(table_obj);
}
......@@ -825,7 +828,7 @@ void WasmTableObject::Grow(Isolate* isolate, uint32_t count) {
Handle<FixedArray> dispatch_tables(this->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
uint32_t old_size = functions()->length();
uint32_t old_size = elements()->length();
// Tables are stored in the instance object, no code patching is
// necessary. We simply have to grow the raw tables in each instance
......@@ -846,7 +849,7 @@ void WasmTableObject::Grow(Isolate* isolate, uint32_t count) {
void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t table_index, Handle<JSFunction> function) {
Handle<FixedArray> array(table->functions(), isolate);
Handle<FixedArray> array(table->elements(), isolate);
if (function.is_null()) {
ClearDispatchTables(isolate, table, table_index); // Degenerate case.
array->set(table_index, ReadOnlyRoots(isolate).null_value());
......@@ -1447,7 +1450,7 @@ bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
auto max = instance->indirect_function_table_size();
if (!IsInBounds(dst, count, max)) return false;
if (!IsInBounds(src, count, max)) return false;
if (dst == src) return true; // no-op
if (dst == src) return true; // no-op
if (!instance->has_table_object()) {
// No table object, only need to update this instance.
......@@ -1469,7 +1472,7 @@ bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
}
// Copy the function entries.
Handle<FixedArray> functions(table->functions(), isolate);
Handle<FixedArray> functions(table->elements(), isolate);
if (src < dst) {
for (uint32_t i = count; i > 0; i--) {
functions->set(dst + i - 1, functions->get(src + i - 1));
......
......@@ -254,14 +254,14 @@ class WasmTableObject : public JSObject {
public:
DECL_CAST(WasmTableObject)
DECL_ACCESSORS(functions, FixedArray)
DECL_ACCESSORS(elements, FixedArray)
// TODO(titzer): introduce DECL_I64_ACCESSORS macro
DECL_ACCESSORS(maximum_length, Object)
DECL_ACCESSORS(dispatch_tables, FixedArray)
// Layout description.
#define WASM_TABLE_OBJECT_FIELDS(V) \
V(kFunctionsOffset, kTaggedSize) \
V(kElementsOffset, kTaggedSize) \
V(kMaximumLengthOffset, kTaggedSize) \
V(kDispatchTablesOffset, kTaggedSize) \
V(kSize, 0)
......@@ -405,6 +405,7 @@ class WasmInstanceObject : public JSObject {
DECL_OPTIONAL_ACCESSORS(imported_mutable_globals_buffers, FixedArray)
DECL_OPTIONAL_ACCESSORS(debug_info, WasmDebugInfo)
DECL_OPTIONAL_ACCESSORS(table_object, WasmTableObject)
DECL_OPTIONAL_ACCESSORS(tables, FixedArray)
DECL_ACCESSORS(imported_function_refs, FixedArray)
DECL_OPTIONAL_ACCESSORS(indirect_function_table_refs, FixedArray)
DECL_OPTIONAL_ACCESSORS(managed_native_allocations, Foreign)
......@@ -449,6 +450,7 @@ class WasmInstanceObject : public JSObject {
V(kImportedMutableGlobalsBuffersOffset, kTaggedSize) \
V(kDebugInfoOffset, kTaggedSize) \
V(kTableObjectOffset, kTaggedSize) \
V(kTablesOffset, kTaggedSize) \
V(kImportedFunctionRefsOffset, kTaggedSize) \
V(kIndirectFunctionTableRefsOffset, kTaggedSize) \
V(kManagedNativeAllocationsOffset, kTaggedSize) \
......
......@@ -211,6 +211,8 @@ let kExprSetLocal = 0x21;
let kExprTeeLocal = 0x22;
let kExprGetGlobal = 0x23;
let kExprSetGlobal = 0x24;
let kExprGetTable = 0x25;
let kExprSetTable = 0x26;
let kExprI32Const = 0x41;
let kExprI64Const = 0x42;
let kExprF32Const = 0x43;
......@@ -469,6 +471,7 @@ let kTrapTypeError = 8;
let kTrapUnalignedAccess = 9;
let kTrapDataSegmentDropped = 10;
let kTrapElemSegmentDropped = 11;
let kTrapTableOutOfBounds = 12;
let kTrapMsgs = [
"unreachable",
......@@ -482,7 +485,8 @@ let kTrapMsgs = [
"wasm function signature contains illegal type",
"operation does not support unaligned accesses",
"data segment has been dropped",
"element segment has been dropped"
"element segment has been dropped",
"table access out of bounds"
];
function assertTraps(trap, code) {
......@@ -656,21 +660,37 @@ class WasmGlobalBuilder {
}
}
class WasmTableBuilder {
constructor(module, type, initial_size, max_size) {
this.module = module;
this.type = type;
this.initial_size = initial_size;
this.has_max = max_size != undefined;
this.max_size = max_size;
}
exportAs(name) {
this.module.exports.push({name: name, kind: kExternalTable,
index: this.index});
return this;
}
}
class WasmModuleBuilder {
constructor() {
this.types = [];
this.imports = [];
this.exports = [];
this.globals = [];
this.tables = [];
this.exceptions = [];
this.functions = [];
this.table_length_min = 0;
this.table_length_max = undefined;
this.element_segments = [];
this.data_segments = [];
this.explicit = [];
this.num_imported_funcs = 0;
this.num_imported_globals = 0;
this.num_imported_tables = 0;
this.num_imported_exceptions = 0;
return this;
}
......@@ -723,6 +743,16 @@ class WasmModuleBuilder {
return glob;
}
addTable(type, initial_size, max_size = undefined) {
if (type != kWasmAnyRef && type != kWasmAnyFunc) {
throw new Error('Tables must be of type kWasmAnyRef or kWasmAnyFunc');
}
let table = new WasmTableBuilder(this, type, initial_size, max_size);
table.index = this.tables.length + this.num_imported_tables;
this.tables.push(table);
return table;
}
addException(type) {
let type_index = (typeof type) == "number" ? type : this.addType(type);
let except_index = this.exceptions.length + this.num_imported_exceptions;
......@@ -766,9 +796,13 @@ class WasmModuleBuilder {
}
addImportedTable(module, name, initial, maximum) {
if (this.tables.length != 0) {
throw new Error('Imported tables must be declared before local ones');
}
let o = {module: module, name: name, kind: kExternalTable, initial: initial,
maximum: maximum};
this.imports.push(o);
return this.num_imported_tables++;
}
addImportedException(module, name, type) {
......@@ -807,15 +841,19 @@ class WasmModuleBuilder {
}
addElementSegment(base, is_global, array, is_import = false) {
if (this.tables.length + this.num_imported_tables == 0) {
this.addTable(kWasmAnyFunc, 0);
}
this.element_segments.push({base: base, is_global: is_global,
array: array, is_active: true});
if (!is_global) {
var length = base + array.length;
if (length > this.table_length_min && !is_import) {
this.table_length_min = length;
if (!is_import && length > this.tables[0].initial_size) {
this.tables[0].initial_size = length;
}
if (length > this.table_length_max && !is_import) {
this.table_length_max = length;
if (!is_import && this.tables[0].has_max &&
length > this.tables[0].max_size) {
this.tables[0].max_size = length;
}
}
return this;
......@@ -831,12 +869,17 @@ class WasmModuleBuilder {
if (typeof n != 'number')
throw new Error('invalid table (entries have to be numbers): ' + array);
}
return this.addElementSegment(this.table_length_min, false, array);
if (this.tables.length == 0) {
this.addTable(kWasmAnyFunc, 0);
}
return this.addElementSegment(this.tables[0].initial_size, false, array);
}
setTableBounds(min, max = undefined) {
this.table_length_min = min;
this.table_length_max = max;
if (this.tables.length != 0) {
throw new Error("The table bounds of table '0' have already been set.");
}
this.addTable(kWasmAnyFunc, min, max);
return this;
}
......@@ -923,16 +966,16 @@ class WasmModuleBuilder {
}
// Add table section
if (wasm.table_length_min > 0) {
if (debug) print("emitting table @ " + binary.length);
if (wasm.tables.length > 0) {
if (debug) print ("emitting tables @ " + binary.length);
binary.emit_section(kTableSectionCode, section => {
section.emit_u8(1); // one table entry
section.emit_u8(kWasmAnyFunctionTypeForm);
const max = wasm.table_length_max;
const has_max = max !== undefined;
section.emit_u8(has_max ? kHasMaximumFlag : 0);
section.emit_u32v(wasm.table_length_min);
if (has_max) section.emit_u32v(max);
section.emit_u32v(wasm.tables.length);
for (let table of wasm.tables) {
section.emit_u8(table.type);
section.emit_u8(table.has_max);
section.emit_u32v(table.initial_size);
if (table.has_max) section.emit_u32v(table.max_size);
}
});
}
......
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