code, Handle old_ref, Handle new_ref) { for (RelocIterator it(*code, 1 << RelocInfo::EMBEDDED_OBJECT); !it.done(); it.next()) { if (it.rinfo()->target_object() == *old_ref) { it.rinfo()->set_target_object(*new_ref); } } } Handle NewArrayBuffer(Isolate* isolate, size_t size) { if (size > (WasmModule::kV8MaxPages * WasmModule::kPageSize)) { // TODO(titzer): lift restriction on maximum memory allocated here. return Handle::null(); } void* memory = isolate->array_buffer_allocator()->Allocate(size); if (memory == nullptr) { return Handle::null(); } #if DEBUG // Double check the API allocator actually zero-initialized the memory. const byte* bytes = reinterpret_cast(memory); for (size_t i = 0; i < size; ++i) { DCHECK_EQ(0, bytes[i]); } #endif Handle buffer = isolate->factory()->NewJSArrayBuffer(); JSArrayBuffer::Setup(buffer, isolate, false, memory, static_cast(size)); buffer->set_is_neuterable(false); return buffer; } void RelocateMemoryReferencesInCode(Handle code_table, Address old_start, Address start, uint32_t prev_size, uint32_t new_size) { for (int i = 0; i < code_table->length(); ++i) { DCHECK(code_table->get(i)->IsCode()); Handle code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = (1 << RelocInfo::WASM_MEMORY_REFERENCE) | (1 << RelocInfo::WASM_MEMORY_SIZE_REFERENCE); for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_memory_reference(old_start, start, prev_size, new_size); } } } void RelocateGlobals(Handle code_table, Address old_start, Address globals_start) { for (int i = 0; i < code_table->length(); ++i) { DCHECK(code_table->get(i)->IsCode()); Handle code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } Handle CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = (1 << RelocInfo::WASM_MEMORY_REFERENCE) | (1 << RelocInfo::WASM_MEMORY_SIZE_REFERENCE); for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_memory_reference(old_start, start, prev_size, new_size); } } } void RelocateGlobals(Handle code_table, Address old_start, Address globals_start) { for (int i = 0; i < code_table->length(); ++i) { DCHECK(code_table->get(i)->IsCode()); Handle code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } Handle CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = (1 << RelocInfo::WASM_MEMORY_REFERENCE) | (1 << RelocInfo::WASM_MEMORY_SIZE_REFERENCE); for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_memory_reference(old_start, start, prev_size, new_size); } } } void RelocateGlobals(Handle code_table, Address old_start, Address globals_start) { for (int i = 0; i < code_table->length(); ++i) { DCHECK(code_table->get(i)->IsCode()); Handle code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } Handle CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = Handle(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } Handle CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(Code::cast(code_table->get(i))); AllowDeferredHandleDereference embedding_raw_address; int mask = 1 << RelocInfo::WASM_GLOBAL_REFERENCE; for (RelocIterator it(*code, mask); !it.done(); it.next()) { it.rinfo()->update_wasm_global_reference(old_start, globals_start); } } } Handle CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
CreatePlaceholder(Factory* factory, uint32_t index, Code::Kind kind) { // Create a placeholder code object and encode the corresponding index in // the {constant_pool_offset} field of the code object. // TODO(titzer): instead of placeholders, use a reloc_info mode. static byte buffer[] = {0, 0, 0, 0}; // fake instructions. static CodeDesc desc = { buffer, arraysize(buffer), arraysize(buffer), 0, 0, nullptr, 0, nullptr}; Handle code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = factory->NewCode(desc, Code::KindField::encode(kind), Handle::null()); code->set_constant_pool_offset(static_cast(index) + kPlaceholderMarker); return code; } bool LinkFunction(Handle unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
unlinked, std::vector>& code_table) { bool modified = false; int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (RelocIterator it(*unlinked, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsCodeTarget(mode)) { Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target->constant_pool_offset() < kPlaceholderMarker) continue; switch (target->kind()) { case Code::WASM_FUNCTION: // fall through case Code::WASM_TO_JS_FUNCTION: // fall through case Code::JS_TO_WASM_FUNCTION: { // Patch direct calls to placeholder code objects. uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; Handle new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
new_target = code_table[index]; if (target != *new_target) { it.rinfo()->set_target_address(new_target->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); modified = true; } break; } default: break; } } } return modified; } void FlushICache(Isolate* isolate, Handle code_table) { for (int i = 0; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = code_table->GetValueChecked(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate, i); Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Fetches the compilation unit of a wasm function and executes its parallel // phase. bool FetchAndExecuteCompilationUnit( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Mutex* result_mutex, base::AtomicNumber* next_unit) { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; DisallowCodeDependencyChange no_dependency_change; // - 1 because AtomicIncrement returns the value after the atomic increment. size_t index = next_unit->Increment(1) - 1; if (index >= compilation_units->size()) { return false; } compiler::WasmCompilationUnit* unit = compilation_units->at(index); if (unit != nullptr) { unit->ExecuteCompilation(); base::LockGuard guard(result_mutex); executed_units->push(unit); } return true; } class WasmCompilationTask : public CancelableTask { public: WasmCompilationTask( Isolate* isolate, std::vector* compilation_units, std::queue* executed_units, base::Semaphore* on_finished, base::Mutex* result_mutex, base::AtomicNumber* next_unit) : CancelableTask(isolate), isolate_(isolate), compilation_units_(compilation_units), executed_units_(executed_units), on_finished_(on_finished), result_mutex_(result_mutex), next_unit_(next_unit) {} void RunInternal() override { while (FetchAndExecuteCompilationUnit(isolate_, compilation_units_, executed_units_, result_mutex_, next_unit_)) { } on_finished_->Signal(); } Isolate* isolate_; std::vector* compilation_units_; std::queue* executed_units_; base::Semaphore* on_finished_; base::Mutex* result_mutex_; base::AtomicNumber* next_unit_; }; static void RecordStats(Isolate* isolate, Code* code) { isolate->counters()->wasm_generated_code_size()->Increment(code->body_size()); isolate->counters()->wasm_reloc_size()->Increment( code->relocation_info()->length()); } static void RecordStats(Isolate* isolate, Handle functions) { DisallowHeapAllocation no_gc; for (int i = 0; i < functions->length(); ++i) { RecordStats(isolate, Code::cast(functions->get(i))); } } Address GetGlobalStartAddressFromCodeTemplate(Object* undefined, JSObject* object) { auto instance = WasmInstanceObject::cast(object); Address old_address = nullptr; if (instance->has_globals_buffer()) { old_address = static_cast(instance->get_globals_buffer()->backing_store()); } return old_address; } void InitializeParallelCompilation( Isolate* isolate, const std::vector& functions, std::vector& compilation_units, ModuleEnv& module_env, ErrorThrower* thrower) { for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < functions.size(); ++i) { const WasmFunction* func = &functions[i]; compilation_units[i] = func->imported ? nullptr : new compiler::WasmCompilationUnit( thrower, isolate, &module_env, func, i); } } uint32_t* StartCompilationTasks( Isolate* isolate, std::vector& compilation_units, std::queue& executed_units, base::Semaphore* pending_tasks, base::Mutex& result_mutex, base::AtomicNumber& next_unit) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); uint32_t* task_ids = new uint32_t[num_tasks]; for (size_t i = 0; i < num_tasks; ++i) { WasmCompilationTask* task = new WasmCompilationTask(isolate, &compilation_units, &executed_units, pending_tasks, &result_mutex, &next_unit); task_ids[i] = task->id(); V8::GetCurrentPlatform()->CallOnBackgroundThread( task, v8::Platform::kShortRunningTask); } return task_ids; } void WaitForCompilationTasks(Isolate* isolate, uint32_t* task_ids, base::Semaphore* pending_tasks) { const size_t num_tasks = Min(static_cast(FLAG_wasm_num_compilation_tasks), V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads()); for (size_t i = 0; i < num_tasks; ++i) { // If the task has not started yet, then we abort it. Otherwise we wait for // it to finish. if (isolate->cancelable_task_manager()->TryAbort(task_ids[i]) != CancelableTaskManager::kTaskAborted) { pending_tasks->Wait(); } } } void FinishCompilationUnits( std::queue& executed_units, std::vector>& results, base::Mutex& result_mutex) { while (true) { compiler::WasmCompilationUnit* unit = nullptr; { base::LockGuard guard(&result_mutex); if (executed_units.empty()) { break; } unit = executed_units.front(); executed_units.pop(); } int j = unit->index(); results[j] = unit->FinishCompilation(); delete unit; } } void CompileInParallel(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { // Data structures for the parallel compilation. std::vector compilation_units( module->functions.size()); std::queue executed_units; //----------------------------------------------------------------------- // For parallel compilation: // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation. // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. // 5) The main thread finishes the compilation. // Turn on the {CanonicalHandleScope} so that the background threads can // use the node cache. CanonicalHandleScope canonical(isolate); // 1) The main thread allocates a compilation unit for each wasm function // and stores them in the vector {compilation_units}. InitializeParallelCompilation(isolate, module->functions, compilation_units, *module_env, thrower); // Objects for the synchronization with the background threads. base::Mutex result_mutex; base::AtomicNumber next_unit( static_cast(FLAG_skip_compiling_wasm_funcs)); // 2) The main thread spawns {WasmCompilationTask} instances which run on // the background threads. std::unique_ptr task_ids(StartCompilationTasks( isolate, compilation_units, executed_units, module->pending_tasks.get(), result_mutex, next_unit)); // 3.a) The background threads and the main thread pick one compilation // unit at a time and execute the parallel phase of the compilation // unit. After finishing the execution of the parallel phase, the // result is enqueued in {executed_units}. while (FetchAndExecuteCompilationUnit(isolate, &compilation_units, &executed_units, &result_mutex, &next_unit)) { // 3.b) If {executed_units} contains a compilation unit, the main thread // dequeues it and finishes the compilation unit. Compilation units // are finished concurrently to the background threads to save // memory. FinishCompilationUnits(executed_units, functions, result_mutex); } // 4) After the parallel phase of all compilation units has started, the // main thread waits for all {WasmCompilationTask} instances to finish. WaitForCompilationTasks(isolate, task_ids.get(), module->pending_tasks.get()); // Finish the compilation of the remaining compilation units. FinishCompilationUnits(executed_units, functions, result_mutex); } void CompileSequentially(Isolate* isolate, const WasmModule* module, std::vector>& functions, ErrorThrower* thrower, ModuleEnv* module_env) { DCHECK(!thrower->error()); for (uint32_t i = FLAG_skip_compiling_wasm_funcs; i < module->functions.size(); ++i) { const WasmFunction& func = module->functions[i]; if (func.imported) continue; // Imports are compiled at instantiation time. WasmName str = module->GetName(func.name_offset, func.name_length); Handle code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = Handle::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
::null(); // Compile the function. code = compiler::WasmCompilationUnit::CompileWasmFunction( thrower, isolate, module_env, &func); if (code.is_null()) { thrower->CompileError("Compilation of #%d:%.*s failed.", i, str.length(), str.start()); break; } // Install the code into the linker table. functions[i] = code; } } void PatchDirectCalls(Handle old_functions, Handle new_functions, int start) { DCHECK_EQ(new_functions->length(), old_functions->length()); DisallowHeapAllocation no_gc; std::map old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
old_to_new_code; for (int i = 0; i < new_functions->length(); ++i) { old_to_new_code.insert(std::make_pair(Code::cast(old_functions->get(i)), Code::cast(new_functions->get(i)))); } int mode_mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET); AllowDeferredHandleDereference embedding_raw_address; for (int i = start; i < new_functions->length(); ++i) { Code* wasm_function = Code::cast(new_functions->get(i)); for (RelocIterator it(wasm_function, mode_mask); !it.done(); it.next()) { Code* old_code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (old_code->kind() == Code::WASM_TO_JS_FUNCTION || old_code->kind() == Code::WASM_FUNCTION) { auto found = old_to_new_code.find(old_code); DCHECK(found != old_to_new_code.end()); Code* new_code = found->second; if (new_code != old_code) { it.rinfo()->set_target_address(new_code->instruction_start(), UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH); } } } } } static void ResetCompiledModule(Isolate* isolate, WasmInstanceObject* owner, WasmCompiledModule* compiled_module) { TRACE("Resetting %d\n", compiled_module->instance_id()); Object* undefined = *isolate->factory()->undefined_value(); uint32_t old_mem_size = compiled_module->mem_size(); uint32_t default_mem_size = compiled_module->default_mem_size(); Object* mem_start = compiled_module->ptr_to_memory(); Address old_mem_address = nullptr; Address globals_start = GetGlobalStartAddressFromCodeTemplate(undefined, owner); // Reset function tables. FixedArray* function_tables = nullptr; FixedArray* empty_function_tables = nullptr; if (compiled_module->has_function_tables()) { function_tables = compiled_module->ptr_to_function_tables(); empty_function_tables = compiled_module->ptr_to_empty_function_tables(); compiled_module->set_ptr_to_function_tables(empty_function_tables); } if (old_mem_size > 0) { CHECK_NE(mem_start, undefined); old_mem_address = static_cast(JSArrayBuffer::cast(mem_start)->backing_store()); } int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE) | RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); // Patch code to update memory references, global references, and function // 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 || function_tables)) { FixedArray* functions = FixedArray::cast(fct_obj); for (int i = 0; i < functions->length(); ++i) { Code* code = Code::cast(functions->get(i)); bool changed = false; for (RelocIterator it(code, mode_mask); !it.done(); it.next()) { RelocInfo::Mode mode = it.rinfo()->rmode(); if (RelocInfo::IsWasmMemoryReference(mode) || RelocInfo::IsWasmMemorySizeReference(mode)) { it.rinfo()->update_wasm_memory_reference( old_mem_address, nullptr, old_mem_size, default_mem_size); changed = true; } else if (RelocInfo::IsWasmGlobalReference(mode)) { it.rinfo()->update_wasm_global_reference(globals_start, nullptr); changed = true; } else if (RelocInfo::IsEmbeddedObject(mode) && function_tables) { Object* old = it.rinfo()->target_object(); 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; } } } } if (changed) { Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } } compiled_module->reset_memory(); } static void InstanceFinalizer(const v8::WeakCallbackInfo& data) { JSObject** p = reinterpret_cast(data.GetParameter()); WasmInstanceObject* owner = reinterpret_cast(*p); WasmCompiledModule* compiled_module = owner->get_compiled_module(); TRACE("Finalizing %d {\n", compiled_module->instance_id()); Isolate* isolate = reinterpret_cast(data.GetIsolate()); DCHECK(compiled_module->has_weak_wasm_module()); WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module(); // weak_wasm_module may have been cleared, meaning the module object // was GC-ed. In that case, there won't be any new instances created, // and we don't need to maintain the links between instances. if (!weak_wasm_module->cleared()) { JSObject* wasm_module = JSObject::cast(weak_wasm_module->value()); WasmCompiledModule* current_template = WasmCompiledModule::cast(wasm_module->GetInternalField(0)); TRACE("chain before {\n"); TRACE_CHAIN(current_template); TRACE("}\n"); DCHECK(!current_template->has_weak_prev_instance()); WeakCell* next = compiled_module->ptr_to_weak_next_instance(); WeakCell* prev = compiled_module->ptr_to_weak_prev_instance(); if (current_template == compiled_module) { if (next == nullptr) { ResetCompiledModule(isolate, owner, compiled_module); } else { DCHECK(next->value()->IsFixedArray()); wasm_module->SetInternalField(0, next->value()); DCHECK_NULL(prev); WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } } else { DCHECK(!(prev == nullptr && next == nullptr)); // the only reason prev or next would be cleared is if the // respective objects got collected, but if that happened, // we would have relinked the list. if (prev != nullptr) { DCHECK(!prev->cleared()); if (next == nullptr) { WasmCompiledModule::cast(prev->value())->reset_weak_next_instance(); } else { WasmCompiledModule::cast(prev->value()) ->set_ptr_to_weak_next_instance(next); } } if (next != nullptr) { DCHECK(!next->cleared()); if (prev == nullptr) { WasmCompiledModule::cast(next->value())->reset_weak_prev_instance(); } else { WasmCompiledModule::cast(next->value()) ->set_ptr_to_weak_prev_instance(prev); } } } TRACE("chain after {\n"); TRACE_CHAIN(WasmCompiledModule::cast(wasm_module->GetInternalField(0))); TRACE("}\n"); } compiled_module->reset_weak_owning_instance(); GlobalHandles::Destroy(reinterpret_cast(p)); TRACE("}\n"); } std::pair GetFunctionOffsetAndLength( Handle compiled_module, int func_index) { WasmModule* module = compiled_module->module(); if (func_index < 0 || static_cast(func_index) > module->functions.size()) { return {0, 0}; } WasmFunction& func = module->functions[func_index]; return {static_cast(func.code_start_offset), static_cast(func.code_end_offset - func.code_start_offset)}; } } // namespace const char* wasm::SectionName(WasmSectionCode code) { switch (code) { case kUnknownSectionCode: return "Unknown"; case kTypeSectionCode: return "Type"; case kImportSectionCode: return "Import"; case kFunctionSectionCode: return "Function"; case kTableSectionCode: return "Table"; case kMemorySectionCode: return "Memory"; case kGlobalSectionCode: return "Global"; case kExportSectionCode: return "Export"; case kStartSectionCode: return "Start"; case kCodeSectionCode: return "Code"; case kElementSectionCode: return "Element"; case kDataSectionCode: return "Data"; case kNameSectionCode: return "Name"; default: return ""; } } std::ostream& wasm::operator<<(std::ostream& os, const WasmModule& module) { os << "WASM module with "; os << (module.min_mem_pages * module.kPageSize) << " min mem"; os << (module.max_mem_pages * module.kPageSize) << " max mem"; os << module.functions.size() << " functions"; os << module.functions.size() << " globals"; os << module.functions.size() << " data segments"; return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunction& function) { os << "WASM function with signature " << *function.sig; os << " code bytes: " << (function.code_end_offset - function.code_start_offset); return os; } std::ostream& wasm::operator<<(std::ostream& os, const WasmFunctionName& pair) { os << "#" << pair.function_->func_index << ":"; if (pair.function_->name_offset > 0) { if (pair.module_) { WasmName name = pair.module_->GetName(pair.function_->name_offset, pair.function_->name_length); os.write(name.start(), name.length()); } else { os << "+" << pair.function_->func_index; } } else { os << "?"; } return os; } Object* wasm::GetOwningWasmInstance(Code* code) { DCHECK(code->kind() == Code::WASM_FUNCTION); DisallowHeapAllocation no_gc; FixedArray* deopt_data = code->deoptimization_data(); DCHECK_NOT_NULL(deopt_data); DCHECK(deopt_data->length() == 2); Object* weak_link = deopt_data->get(0); if (!weak_link->IsWeakCell()) return nullptr; WeakCell* cell = WeakCell::cast(weak_link); return cell->value(); } int wasm::GetFunctionCodeOffset(Handle compiled_module, int func_index) { return GetFunctionOffsetAndLength(compiled_module, func_index).first; } bool wasm::GetPositionInfo(Handle compiled_module, uint32_t position, Script::PositionInfo* info) { std::vector& functions = compiled_module->module()->functions; // Binary search for a function containing the given position. int left = 0; // inclusive int right = static_cast(functions.size()); // exclusive if (right == 0) return false; while (right - left > 1) { int mid = left + (right - left) / 2; if (functions[mid].code_start_offset <= position) { left = mid; } else { right = mid; } } // If the found entry does not contains the given position, return false. WasmFunction& func = functions[left]; if (position < func.code_start_offset || position >= func.code_end_offset) { return false; } info->line = left; info->column = position - func.code_start_offset; info->line_start = func.code_start_offset; info->line_end = func.code_end_offset; return true; } WasmModule::WasmModule(Zone* owned, const byte* module_start) : owned_zone(owned), module_start(module_start), pending_tasks(new base::Semaphore(0)) {} MaybeHandle WasmModule::CompileFunctions( Isolate* isolate, Handle module_wrapper, ErrorThrower* thrower) const { Factory* factory = isolate->factory(); MaybeHandle nothing; WasmInstance temp_instance(this); temp_instance.context = isolate->native_context(); temp_instance.mem_size = WasmModule::kPageSize * this->min_mem_pages; temp_instance.mem_start = nullptr; temp_instance.globals_start = nullptr; // Initialize the indirect tables with placeholders. int function_table_count = static_cast(this->function_tables.size()); Handle function_tables = factory->NewFixedArray(function_table_count); for (int i = 0; i < function_table_count; ++i) { temp_instance.function_tables[i] = factory->NewFixedArray(0); function_tables->set(i, *temp_instance.function_tables[i]); } HistogramTimerScope wasm_compile_module_time_scope( isolate->counters()->wasm_compile_module_time()); ModuleEnv module_env; module_env.module = this; module_env.instance = &temp_instance; module_env.origin = origin; // The {code_table} array contains import wrappers and functions (which // are both included in {functions.size()}, and export wrappers. int code_table_size = static_cast(functions.size() + num_exported_functions); Handle code_table = factory->NewFixedArray(static_cast(code_table_size), TENURED); // Initialize the code table with placeholders. for (uint32_t i = 0; i < functions.size(); ++i) { Code::Kind kind = Code::WASM_FUNCTION; if (i < num_imported_functions) kind = Code::WASM_TO_JS_FUNCTION; Handle placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
placeholder = CreatePlaceholder(factory, i, kind); code_table->set(static_cast(i), *placeholder); temp_instance.function_code[i] = placeholder; } isolate->counters()->wasm_functions_per_module()->AddSample( static_cast(functions.size())); if (!FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks != 0) { // Avoid a race condition by collecting results into a second vector. std::vector> results; results.reserve(temp_instance.function_code.size()); for (size_t i = 0; i < temp_instance.function_code.size(); ++i) { results.push_back(temp_instance.function_code[i]); } CompileInParallel(isolate, this, results, thrower, &module_env); for (size_t i = 0; i < results.size(); ++i) { temp_instance.function_code[i] = results[i]; } } else { CompileSequentially(isolate, this, temp_instance.function_code, thrower, &module_env); } if (thrower->error()) return nothing; // At this point, compilation has completed. Update the code table. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Code* code = *temp_instance.function_code[i]; code_table->set(static_cast(i), code); } // Link the functions in the module. for (size_t i = FLAG_skip_compiling_wasm_funcs; i < temp_instance.function_code.size(); ++i) { Handle code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = temp_instance.function_code[i]; bool modified = LinkFunction(code, temp_instance.function_code); if (modified) { // TODO(mtrofin): do we need to flush the cache here? Assembler::FlushICache(isolate, code->instruction_start(), code->instruction_size()); } } // Create the compiled module object, and populate with compiled functions // and information needed at instantiation time. This object needs to be // serializable. Instantiation may occur off a deserialized version of this // object. Handle ret = WasmCompiledModule::New(isolate, module_wrapper); ret->set_code_table(code_table); ret->set_min_mem_pages(min_mem_pages); ret->set_max_mem_pages(max_mem_pages); if (function_table_count > 0) { ret->set_function_tables(function_tables); ret->set_empty_function_tables(function_tables); } // Compile JS->WASM wrappers for exported functions. int func_index = 0; for (auto exp : export_table) { if (exp.kind != kExternalFunction) continue; Handle wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
wasm_code = code_table->GetValueChecked(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate, exp.index); Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
wrapper_code = compiler::CompileJSToWasmWrapper( isolate, &module_env, wasm_code, exp.index); int export_index = static_cast(functions.size() + func_index); code_table->set(export_index, *wrapper_code); func_index++; } { // TODO(wasm): only save the sections necessary to deserialize a // {WasmModule}. E.g. function bodies could be omitted. size_t module_bytes_len = module_end - module_start; DCHECK_LE(module_bytes_len, static_cast(kMaxInt)); Vector module_bytes_vec(module_start, static_cast(module_bytes_len)); Handle module_bytes_string = factory->NewStringFromOneByte(module_bytes_vec, TENURED) .ToHandleChecked(); DCHECK(module_bytes_string->IsSeqOneByteString()); ret->set_module_bytes(Handle::cast(module_bytes_string)); } return ret; } static WasmFunction* GetWasmFunctionForImportWrapper(Isolate* isolate, Handle target) { if (target->IsJSFunction()) { Handle func = Handle::cast(target); if (func->code()->kind() == Code::JS_TO_WASM_FUNCTION) { auto exported = Handle::cast(func); Handle other_instance(exported->instance(), isolate); int func_index = exported->function_index(); return &other_instance->module()->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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
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) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code; WasmFunction* other_func = GetWasmFunctionForImportWrapper(isolate, target); if (other_func) { if (sig->Equals(other_func->sig)) { // Signature matched. Unwrap the JS->WASM wrapper and return the raw // WASM function code. return UnwrapImportWrapper(target); } else { return Handle::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
::null(); } } 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) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code) { DCHECK_EQ(0, dispatch_tables->length() % 3); for (int i = 0; i < dispatch_tables->length(); i += 3) { 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. Handle instance( WasmInstanceObject::cast(dispatch_tables->get(i)), isolate); int sig_index = static_cast( instance->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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
::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. class WasmInstanceBuilder { public: WasmInstanceBuilder(Isolate* isolate, ErrorThrower* thrower, Handle module_object, Handle ffi, Handle memory) : isolate_(isolate), thrower_(thrower), module_object_(module_object), ffi_(ffi), memory_(memory) {} // Build an instance, in all of its glory. MaybeHandle Build() { MaybeHandle nothing; HistogramTimerScope wasm_instantiate_module_time_scope( isolate_->counters()->wasm_instantiate_module_time()); Factory* factory = isolate_->factory(); //-------------------------------------------------------------------------- // Reuse the compiled module (if no owner), otherwise clone. //-------------------------------------------------------------------------- Handle code_table; Handle old_code_table; MaybeHandle owner; TRACE("Starting new module instantiation\n"); { // Root the owner, if any, before doing any allocations, which // may trigger GC. // Both owner and original template need to be in sync. Even // after we lose the original template handle, the code // objects we copied from it have data relative to the // instance - such as globals addresses. Handle original; { DisallowHeapAllocation no_gc; original = handle( WasmCompiledModule::cast(module_object_->GetInternalField(0))); if (original->has_weak_owning_instance()) { owner = handle(WasmInstanceObject::cast( original->weak_owning_instance()->value())); } } DCHECK(!original.is_null()); // Always make a new copy of the code_table, since the old_code_table // may still have placeholders for imports. old_code_table = original->code_table(); code_table = factory->CopyFixedArray(old_code_table); if (original->has_weak_owning_instance()) { // Clone, but don't insert yet the clone in the instances chain. // We do that last. Since we are holding on to the owner instance, // the owner + original state used for cloning and patching // won't be mutated by possible finalizer runs. DCHECK(!owner.is_null()); TRACE("Cloning from %d\n", original->instance_id()); compiled_module_ = WasmCompiledModule::Clone(isolate_, original); // Avoid creating too many handles in the outer scope. HandleScope scope(isolate_); // Clone the code for WASM functions and exports. for (int i = 0; i < code_table->length(); ++i) { Handle orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
orig_code = code_table->GetValueChecked(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate_, i); switch (orig_code->kind()) { case Code::WASM_TO_JS_FUNCTION: // Imports will be overwritten with newly compiled wrappers. break; case Code::JS_TO_WASM_FUNCTION: case Code::WASM_FUNCTION: { Handle code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = factory->CopyCode(orig_code); code_table->set(i, *code); break; } default: UNREACHABLE(); } } RecordStats(isolate_, code_table); } else { // There was no owner, so we can reuse the original. compiled_module_ = original; TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); } compiled_module_->set_code_table(code_table); } module_ = reinterpret_cast( *compiled_module_->module_wrapper()) ->get(); //-------------------------------------------------------------------------- // Allocate the instance object. //-------------------------------------------------------------------------- Handle instance = WasmInstanceObject::New(isolate_, compiled_module_); //-------------------------------------------------------------------------- // Set up the globals for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_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()) { thrower_->RangeError("Out of memory: wasm globals"); return nothing; } Address old_address = owner.is_null() ? nullptr : GetGlobalStartAddressFromCodeTemplate( isolate_->heap()->undefined_value(), *owner.ToHandleChecked()); RelocateGlobals(code_table, old_address, static_cast(global_buffer->backing_store())); instance->set_globals_buffer(*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(code_table, instance); if (num_imported_functions < 0) return nothing; //-------------------------------------------------------------------------- // Process the initialization for the module's globals. //-------------------------------------------------------------------------- InitGlobals(); //-------------------------------------------------------------------------- // Set up the memory for the new instance. //-------------------------------------------------------------------------- MaybeHandle old_memory; uint32_t min_mem_pages = module_->min_mem_pages; isolate_->counters()->wasm_min_mem_pages_count()->AddSample(min_mem_pages); if (!memory_.is_null()) { // Set externally passed ArrayBuffer non neuterable. memory_->set_is_neuterable(false); } else if (min_mem_pages > 0) { memory_ = AllocateMemory(min_mem_pages); if (memory_.is_null()) return nothing; // failed to allocate memory } if (!memory_.is_null()) { instance->set_memory_buffer(*memory_); Address mem_start = static_cast(memory_->backing_store()); uint32_t mem_size = static_cast(memory_->byte_length()->Number()); LoadDataSegments(mem_start, mem_size); uint32_t old_mem_size = compiled_module_->mem_size(); Address old_mem_start = compiled_module_->has_memory() ? static_cast( compiled_module_->memory()->backing_store()) : nullptr; RelocateMemoryReferencesInCode(code_table, old_mem_start, mem_start, old_mem_size, mem_size); compiled_module_->set_memory(memory_); } else { LoadDataSegments(nullptr, 0); } //-------------------------------------------------------------------------- // Set up the runtime support for the new instance. //-------------------------------------------------------------------------- Handle weak_link = factory->NewWeakCell(instance); for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs; i < code_table->length(); ++i) { Handle code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
code = code_table->GetValueChecked(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate_, i); if (code->kind() == Code::WASM_FUNCTION) { Handle deopt_data = factory->NewFixedArray(2, TENURED); deopt_data->set(0, *weak_link); deopt_data->set(1, Smi::FromInt(static_cast(i))); deopt_data->set_length(2); code->set_deoptimization_data(*deopt_data); } } //-------------------------------------------------------------------------- // Set up the exports object for the new 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. PatchDirectCalls(old_code_table, code_table, num_imported_functions); } FlushICache(isolate_, code_table); //-------------------------------------------------------------------------- // Set up and link the new instance. //-------------------------------------------------------------------------- { Handle global_handle = isolate_->global_handles()->Create(*instance); Handle link_to_clone = factory->NewWeakCell(compiled_module_); Handle link_to_owning_instance = factory->NewWeakCell(instance); MaybeHandle link_to_original; MaybeHandle original; if (!owner.is_null()) { // prepare the data needed for publishing in a chain, but don't link // just yet, because // we want all the publishing to happen free from GC interruptions, and // so we do it in // one GC-free scope afterwards. original = handle(owner.ToHandleChecked()->get_compiled_module()); link_to_original = factory->NewWeakCell(original.ToHandleChecked()); } // Publish the new instance to the instances chain. { DisallowHeapAllocation no_gc; if (!link_to_original.is_null()) { compiled_module_->set_weak_next_instance( link_to_original.ToHandleChecked()); original.ToHandleChecked()->set_weak_prev_instance(link_to_clone); compiled_module_->set_weak_wasm_module( original.ToHandleChecked()->weak_wasm_module()); } module_object_->SetInternalField(0, *compiled_module_); compiled_module_->set_weak_owning_instance(link_to_owning_instance); GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(), &InstanceFinalizer, v8::WeakCallbackType::kFinalizer); } } DCHECK(wasm::IsWasmInstance(*instance)); if (instance->has_memory_object()) { instance->get_memory_object()->AddInstance(*instance); } //-------------------------------------------------------------------------- // Run the start function if one was specified. //-------------------------------------------------------------------------- if (module_->start_function_index >= 0) { HandleScope scope(isolate_); ModuleEnv module_env; module_env.module = module_; module_env.instance = nullptr; module_env.origin = module_->origin; int start_index = module_->start_function_index; Handle startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
startup_code = code_table->GetValueChecked(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate_, start_index); FunctionSig* sig = module_->functions[start_index].sig; Handle wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
wrapper_code = compiler::CompileJSToWasmWrapper( isolate_, &module_env, startup_code, start_index); Handle startup_fct = WasmExportedFunction::New( isolate_, instance, factory->InternalizeUtf8String("start"), wrapper_code, static_cast(sig->parameter_count()), start_index); RecordStats(isolate_, *startup_code); // Call the JS function. Handle undefined = factory->undefined_value(); MaybeHandle retval = Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); if (retval.is_null()) { DCHECK(isolate_->has_pending_exception()); isolate_->OptionalRescheduleException(false); // It's unfortunate that the new instance is already linked in the // chain. However, we need to set up everything before executing the // start function, such that stack trace information can be generated // correctly already in the start function. return nothing; } } DCHECK(!isolate_->has_pending_exception()); TRACE("Finishing instance %d\n", compiled_module_->instance_id()); TRACE_CHAIN(WasmCompiledModule::cast(module_object_->GetInternalField(0))); return instance; } private: // Represents the initialized state of a table. struct TableInstance { Handle table_object; // WebAssembly.Table instance Handle js_wrappers; // JSFunctions exported Handle dispatch_table; // internal (code, sig) pairs }; Isolate* isolate_; WasmModule* module_; ErrorThrower* thrower_; 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, Handle module_name, MaybeHandle function_name) { Handle function_name_handle; if (function_name.ToHandle(&function_name_handle)) { thrower_->TypeError( "Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), function_name_handle->length(), function_name_handle->ToCString().get(), error); } else { thrower_->TypeError("Import #%d module=\"%.*s\" error: %s", index, module_name->length(), module_name->ToCString().get(), error); } thrower_->TypeError("Import "); return MaybeHandle(); } // Look up an import value in the {ffi_} object. MaybeHandle LookupImport(uint32_t index, Handle module_name, MaybeHandle import_name) { if (ffi_.is_null()) { return ReportFFIError("FFI is not an object", index, module_name, import_name); } // Look up the module first. MaybeHandle result = Object::GetProperty(ffi_, module_name); if (result.is_null()) { return ReportFFIError("module not found", index, module_name, import_name); } Handle module = result.ToHandleChecked(); if (!import_name.is_null()) { // Look up the value in the module. if (!module->IsJSReceiver()) { return ReportFFIError("module is not an object or function", index, module_name, import_name); } result = Object::GetProperty(module, import_name.ToHandleChecked()); if (result.is_null()) { return ReportFFIError("import not found", index, module_name, import_name); } } else { // No function specified. Use the "default export". result = module; } return result; } uint32_t EvalUint32InitExpr(const 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)); } default: UNREACHABLE(); return 0; } } // Load data segments into the memory. void LoadDataSegments(Address mem_addr, size_t mem_size) { Handle module_bytes = compiled_module_->module_bytes(); for (const WasmDataSegment& segment : module_->data_segments) { uint32_t source_size = segment.source_size; // Segments of size == 0 are just nops. if (source_size == 0) continue; uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); if (dest_offset >= mem_size || source_size >= mem_size || dest_offset > (mem_size - source_size)) { thrower_->TypeError("data segment (start = %" PRIu32 ", size = %" PRIu32 ") does not fit into memory (size = %" PRIuS ")", dest_offset, source_size, mem_size); return; } byte* dest = mem_addr + dest_offset; const byte* src = reinterpret_cast( module_bytes->GetCharsAddress() + segment.source_offset); memcpy(dest, src, source_size); } } void WriteGlobalValue(WasmGlobal& global, Handle value) { double num = 0; if (value->IsSmi()) { num = Smi::cast(*value)->value(); } else if (value->IsHeapNumber()) { num = HeapNumber::cast(*value)->value(); } else { UNREACHABLE(); } TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, WasmOpcodes::TypeName(global.type)); switch (global.type) { case kAstI32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstI64: // TODO(titzer): initialization of imported i64 globals. UNREACHABLE(); break; case kAstF32: *GetRawGlobalPtr(global) = static_cast(num); break; case kAstF64: *GetRawGlobalPtr(global) = static_cast(num); break; default: UNREACHABLE(); } } // 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(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]; Handle module_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.module_name_offset, import.module_name_length) .ToHandleChecked(); Handle function_name = Handle::null(); if (import.field_name_length > 0) { function_name = ExtractStringFromModuleBytes(isolate_, compiled_module_, import.field_name_offset, import.field_name_length) .ToHandleChecked(); } MaybeHandle result = LookupImport(index, module_name, function_name); if (thrower_->error()) return -1; switch (import.kind) { case kExternalFunction: { // Function imports must be callable. Handle function = result.ToHandleChecked(); if (!function->IsCallable()) { ReportFFIError("function import requires a callable", index, module_name, function_name); return -1; } Handle import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
import_wrapper = CompileImportWrapper( isolate_, index, module_->functions[import.index].sig, Handle::cast(function), module_name, function_name); if (import_wrapper.is_null()) { ReportFFIError("imported function does not match the expected type", index, module_name, function_name); return -1; } code_table->set(num_imported_functions, *import_wrapper); RecordStats(isolate_, *import_wrapper); num_imported_functions++; break; } 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 = Handle( table_instance.table_object->get_functions(), isolate_); // TODO(titzer): import table size must match exactly for now. int table_size = table_instance.js_wrappers->length(); if (table_size != static_cast(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)) { ReportFFIError("memory import must be a WebAssembly.Memory object", index, module_name, function_name); return -1; } auto memory = Handle::cast(object); instance->set_memory_object(*memory); memory_ = Handle(memory->get_buffer(), isolate_); break; } case kExternalGlobal: { // Global imports are converted to numbers and written into the // {globals_} array buffer. Handle object = result.ToHandleChecked(); MaybeHandle number = Object::ToNumber(object); if (number.is_null()) { ReportFFIError("global import could not be converted to number", index, module_name, function_name); return -1; } Handle val = number.ToHandleChecked(); WriteGlobalValue(module_->globals[import.index], val); break; } default: UNREACHABLE(); break; } } return num_imported_functions; } template T* GetRawGlobalPtr(WasmGlobal& global) { return reinterpret_cast(raw_buffer_ptr(globals_, global.offset)); } // Process initialization of globals. void InitGlobals() { for (auto global : module_->globals) { switch (global.init.kind) { case WasmInitExpr::kI32Const: *GetRawGlobalPtr(global) = global.init.val.i32_const; break; case WasmInitExpr::kI64Const: *GetRawGlobalPtr(global) = global.init.val.i64_const; break; case WasmInitExpr::kF32Const: *GetRawGlobalPtr(global) = global.init.val.f32_const; break; case WasmInitExpr::kF64Const: *GetRawGlobalPtr(global) = global.init.val.f64_const; break; case WasmInitExpr::kGlobalIndex: { // Initialize with another global. uint32_t new_offset = global.offset; uint32_t old_offset = module_->globals[global.init.val.global_index].offset; TRACE("init [globals+%u] = [globals+%d]\n", global.offset, old_offset); 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); break; } case WasmInitExpr::kNone: // Happens with imported globals. break; default: UNREACHABLE(); break; } } } // Allocate memory for a module instance as a new JSArrayBuffer. Handle AllocateMemory(uint32_t min_mem_pages) { if (min_mem_pages > WasmModule::kV8MaxPages) { thrower_->RangeError("Out of memory: wasm memory too large"); return Handle::null(); } Handle mem_buffer = NewArrayBuffer(isolate_, min_mem_pages * WasmModule::kPageSize); if (mem_buffer.is_null()) { thrower_->RangeError("Out of memory: wasm memory"); } return mem_buffer; } // Process the exports, creating wrappers for functions, tables, memories, // and globals. void ProcessExports(Handle code_table, Handle instance) { 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; } } 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_->export_table.size() > 0 && module_->origin == kWasmOrigin) { // Create the "exports" object. Handle object_function = Handle( isolate_->native_context()->object_function(), isolate_); exports_object = isolate_->factory()->NewJSObject(object_function, TENURED); Handle exports_name = isolate_->factory()->InternalizeUtf8String("exports"); JSObject::AddProperty(instance, exports_name, exports_object, READ_ONLY); } PropertyDescriptor desc; desc.set_writable(false); // Process each export in the export table. int export_index = 0; for (auto exp : module_->export_table) { Handle name = ExtractStringFromModuleBytes(isolate_, compiled_module_, exp.name_offset, exp.name_length) .ToHandleChecked(); switch (exp.kind) { case kExternalFunction: { // Wrap and export the code as a JSFunction. WasmFunction& function = module_->functions[exp.index]; int func_index = static_cast(module_->functions.size() + export_index); 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_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
export_code = code_table->GetValueChecked(isolate_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr
(isolate_, func_index); js_function = WasmExportedFunction::New( isolate_, instance, name, export_code, static_cast(function.sig->parameter_count()), function.func_index); js_wrappers_[exp.index] = js_function; } desc.set_value(js_function); export_index++; break; } case kExternalTable: { // Export a table as a WebAssembly.Table object. TableInstance& table_instance = table_instances_[exp.index]; WasmIndirectFunctionTable& table = module_->function_tables[exp.index]; if (table_instance.table_object.is_null()) { uint32_t maximum = table.has_max ? table.max_size : WasmModule::kV8MaxTableSize; table_instance.table_object = WasmTableObject::New( isolate_, table.min_size, maximum, &table_instance.js_wrappers); } desc.set_value(table_instance.table_object); break; } case kExternalMemory: { // Export the memory as a WebAssembly.Memory object. Handle memory_object; if (!instance->has_memory_object()) { // If there was no imported WebAssembly.Memory object, create one. Handle buffer(instance->get_memory_buffer(), isolate_); memory_object = WasmMemoryObject::New( isolate_, buffer, (module_->max_mem_pages != 0) ? module_->max_mem_pages : -1); instance->set_memory_object(*memory_object); } else { memory_object = Handle( instance->get_memory_object(), isolate_); } desc.set_value(memory_object); break; } case kExternalGlobal: { // Export the value of the global variable as a number. WasmGlobal& global = module_->globals[exp.index]; double num = 0; switch (global.type) { case kAstI32: num = *GetRawGlobalPtr