From b7aff1ff647595b7e529b6df0c719d513473703a Mon Sep 17 00:00:00 2001 From: titzer Date: Sat, 29 Oct 2016 14:06:57 -0700 Subject: [PATCH] [wasm] Support for restricted table imports. This CL implements basic table import functionality. Missing: growing of tables (WebAssembly.Grow) doesn't change dispatch tables Missing: allowing larger table imports than minimum size R=rossberg@chromium.org,bradnelson@chromium.org BUG=v8:5507 Review-Url: https://codereview.chromium.org/2454503005 Cr-Commit-Position: refs/heads/master@{#40661} --- src/compiler/wasm-compiler.cc | 56 +- src/compiler/wasm-compiler.h | 3 +- src/wasm/module-decoder.cc | 74 +-- src/wasm/wasm-interpreter.cc | 10 +- src/wasm/wasm-js.cc | 87 ++- src/wasm/wasm-js.h | 21 +- src/wasm/wasm-module.cc | 605 +++++++++++------- src/wasm/wasm-module.h | 11 +- test/cctest/wasm/wasm-run-utils.h | 4 +- test/mjsunit/wasm/indirect-tables.js | 279 +++++++- test/mjsunit/wasm/wasm-module-builder.js | 12 + .../unittests/wasm/module-decoder-unittest.cc | 11 +- 12 files changed, 825 insertions(+), 348 deletions(-) diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index e1c3aa7dca..1e7b2f76f5 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -298,6 +298,7 @@ WasmGraphBuilder::WasmGraphBuilder( mem_buffer_(nullptr), mem_size_(nullptr), function_tables_(zone), + function_table_sizes_(zone), control_(nullptr), effect_(nullptr), cur_buffer_(def_buffer_), @@ -1712,7 +1713,7 @@ Node* WasmGraphBuilder::GrowMemory(Node* input) { graph(), jsgraph()->common(), graph()->NewNode( jsgraph()->machine()->Uint32LessThanOrEqual(), input, - jsgraph()->Uint32Constant(wasm::WasmModule::kMaxMemPages)), + jsgraph()->Uint32Constant(wasm::WasmModule::kV8MaxPages)), BranchHint::kTrue); check_input_range.Chain(*control_); @@ -2154,28 +2155,17 @@ Node* WasmGraphBuilder::CallDirect(uint32_t index, Node** args, Node*** rets, return BuildWasmCall(sig, args, rets, position); } -Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args, Node*** rets, +Node* WasmGraphBuilder::CallIndirect(uint32_t sig_index, Node** args, + Node*** rets, wasm::WasmCodePosition position) { DCHECK_NOT_NULL(args[0]); DCHECK(module_ && module_->instance); - MachineOperatorBuilder* machine = jsgraph()->machine(); - - // Compute the code object by loading it from the function table. - Node* key = args[0]; - // Assume only one table for now. - DCHECK_LE(module_->instance->function_tables.size(), 1u); - // Bounds check the index. - uint32_t table_size = - module_->IsValidTable(0) ? module_->GetTable(0)->max_size : 0; - wasm::FunctionSig* sig = module_->GetSignature(index); - if (table_size > 0) { - // Bounds check against the table size. - Node* size = Uint32Constant(table_size); - Node* in_bounds = graph()->NewNode(machine->Uint32LessThan(), key, size); - trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, in_bounds, position); - } else { + uint32_t table_index = 0; + wasm::FunctionSig* sig = module_->GetSignature(sig_index); + + if (!module_->IsValidTable(table_index)) { // No function table. Generate a trap and return a constant. trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, Int32Constant(0), position); (*rets) = Buffer(sig->return_count()); @@ -2184,7 +2174,16 @@ Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args, Node*** rets, } return trap_->GetTrapValue(sig); } - Node* table = FunctionTable(0); + + EnsureFunctionTableNodes(); + MachineOperatorBuilder* machine = jsgraph()->machine(); + Node* key = args[0]; + + // Bounds check against the table size. + Node* size = function_table_sizes_[table_index]; + Node* in_bounds = graph()->NewNode(machine->Uint32LessThan(), key, size); + trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, in_bounds, position); + Node* table = function_tables_[table_index]; // Load signature from the table and check. // The table is a FixedArray; signatures are encoded as SMIs. @@ -2208,6 +2207,7 @@ Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args, Node*** rets, } // Load code object from the table. + uint32_t table_size = module_->module->function_tables[table_index].min_size; uint32_t offset = fixed_offset + kPointerSize * table_size; Node* load_code = graph()->NewNode( machine->Load(MachineType::AnyTagged()), table, @@ -2854,17 +2854,15 @@ Node* WasmGraphBuilder::MemSize(uint32_t offset) { } } -Node* WasmGraphBuilder::FunctionTable(uint32_t index) { - DCHECK(module_ && module_->instance && - index < module_->instance->function_tables.size()); - if (!function_tables_.size()) { - for (size_t i = 0; i < module_->instance->function_tables.size(); ++i) { - DCHECK(!module_->instance->function_tables[i].is_null()); - function_tables_.push_back( - HeapConstant(module_->instance->function_tables[i])); - } +void WasmGraphBuilder::EnsureFunctionTableNodes() { + if (function_tables_.size() > 0) return; + for (size_t i = 0; i < module_->instance->function_tables.size(); ++i) { + auto handle = module_->instance->function_tables[i]; + DCHECK(!handle.is_null()); + function_tables_.push_back(HeapConstant(handle)); + uint32_t table_size = module_->module->function_tables[i].min_size; + function_table_sizes_.push_back(Uint32Constant(table_size)); } - return function_tables_[index]; } Node* WasmGraphBuilder::GetGlobal(uint32_t index) { diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h index 1c476af053..b4bc350297 100644 --- a/src/compiler/wasm-compiler.h +++ b/src/compiler/wasm-compiler.h @@ -170,7 +170,7 @@ class WasmGraphBuilder { Node* ToJS(Node* node, wasm::LocalType type); Node* FromJS(Node* node, Node* context, wasm::LocalType type); Node* Invert(Node* node); - Node* FunctionTable(uint32_t index); + void EnsureFunctionTableNodes(); //----------------------------------------------------------------------- // Operations that concern the linear memory. @@ -219,6 +219,7 @@ class WasmGraphBuilder { Node* mem_buffer_; Node* mem_size_; NodeVector function_tables_; + NodeVector function_table_sizes_; Node** control_; Node** effect_; Node** cur_buffer_; diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc index d01a4f9a97..9721f5af63 100644 --- a/src/wasm/module-decoder.cc +++ b/src/wasm/module-decoder.cc @@ -31,8 +31,6 @@ namespace { const char* kNameString = "name"; const size_t kNameStringLength = 4; -static const uint32_t kMaxTableSize = 1 << 28; - LocalType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { switch (expr.kind) { case WasmInitExpr::kNone: @@ -311,19 +309,22 @@ class ModuleDecoder : public Decoder { // ===== Imported table ========================================== import->index = static_cast(module->function_tables.size()); - module->function_tables.push_back( - {0, 0, std::vector(), true, false, SignatureMap()}); + module->function_tables.push_back({0, 0, false, + std::vector(), true, + false, SignatureMap()}); expect_u8("element type", kWasmAnyFunctionTypeForm); WasmIndirectFunctionTable* table = &module->function_tables.back(); - consume_resizable_limits("element count", "elements", kMaxTableSize, - &table->size, &table->max_size); + consume_resizable_limits( + "element count", "elements", WasmModule::kV8MaxTableSize, + &table->min_size, &table->has_max, &table->max_size); break; } case kExternalMemory: { // ===== Imported memory ========================================= - consume_resizable_limits( - "memory", "pages", WasmModule::kMaxLegalPages, - &module->min_mem_pages, &module->max_mem_pages); + bool has_max = false; + consume_resizable_limits("memory", "pages", WasmModule::kV8MaxPages, + &module->min_mem_pages, &has_max, + &module->max_mem_pages); break; } case kExternalGlobal: { @@ -375,15 +376,16 @@ class ModuleDecoder : public Decoder { error(pos, pos, "invalid table count %d, maximum 1", table_count); } if (module->function_tables.size() < 1) { - module->function_tables.push_back( - {0, 0, std::vector(), false, false, SignatureMap()}); + module->function_tables.push_back({0, 0, false, std::vector(), + false, false, SignatureMap()}); } for (uint32_t i = 0; ok() && i < table_count; i++) { WasmIndirectFunctionTable* table = &module->function_tables.back(); expect_u8("table type", kWasmAnyFunctionTypeForm); - consume_resizable_limits("table elements", "elements", kMaxUInt32, - &table->size, &table->max_size); + consume_resizable_limits("table elements", "elements", + WasmModule::kV8MaxTableSize, &table->min_size, + &table->has_max, &table->max_size); } section_iter.advance(); } @@ -398,8 +400,9 @@ class ModuleDecoder : public Decoder { } for (uint32_t i = 0; ok() && i < memory_count; i++) { - consume_resizable_limits("memory", "pages", WasmModule::kMaxLegalPages, - &module->min_mem_pages, + bool has_max = false; + consume_resizable_limits("memory", "pages", WasmModule::kV8MaxPages, + &module->min_mem_pages, &has_max, &module->max_mem_pages); } section_iter.advance(); @@ -623,7 +626,6 @@ class ModuleDecoder : public Decoder { if (ok()) { CalculateGlobalOffsets(module); - PreinitializeIndirectFunctionTables(module); } const WasmModule* finished_module = module; ModuleResult result = toResult(finished_module); @@ -749,30 +751,6 @@ class ModuleDecoder : public Decoder { module->globals_size = offset; } - // TODO(titzer): this only works without overlapping initializations from - // global bases for entries - void PreinitializeIndirectFunctionTables(WasmModule* module) { - // Fill all tables with invalid entries first. - for (WasmIndirectFunctionTable& table : module->function_tables) { - table.values.resize(table.size); - for (size_t i = 0; i < table.size; i++) { - table.values[i] = kInvalidFunctionIndex; - } - } - for (WasmTableInit& init : module->table_inits) { - if (init.offset.kind != WasmInitExpr::kI32Const) continue; - if (init.table_index >= module->function_tables.size()) continue; - WasmIndirectFunctionTable& table = - module->function_tables[init.table_index]; - for (size_t i = 0; i < init.entries.size(); i++) { - size_t index = i + init.offset.val.i32_const; - if (index < table.values.size()) { - table.values[index] = init.entries[i]; - } - } - } - } - // Verifies the body (code) of a given function. void VerifyFunctionBody(uint32_t func_num, ModuleEnv* menv, WasmFunction* function) { @@ -866,29 +844,33 @@ class ModuleDecoder : public Decoder { void consume_resizable_limits(const char* name, const char* units, uint32_t max_value, uint32_t* initial, - uint32_t* maximum) { + bool* has_max, uint32_t* maximum) { uint32_t flags = consume_u32v("resizable limits flags"); const byte* pos = pc(); *initial = consume_u32v("initial size"); + *has_max = false; if (*initial > max_value) { error(pos, pos, - "initial %s size (%u %s) is larger than maximum allowable (%u)", + "initial %s size (%u %s) is larger than implementation limit (%u)", name, *initial, units, max_value); } if (flags & 1) { + *has_max = true; pos = pc(); *maximum = consume_u32v("maximum size"); if (*maximum > max_value) { - error(pos, pos, - "maximum %s size (%u %s) is larger than maximum allowable (%u)", - name, *maximum, units, max_value); + error( + pos, pos, + "maximum %s size (%u %s) is larger than implementation limit (%u)", + name, *maximum, units, max_value); } if (*maximum < *initial) { error(pos, pos, "maximum %s size (%u %s) is less than initial (%u %s)", name, *maximum, units, *initial, units); } } else { - *maximum = 0; + *has_max = false; + *maximum = max_value; } } diff --git a/src/wasm/wasm-interpreter.cc b/src/wasm/wasm-interpreter.cc index ed6b870591..e88832a544 100644 --- a/src/wasm/wasm-interpreter.cc +++ b/src/wasm/wasm-interpreter.cc @@ -663,16 +663,13 @@ static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, WasmInstance* instance) { // TODO(ahaas): Move memory allocation to wasm-module.cc for better // encapsulation. - if (delta_pages > wasm::WasmModule::kMaxMemPages) { + if (delta_pages > wasm::WasmModule::kV8MaxPages) { return -1; } uint32_t old_size = instance->mem_size; uint32_t new_size; byte* new_mem_start; if (instance->mem_size == 0) { - if (delta_pages > wasm::WasmModule::kMaxMemPages) { - return -1; - } // TODO(gdeepti): Fix bounds check to take into account size of memtype. new_size = delta_pages * wasm::WasmModule::kPageSize; new_mem_start = static_cast(calloc(new_size, sizeof(byte))); @@ -683,7 +680,7 @@ static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, DCHECK_NOT_NULL(instance->mem_start); new_size = old_size + delta_pages * wasm::WasmModule::kPageSize; if (new_size > - wasm::WasmModule::kMaxMemPages * wasm::WasmModule::kPageSize) { + wasm::WasmModule::kV8MaxPages * wasm::WasmModule::kPageSize) { return -1; } new_mem_start = static_cast(realloc(instance->mem_start, new_size)); @@ -695,9 +692,6 @@ static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, } instance->mem_start = new_mem_start; instance->mem_size = new_size; - // realloc - // update mem_start - // update mem_size return static_cast(old_size / WasmModule::kPageSize); } diff --git a/src/wasm/wasm-js.cc b/src/wasm/wasm-js.cc index 4b2b897d82..36ffed223d 100644 --- a/src/wasm/wasm-js.cc +++ b/src/wasm/wasm-js.cc @@ -29,6 +29,7 @@ namespace v8 { static const int kWasmTableArrayFieldIndex = 0; static const int kWasmTableMaximumFieldIndex = 1; +static const int kWasmTableDispatchTablesFieldIndex = 2; enum WasmMemoryObjectData { kWasmMemoryBuffer, @@ -37,8 +38,8 @@ enum WasmMemoryObjectData { }; enum WasmInternalFieldCountData { - kWasmTableInternalFieldCount = 2, - kWasmMemoryInternalFieldCount + kWasmTableInternalFieldCount = 3, + kWasmMemoryInternalFieldCount = 3 }; namespace { @@ -518,9 +519,19 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo& args) { return; } - i::Handle::cast(array)->set(i, *value); + i::Handle dispatch_tables( + i::FixedArray::cast( + receiver->GetInternalField(kWasmTableDispatchTablesFieldIndex)), + i_isolate); + if (value->IsNull(i_isolate)) { + i::wasm::UpdateDispatchTables(i_isolate, dispatch_tables, i, + i::Handle::null()); + } else { + i::wasm::UpdateDispatchTables(i_isolate, dispatch_tables, i, + i::Handle::cast(value)); + } - // TODO(titzer): update relevant instances. + i::Handle::cast(array)->set(i, *value); } void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo& args) { @@ -612,26 +623,60 @@ i::Handle i::WasmJs::CreateWasmMemoryObject( i::Handle i::WasmJs::CreateWasmTableObject( i::Isolate* i_isolate, uint32_t initial, bool has_maximum, uint32_t maximum, - i::Handle* array) { + i::Handle* js_functions) { i::Handle table_ctor( i_isolate->native_context()->wasm_table_constructor()); i::Handle table_obj = i_isolate->factory()->NewJSObject(table_ctor); - *array = i_isolate->factory()->NewFixedArray(initial); + *js_functions = i_isolate->factory()->NewFixedArray(initial); i::Object* null = i_isolate->heap()->null_value(); // TODO(titzer): consider moving FixedArray to size_t. - for (int i = 0; i < static_cast(initial); ++i) (*array)->set(i, null); - table_obj->SetInternalField(kWasmTableArrayFieldIndex, *(*array)); + for (int i = 0; i < static_cast(initial); ++i) { + (*js_functions)->set(i, null); + } + table_obj->SetInternalField(kWasmTableArrayFieldIndex, *(*js_functions)); table_obj->SetInternalField( kWasmTableMaximumFieldIndex, has_maximum ? static_cast(i::Smi::FromInt(maximum)) : static_cast(i_isolate->heap()->undefined_value())); + Handle dispatch_tables = i_isolate->factory()->NewFixedArray(0); + table_obj->SetInternalField(kWasmTableDispatchTablesFieldIndex, + *dispatch_tables); i::Handle table_sym(i_isolate->native_context()->wasm_table_sym()); i::Object::SetProperty(table_obj, table_sym, table_obj, i::STRICT).Check(); return table_obj; } +i::Handle i::WasmJs::AddWasmTableDispatchTable( + i::Isolate* i_isolate, i::Handle table_obj, + i::Handle instance, int table_index, + i::Handle dispatch_table) { + DCHECK(IsWasmTableObject(i_isolate, table_obj)); + i::Handle dispatch_tables( + i::FixedArray::cast( + table_obj->GetInternalField(kWasmTableDispatchTablesFieldIndex)), + i_isolate); + DCHECK_EQ(0, dispatch_tables->length() % 3); + + if (instance.is_null()) return dispatch_tables; + // TODO(titzer): use weak cells here to avoid leaking instances. + + // Grow the dispatch table and add a new pair at the end. + i::Handle new_dispatch_tables = + i_isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, 3); + + new_dispatch_tables->set(dispatch_tables->length() + 0, *instance); + new_dispatch_tables->set(dispatch_tables->length() + 1, + Smi::FromInt(table_index)); + new_dispatch_tables->set(dispatch_tables->length() + 2, *dispatch_table); + + table_obj->SetInternalField(kWasmTableDispatchTablesFieldIndex, + *new_dispatch_tables); + + return new_dispatch_tables; +} + // TODO(titzer): we use the API to create the function template because the // internal guts are too ugly to replicate here. static i::Handle NewTemplate(i::Isolate* i_isolate, @@ -817,17 +862,35 @@ void WasmJs::InstallWasmMapsIfNeeded(Isolate* isolate, } } -bool WasmJs::IsWasmMemoryObject(Isolate* isolate, Handle value) { +static bool HasBrand(i::Handle value, i::Handle symbol) { if (value->IsJSObject()) { i::Handle object = i::Handle::cast(value); - i::Handle sym(isolate->context()->wasm_memory_sym(), isolate); - Maybe has_brand = i::JSObject::HasOwnProperty(object, sym); + Maybe has_brand = i::JSObject::HasOwnProperty(object, symbol); if (has_brand.IsNothing()) return false; if (has_brand.ToChecked()) return true; } return false; } +bool WasmJs::IsWasmMemoryObject(Isolate* isolate, Handle value) { + i::Handle symbol(isolate->context()->wasm_memory_sym(), isolate); + return HasBrand(value, symbol); +} + +bool WasmJs::IsWasmTableObject(Isolate* isolate, Handle value) { + i::Handle symbol(isolate->context()->wasm_table_sym(), isolate); + return HasBrand(value, symbol); +} + +Handle WasmJs::GetWasmTableFunctions(Isolate* isolate, + Handle value) { + DCHECK(IsWasmTableObject(isolate, value)); + Handle arr( + JSObject::cast(*value)->GetInternalField(kWasmTableArrayFieldIndex), + isolate); + return Handle::cast(arr); +} + Handle WasmJs::GetWasmMemoryArrayBuffer(Isolate* isolate, Handle value) { DCHECK(IsWasmMemoryObject(isolate, value)); @@ -847,7 +910,7 @@ uint32_t WasmJs::GetWasmMemoryMaximumSize(Isolate* isolate, DCHECK(IsWasmMemoryObject(isolate, value)); Object* max_mem = JSObject::cast(*value)->GetInternalField(kWasmMemoryMaximum); - if (max_mem->IsUndefined(isolate)) return wasm::WasmModule::kMaxMemPages; + if (max_mem->IsUndefined(isolate)) return wasm::WasmModule::kV8MaxPages; uint32_t max_pages = Smi::cast(max_mem)->value(); return max_pages; } diff --git a/src/wasm/wasm-js.h b/src/wasm/wasm-js.h index c7e11ce924..3cd7bacaa4 100644 --- a/src/wasm/wasm-js.h +++ b/src/wasm/wasm-js.h @@ -24,16 +24,25 @@ class WasmJs { Handle global, Handle context); + // WebAssembly.Table. + static Handle CreateWasmTableObject( + Isolate* isolate, uint32_t initial, bool has_maximum, uint32_t maximum, + Handle* js_functions); + + static bool IsWasmTableObject(Isolate* isolate, Handle value); + + static Handle GetWasmTableFunctions(Isolate* isolate, + Handle object); + + static Handle AddWasmTableDispatchTable( + Isolate* isolate, Handle table_obj, Handle instance, + int table_index, Handle dispatch_table); + + // WebAssembly.Memory static Handle CreateWasmMemoryObject(Isolate* isolate, Handle buffer, bool has_maximum, int maximum); - static Handle CreateWasmTableObject(Isolate* isolate, - uint32_t initial, - bool has_maximum, - uint32_t maximum, - Handle* array); - static bool IsWasmMemoryObject(Isolate* isolate, Handle value); static Handle GetWasmMemoryArrayBuffer(Isolate* isolate, diff --git a/src/wasm/wasm-module.cc b/src/wasm/wasm-module.cc index 4aa5445b49..aa385f824e 100644 --- a/src/wasm/wasm-module.cc +++ b/src/wasm/wasm-module.cc @@ -37,6 +37,34 @@ namespace base = v8::base; instance->PrintInstancesChain(); \ } while (false) +static const int kInvalidSigIndex = -1; + +// Collects all the data values to which a given WASM code object may be +// specialized. +struct Specialization { + // The native context, which is used in JS->WASM and WASM->JS wrappers + // and calls to the runtime. + Handle context; + + // Specialization to the memory. + byte* memory_base; + uint32_t memory_size; + + // Specialization to the globals. + byte* globals_base; + + // Specialization to the function table. + uint32_t function_table_size; + Handle function_table_sigs; + Handle function_table_code; + + Specialization() + : memory_base(nullptr), + memory_size(0), + globals_base(nullptr), + function_table_size(0) {} +}; + namespace { static const int kPlaceholderMarker = 1000000000; @@ -87,7 +115,7 @@ void ReplaceReferenceInCode(Handle code, Handle old_ref, } Handle NewArrayBuffer(Isolate* isolate, size_t size) { - if (size > (WasmModule::kMaxMemPages * WasmModule::kPageSize)) { + if (size > (WasmModule::kV8MaxPages * WasmModule::kPageSize)) { // TODO(titzer): lift restriction on maximum memory allocated here. return Handle::null(); } @@ -500,7 +528,7 @@ static void ResetCompiledModule(Isolate* isolate, JSObject* owner, // table references. Object* fct_obj = compiled_module->ptr_to_code_table(); if (fct_obj != nullptr && fct_obj != undefined && - (old_mem_size > 0 || globals_start != nullptr)) { + (old_mem_size > 0 || globals_start != nullptr || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); @@ -517,9 +545,9 @@ static void ResetCompiledModule(Isolate* isolate, JSObject* owner, changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); - for (int i = 0; i < function_tables->length(); ++i) { - if (function_tables->get(i) == old) { - it.rinfo()->set_target_object(empty_function_tables->get(i)); + for (int j = 0; j < function_tables->length(); ++j) { + if (function_tables->get(j) == old) { + it.rinfo()->set_target_object(empty_function_tables->get(j)); changed = true; } } @@ -843,6 +871,101 @@ MaybeHandle WasmModule::CompileFunctions( return ret; } +static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, + Handle target) { + if (target->IsJSFunction()) { + Handle func = Handle::cast(target); + Handle export_wrapper_code = handle(func->code()); + if (export_wrapper_code->kind() == Code::JS_TO_WASM_FUNCTION) { + Handle other_instance( + JSObject::cast(func->GetInternalField(kInternalModuleInstance)), + isolate); + int func_index = + Smi::cast(func->GetInternalField(kInternalFunctionIndex))->value(); + return &GetCppModule(other_instance)->functions[func_index]; + } + } + return nullptr; +} + +static Handle UnwrapImportWrapper(Handle target) { + Handle func = Handle::cast(target); + Handle export_wrapper_code = handle(func->code()); + int found = 0; + int mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); + Handle code; + for (RelocIterator it(*export_wrapper_code, mask); !it.done(); it.next()) { + RelocInfo* rinfo = it.rinfo(); + Address target_address = rinfo->target_address(); + Code* target = Code::GetCodeFromTargetAddress(target_address); + if (target->kind() == Code::WASM_FUNCTION || + target->kind() == Code::WASM_TO_JS_FUNCTION) { + ++found; + code = handle(target); + } + } + DCHECK(found == 1); + return code; +} + +static Handle CompileImportWrapper(Isolate* isolate, int index, + FunctionSig* sig, + Handle target, + Handle module_name, + MaybeHandle import_name) { + Handle code; + WasmFunction* other_func = GetWasmFunctionForImportWrapper(isolate, target); + if (other_func && sig->Equals(other_func->sig)) { + // Signature matched. Unwrap the JS->WASM wrapper and return the raw + // WASM function code. + return UnwrapImportWrapper(target); + } else { + // Signature mismatch. Compile a new wrapper for the new signature. + return compiler::CompileWasmToJSWrapper(isolate, target, sig, index, + module_name, import_name); + } +} + +static void UpdateDispatchTablesInternal(Isolate* isolate, + Handle dispatch_tables, + int index, WasmFunction* function, + Handle code) { + DCHECK_EQ(0, dispatch_tables->length() % 3); + for (int i = 0; i < dispatch_tables->length(); i += 3) { + Handle instance(dispatch_tables->get(i), isolate); + WasmModule* module = GetCppModule(Handle::cast(instance)); + int table_index = Smi::cast(dispatch_tables->get(i + 1))->value(); + Handle dispatch_table( + FixedArray::cast(dispatch_tables->get(i + 2)), isolate); + if (function) { + // TODO(titzer): the signature might need to be copied to avoid + // a dangling pointer in the signature map. + int sig_index = static_cast( + module->function_tables[table_index].map.FindOrInsert(function->sig)); + dispatch_table->set(index, Smi::FromInt(sig_index)); + dispatch_table->set(index + (dispatch_table->length() / 2), *code); + } else { + Code* code = nullptr; + dispatch_table->set(index, Smi::FromInt(-1)); + dispatch_table->set(index + (dispatch_table->length() / 2), code); + } + } +} + +void wasm::UpdateDispatchTables(Isolate* isolate, + Handle dispatch_tables, int index, + Handle function) { + if (function.is_null()) { + UpdateDispatchTablesInternal(isolate, dispatch_tables, index, nullptr, + Handle::null()); + } else { + UpdateDispatchTablesInternal( + isolate, dispatch_tables, index, + GetWasmFunctionForImportWrapper(isolate, function), + UnwrapImportWrapper(function)); + } +} + // A helper class to simplify instantiating a module from a compiled module. // It closes over the {Isolate}, the {ErrorThrower}, the {WasmCompiledModule}, // etc. @@ -951,13 +1074,12 @@ class WasmInstanceBuilder { // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_globals; - MaybeHandle globals; uint32_t globals_size = module_->globals_size; if (globals_size > 0) { Handle global_buffer = NewArrayBuffer(isolate_, globals_size); - globals = global_buffer; - if (globals.is_null()) { + globals_ = global_buffer; + if (globals_.is_null()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } @@ -971,16 +1093,28 @@ class WasmInstanceBuilder { instance->SetInternalField(kWasmGlobalsArrayBuffer, *global_buffer); } + //-------------------------------------------------------------------------- + // Prepare for initialization of function tables. + //-------------------------------------------------------------------------- + int function_table_count = + static_cast(module_->function_tables.size()); + table_instances_.reserve(module_->function_tables.size()); + for (int index = 0; index < function_table_count; ++index) { + table_instances_.push_back({Handle::null(), + Handle::null(), + Handle::null()}); + } + //-------------------------------------------------------------------------- // Process the imports for the module. //-------------------------------------------------------------------------- - int num_imported_functions = ProcessImports(globals, code_table, instance); + int num_imported_functions = ProcessImports(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- - InitGlobals(globals); + InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. @@ -1004,7 +1138,7 @@ class WasmInstanceBuilder { Address mem_start = static_cast
(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); - LoadDataSegments(globals, mem_start, mem_size); + LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = @@ -1034,76 +1168,15 @@ class WasmInstanceBuilder { } } - //-------------------------------------------------------------------------- - // Set up the indirect function tables for the new instance. - //-------------------------------------------------------------------------- - int function_table_count = - static_cast(module_->function_tables.size()); - std::vector inited_tables; - inited_tables.reserve(module_->function_tables.size()); - if (function_table_count > 0) { - Handle old_function_tables = - compiled_module_->function_tables(); - Handle new_function_tables = - factory->NewFixedArray(function_table_count); - for (int index = 0; index < function_table_count; ++index) { - WasmIndirectFunctionTable& table = module_->function_tables[index]; - uint32_t table_size = table.size; - Handle new_table = factory->NewFixedArray(table_size * 2); - - inited_tables.push_back({Handle::null(), - Handle::null(), new_table, - std::vector()}); - InitializedTable& init_table = inited_tables.back(); - if (table.exported) { - init_table.new_entries.insert(init_table.new_entries.begin(), - table_size, nullptr); - } - - for (int i = 0; i < new_table->length(); ++i) { - static const int kInvalidSigIndex = -1; - // Fill the table with invalid signature indexes so that uninitialized - // entries will always fail the signature check. - new_table->set(i, Smi::FromInt(kInvalidSigIndex)); - } - for (auto table_init : module_->table_inits) { - uint32_t base = EvalUint32InitExpr(globals, table_init.offset); - if (base > table_size || - (base + table_init.entries.size() > table_size)) { - thrower_->CompileError("table initializer is out of bounds"); - continue; - } - for (size_t i = 0; i < table_init.entries.size(); ++i) { - WasmFunction* func = &module_->functions[table_init.entries[i]]; - if (table.exported) { - init_table.new_entries[i + base] = func; - } - FunctionSig* sig = func->sig; - int32_t sig_index = table.map.Find(sig); - new_table->set(static_cast(i + base), Smi::FromInt(sig_index)); - new_table->set(static_cast(i + base + table_size), - code_table->get(table_init.entries[i])); - } - } - new_function_tables->set(static_cast(index), *new_table); - } - // Patch all code that has references to the old indirect table. - for (int i = 0; i < code_table->length(); ++i) { - if (!code_table->get(i)->IsCode()) continue; - Handle code(Code::cast(code_table->get(i)), isolate_); - for (int j = 0; j < function_table_count; ++j) { - ReplaceReferenceInCode( - code, Handle(old_function_tables->get(j), isolate_), - Handle(new_function_tables->get(j), isolate_)); - } - } - compiled_module_->set_function_tables(new_function_tables); - } - //-------------------------------------------------------------------------- // Set up the exports object for the new instance. //-------------------------------------------------------------------------- - ProcessExports(globals, inited_tables, code_table, instance); + ProcessExports(code_table, instance); + + //-------------------------------------------------------------------------- + // Set up the indirect function tables for the new instance. + //-------------------------------------------------------------------------- + if (function_table_count > 0) InitializeTables(code_table, instance); if (num_imported_functions > 0 || !owner.is_null()) { // If the code was cloned, or new imports were compiled, patch. @@ -1192,11 +1265,10 @@ class WasmInstanceBuilder { private: // Represents the initialized state of a table. - struct InitializedTable { + struct TableInstance { Handle table_object; // WebAssembly.Table instance - Handle js_functions; // JSFunctions exported + Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs - std::vector new_entries; // overwriting entries }; Isolate* isolate_; @@ -1205,7 +1277,10 @@ class WasmInstanceBuilder { Handle module_object_; Handle ffi_; Handle memory_; + Handle globals_; Handle compiled_module_; + std::vector table_instances_; + std::vector> js_wrappers_; // Helper routine to print out errors with imports (FFI). MaybeHandle ReportFFIError(const char* error, uint32_t index, @@ -1264,14 +1339,13 @@ class WasmInstanceBuilder { return result; } - uint32_t EvalUint32InitExpr(MaybeHandle globals, - WasmInitExpr& expr) { + uint32_t EvalUint32InitExpr(WasmInitExpr& expr) { switch (expr.kind) { case WasmInitExpr::kI32Const: return expr.val.i32_const; case WasmInitExpr::kGlobalIndex: { uint32_t offset = module_->globals[expr.val.global_index].offset; - return *reinterpret_cast(raw_buffer_ptr(globals, offset)); + return *reinterpret_cast(raw_buffer_ptr(globals_, offset)); } default: UNREACHABLE(); @@ -1280,11 +1354,10 @@ class WasmInstanceBuilder { } // Load data segments into the memory. - void LoadDataSegments(MaybeHandle globals, Address mem_addr, - size_t mem_size) { + void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (auto segment : module_->data_segments) { - uint32_t dest_offset = EvalUint32InitExpr(globals, segment.dest_addr); + uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); uint32_t source_size = segment.source_size; if (dest_offset >= mem_size || source_size >= mem_size || dest_offset >= (mem_size - source_size)) { @@ -1297,55 +1370,7 @@ class WasmInstanceBuilder { } } - Handle CompileImportWrapper(int index, const WasmImport& import, - Handle target, - Handle module_name, - MaybeHandle import_name) { - FunctionSig* sig = module_->functions[import.index].sig; - Handle code; - bool is_match = false; - Handle export_wrapper_code; - if (target->IsJSFunction()) { - Handle func = Handle::cast(target); - export_wrapper_code = handle(func->code()); - if (export_wrapper_code->kind() == Code::JS_TO_WASM_FUNCTION) { - // Compare signature of other exported wasm function. - Handle other_instance( - JSObject::cast(func->GetInternalField(kInternalModuleInstance)), - isolate_); - int func_index = - Smi::cast(func->GetInternalField(kInternalFunctionIndex))->value(); - FunctionSig* other_sig = - GetCppModule(other_instance)->functions[func_index].sig; - is_match = sig->Equals(other_sig); - } - } - if (is_match) { - // Signature matched. Unwrap the JS->WASM wrapper and return the naked - // WASM function code. - int wasm_count = 0; - int const mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); - for (RelocIterator it(*export_wrapper_code, mask); !it.done(); - it.next()) { - RelocInfo* rinfo = it.rinfo(); - Address target_address = rinfo->target_address(); - Code* target = Code::GetCodeFromTargetAddress(target_address); - if (target->kind() == Code::WASM_FUNCTION) { - ++wasm_count; - code = handle(target); - } - } - DCHECK(wasm_count == 1); - return code; - } else { - // Signature mismatch. Compile a new wrapper for the new signature. - return compiler::CompileWasmToJSWrapper(isolate_, target, sig, index, - module_name, import_name); - } - } - - void WriteGlobalValue(WasmGlobal& global, MaybeHandle globals, - Handle value) { + void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); @@ -1358,17 +1383,17 @@ class WasmInstanceBuilder { WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: - *GetRawGlobalPtr(global, globals) = static_cast(num); + *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: - *GetRawGlobalPtr(global, globals) = static_cast(num); + *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: - *GetRawGlobalPtr(global, globals) = static_cast(num); + *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); @@ -1378,9 +1403,9 @@ class WasmInstanceBuilder { // Process the imports, including functions, tables, globals, and memory, in // order, loading them from the {ffi_} object. Returns the number of imported // functions. - int ProcessImports(MaybeHandle globals, - Handle code_table, Handle instance) { + int ProcessImports(Handle code_table, Handle instance) { int num_imported_functions = 0; + int num_imported_tables = 0; for (int index = 0; index < static_cast(module_->import_table.size()); ++index) { WasmImport& import = module_->import_table[index]; @@ -1412,16 +1437,64 @@ class WasmInstanceBuilder { } Handle import_wrapper = CompileImportWrapper( - index, import, Handle::cast(function), module_name, - function_name); + isolate_, index, module_->functions[import.index].sig, + Handle::cast(function), module_name, function_name); code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } - case kExternalTable: - // TODO(titzer): Table imports must be a WebAssembly.Table. + case kExternalTable: { + Handle value = result.ToHandleChecked(); + if (!WasmJs::IsWasmTableObject(isolate_, value)) { + ReportFFIError("table import requires a WebAssembly.Table", index, + module_name, function_name); + return -1; + } + WasmIndirectFunctionTable& table = + module_->function_tables[num_imported_tables]; + TableInstance& table_instance = table_instances_[num_imported_tables]; + table_instance.table_object = Handle::cast(value); + table_instance.js_wrappers = WasmJs::GetWasmTableFunctions( + isolate_, table_instance.table_object); + + // TODO(titzer): import table size must match exactly for now. + int table_size = table_instance.js_wrappers->length(); + if (table_size != table.min_size) { + thrower_->TypeError( + "table import %d is wrong size (%d), expected %u", index, + table_size, table.min_size); + return -1; + } + + // Allocate a new dispatch table. + table_instance.dispatch_table = + isolate_->factory()->NewFixedArray(table_size * 2); + for (int i = 0; i < table_size * 2; ++i) { + table_instance.dispatch_table->set(i, + Smi::FromInt(kInvalidSigIndex)); + } + // Initialize the dispatch table with the (foreign) JS functions + // that are already in the table. + for (int i = 0; i < table_size; ++i) { + Handle val(table_instance.js_wrappers->get(i), isolate_); + if (!val->IsJSFunction()) continue; + WasmFunction* function = + GetWasmFunctionForImportWrapper(isolate_, val); + if (function == nullptr) { + thrower_->TypeError("table import %d[%d] is not a WASM function", + index, i); + return -1; + } + int sig_index = table.map.FindOrInsert(function->sig); + table_instance.dispatch_table->set(i, Smi::FromInt(sig_index)); + table_instance.dispatch_table->set(i + table_size, + *UnwrapImportWrapper(val)); + } + + num_imported_tables++; break; + } case kExternalMemory: { Handle object = result.ToHandleChecked(); if (!WasmJs::IsWasmMemoryObject(isolate_, object)) { @@ -1435,7 +1508,7 @@ class WasmInstanceBuilder { } case kExternalGlobal: { // Global imports are converted to numbers and written into the - // {globals} array buffer. + // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { @@ -1444,7 +1517,7 @@ class WasmInstanceBuilder { return -1; } Handle val = number.ToHandleChecked(); - WriteGlobalValue(module_->globals[import.index], globals, val); + WriteGlobalValue(module_->globals[import.index], val); break; } default: @@ -1456,27 +1529,25 @@ class WasmInstanceBuilder { } template - T* GetRawGlobalPtr(WasmGlobal& global, MaybeHandle globals) { - return reinterpret_cast(raw_buffer_ptr(globals, global.offset)); + T* GetRawGlobalPtr(WasmGlobal& global) { + return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. - void InitGlobals(MaybeHandle globals) { + void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: - *GetRawGlobalPtr(global, globals) = - global.init.val.i32_const; + *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: - *GetRawGlobalPtr(global, globals) = - global.init.val.i64_const; + *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: - *GetRawGlobalPtr(global, globals) = global.init.val.f32_const; + *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: - *GetRawGlobalPtr(global, globals) = global.init.val.f64_const; + *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. @@ -1488,8 +1559,8 @@ class WasmInstanceBuilder { size_t size = (global.type == kAstI64 || global.type == kAstF64) ? sizeof(double) : sizeof(int32_t); - memcpy(raw_buffer_ptr(globals, new_offset), - raw_buffer_ptr(globals, old_offset), size); + memcpy(raw_buffer_ptr(globals_, new_offset), + raw_buffer_ptr(globals_, old_offset), size); break; } case WasmInitExpr::kNone: @@ -1504,7 +1575,7 @@ class WasmInstanceBuilder { // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { - if (min_mem_pages > WasmModule::kMaxMemPages) { + if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } @@ -1519,30 +1590,29 @@ class WasmInstanceBuilder { // Process the exports, creating wrappers for functions, tables, memories, // and globals. - void ProcessExports(MaybeHandle globals, - std::vector& inited_tables, - Handle code_table, + void ProcessExports(Handle code_table, Handle instance) { - if (module_->export_table.size() == 0) return; - - // Allocate a table to cache the exported JSFunctions if needed. - bool has_exported_functions = module_->num_exported_functions > 0; - if (!has_exported_functions) { - for (auto table : module_->function_tables) { - if (table.exported) { - has_exported_functions = true; - break; - } + bool needs_wrappers = module_->num_exported_functions > 0; + for (auto table_instance : table_instances_) { + if (!table_instance.js_wrappers.is_null()) { + needs_wrappers = true; + break; } } - Handle exported_functions = - has_exported_functions - ? isolate_->factory()->NewFixedArray( - static_cast(module_->functions.size())) - : Handle::null(); + for (auto table : module_->function_tables) { + if (table.exported) { + needs_wrappers = true; + break; + } + } + if (needs_wrappers) { + // Fill the table to cache the exported JSFunction wrappers. + js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(), + Handle::null()); + } Handle exports_object = instance; - if (module_->origin == kWasmOrigin) { + if (module_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); @@ -1556,6 +1626,7 @@ class WasmInstanceBuilder { PropertyDescriptor desc; desc.set_writable(false); + // Process each export in the export table. int func_index = 0; for (auto exp : module_->export_table) { Handle name = @@ -1568,34 +1639,31 @@ class WasmInstanceBuilder { WasmFunction& function = module_->functions[exp.index]; int export_index = static_cast(module_->functions.size() + func_index); - Handle value(exported_functions->get(exp.index), isolate_); - Handle js_function; - if (value->IsJSFunction()) { - // There already is a JSFunction for this WASM function. - js_function = Handle::cast(value); - } else { + Handle js_function = js_wrappers_[exp.index]; + if (js_function.is_null()) { // Wrap the exported code as a JSFunction. Handle export_code = code_table->GetValueChecked(isolate_, export_index); js_function = WrapExportCodeAsJSFunction(isolate_, export_code, name, function.sig, func_index, instance); - exported_functions->set(exp.index, *js_function); + js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); func_index++; break; } case kExternalTable: { - InitializedTable& init_table = inited_tables[exp.index]; + // Export a table as a WebAssembly.Table object. + TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; - if (init_table.table_object.is_null()) { - init_table.table_object = WasmJs::CreateWasmTableObject( - isolate_, table.size, table.max_size != 0, table.max_size, - &init_table.js_functions); + if (table_instance.table_object.is_null()) { + table_instance.table_object = WasmJs::CreateWasmTableObject( + isolate_, table.min_size, table.has_max, table.max_size, + &table_instance.js_wrappers); } - desc.set_value(init_table.table_object); + desc.set_value(table_instance.table_object); break; } case kExternalMemory: { @@ -1622,13 +1690,13 @@ class WasmInstanceBuilder { double num = 0; switch (global.type) { case kAstI32: - num = *GetRawGlobalPtr(global, globals); + num = *GetRawGlobalPtr(global); break; case kAstF32: - num = *GetRawGlobalPtr(global, globals); + num = *GetRawGlobalPtr(global); break; case kAstF64: - num = *GetRawGlobalPtr(global, globals); + num = *GetRawGlobalPtr(global); break; default: UNREACHABLE(); @@ -1649,47 +1717,120 @@ class WasmInstanceBuilder { return; } } + } - // Fill out the contents of WebAssembly.Table instances. - if (!exported_functions.is_null()) { - // TODO(titzer): We compile JS->WASM wrappers for any function that is a - // member of an exported indirect table that is not itself exported. This - // should be done at compile time and cached instead. - WasmInstance temp_instance(module_); - temp_instance.context = isolate_->native_context(); - temp_instance.mem_size = 0; - temp_instance.mem_start = nullptr; - temp_instance.globals_start = nullptr; + void InitializeTables(Handle code_table, + Handle instance) { + Handle old_function_tables = + compiled_module_->function_tables(); + int function_table_count = + static_cast(module_->function_tables.size()); + Handle new_function_tables = + isolate_->factory()->NewFixedArray(function_table_count); + for (int index = 0; index < function_table_count; ++index) { + WasmIndirectFunctionTable& table = module_->function_tables[index]; + TableInstance& table_instance = table_instances_[index]; + int table_size = static_cast(table.min_size); - ModuleEnv module_env; - module_env.module = module_; - module_env.instance = &temp_instance; - module_env.origin = module_->origin; - - // Fill the exported tables with JSFunctions. - for (auto inited_table : inited_tables) { - if (inited_table.js_functions.is_null()) continue; - for (int i = 0; i < static_cast(inited_table.new_entries.size()); - i++) { - if (inited_table.new_entries[i] == nullptr) continue; - int func_index = - static_cast(inited_table.new_entries[i]->func_index); - if (!exported_functions->get(func_index)->IsJSFunction()) { - // No JSFunction entry yet exists for this function. Create one. - Handle wasm_code(Code::cast(code_table->get(func_index)), - isolate_); - Handle wrapper_code = compiler::CompileJSToWasmWrapper( - isolate_, &module_env, wasm_code, func_index); - Handle js_function = WrapExportCodeAsJSFunction( - isolate_, wrapper_code, isolate_->factory()->empty_string(), - module_->functions[func_index].sig, func_index, instance); - exported_functions->set(func_index, *js_function); - } - inited_table.js_functions->set(i, - exported_functions->get(func_index)); + if (table_instance.dispatch_table.is_null()) { + // Create a new dispatch table if necessary. + table_instance.dispatch_table = + isolate_->factory()->NewFixedArray(table_size * 2); + for (int i = 0; i < table_size; ++i) { + // Fill the table with invalid signature indexes so that + // uninitialized entries will always fail the signature check. + table_instance.dispatch_table->set(i, Smi::FromInt(kInvalidSigIndex)); } } + + new_function_tables->set(static_cast(index), + *table_instance.dispatch_table); + + Handle all_dispatch_tables; + if (!table_instance.table_object.is_null()) { + // Get the existing dispatch table(s) with the WebAssembly.Table object. + all_dispatch_tables = WasmJs::AddWasmTableDispatchTable( + isolate_, table_instance.table_object, Handle::null(), + index, Handle::null()); + } + + // TODO(titzer): this does redundant work if there are multiple tables, + // since initializations are not sorted by table index. + for (auto table_init : module_->table_inits) { + uint32_t base = EvalUint32InitExpr(table_init.offset); + if (base > static_cast(table_size) || + (base + table_init.entries.size() > + static_cast(table_size))) { + thrower_->CompileError("table initializer is out of bounds"); + continue; + } + for (int i = 0; i < static_cast(table_init.entries.size()); ++i) { + uint32_t func_index = table_init.entries[i]; + WasmFunction* function = &module_->functions[func_index]; + int table_index = static_cast(i + base); + int32_t sig_index = table.map.Find(function->sig); + DCHECK_GE(sig_index, 0); + table_instance.dispatch_table->set(table_index, + Smi::FromInt(sig_index)); + table_instance.dispatch_table->set(table_index + table_size, + code_table->get(func_index)); + + if (!all_dispatch_tables.is_null()) { + Handle wasm_code(Code::cast(code_table->get(func_index)), + isolate_); + if (js_wrappers_[func_index].is_null()) { + // No JSFunction entry yet exists for this function. Create one. + // TODO(titzer): We compile JS->WASM wrappers for functions are + // not exported but are in an exported table. This should be done + // at module compile time and cached instead. + WasmInstance temp_instance(module_); + temp_instance.context = isolate_->native_context(); + temp_instance.mem_size = 0; + temp_instance.mem_start = nullptr; + temp_instance.globals_start = nullptr; + + ModuleEnv module_env; + module_env.module = module_; + module_env.instance = &temp_instance; + module_env.origin = module_->origin; + + Handle wrapper_code = compiler::CompileJSToWasmWrapper( + isolate_, &module_env, wasm_code, func_index); + Handle js_function = WrapExportCodeAsJSFunction( + isolate_, wrapper_code, isolate_->factory()->empty_string(), + function->sig, func_index, instance); + js_wrappers_[func_index] = js_function; + } + table_instance.js_wrappers->set(table_index, + *js_wrappers_[func_index]); + + UpdateDispatchTablesInternal(isolate_, all_dispatch_tables, + table_index, function, wasm_code); + } + } + } + + // TODO(titzer): we add the new dispatch table at the end to avoid + // redundant work and also because the new instance is not yet fully + // initialized. + if (!table_instance.table_object.is_null()) { + // Add the new dispatch table to the WebAssembly.Table object. + all_dispatch_tables = WasmJs::AddWasmTableDispatchTable( + isolate_, table_instance.table_object, instance, index, + table_instance.dispatch_table); + } } + // Patch all code that has references to the old indirect tables. + for (int i = 0; i < code_table->length(); ++i) { + if (!code_table->get(i)->IsCode()) continue; + Handle code(Code::cast(code_table->get(i)), isolate_); + for (int j = 0; j < function_table_count; ++j) { + ReplaceReferenceInCode( + code, Handle(old_function_tables->get(j), isolate_), + Handle(new_function_tables->get(j), isolate_)); + } + } + compiled_module_->set_function_tables(new_function_tables); } }; @@ -1960,7 +2101,7 @@ int32_t wasm::GetInstanceMemorySize(Isolate* isolate, } uint32_t GetMaxInstanceMemorySize(Isolate* isolate, Handle instance) { - uint32_t max_pages = WasmModule::kMaxMemPages; + uint32_t max_pages = WasmModule::kV8MaxPages; Handle memory_object(instance->GetInternalField(kWasmMemObject), isolate); if (memory_object->IsUndefined(isolate)) return max_pages; @@ -1972,7 +2113,7 @@ int32_t wasm::GrowInstanceMemory(Isolate* isolate, Handle instance, if (!IsWasmInstance(*instance)) return -1; if (pages == 0) return GetInstanceMemorySize(isolate, instance); uint32_t max_pages = GetMaxInstanceMemorySize(isolate, instance); - if (WasmModule::kMaxMemPages < max_pages) return -1; + if (WasmModule::kV8MaxPages < max_pages) return -1; Address old_mem_start = nullptr; uint32_t old_size = 0, new_size = 0; diff --git a/src/wasm/wasm-module.h b/src/wasm/wasm-module.h index 2376a5feba..3ac410476d 100644 --- a/src/wasm/wasm-module.h +++ b/src/wasm/wasm-module.h @@ -133,8 +133,10 @@ struct WasmDataSegment { // Static representation of a wasm indirect call table. struct WasmIndirectFunctionTable { - uint32_t size; // initial table size. + uint32_t min_size; // minimum table size. uint32_t max_size; // maximum table size. + bool has_max; // true if there is a maximum size. + // TODO(titzer): Move this to WasmInstance. Needed by interpreter only. std::vector values; // function table, -1 indicating invalid. bool imported; // true if imported. bool exported; // true if exported. @@ -173,9 +175,9 @@ class WasmCompiledModule; // Static representation of a module. struct V8_EXPORT_PRIVATE WasmModule { static const uint32_t kPageSize = 0x10000; // Page size, 64kb. - static const uint32_t kMaxLegalPages = 65536; // Maximum legal pages static const uint32_t kMinMemPages = 1; // Minimum memory size = 64kb - static const uint32_t kMaxMemPages = 16384; // Maximum memory size = 1gb + static const size_t kV8MaxPages = 16384; // Maximum memory size = 1gb + static const size_t kV8MaxTableSize = 16 * 1024 * 1024; Zone* owned_zone; const byte* module_start = nullptr; // starting address for the module bytes @@ -557,6 +559,9 @@ int32_t GetInstanceMemorySize(Isolate* isolate, Handle instance); int32_t GrowInstanceMemory(Isolate* isolate, Handle instance, uint32_t pages); +void UpdateDispatchTables(Isolate* isolate, Handle dispatch_tables, + int index, Handle js_function); + namespace testing { void ValidateInstancesChain(Isolate* isolate, Handle wasm_module, diff --git a/test/cctest/wasm/wasm-run-utils.h b/test/cctest/wasm/wasm-run-utils.h index d63586042a..4045356ff6 100644 --- a/test/cctest/wasm/wasm-run-utils.h +++ b/test/cctest/wasm/wasm-run-utils.h @@ -223,10 +223,12 @@ class TestingModule : public ModuleEnv { void AddIndirectFunctionTable(uint16_t* function_indexes, uint32_t table_size) { - module_.function_tables.push_back({table_size, table_size, + module_.function_tables.push_back({table_size, table_size, true, std::vector(), false, false, SignatureMap()}); WasmIndirectFunctionTable& table = module_.function_tables.back(); + table.min_size = table_size; + table.max_size = table_size; for (uint32_t i = 0; i < table_size; ++i) { table.values.push_back(function_indexes[i]); table.map.FindOrInsert(module_.functions[function_indexes[i]].sig); diff --git a/test/mjsunit/wasm/indirect-tables.js b/test/mjsunit/wasm/indirect-tables.js index cdb489cb83..62b900586c 100644 --- a/test/mjsunit/wasm/indirect-tables.js +++ b/test/mjsunit/wasm/indirect-tables.js @@ -30,6 +30,8 @@ function AddFunctions(builder) { return {mul: mul, add: add, sub: sub}; } +function js_div(a, b) { return (a / b) | 0; } + (function ExportedTableTest() { print("ExportedTableTest..."); @@ -56,8 +58,6 @@ function AddFunctions(builder) { let module = new WebAssembly.Module(builder.toBuffer()); - function js_div(a, b) { return (a / b) | 0; } - for (let i = 0; i < 5; i++) { print(" base = " + i); let instance = new WebAssembly.Instance(module, {base: i, js_div: js_div}); @@ -100,3 +100,278 @@ function AddFunctions(builder) { assertEquals(-44, exp_div(-88.1, 2)); } })(); + + +(function ImportedTableTest() { + let kTableSize = 10; + print("ImportedTableTest..."); + var builder = new WasmModuleBuilder(); + + let d = builder.addImport("js_div", kSig_i_ii); + let f = AddFunctions(builder); + builder.setFunctionTableLength(kTableSize); + let g = builder.addImportedGlobal("base", undefined, kAstI32); + builder.addFunctionTableInit(g, true, [f.mul.index, f.add.index, + f.sub.index, + d]); + builder.addExportOfKind("table", kExternalTable, 0); + + let m1 = new WebAssembly.Module(builder.toBuffer()); + + var builder = new WasmModuleBuilder(); + + builder.addImportedTable("table", undefined, kTableSize, kTableSize); + builder.addFunction("main", kSig_i_ii) + .addBody([ + kExprI32Const, 33, // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + kExprCallIndirect, 0, kTableZero]) // -- + .exportAs("main"); + + let m2 = new WebAssembly.Module(builder.toBuffer()); + + // Run 5 trials at different table bases. + for (let i = 0; i < 5; i++) { + print(" base = " + i); + let i1 = new WebAssembly.Instance(m1, {base: i, js_div: js_div}); + let table = i1.exports.table; + assertEquals(10, table.length); + let i2 = new WebAssembly.Instance(m2, {table: table}); + let main = i2.exports.main; + + for (var j = 0; j < i; j++) { + assertThrows(() => main(0, j)); + assertSame(null, table.get(j)); + } + + // mul + assertEquals("function", typeof table.get(i+0)); + assertEquals(0, main(0, i+0)); + assertEquals(66, main(2, i+0)); + + // add + assertEquals("function", typeof table.get(i+1)); + assertEquals(33, main(0, i+1)); + assertEquals(38, main(5, i+1)); + + // sub + assertEquals("function", typeof table.get(i+2)); + assertEquals(32, main(1, i+2)); + assertEquals(28, main(5, i+2)); + + // div + assertEquals("function", typeof table.get(i+3)); + assertEquals(8, main(4, i+3)); + assertEquals(3, main(11, i+3)); + + for (var j = i + 4; j < (kTableSize + 5); j++) { + assertThrows(x => main(0, j)); + if (j < kTableSize) assertSame(null, table.get(j)); + } + } +})(); + +(function ImportedTableTest() { + let kTableSize = 10; + print("ManualTableTest..."); + + var builder = new WasmModuleBuilder(); + + let d = builder.addImport("js_div", kSig_i_ii); + builder.addImportedTable("table", undefined, kTableSize, kTableSize); + let g = builder.addImportedGlobal("base", undefined, kAstI32); + let f = AddFunctions(builder); + builder.addFunctionTableInit(g, true, [f.mul.index, f.add.index, + f.sub.index, + d]); + builder.addFunction("main", kSig_i_ii) + .addBody([ + kExprI32Const, 55, // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + kExprCallIndirect, 0, kTableZero]) // -- + .exportAs("main"); + + let m2 = new WebAssembly.Module(builder.toBuffer()); + + // Run 5 trials at different table bases. + for (let i = 0; i < 5; i++) { + print(" base = " + i); + let table = new WebAssembly.Table({element: "anyfunc", + initial: kTableSize}); + assertEquals(10, table.length); + let i2 = new WebAssembly.Instance(m2, {base: i, table: table, + js_div: js_div}); + let main = i2.exports.main; + + for (var j = 0; j < i; j++) { + assertThrows(() => main(0, j)); + assertSame(null, table.get(j)); + } + + // mul + assertEquals("function", typeof table.get(i+0)); + assertEquals(0, main(0, i+0)); + assertEquals(110, main(2, i+0)); + + // add + assertEquals("function", typeof table.get(i+1)); + assertEquals(55, main(0, i+1)); + assertEquals(60, main(5, i+1)); + + // sub + assertEquals("function", typeof table.get(i+2)); + assertEquals(54, main(1, i+2)); + assertEquals(50, main(5, i+2)); + + // div + assertEquals("function", typeof table.get(i+3)); + assertEquals(13, main(4, i+3)); + assertEquals(5, main(11, i+3)); + + for (var j = i + 4; j < (kTableSize + 5); j++) { + assertThrows(x => main(0, j)); + if (j < kTableSize) assertSame(null, table.get(j)); + } + } +})(); + + +(function CumulativeTest() { + print("CumulativeTest..."); + + let kTableSize = 10; + let table = new WebAssembly.Table({element: "anyfunc", initial: 10}); + + var builder = new WasmModuleBuilder(); + + builder.addImportedTable("table", undefined, kTableSize, kTableSize); + let g = builder.addImportedGlobal("base", undefined, kAstI32); + let sig_index = builder.addType(kSig_i_v); + builder.addFunction("g", sig_index) + .addBody([ + kExprGetGlobal, g + ]); + builder.addFunction("main", kSig_i_ii) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, sig_index, kTableZero]) // -- + .exportAs("main"); + builder.addFunctionTableInit(g, true, [g]); + + let module = new WebAssembly.Module(builder.toBuffer()); + + for (var i = 0; i < kTableSize; i++) { + print(" base = " + i); + let instance = new WebAssembly.Instance(module, {base: i, table: table}); + + for (var j = 0; j < kTableSize; j++) { + let func = table.get(j); + if (j > i) { + assertSame(null, func); + assertTraps(kTrapFuncSigMismatch, () => instance.exports.main(j)); + } else { + assertEquals("function", typeof func); + assertEquals(j, func()); + assertEquals(j, instance.exports.main(j)); + } + } + } +})(); + +(function TwoWayTest() { + print("TwoWayTest..."); + let kTableSize = 3; + + // Module {m1} defines the table and exports it. + var builder = new WasmModuleBuilder(); + builder.addType(kSig_i_i); + builder.addType(kSig_i_ii); + var sig_index1 = builder.addType(kSig_i_v); + var f1 = builder.addFunction("f1", sig_index1) + .addBody([kExprI32Const, 11]); + + builder.addFunction("main", kSig_i_ii) + .addBody([ + kExprGetLocal, 0, // -- + kExprCallIndirect, sig_index1, kTableZero]) // -- + .exportAs("main"); + + builder.setFunctionTableLength(kTableSize); + builder.addFunctionTableInit(0, false, [f1.index]); + builder.addExportOfKind("table", kExternalTable, 0); + + var m1 = new WebAssembly.Module(builder.toBuffer()); + + // Module {m2} imports the table and adds {f2}. + var builder = new WasmModuleBuilder(); + builder.addType(kSig_i_ii); + var sig_index2 = builder.addType(kSig_i_v); + var f2 = builder.addFunction("f2", sig_index2) + .addBody([kExprI32Const, 22]); + + builder.addFunction("main", kSig_i_ii) + .addBody([ + kExprGetLocal, 0, // -- + kExprCallIndirect, sig_index2, kTableZero]) // -- + .exportAs("main"); + + builder.setFunctionTableLength(kTableSize); + builder.addFunctionTableInit(1, false, [f2.index]); + builder.addImportedTable("table", undefined, kTableSize, kTableSize); + + var m2 = new WebAssembly.Module(builder.toBuffer()); + + assertFalse(sig_index1 == sig_index2); + + var i1 = new WebAssembly.Instance(m1); + var i2 = new WebAssembly.Instance(m2, {table: i1.exports.table}); + + assertEquals(11, i1.exports.main(0)); + assertEquals(11, i2.exports.main(0)); + + assertEquals(22, i1.exports.main(1)); + assertEquals(22, i2.exports.main(1)); + + assertThrows(() => i1.exports.main(2)); + assertThrows(() => i2.exports.main(2)); + assertThrows(() => i1.exports.main(3)); + assertThrows(() => i2.exports.main(3)); + +})(); + +(function MismatchedTableSize() { + print("MismatchedTableSize..."); + let kTableSize = 5; + + for (var expsize = 1; expsize < 4; expsize++) { + for (var impsize = 1; impsize < 4; impsize++) { + print(" expsize = " + expsize + ", impsize = " + impsize); + var builder = new WasmModuleBuilder(); + builder.setFunctionTableLength(expsize); + builder.addExportOfKind("expfoo", kExternalTable, 0); + + let m1 = new WebAssembly.Module(builder.toBuffer()); + + var builder = new WasmModuleBuilder(); + builder.addImportedTable("impfoo", undefined, impsize, impsize); + + let m2 = new WebAssembly.Module(builder.toBuffer()); + + var i1 = new WebAssembly.Instance(m1); + + // TODO(titzer): v8 currently requires import table size to match + // export table size. + var ffi = {impfoo: i1.exports.expfoo}; + if (expsize == impsize) { + var i2 = new WebAssembly.Instance(m2, ffi); + } else { + assertThrows(() => new WebAssembly.Instance(m2, ffi)); + } + } + } + + + +})(); diff --git a/test/mjsunit/wasm/wasm-module-builder.js b/test/mjsunit/wasm/wasm-module-builder.js index 85b2638eb1..4f4633cbd5 100644 --- a/test/mjsunit/wasm/wasm-module-builder.js +++ b/test/mjsunit/wasm/wasm-module-builder.js @@ -204,6 +204,12 @@ class WasmModuleBuilder { return this; } + addImportedTable(module, name, initial, maximum) { + let o = {module: module, name: name, kind: kExternalTable, initial: initial, + maximum: maximum}; + this.imports.push(o); + } + addExport(name, index) { this.exports.push({name: name, kind: kExternalFunction, index: index}); return this; @@ -289,6 +295,12 @@ class WasmModuleBuilder { section.emit_u8(has_max ? 1 : 0); // flags section.emit_u32v(imp.initial); // initial if (has_max) section.emit_u32v(imp.maximum); // maximum + } else if (imp.kind == kExternalTable) { + section.emit_u8(kWasmAnyFunctionTypeForm); + var has_max = (typeof imp.maximum) != "undefined"; + section.emit_u8(has_max ? 1 : 0); // flags + section.emit_u32v(imp.initial); // initial + if (has_max) section.emit_u32v(imp.maximum); // maximum } else { throw new Error("unknown/unsupported import kind " + imp.kind); } diff --git a/test/unittests/wasm/module-decoder-unittest.cc b/test/unittests/wasm/module-decoder-unittest.cc index 724d9ccf6d..542c3c9d42 100644 --- a/test/unittests/wasm/module-decoder-unittest.cc +++ b/test/unittests/wasm/module-decoder-unittest.cc @@ -510,8 +510,7 @@ TEST_F(WasmModuleVerifyTest, OneIndirectFunction) { EXPECT_EQ(1, result.val->signatures.size()); EXPECT_EQ(1, result.val->functions.size()); EXPECT_EQ(1, result.val->function_tables.size()); - EXPECT_EQ(1, result.val->function_tables[0].values.size()); - EXPECT_EQ(-1, result.val->function_tables[0].values[0]); + EXPECT_EQ(1, result.val->function_tables[0].min_size); } if (result.val) delete result.val; } @@ -537,8 +536,7 @@ TEST_F(WasmModuleVerifyTest, OneIndirectFunction_one_entry) { EXPECT_EQ(1, result.val->signatures.size()); EXPECT_EQ(1, result.val->functions.size()); EXPECT_EQ(1, result.val->function_tables.size()); - EXPECT_EQ(1, result.val->function_tables[0].values.size()); - EXPECT_EQ(0, result.val->function_tables[0].values[0]); + EXPECT_EQ(1, result.val->function_tables[0].min_size); } if (result.val) delete result.val; } @@ -575,10 +573,7 @@ TEST_F(WasmModuleVerifyTest, MultipleIndirectFunctions) { EXPECT_EQ(2, result.val->signatures.size()); EXPECT_EQ(4, result.val->functions.size()); EXPECT_EQ(1, result.val->function_tables.size()); - EXPECT_EQ(8, result.val->function_tables[0].values.size()); - for (int i = 0; i < 8; i++) { - EXPECT_EQ(i & 3, result.val->function_tables[0].values[i]); - } + EXPECT_EQ(8, result.val->function_tables[0].min_size); } if (result.val) delete result.val; }