| // Copyright 2017 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <src/wasm/module-compiler.h> |
| |
| #include <atomic> |
| |
| #include "src/asmjs/asm-js.h" |
| #include "src/assembler-inl.h" |
| #include "src/code-stubs.h" |
| #include "src/counters.h" |
| #include "src/property-descriptor.h" |
| #include "src/wasm/compilation-manager.h" |
| #include "src/wasm/module-decoder.h" |
| #include "src/wasm/wasm-js.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-objects.h" |
| #include "src/wasm/wasm-result.h" |
| |
| #define TRACE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| #define TRACE_CHAIN(instance) \ |
| do { \ |
| instance->PrintInstancesChain(); \ |
| } while (false) |
| |
| #define TRACE_COMPILE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \ |
| } while (false) |
| |
| static const int kInvalidSigIndex = -1; |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| ModuleCompiler::CodeGenerationSchedule::CodeGenerationSchedule( |
| base::RandomNumberGenerator* random_number_generator, size_t max_memory) |
| : random_number_generator_(random_number_generator), |
| max_memory_(max_memory) { |
| DCHECK_NOT_NULL(random_number_generator_); |
| DCHECK_GT(max_memory_, 0); |
| } |
| |
| void ModuleCompiler::CodeGenerationSchedule::Schedule( |
| std::unique_ptr<compiler::WasmCompilationUnit>&& item) { |
| size_t cost = item->memory_cost(); |
| schedule_.push_back(std::move(item)); |
| allocated_memory_.Increment(cost); |
| } |
| |
| bool ModuleCompiler::CodeGenerationSchedule::CanAcceptWork() const { |
| return (!throttle_ || allocated_memory_.Value() <= max_memory_); |
| } |
| |
| bool ModuleCompiler::CodeGenerationSchedule::ShouldIncreaseWorkload() const { |
| // Half the memory is unused again, we can increase the workload again. |
| return (!throttle_ || allocated_memory_.Value() <= max_memory_ / 2); |
| } |
| |
| std::unique_ptr<compiler::WasmCompilationUnit> |
| ModuleCompiler::CodeGenerationSchedule::GetNext() { |
| DCHECK(!IsEmpty()); |
| size_t index = GetRandomIndexInSchedule(); |
| auto ret = std::move(schedule_[index]); |
| std::swap(schedule_[schedule_.size() - 1], schedule_[index]); |
| schedule_.pop_back(); |
| allocated_memory_.Decrement(ret->memory_cost()); |
| return ret; |
| } |
| |
| size_t ModuleCompiler::CodeGenerationSchedule::GetRandomIndexInSchedule() { |
| double factor = random_number_generator_->NextDouble(); |
| size_t index = (size_t)(factor * schedule_.size()); |
| DCHECK_GE(index, 0); |
| DCHECK_LT(index, schedule_.size()); |
| return index; |
| } |
| |
| ModuleCompiler::ModuleCompiler(Isolate* isolate, |
| std::unique_ptr<WasmModule> module, |
| Handle<Code> centry_stub) |
| : isolate_(isolate), |
| module_(std::move(module)), |
| async_counters_(isolate->async_counters()), |
| executed_units_( |
| isolate->random_number_generator(), |
| (isolate->heap()->memory_allocator()->code_range()->valid() |
| ? isolate->heap()->memory_allocator()->code_range()->size() |
| : isolate->heap()->code_space()->Capacity()) / |
| 2), |
| num_background_tasks_( |
| Min(static_cast<size_t>(FLAG_wasm_num_compilation_tasks), |
| V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads())), |
| stopped_compilation_tasks_(num_background_tasks_), |
| centry_stub_(centry_stub) {} |
| |
| // The actual runnable task that performs compilations in the background. |
| ModuleCompiler::CompilationTask::CompilationTask(ModuleCompiler* compiler) |
| : CancelableTask(&compiler->background_task_manager_), |
| compiler_(compiler) {} |
| |
| void ModuleCompiler::CompilationTask::RunInternal() { |
| while (compiler_->executed_units_.CanAcceptWork() && |
| compiler_->FetchAndExecuteCompilationUnit()) { |
| } |
| |
| compiler_->OnBackgroundTaskStopped(); |
| } |
| |
| void ModuleCompiler::OnBackgroundTaskStopped() { |
| base::LockGuard<base::Mutex> guard(&tasks_mutex_); |
| ++stopped_compilation_tasks_; |
| DCHECK_LE(stopped_compilation_tasks_, num_background_tasks_); |
| } |
| |
| // Run by each compilation task The no_finisher_callback is called |
| // within the result_mutex_ lock when no finishing task is running, |
| // i.e. when the finisher_is_running_ flag is not set. |
| bool ModuleCompiler::FetchAndExecuteCompilationUnit( |
| std::function<void()> no_finisher_callback) { |
| DisallowHeapAllocation no_allocation; |
| DisallowHandleAllocation no_handles; |
| DisallowHandleDereference no_deref; |
| DisallowCodeDependencyChange no_dependency_change; |
| |
| std::unique_ptr<compiler::WasmCompilationUnit> unit; |
| { |
| base::LockGuard<base::Mutex> guard(&compilation_units_mutex_); |
| if (compilation_units_.empty()) return false; |
| unit = std::move(compilation_units_.back()); |
| compilation_units_.pop_back(); |
| } |
| unit->ExecuteCompilation(); |
| { |
| base::LockGuard<base::Mutex> guard(&result_mutex_); |
| executed_units_.Schedule(std::move(unit)); |
| if (no_finisher_callback != nullptr && !finisher_is_running_) { |
| no_finisher_callback(); |
| // We set the flag here so that not more than one finisher is started. |
| finisher_is_running_ = true; |
| } |
| } |
| return true; |
| } |
| |
| size_t ModuleCompiler::InitializeCompilationUnits( |
| const std::vector<WasmFunction>& functions, |
| const ModuleWireBytes& wire_bytes, compiler::ModuleEnv* module_env) { |
| uint32_t start = module_env->module->num_imported_functions + |
| FLAG_skip_compiling_wasm_funcs; |
| uint32_t num_funcs = static_cast<uint32_t>(functions.size()); |
| uint32_t funcs_to_compile = start > num_funcs ? 0 : num_funcs - start; |
| CompilationUnitBuilder builder(this); |
| for (uint32_t i = start; i < num_funcs; ++i) { |
| const WasmFunction* func = &functions[i]; |
| uint32_t buffer_offset = func->code.offset(); |
| Vector<const uint8_t> bytes(wire_bytes.start() + func->code.offset(), |
| func->code.end_offset() - func->code.offset()); |
| WasmName name = wire_bytes.GetName(func); |
| builder.AddUnit(module_env, func, buffer_offset, bytes, name); |
| } |
| builder.Commit(); |
| return funcs_to_compile; |
| } |
| |
| void ModuleCompiler::RestartCompilationTasks() { |
| base::LockGuard<base::Mutex> guard(&tasks_mutex_); |
| for (; stopped_compilation_tasks_ > 0; --stopped_compilation_tasks_) { |
| V8::GetCurrentPlatform()->CallOnBackgroundThread( |
| new CompilationTask(this), |
| v8::Platform::ExpectedRuntime::kShortRunningTask); |
| } |
| } |
| |
| size_t ModuleCompiler::FinishCompilationUnits( |
| std::vector<Handle<Code>>& results, ErrorThrower* thrower) { |
| size_t finished = 0; |
| while (true) { |
| int func_index = -1; |
| MaybeHandle<Code> result = FinishCompilationUnit(thrower, &func_index); |
| if (func_index < 0) break; |
| ++finished; |
| DCHECK_IMPLIES(result.is_null(), thrower->error()); |
| if (result.is_null()) break; |
| results[func_index] = result.ToHandleChecked(); |
| } |
| bool do_restart; |
| { |
| base::LockGuard<base::Mutex> guard(&compilation_units_mutex_); |
| do_restart = !compilation_units_.empty(); |
| } |
| if (do_restart) RestartCompilationTasks(); |
| return finished; |
| } |
| |
| void ModuleCompiler::SetFinisherIsRunning(bool value) { |
| base::LockGuard<base::Mutex> guard(&result_mutex_); |
| finisher_is_running_ = value; |
| } |
| |
| MaybeHandle<Code> ModuleCompiler::FinishCompilationUnit(ErrorThrower* thrower, |
| int* func_index) { |
| std::unique_ptr<compiler::WasmCompilationUnit> unit; |
| { |
| base::LockGuard<base::Mutex> guard(&result_mutex_); |
| if (executed_units_.IsEmpty()) return {}; |
| unit = executed_units_.GetNext(); |
| } |
| *func_index = unit->func_index(); |
| return unit->FinishCompilation(thrower); |
| } |
| |
| void ModuleCompiler::CompileInParallel(const ModuleWireBytes& wire_bytes, |
| compiler::ModuleEnv* module_env, |
| std::vector<Handle<Code>>& results, |
| ErrorThrower* thrower) { |
| const WasmModule* module = module_env->module; |
| // Data structures for the parallel compilation. |
| |
| //----------------------------------------------------------------------- |
| // 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 {CompilationTask} 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 {CompilationTask} 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}. |
| InitializeCompilationUnits(module->functions, wire_bytes, module_env); |
| executed_units_.EnableThrottling(); |
| |
| // 2) The main thread spawns {CompilationTask} instances which run on |
| // the background threads. |
| RestartCompilationTasks(); |
| |
| // 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}. |
| // The foreground task bypasses waiting on memory threshold, because |
| // its results will immediately be converted to code (below). |
| while (FetchAndExecuteCompilationUnit()) { |
| // 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(results, thrower); |
| } |
| // 4) After the parallel phase of all compilation units has started, the |
| // main thread waits for all {CompilationTask} instances to finish - which |
| // happens once they all realize there's no next work item to process. |
| background_task_manager_.CancelAndWait(); |
| // Finish all compilation units which have been executed while we waited. |
| FinishCompilationUnits(results, thrower); |
| } |
| |
| void ModuleCompiler::CompileSequentially(const ModuleWireBytes& wire_bytes, |
| compiler::ModuleEnv* module_env, |
| std::vector<Handle<Code>>& results, |
| ErrorThrower* thrower) { |
| DCHECK(!thrower->error()); |
| |
| const WasmModule* module = module_env->module; |
| 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. |
| |
| // Compile the function. |
| MaybeHandle<Code> code = compiler::WasmCompilationUnit::CompileWasmFunction( |
| thrower, isolate_, wire_bytes, module_env, &func); |
| if (code.is_null()) { |
| TruncatedUserString<> name(wire_bytes.GetName(&func)); |
| thrower->CompileError("Compilation of #%d:%.*s failed.", i, name.length(), |
| name.start()); |
| break; |
| } |
| results[i] = code.ToHandleChecked(); |
| } |
| } |
| |
| void ModuleCompiler::ValidateSequentially(const ModuleWireBytes& wire_bytes, |
| compiler::ModuleEnv* module_env, |
| ErrorThrower* thrower) { |
| DCHECK(!thrower->error()); |
| |
| const WasmModule* module = module_env->module; |
| for (uint32_t i = 0; i < module->functions.size(); ++i) { |
| const WasmFunction& func = module->functions[i]; |
| if (func.imported) continue; |
| |
| const byte* base = wire_bytes.start(); |
| FunctionBody body{func.sig, func.code.offset(), base + func.code.offset(), |
| base + func.code.end_offset()}; |
| DecodeResult result = VerifyWasmCodeWithStats( |
| isolate_->allocator(), module, body, module->is_wasm(), counters()); |
| if (result.failed()) { |
| TruncatedUserString<> name(wire_bytes.GetName(&func)); |
| thrower->CompileError("Compiling function #%d:%.*s failed: %s @+%u", i, |
| name.length(), name.start(), |
| result.error_msg().c_str(), result.error_offset()); |
| break; |
| } |
| } |
| } |
| |
| MaybeHandle<WasmModuleObject> ModuleCompiler::CompileToModuleObject( |
| ErrorThrower* thrower, const ModuleWireBytes& wire_bytes, |
| Handle<Script> asm_js_script, |
| Vector<const byte> asm_js_offset_table_bytes) { |
| |
| TimedHistogramScope wasm_compile_module_time_scope( |
| module_->is_wasm() ? counters()->wasm_compile_wasm_module_time() |
| : counters()->wasm_compile_asm_module_time()); |
| return CompileToModuleObjectInternal( |
| isolate_, thrower, wire_bytes, asm_js_script, asm_js_offset_table_bytes); |
| } |
| |
| namespace { |
| bool compile_lazy(const WasmModule* module) { |
| return FLAG_wasm_lazy_compilation || |
| (FLAG_asm_wasm_lazy_compilation && module->is_asm_js()); |
| } |
| |
| void FlushICache(Isolate* isolate, Handle<FixedArray> code_table) { |
| for (int i = 0; i < code_table->length(); ++i) { |
| Handle<Code> code = code_table->GetValueChecked<Code>(isolate, i); |
| Assembler::FlushICache(isolate, code->instruction_start(), |
| code->instruction_size()); |
| } |
| } |
| |
| byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) { |
| return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset; |
| } |
| |
| void RecordStats(Code* code, Counters* counters) { |
| counters->wasm_generated_code_size()->Increment(code->body_size()); |
| counters->wasm_reloc_size()->Increment(code->relocation_info()->length()); |
| } |
| |
| void RecordStats(Handle<FixedArray> functions, Counters* counters) { |
| DisallowHeapAllocation no_gc; |
| for (int i = 0; i < functions->length(); ++i) { |
| Object* val = functions->get(i); |
| if (val->IsCode()) RecordStats(Code::cast(val), counters); |
| } |
| } |
| Handle<Script> CreateWasmScript(Isolate* isolate, |
| const ModuleWireBytes& wire_bytes) { |
| Handle<Script> script = |
| isolate->factory()->NewScript(isolate->factory()->empty_string()); |
| script->set_context_data(isolate->native_context()->debug_context_id()); |
| script->set_type(Script::TYPE_WASM); |
| |
| int hash = StringHasher::HashSequentialString( |
| reinterpret_cast<const char*>(wire_bytes.start()), |
| static_cast<int>(wire_bytes.length()), kZeroHashSeed); |
| |
| const int kBufferSize = 32; |
| char buffer[kBufferSize]; |
| int url_chars = SNPrintF(ArrayVector(buffer), "wasm://wasm/%08x", hash); |
| DCHECK(url_chars >= 0 && url_chars < kBufferSize); |
| MaybeHandle<String> url_str = isolate->factory()->NewStringFromOneByte( |
| Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), url_chars), |
| TENURED); |
| script->set_source_url(*url_str.ToHandleChecked()); |
| |
| int name_chars = SNPrintF(ArrayVector(buffer), "wasm-%08x", hash); |
| DCHECK(name_chars >= 0 && name_chars < kBufferSize); |
| MaybeHandle<String> name_str = isolate->factory()->NewStringFromOneByte( |
| Vector<const uint8_t>(reinterpret_cast<uint8_t*>(buffer), name_chars), |
| TENURED); |
| script->set_name(*name_str.ToHandleChecked()); |
| |
| return script; |
| } |
| |
| // Ensure that the code object in <code_table> at offset <func_index> has |
| // deoptimization data attached. This is needed for lazy compile stubs which are |
| // called from JS_TO_WASM functions or via exported function tables. The deopt |
| // data is used to determine which function this lazy compile stub belongs to. |
| Handle<Code> EnsureExportedLazyDeoptData(Isolate* isolate, |
| Handle<WasmInstanceObject> instance, |
| Handle<FixedArray> code_table, |
| int func_index) { |
| Handle<Code> code(Code::cast(code_table->get(func_index)), isolate); |
| if (code->builtin_index() != Builtins::kWasmCompileLazy) { |
| // No special deopt data needed for compiled functions, and imported |
| // functions, which map to Illegal at this point (they get compiled at |
| // instantiation time). |
| DCHECK(code->kind() == Code::WASM_FUNCTION || |
| code->kind() == Code::WASM_TO_JS_FUNCTION || |
| code->builtin_index() == Builtins::kIllegal); |
| return code; |
| } |
| // deopt_data: |
| // #0: weak instance |
| // #1: func_index |
| // might be extended later for table exports (see |
| // EnsureTableExportLazyDeoptData). |
| Handle<FixedArray> deopt_data(code->deoptimization_data()); |
| DCHECK_EQ(0, deopt_data->length() % 2); |
| if (deopt_data->length() == 0) { |
| code = isolate->factory()->CopyCode(code); |
| code_table->set(func_index, *code); |
| deopt_data = isolate->factory()->NewFixedArray(2, TENURED); |
| code->set_deoptimization_data(*deopt_data); |
| if (!instance.is_null()) { |
| Handle<WeakCell> weak_instance = |
| isolate->factory()->NewWeakCell(instance); |
| deopt_data->set(0, *weak_instance); |
| } |
| deopt_data->set(1, Smi::FromInt(func_index)); |
| } |
| DCHECK_IMPLIES(!instance.is_null(), |
| WeakCell::cast(code->deoptimization_data()->get(0))->value() == |
| *instance); |
| DCHECK_EQ(func_index, Smi::ToInt(code->deoptimization_data()->get(1))); |
| return code; |
| } |
| |
| // Ensure that the code object in <code_table> at offset <func_index> has |
| // deoptimization data attached. This is needed for lazy compile stubs which are |
| // called from JS_TO_WASM functions or via exported function tables. The deopt |
| // data is used to determine which function this lazy compile stub belongs to. |
| Handle<Code> EnsureTableExportLazyDeoptData( |
| Isolate* isolate, Handle<WasmInstanceObject> instance, |
| Handle<FixedArray> code_table, int func_index, |
| Handle<FixedArray> export_table, int export_index, |
| std::unordered_map<uint32_t, uint32_t>& table_export_count) { |
| Handle<Code> code = |
| EnsureExportedLazyDeoptData(isolate, instance, code_table, func_index); |
| if (code->builtin_index() != Builtins::kWasmCompileLazy) return code; |
| |
| // deopt_data: |
| // #0: weak instance |
| // #1: func_index |
| // [#2: export table |
| // #3: export table index] |
| // [#4: export table |
| // #5: export table index] |
| // ... |
| // table_export_count counts down and determines the index for the new export |
| // table entry. |
| auto table_export_entry = table_export_count.find(func_index); |
| DCHECK(table_export_entry != table_export_count.end()); |
| DCHECK_LT(0, table_export_entry->second); |
| uint32_t this_idx = 2 * table_export_entry->second; |
| --table_export_entry->second; |
| Handle<FixedArray> deopt_data(code->deoptimization_data()); |
| DCHECK_EQ(0, deopt_data->length() % 2); |
| if (deopt_data->length() == 2) { |
| // Then only the "header" (#0 and #1) exists. Extend for the export table |
| // entries (make space for this_idx + 2 elements). |
| deopt_data = isolate->factory()->CopyFixedArrayAndGrow(deopt_data, this_idx, |
| TENURED); |
| code->set_deoptimization_data(*deopt_data); |
| } |
| DCHECK_LE(this_idx + 2, deopt_data->length()); |
| DCHECK(deopt_data->get(this_idx)->IsUndefined(isolate)); |
| DCHECK(deopt_data->get(this_idx + 1)->IsUndefined(isolate)); |
| deopt_data->set(this_idx, *export_table); |
| deopt_data->set(this_idx + 1, Smi::FromInt(export_index)); |
| return code; |
| } |
| |
| bool in_bounds(uint32_t offset, uint32_t size, uint32_t upper) { |
| return offset + size <= upper && offset + size >= offset; |
| } |
| |
| using WasmInstanceMap = |
| IdentityMap<Handle<WasmInstanceObject>, FreeStoreAllocationPolicy>; |
| |
| Handle<Code> UnwrapOrCompileImportWrapper( |
| Isolate* isolate, int index, FunctionSig* sig, Handle<JSReceiver> target, |
| Handle<String> module_name, MaybeHandle<String> import_name, |
| ModuleOrigin origin, WasmInstanceMap* imported_instances) { |
| WasmFunction* other_func = GetWasmFunctionForImportWrapper(isolate, target); |
| if (other_func) { |
| if (!sig->Equals(other_func->sig)) return Handle<Code>::null(); |
| // Signature matched. Unwrap the import wrapper and return the raw wasm |
| // function code. |
| // Remember the wasm instance of the import. We have to keep it alive. |
| Handle<WasmInstanceObject> imported_instance( |
| Handle<WasmExportedFunction>::cast(target)->instance(), isolate); |
| imported_instances->Set(imported_instance, imported_instance); |
| return UnwrapImportWrapper(target); |
| } |
| // No wasm function or being debugged. Compile a new wrapper for the new |
| // signature. |
| return compiler::CompileWasmToJSWrapper(isolate, target, sig, index, |
| module_name, import_name, origin); |
| } |
| |
| double MonotonicallyIncreasingTimeInMs() { |
| return V8::GetCurrentPlatform()->MonotonicallyIncreasingTime() * |
| base::Time::kMillisecondsPerSecond; |
| } |
| |
| void FunctionTableFinalizer(const v8::WeakCallbackInfo<void>& data) { |
| GlobalHandles::Destroy(reinterpret_cast<Object**>( |
| reinterpret_cast<JSObject**>(data.GetParameter()))); |
| } |
| |
| std::unique_ptr<compiler::ModuleEnv> CreateDefaultModuleEnv( |
| Isolate* isolate, WasmModule* module, Handle<Code> illegal_builtin) { |
| std::vector<GlobalHandleAddress> function_tables; |
| std::vector<GlobalHandleAddress> signature_tables; |
| std::vector<SignatureMap*> signature_maps; |
| |
| for (size_t i = 0; i < module->function_tables.size(); i++) { |
| Handle<Object> func_table = |
| isolate->global_handles()->Create(isolate->heap()->undefined_value()); |
| Handle<Object> sig_table = |
| isolate->global_handles()->Create(isolate->heap()->undefined_value()); |
| GlobalHandles::MakeWeak(func_table.location(), func_table.location(), |
| &FunctionTableFinalizer, |
| v8::WeakCallbackType::kFinalizer); |
| GlobalHandles::MakeWeak(sig_table.location(), sig_table.location(), |
| &FunctionTableFinalizer, |
| v8::WeakCallbackType::kFinalizer); |
| function_tables.push_back(func_table.address()); |
| signature_tables.push_back(sig_table.address()); |
| signature_maps.push_back(&module->function_tables[i].map); |
| } |
| |
| std::vector<Handle<Code>> empty_code; |
| |
| compiler::ModuleEnv result = { |
| module, // -- |
| function_tables, // -- |
| signature_tables, // -- |
| signature_maps, // -- |
| empty_code, // -- |
| illegal_builtin, // -- |
| 0, // -- |
| 0, // -- |
| 0 // -- |
| }; |
| return std::unique_ptr<compiler::ModuleEnv>(new compiler::ModuleEnv(result)); |
| } |
| |
| Handle<WasmCompiledModule> NewCompiledModule( |
| Isolate* isolate, Handle<WasmSharedModuleData> shared, |
| Handle<FixedArray> code_table, Handle<FixedArray> export_wrappers, |
| compiler::ModuleEnv* env) { |
| Handle<WasmCompiledModule> compiled_module = |
| WasmCompiledModule::New(isolate, shared, code_table, export_wrappers, |
| env->function_tables, env->signature_tables); |
| return compiled_module; |
| } |
| |
| template <typename T> |
| void ReopenHandles(Isolate* isolate, const std::vector<Handle<T>>& vec) { |
| auto& mut = const_cast<std::vector<Handle<T>>&>(vec); |
| for (size_t i = 0; i < mut.size(); i++) { |
| mut[i] = Handle<T>(*mut[i], isolate); |
| } |
| } |
| |
| } // namespace |
| |
| MaybeHandle<WasmModuleObject> ModuleCompiler::CompileToModuleObjectInternal( |
| Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& wire_bytes, |
| Handle<Script> asm_js_script, |
| Vector<const byte> asm_js_offset_table_bytes) { |
| Factory* factory = isolate->factory(); |
| // Check whether lazy compilation is enabled for this module. |
| bool lazy_compile = compile_lazy(module_.get()); |
| |
| // If lazy compile: Initialize the code table with the lazy compile builtin. |
| // Otherwise: Initialize with the illegal builtin. All call sites will be |
| // patched at instantiation. |
| Handle<Code> init_builtin = lazy_compile |
| ? BUILTIN_CODE(isolate_, WasmCompileLazy) |
| : BUILTIN_CODE(isolate_, Illegal); |
| |
| auto env = CreateDefaultModuleEnv(isolate, module_.get(), init_builtin); |
| |
| // 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<int>(module_->functions.size()); |
| int export_wrappers_size = static_cast<int>(module_->num_exported_functions); |
| Handle<FixedArray> code_table = |
| factory->NewFixedArray(static_cast<int>(code_table_size), TENURED); |
| Handle<FixedArray> export_wrappers = |
| factory->NewFixedArray(static_cast<int>(export_wrappers_size), TENURED); |
| // Initialize the code table. |
| for (int i = 0, e = code_table->length(); i < e; ++i) { |
| code_table->set(i, *init_builtin); |
| } |
| |
| for (int i = 0, e = export_wrappers->length(); i < e; ++i) { |
| export_wrappers->set(i, *init_builtin); |
| } |
| |
| if (!lazy_compile) { |
| size_t funcs_to_compile = |
| module_->functions.size() - module_->num_imported_functions; |
| bool compile_parallel = |
| !FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks > 0 && |
| funcs_to_compile > 1 && |
| V8::GetCurrentPlatform()->NumberOfAvailableBackgroundThreads() > 0; |
| // Avoid a race condition by collecting results into a second vector. |
| std::vector<Handle<Code>> results(env->module->functions.size()); |
| |
| if (compile_parallel) { |
| CompileInParallel(wire_bytes, env.get(), results, thrower); |
| } else { |
| CompileSequentially(wire_bytes, env.get(), results, thrower); |
| } |
| if (thrower->error()) return {}; |
| |
| // At this point, compilation has completed. Update the code table. |
| for (size_t i = |
| module_->num_imported_functions + FLAG_skip_compiling_wasm_funcs; |
| i < results.size(); ++i) { |
| Code* code = *results[i]; |
| code_table->set(static_cast<int>(i), code); |
| RecordStats(code, counters()); |
| } |
| } else if (module_->is_wasm()) { |
| // Validate wasm modules for lazy compilation. Don't validate asm.js |
| // modules, they are valid by construction (otherwise a CHECK will fail |
| // during lazy compilation). |
| // TODO(clemensh): According to the spec, we can actually skip validation |
| // at module creation time, and return a function that always traps at |
| // (lazy) compilation time. |
| ValidateSequentially(wire_bytes, env.get(), thrower); |
| } |
| if (thrower->error()) return {}; |
| |
| // Create heap objects for script, module bytes and asm.js offset table to |
| // be stored in the shared module data. |
| Handle<Script> script; |
| Handle<ByteArray> asm_js_offset_table; |
| if (asm_js_script.is_null()) { |
| script = CreateWasmScript(isolate_, wire_bytes); |
| } else { |
| script = asm_js_script; |
| asm_js_offset_table = |
| isolate_->factory()->NewByteArray(asm_js_offset_table_bytes.length()); |
| asm_js_offset_table->copy_in(0, asm_js_offset_table_bytes.start(), |
| asm_js_offset_table_bytes.length()); |
| } |
| // TODO(wasm): only save the sections necessary to deserialize a |
| // {WasmModule}. E.g. function bodies could be omitted. |
| Handle<String> module_bytes = |
| factory |
| ->NewStringFromOneByte({wire_bytes.start(), wire_bytes.length()}, |
| TENURED) |
| .ToHandleChecked(); |
| DCHECK(module_bytes->IsSeqOneByteString()); |
| |
| // The {module_wrapper} will take ownership of the {WasmModule} object, |
| // and it will be destroyed when the GC reclaims the wrapper object. |
| Handle<WasmModuleWrapper> module_wrapper = |
| WasmModuleWrapper::New(isolate_, module_.release()); |
| WasmModule* module = module_wrapper->get(); |
| |
| // Create the shared module data. |
| // TODO(clemensh): For the same module (same bytes / same hash), we should |
| // only have one WasmSharedModuleData. Otherwise, we might only set |
| // breakpoints on a (potentially empty) subset of the instances. |
| |
| Handle<WasmSharedModuleData> shared = WasmSharedModuleData::New( |
| isolate_, module_wrapper, Handle<SeqOneByteString>::cast(module_bytes), |
| script, asm_js_offset_table); |
| if (lazy_compile) WasmSharedModuleData::PrepareForLazyCompilation(shared); |
| |
| // 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<WasmCompiledModule> compiled_module = NewCompiledModule( |
| isolate_, shared, code_table, export_wrappers, env.get()); |
| |
| // If we created a wasm script, finish it now and make it public to the |
| // debugger. |
| if (asm_js_script.is_null()) { |
| script->set_wasm_compiled_module(*compiled_module); |
| isolate_->debug()->OnAfterCompile(script); |
| } |
| |
| // Compile JS->wasm wrappers for exported functions. |
| JSToWasmWrapperCache js_to_wasm_cache; |
| int wrapper_index = 0; |
| for (auto exp : module->export_table) { |
| if (exp.kind != kExternalFunction) continue; |
| Handle<Code> wasm_code = EnsureExportedLazyDeoptData( |
| isolate_, Handle<WasmInstanceObject>::null(), code_table, exp.index); |
| Handle<Code> wrapper_code = js_to_wasm_cache.CloneOrCompileJSToWasmWrapper( |
| isolate_, module, wasm_code, exp.index); |
| export_wrappers->set(wrapper_index, *wrapper_code); |
| RecordStats(*wrapper_code, counters()); |
| ++wrapper_index; |
| } |
| return WasmModuleObject::New(isolate_, compiled_module); |
| } |
| |
| Handle<Code> JSToWasmWrapperCache::CloneOrCompileJSToWasmWrapper( |
| Isolate* isolate, wasm::WasmModule* module, Handle<Code> wasm_code, |
| uint32_t index) { |
| const wasm::WasmFunction* func = &module->functions[index]; |
| int cached_idx = sig_map_.Find(func->sig); |
| if (cached_idx >= 0) { |
| Handle<Code> code = isolate->factory()->CopyCode(code_cache_[cached_idx]); |
| // Now patch the call to wasm code. |
| for (RelocIterator it(*code, RelocInfo::kCodeTargetMask);; it.next()) { |
| DCHECK(!it.done()); |
| Code* target = |
| Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); |
| if (target->kind() == Code::WASM_FUNCTION || |
| target->kind() == Code::WASM_TO_JS_FUNCTION || |
| target->builtin_index() == Builtins::kIllegal || |
| target->builtin_index() == Builtins::kWasmCompileLazy) { |
| it.rinfo()->set_target_address(isolate, wasm_code->instruction_start()); |
| break; |
| } |
| } |
| return code; |
| } |
| |
| Handle<Code> code = |
| compiler::CompileJSToWasmWrapper(isolate, module, wasm_code, index); |
| uint32_t new_cache_idx = sig_map_.FindOrInsert(func->sig); |
| DCHECK_EQ(code_cache_.size(), new_cache_idx); |
| USE(new_cache_idx); |
| code_cache_.push_back(code); |
| return code; |
| } |
| |
| InstanceBuilder::InstanceBuilder( |
| Isolate* isolate, ErrorThrower* thrower, |
| Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> ffi, |
| MaybeHandle<JSArrayBuffer> memory, |
| WeakCallbackInfo<void>::Callback instance_finalizer_callback) |
| : isolate_(isolate), |
| module_(module_object->compiled_module()->module()), |
| async_counters_(isolate->async_counters()), |
| thrower_(thrower), |
| module_object_(module_object), |
| ffi_(ffi), |
| memory_(memory), |
| instance_finalizer_callback_(instance_finalizer_callback) { |
| sanitized_imports_.reserve(module_->import_table.size()); |
| } |
| |
| // Build an instance, in all of its glory. |
| MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() { |
| // Check that an imports argument was provided, if the module requires it. |
| // No point in continuing otherwise. |
| if (!module_->import_table.empty() && ffi_.is_null()) { |
| thrower_->TypeError( |
| "Imports argument must be present and must be an object"); |
| return {}; |
| } |
| |
| SanitizeImports(); |
| if (thrower_->error()) return {}; |
| |
| // From here on, we expect the build pipeline to run without exiting to JS. |
| // Exception is when we run the startup function. |
| DisallowJavascriptExecution no_js(isolate_); |
| // Record build time into correct bucket, then build instance. |
| TimedHistogramScope wasm_instantiate_module_time_scope( |
| module_->is_wasm() ? counters()->wasm_instantiate_wasm_module_time() |
| : counters()->wasm_instantiate_asm_module_time()); |
| Factory* factory = isolate_->factory(); |
| |
| //-------------------------------------------------------------------------- |
| // Reuse the compiled module (if no owner), otherwise clone. |
| //-------------------------------------------------------------------------- |
| Handle<FixedArray> code_table; |
| Handle<FixedArray> wrapper_table; |
| // We keep around a copy of the old code table, because we'll be replacing |
| // imports for the new instance, and then we need the old imports to be |
| // able to relocate. |
| Handle<FixedArray> old_code_table; |
| MaybeHandle<WasmInstanceObject> 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<WasmCompiledModule> original; |
| { |
| DisallowHeapAllocation no_gc; |
| original = handle(module_object_->compiled_module()); |
| if (original->has_weak_owning_instance()) { |
| owner = handle(WasmInstanceObject::cast( |
| original->weak_owning_instance()->value())); |
| } |
| } |
| DCHECK(!original.is_null()); |
| 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()); |
| old_code_table = original->code_table(); |
| compiled_module_ = WasmCompiledModule::Clone(isolate_, original); |
| code_table = compiled_module_->code_table(); |
| wrapper_table = compiled_module_->export_wrappers(); |
| // 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<Code> orig_code(Code::cast(code_table->get(i)), isolate_); |
| switch (orig_code->kind()) { |
| case Code::WASM_TO_JS_FUNCTION: |
| // Imports will be overwritten with newly compiled wrappers. |
| break; |
| case Code::BUILTIN: |
| DCHECK_EQ(Builtins::kWasmCompileLazy, orig_code->builtin_index()); |
| // If this code object has deoptimization data, then we need a |
| // unique copy to attach updated deoptimization data. |
| if (orig_code->deoptimization_data()->length() > 0) { |
| Handle<Code> code = factory->CopyCode(orig_code); |
| Handle<FixedArray> deopt_data = |
| factory->NewFixedArray(2, TENURED); |
| deopt_data->set(1, Smi::FromInt(i)); |
| code->set_deoptimization_data(*deopt_data); |
| code_table->set(i, *code); |
| } |
| break; |
| case Code::WASM_FUNCTION: { |
| Handle<Code> code = factory->CopyCode(orig_code); |
| code_table->set(i, *code); |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| for (int i = 0; i < wrapper_table->length(); ++i) { |
| Handle<Code> orig_code(Code::cast(wrapper_table->get(i)), isolate_); |
| DCHECK_EQ(orig_code->kind(), Code::JS_TO_WASM_FUNCTION); |
| Handle<Code> code = factory->CopyCode(orig_code); |
| wrapper_table->set(i, *code); |
| } |
| |
| RecordStats(code_table, counters()); |
| RecordStats(wrapper_table, counters()); |
| } else { |
| // There was no owner, so we can reuse the original. |
| compiled_module_ = original; |
| old_code_table = factory->CopyFixedArray(compiled_module_->code_table()); |
| code_table = compiled_module_->code_table(); |
| wrapper_table = compiled_module_->export_wrappers(); |
| TRACE("Reusing existing instance %d\n", compiled_module_->instance_id()); |
| } |
| compiled_module_->set_native_context(isolate_->native_context()); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Allocate the instance object. |
| //-------------------------------------------------------------------------- |
| Zone instantiation_zone(isolate_->allocator(), ZONE_NAME); |
| CodeSpecialization code_specialization(isolate_, &instantiation_zone); |
| Handle<WasmInstanceObject> instance = |
| WasmInstanceObject::New(isolate_, compiled_module_); |
| |
| //-------------------------------------------------------------------------- |
| // Set up the globals for the new instance. |
| //-------------------------------------------------------------------------- |
| MaybeHandle<JSArrayBuffer> old_globals; |
| uint32_t globals_size = module_->globals_size; |
| if (globals_size > 0) { |
| const bool enable_guard_regions = false; |
| Handle<JSArrayBuffer> global_buffer = |
| NewArrayBuffer(isolate_, globals_size, enable_guard_regions); |
| globals_ = global_buffer; |
| if (globals_.is_null()) { |
| thrower_->RangeError("Out of memory: wasm globals"); |
| return {}; |
| } |
| Address old_globals_start = compiled_module_->GetGlobalsStartOrNull(); |
| Address new_globals_start = |
| static_cast<Address>(global_buffer->backing_store()); |
| code_specialization.RelocateGlobals(old_globals_start, new_globals_start); |
| // The address of the backing buffer for the golbals is in native memory |
| // and, thus, not moving. We need it saved for |
| // serialization/deserialization purposes - so that the other end |
| // understands how to relocate the references. We still need to save the |
| // JSArrayBuffer on the instance, to keep it all alive. |
| WasmCompiledModule::SetGlobalsStartAddressFrom(factory, compiled_module_, |
| global_buffer); |
| instance->set_globals_buffer(*global_buffer); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Prepare for initialization of function tables. |
| //-------------------------------------------------------------------------- |
| int function_table_count = static_cast<int>(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<WasmTableObject>::null(), Handle<FixedArray>::null(), |
| Handle<FixedArray>::null(), Handle<FixedArray>::null()}); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Process the imports for the module. |
| //-------------------------------------------------------------------------- |
| int num_imported_functions = ProcessImports(code_table, instance); |
| if (num_imported_functions < 0) return {}; |
| |
| //-------------------------------------------------------------------------- |
| // Process the initialization for the module's globals. |
| //-------------------------------------------------------------------------- |
| InitGlobals(); |
| |
| //-------------------------------------------------------------------------- |
| // Set up the indirect function tables for the new instance. |
| //-------------------------------------------------------------------------- |
| if (function_table_count > 0) |
| InitializeTables(instance, &code_specialization); |
| |
| //-------------------------------------------------------------------------- |
| // Set up the memory for the new instance. |
| //-------------------------------------------------------------------------- |
| uint32_t initial_pages = module_->initial_pages; |
| (module_->is_wasm() ? counters()->wasm_wasm_min_mem_pages_count() |
| : counters()->wasm_asm_min_mem_pages_count()) |
| ->AddSample(initial_pages); |
| |
| if (!memory_.is_null()) { |
| Handle<JSArrayBuffer> memory = memory_.ToHandleChecked(); |
| // Set externally passed ArrayBuffer non neuterable. |
| memory->set_is_neuterable(false); |
| |
| DCHECK_IMPLIES(EnableGuardRegions(), |
| module_->is_asm_js() || memory->has_guard_region()); |
| } else if (initial_pages > 0) { |
| memory_ = AllocateMemory(initial_pages); |
| if (memory_.is_null()) return {}; // failed to allocate memory |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Check that indirect function table segments are within bounds. |
| //-------------------------------------------------------------------------- |
| for (WasmTableInit& table_init : module_->table_inits) { |
| DCHECK(table_init.table_index < table_instances_.size()); |
| uint32_t base = EvalUint32InitExpr(table_init.offset); |
| uint32_t table_size = |
| table_instances_[table_init.table_index].function_table->length(); |
| if (!in_bounds(base, static_cast<uint32_t>(table_init.entries.size()), |
| table_size)) { |
| thrower_->LinkError("table initializer is out of bounds"); |
| return {}; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Check that memory segments are within bounds. |
| //-------------------------------------------------------------------------- |
| for (WasmDataSegment& seg : module_->data_segments) { |
| uint32_t base = EvalUint32InitExpr(seg.dest_addr); |
| uint32_t mem_size = 0; |
| if (!memory_.is_null()) { |
| CHECK(memory_.ToHandleChecked()->byte_length()->ToUint32(&mem_size)); |
| } |
| if (!in_bounds(base, seg.source.length(), mem_size)) { |
| thrower_->LinkError("data segment is out of bounds"); |
| return {}; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Initialize memory. |
| //-------------------------------------------------------------------------- |
| if (!memory_.is_null()) { |
| Handle<JSArrayBuffer> memory = memory_.ToHandleChecked(); |
| Address mem_start = static_cast<Address>(memory->backing_store()); |
| uint32_t mem_size; |
| CHECK(memory->byte_length()->ToUint32(&mem_size)); |
| LoadDataSegments(mem_start, mem_size); |
| |
| uint32_t old_mem_size = compiled_module_->GetEmbeddedMemSizeOrZero(); |
| Address old_mem_start = compiled_module_->GetEmbeddedMemStartOrNull(); |
| // We might get instantiated again with the same memory. No patching |
| // needed in this case. |
| if (old_mem_start != mem_start || old_mem_size != mem_size) { |
| code_specialization.RelocateMemoryReferences(old_mem_start, old_mem_size, |
| mem_start, mem_size); |
| } |
| // Just like with globals, we need to keep both the JSArrayBuffer |
| // and save the start pointer. |
| instance->set_memory_buffer(*memory); |
| WasmCompiledModule::SetSpecializationMemInfoFrom(factory, compiled_module_, |
| memory); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the runtime support for the new instance. |
| //-------------------------------------------------------------------------- |
| Handle<WeakCell> weak_link = factory->NewWeakCell(instance); |
| |
| for (int i = num_imported_functions + FLAG_skip_compiling_wasm_funcs, |
| num_functions = static_cast<int>(module_->functions.size()); |
| i < num_functions; ++i) { |
| Handle<Code> code = handle(Code::cast(code_table->get(i)), isolate_); |
| if (code->kind() == Code::WASM_FUNCTION) { |
| Handle<FixedArray> deopt_data = factory->NewFixedArray(2, TENURED); |
| deopt_data->set(0, *weak_link); |
| deopt_data->set(1, Smi::FromInt(i)); |
| code->set_deoptimization_data(*deopt_data); |
| continue; |
| } |
| DCHECK_EQ(Builtins::kWasmCompileLazy, code->builtin_index()); |
| int deopt_len = code->deoptimization_data()->length(); |
| if (deopt_len == 0) continue; |
| DCHECK_LE(2, deopt_len); |
| DCHECK_EQ(i, Smi::ToInt(code->deoptimization_data()->get(1))); |
| code->deoptimization_data()->set(0, *weak_link); |
| // Entries [2, deopt_len) encode information about table exports of this |
| // function. This is rebuilt in {LoadTableSegments}, so reset it here. |
| for (int i = 2; i < deopt_len; ++i) { |
| code->deoptimization_data()->set_undefined(isolate_, i); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up the exports object for the new instance. |
| //-------------------------------------------------------------------------- |
| ProcessExports(instance, compiled_module_); |
| if (thrower_->error()) return {}; |
| |
| //-------------------------------------------------------------------------- |
| // Add instance to Memory object |
| //-------------------------------------------------------------------------- |
| if (instance->has_memory_object()) { |
| Handle<WasmMemoryObject> memory(instance->memory_object(), isolate_); |
| WasmMemoryObject::AddInstance(isolate_, memory, instance); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Initialize the indirect function tables. |
| //-------------------------------------------------------------------------- |
| if (function_table_count > 0) LoadTableSegments(code_table, instance); |
| |
| // Patch all code with the relocations registered in code_specialization. |
| code_specialization.RelocateDirectCalls(instance); |
| code_specialization.ApplyToWholeInstance(*instance, SKIP_ICACHE_FLUSH); |
| |
| FlushICache(isolate_, code_table); |
| |
| //-------------------------------------------------------------------------- |
| // Unpack and notify signal handler of protected instructions. |
| //-------------------------------------------------------------------------- |
| if (trap_handler::UseTrapHandler()) { |
| UnpackAndRegisterProtectedInstructions(isolate_, code_table); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Set up and link the new instance. |
| //-------------------------------------------------------------------------- |
| { |
| Handle<Object> global_handle = |
| isolate_->global_handles()->Create(*instance); |
| Handle<WeakCell> link_to_clone = factory->NewWeakCell(compiled_module_); |
| Handle<WeakCell> link_to_owning_instance = factory->NewWeakCell(instance); |
| MaybeHandle<WeakCell> link_to_original; |
| MaybeHandle<WasmCompiledModule> 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()->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_->set_compiled_module(*compiled_module_); |
| compiled_module_->set_weak_owning_instance(link_to_owning_instance); |
| GlobalHandles::MakeWeak( |
| global_handle.location(), global_handle.location(), |
| instance_finalizer_callback_, v8::WeakCallbackType::kFinalizer); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Debugging support. |
| //-------------------------------------------------------------------------- |
| // Set all breakpoints that were set on the shared module. |
| WasmSharedModuleData::SetBreakpointsOnNewInstance(compiled_module_->shared(), |
| instance); |
| |
| if (FLAG_wasm_interpret_all && module_->is_wasm()) { |
| Handle<WasmDebugInfo> debug_info = |
| WasmInstanceObject::GetOrCreateDebugInfo(instance); |
| std::vector<int> func_indexes; |
| for (int func_index = num_imported_functions, |
| num_wasm_functions = static_cast<int>(module_->functions.size()); |
| func_index < num_wasm_functions; ++func_index) { |
| func_indexes.push_back(func_index); |
| } |
| WasmDebugInfo::RedirectToInterpreter( |
| debug_info, Vector<int>(func_indexes.data(), |
| static_cast<int>(func_indexes.size()))); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Run the start function if one was specified. |
| //-------------------------------------------------------------------------- |
| if (module_->start_function_index >= 0) { |
| HandleScope scope(isolate_); |
| int start_index = module_->start_function_index; |
| Handle<Code> startup_code = EnsureExportedLazyDeoptData( |
| isolate_, instance, code_table, start_index); |
| FunctionSig* sig = module_->functions[start_index].sig; |
| Handle<Code> wrapper_code = js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper( |
| isolate_, module_, startup_code, start_index); |
| Handle<WasmExportedFunction> startup_fct = WasmExportedFunction::New( |
| isolate_, instance, MaybeHandle<String>(), start_index, |
| static_cast<int>(sig->parameter_count()), wrapper_code); |
| RecordStats(*startup_code, counters()); |
| // Call the JS function. |
| Handle<Object> undefined = factory->undefined_value(); |
| { |
| // We're OK with JS execution here. The instance is fully setup. |
| AllowJavascriptExecution allow_js(isolate_); |
| MaybeHandle<Object> retval = |
| Execution::Call(isolate_, startup_fct, undefined, 0, nullptr); |
| |
| if (retval.is_null()) { |
| DCHECK(isolate_->has_pending_exception()); |
| // It's unfortunate that the new instance is already linked in the |
| // chain. However, we need to set up everything before executing the |
| // startup function, such that stack trace information can be generated |
| // correctly already in the start function. |
| return {}; |
| } |
| } |
| } |
| |
| DCHECK(!isolate_->has_pending_exception()); |
| TRACE("Finishing instance %d\n", compiled_module_->instance_id()); |
| TRACE_CHAIN(module_object_->compiled_module()); |
| return instance; |
| } |
| |
| // Look up an import value in the {ffi_} object. |
| MaybeHandle<Object> InstanceBuilder::LookupImport(uint32_t index, |
| Handle<String> module_name, |
| |
| Handle<String> import_name) { |
| // We pre-validated in the js-api layer that the ffi object is present, and |
| // a JSObject, if the module has imports. |
| DCHECK(!ffi_.is_null()); |
| |
| // Look up the module first. |
| MaybeHandle<Object> result = |
| Object::GetPropertyOrElement(ffi_.ToHandleChecked(), module_name); |
| if (result.is_null()) { |
| return ReportTypeError("module not found", index, module_name); |
| } |
| |
| Handle<Object> module = result.ToHandleChecked(); |
| |
| // Look up the value in the module. |
| if (!module->IsJSReceiver()) { |
| return ReportTypeError("module is not an object or function", index, |
| module_name); |
| } |
| |
| result = Object::GetPropertyOrElement(module, import_name); |
| if (result.is_null()) { |
| ReportLinkError("import not found", index, module_name, import_name); |
| return MaybeHandle<JSFunction>(); |
| } |
| |
| return result; |
| } |
| |
| // Look up an import value in the {ffi_} object specifically for linking an |
| // asm.js module. This only performs non-observable lookups, which allows |
| // falling back to JavaScript proper (and hence re-executing all lookups) if |
| // module instantiation fails. |
| MaybeHandle<Object> InstanceBuilder::LookupImportAsm( |
| uint32_t index, Handle<String> import_name) { |
| // Check that a foreign function interface object was provided. |
| if (ffi_.is_null()) { |
| return ReportLinkError("missing imports object", index, import_name); |
| } |
| |
| // Perform lookup of the given {import_name} without causing any observable |
| // side-effect. We only accept accesses that resolve to data properties, |
| // which is indicated by the asm.js spec in section 7 ("Linking") as well. |
| Handle<Object> result; |
| LookupIterator it = LookupIterator::PropertyOrElement( |
| isolate_, ffi_.ToHandleChecked(), import_name); |
| switch (it.state()) { |
| case LookupIterator::ACCESS_CHECK: |
| case LookupIterator::INTEGER_INDEXED_EXOTIC: |
| case LookupIterator::INTERCEPTOR: |
| case LookupIterator::JSPROXY: |
| case LookupIterator::ACCESSOR: |
| case LookupIterator::TRANSITION: |
| return ReportLinkError("not a data property", index, import_name); |
| case LookupIterator::NOT_FOUND: |
| // Accepting missing properties as undefined does not cause any |
| // observable difference from JavaScript semantics, we are lenient. |
| result = isolate_->factory()->undefined_value(); |
| break; |
| case LookupIterator::DATA: |
| result = it.GetDataValue(); |
| break; |
| } |
| |
| return result; |
| } |
| |
| uint32_t InstanceBuilder::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<uint32_t*>(raw_buffer_ptr(globals_, offset)); |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| // Load data segments into the memory. |
| void InstanceBuilder::LoadDataSegments(Address mem_addr, size_t mem_size) { |
| Handle<SeqOneByteString> module_bytes(compiled_module_->module_bytes(), |
| isolate_); |
| for (const WasmDataSegment& segment : module_->data_segments) { |
| uint32_t source_size = segment.source.length(); |
| // Segments of size == 0 are just nops. |
| if (source_size == 0) continue; |
| uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); |
| DCHECK( |
| in_bounds(dest_offset, source_size, static_cast<uint32_t>(mem_size))); |
| byte* dest = mem_addr + dest_offset; |
| const byte* src = reinterpret_cast<const byte*>( |
| module_bytes->GetCharsAddress() + segment.source.offset()); |
| memcpy(dest, src, source_size); |
| } |
| } |
| |
| void InstanceBuilder::WriteGlobalValue(WasmGlobal& global, |
| Handle<Object> value) { |
| double num = value->Number(); |
| TRACE("init [globals+%u] = %lf, type = %s\n", global.offset, num, |
| WasmOpcodes::TypeName(global.type)); |
| switch (global.type) { |
| case kWasmI32: |
| *GetRawGlobalPtr<int32_t>(global) = static_cast<int32_t>(num); |
| break; |
| case kWasmI64: |
| // TODO(titzer): initialization of imported i64 globals. |
| UNREACHABLE(); |
| break; |
| case kWasmF32: |
| *GetRawGlobalPtr<float>(global) = static_cast<float>(num); |
| break; |
| case kWasmF64: |
| *GetRawGlobalPtr<double>(global) = static_cast<double>(num); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void InstanceBuilder::SanitizeImports() { |
| Handle<SeqOneByteString> module_bytes( |
| module_object_->compiled_module()->module_bytes()); |
| for (size_t index = 0; index < module_->import_table.size(); ++index) { |
| WasmImport& import = module_->import_table[index]; |
| |
| Handle<String> module_name; |
| MaybeHandle<String> maybe_module_name = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate_, module_bytes, import.module_name); |
| if (!maybe_module_name.ToHandle(&module_name)) { |
| thrower_->LinkError("Could not resolve module name for import %zu", |
| index); |
| return; |
| } |
| |
| Handle<String> import_name; |
| MaybeHandle<String> maybe_import_name = |
| WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate_, module_bytes, import.field_name); |
| if (!maybe_import_name.ToHandle(&import_name)) { |
| thrower_->LinkError("Could not resolve import name for import %zu", |
| index); |
| return; |
| } |
| |
| int int_index = static_cast<int>(index); |
| MaybeHandle<Object> result = |
| module_->is_asm_js() |
| ? LookupImportAsm(int_index, import_name) |
| : LookupImport(int_index, module_name, import_name); |
| if (thrower_->error()) { |
| thrower_->LinkError("Could not find value for import %zu", index); |
| return; |
| } |
| Handle<Object> value = result.ToHandleChecked(); |
| sanitized_imports_.push_back({module_name, import_name, value}); |
| } |
| } |
| |
| // Process the imports, including functions, tables, globals, and memory, in |
| // order, loading them from the {ffi_} object. Returns the number of imported |
| // functions. |
| int InstanceBuilder::ProcessImports(Handle<FixedArray> code_table, |
| Handle<WasmInstanceObject> instance) { |
| int num_imported_functions = 0; |
| int num_imported_tables = 0; |
| WasmInstanceMap imported_wasm_instances(isolate_->heap()); |
| DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size()); |
| for (int index = 0; index < static_cast<int>(module_->import_table.size()); |
| ++index) { |
| WasmImport& import = module_->import_table[index]; |
| |
| Handle<String> module_name = sanitized_imports_[index].module_name; |
| Handle<String> import_name = sanitized_imports_[index].import_name; |
| Handle<Object> value = sanitized_imports_[index].value; |
| |
| switch (import.kind) { |
| case kExternalFunction: { |
| // Function imports must be callable. |
| if (!value->IsCallable()) { |
| ReportLinkError("function import requires a callable", index, |
| module_name, import_name); |
| return -1; |
| } |
| |
| Handle<Code> import_wrapper = UnwrapOrCompileImportWrapper( |
| isolate_, index, module_->functions[import.index].sig, |
| Handle<JSReceiver>::cast(value), module_name, import_name, |
| module_->origin(), &imported_wasm_instances); |
| if (import_wrapper.is_null()) { |
| ReportLinkError("imported function does not match the expected type", |
| index, module_name, import_name); |
| return -1; |
| } |
| code_table->set(num_imported_functions, *import_wrapper); |
| RecordStats(*import_wrapper, counters()); |
| num_imported_functions++; |
| break; |
| } |
| case kExternalTable: { |
| if (!value->IsWasmTableObject()) { |
| ReportLinkError("table import requires a WebAssembly.Table", index, |
| module_name, import_name); |
| return -1; |
| } |
| WasmIndirectFunctionTable& table = |
| module_->function_tables[num_imported_tables]; |
| TableInstance& table_instance = table_instances_[num_imported_tables]; |
| table_instance.table_object = Handle<WasmTableObject>::cast(value); |
| table_instance.js_wrappers = Handle<FixedArray>( |
| table_instance.table_object->functions(), isolate_); |
| |
| int imported_cur_size = table_instance.js_wrappers->length(); |
| if (imported_cur_size < static_cast<int>(table.initial_size)) { |
| thrower_->LinkError( |
| "table import %d is smaller than initial %d, got %u", index, |
| table.initial_size, imported_cur_size); |
| return -1; |
| } |
| |
| if (table.has_maximum_size) { |
| int64_t imported_maximum_size = |
| table_instance.table_object->maximum_length()->Number(); |
| if (imported_maximum_size < 0) { |
| thrower_->LinkError( |
| "table import %d has no maximum length, expected %d", index, |
| table.maximum_size); |
| return -1; |
| } |
| if (imported_maximum_size > table.maximum_size) { |
| thrower_->LinkError( |
| " table import %d has a larger maximum size %" PRIx64 |
| " than the module's declared maximum %u", |
| index, imported_maximum_size, table.maximum_size); |
| return -1; |
| } |
| } |
| |
| // Allocate a new dispatch table and signature table. |
| int table_size = imported_cur_size; |
| table_instance.function_table = |
| isolate_->factory()->NewFixedArray(table_size); |
| table_instance.signature_table = |
| isolate_->factory()->NewFixedArray(table_size); |
| for (int i = 0; i < table_size; ++i) { |
| table_instance.signature_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<Object> val(table_instance.js_wrappers->get(i), isolate_); |
| if (!val->IsJSFunction()) continue; |
| WasmFunction* function = |
| GetWasmFunctionForImportWrapper(isolate_, val); |
| if (function == nullptr) { |
| thrower_->LinkError("table import %d[%d] is not a wasm function", |
| index, i); |
| return -1; |
| } |
| int sig_index = table.map.FindOrInsert(function->sig); |
| table_instance.signature_table->set(i, Smi::FromInt(sig_index)); |
| table_instance.function_table->set(i, *UnwrapImportWrapper(val)); |
| } |
| |
| num_imported_tables++; |
| break; |
| } |
| case kExternalMemory: { |
| // Validation should have failed if more than one memory object was |
| // provided. |
| DCHECK(!instance->has_memory_object()); |
| if (!value->IsWasmMemoryObject()) { |
| ReportLinkError("memory import must be a WebAssembly.Memory object", |
| index, module_name, import_name); |
| return -1; |
| } |
| auto memory = Handle<WasmMemoryObject>::cast(value); |
| instance->set_memory_object(*memory); |
| Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate_); |
| memory_ = buffer; |
| uint32_t imported_cur_pages = static_cast<uint32_t>( |
| buffer->byte_length()->Number() / WasmModule::kPageSize); |
| if (imported_cur_pages < module_->initial_pages) { |
| thrower_->LinkError( |
| "memory import %d is smaller than initial %u, got %u", index, |
| module_->initial_pages, imported_cur_pages); |
| } |
| int32_t imported_maximum_pages = memory->maximum_pages(); |
| if (module_->has_maximum_pages) { |
| if (imported_maximum_pages < 0) { |
| thrower_->LinkError( |
| "memory import %d has no maximum limit, expected at most %u", |
| index, imported_maximum_pages); |
| return -1; |
| } |
| if (static_cast<uint32_t>(imported_maximum_pages) > |
| module_->maximum_pages) { |
| thrower_->LinkError( |
| "memory import %d has a larger maximum size %u than the " |
| "module's declared maximum %u", |
| index, imported_maximum_pages, module_->maximum_pages); |
| return -1; |
| } |
| } |
| break; |
| } |
| case kExternalGlobal: { |
| // Global imports are converted to numbers and written into the |
| // {globals_} array buffer. |
| if (module_->globals[import.index].type == kWasmI64) { |
| ReportLinkError("global import cannot have type i64", index, |
| module_name, import_name); |
| return -1; |
| } |
| if (module_->is_asm_js()) { |
| // Accepting {JSFunction} on top of just primitive values here is a |
| // workaround to support legacy asm.js code with broken binding. Note |
| // that using {NaN} (or Smi::kZero) here is what using the observable |
| // conversion via {ToPrimitive} would produce as well. |
| // TODO(mstarzinger): Still observable if Function.prototype.valueOf |
| // or friends are patched, we might need to check for that as well. |
| if (value->IsJSFunction()) value = isolate_->factory()->nan_value(); |
| if (value->IsPrimitive() && !value->IsSymbol()) { |
| if (module_->globals[import.index].type == kWasmI32) { |
| value = Object::ToInt32(isolate_, value).ToHandleChecked(); |
| } else { |
| value = Object::ToNumber(value).ToHandleChecked(); |
| } |
| } |
| } |
| if (!value->IsNumber()) { |
| ReportLinkError("global import must be a number", index, module_name, |
| import_name); |
| return -1; |
| } |
| WriteGlobalValue(module_->globals[import.index], value); |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| if (!imported_wasm_instances.empty()) { |
| WasmInstanceMap::IteratableScope iteratable_scope(&imported_wasm_instances); |
| Handle<FixedArray> instances_array = isolate_->factory()->NewFixedArray( |
| imported_wasm_instances.size(), TENURED); |
| instance->set_directly_called_instances(*instances_array); |
| int index = 0; |
| for (auto it = iteratable_scope.begin(), end = iteratable_scope.end(); |
| it != end; ++it, ++index) { |
| instances_array->set(index, ***it); |
| } |
| } |
| |
| return num_imported_functions; |
| } |
| |
| template <typename T> |
| T* InstanceBuilder::GetRawGlobalPtr(WasmGlobal& global) { |
| return reinterpret_cast<T*>(raw_buffer_ptr(globals_, global.offset)); |
| } |
| |
| // Process initialization of globals. |
| void InstanceBuilder::InitGlobals() { |
| for (auto global : module_->globals) { |
| switch (global.init.kind) { |
| case WasmInitExpr::kI32Const: |
| *GetRawGlobalPtr<int32_t>(global) = global.init.val.i32_const; |
| break; |
| case WasmInitExpr::kI64Const: |
| *GetRawGlobalPtr<int64_t>(global) = global.init.val.i64_const; |
| break; |
| case WasmInitExpr::kF32Const: |
| *GetRawGlobalPtr<float>(global) = global.init.val.f32_const; |
| break; |
| case WasmInitExpr::kF64Const: |
| *GetRawGlobalPtr<double>(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 == kWasmI64 || global.type == kWasmF64) |
| ? 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<JSArrayBuffer> InstanceBuilder::AllocateMemory(uint32_t num_pages) { |
| if (num_pages > FLAG_wasm_max_mem_pages) { |
| thrower_->RangeError("Out of memory: wasm memory too large"); |
| return Handle<JSArrayBuffer>::null(); |
| } |
| const bool enable_guard_regions = EnableGuardRegions(); |
| Handle<JSArrayBuffer> mem_buffer = NewArrayBuffer( |
| isolate_, num_pages * WasmModule::kPageSize, enable_guard_regions); |
| |
| if (mem_buffer.is_null()) { |
| thrower_->RangeError("Out of memory: wasm memory"); |
| } |
| return mem_buffer; |
| } |
| |
| bool InstanceBuilder::NeedsWrappers() const { |
| if (module_->num_exported_functions > 0) return true; |
| for (auto& table_instance : table_instances_) { |
| if (!table_instance.js_wrappers.is_null()) return true; |
| } |
| for (auto& table : module_->function_tables) { |
| if (table.exported) return true; |
| } |
| return false; |
| } |
| |
| // Process the exports, creating wrappers for functions, tables, memories, |
| // and globals. |
| void InstanceBuilder::ProcessExports( |
| Handle<WasmInstanceObject> instance, |
| Handle<WasmCompiledModule> compiled_module) { |
| Handle<FixedArray> wrapper_table = compiled_module->export_wrappers(); |
| if (NeedsWrappers()) { |
| // Fill the table to cache the exported JSFunction wrappers. |
| js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(), |
| Handle<JSFunction>::null()); |
| } |
| |
| Handle<JSObject> exports_object; |
| if (module_->is_wasm()) { |
| // Create the "exports" object. |
| exports_object = isolate_->factory()->NewJSObjectWithNullProto(); |
| } else if (module_->is_asm_js()) { |
| Handle<JSFunction> object_function = Handle<JSFunction>( |
| isolate_->native_context()->object_function(), isolate_); |
| exports_object = isolate_->factory()->NewJSObject(object_function); |
| } else { |
| UNREACHABLE(); |
| } |
| instance->set_exports_object(*exports_object); |
| |
| Handle<String> single_function_name = |
| isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName); |
| |
| PropertyDescriptor desc; |
| desc.set_writable(module_->is_asm_js()); |
| desc.set_enumerable(true); |
| desc.set_configurable(module_->is_asm_js()); |
| |
| // Store weak references to all exported functions. |
| Handle<FixedArray> weak_exported_functions; |
| if (compiled_module->has_weak_exported_functions()) { |
| weak_exported_functions = compiled_module->weak_exported_functions(); |
| } else { |
| int export_count = 0; |
| for (WasmExport& exp : module_->export_table) { |
| if (exp.kind == kExternalFunction) ++export_count; |
| } |
| weak_exported_functions = isolate_->factory()->NewFixedArray(export_count); |
| compiled_module->set_weak_exported_functions(weak_exported_functions); |
| } |
| |
| // Process each export in the export table. |
| int export_index = 0; // Index into {weak_exported_functions}. |
| for (WasmExport& exp : module_->export_table) { |
| Handle<String> name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate_, compiled_module_, exp.name) |
| .ToHandleChecked(); |
| Handle<JSObject> export_to; |
| if (module_->is_asm_js() && exp.kind == kExternalFunction && |
| String::Equals(name, single_function_name)) { |
| export_to = instance; |
| } else { |
| export_to = exports_object; |
| } |
| |
| switch (exp.kind) { |
| case kExternalFunction: { |
| // Wrap and export the code as a JSFunction. |
| WasmFunction& function = module_->functions[exp.index]; |
| Handle<JSFunction> js_function = js_wrappers_[exp.index]; |
| if (js_function.is_null()) { |
| // Wrap the exported code as a JSFunction. |
| Handle<Code> export_code = |
| wrapper_table->GetValueChecked<Code>(isolate_, export_index); |
| MaybeHandle<String> func_name; |
| if (module_->is_asm_js()) { |
| // For modules arising from asm.js, honor the names section. |
| func_name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate_, compiled_module_, function.name) |
| .ToHandleChecked(); |
| } |
| js_function = WasmExportedFunction::New( |
| isolate_, instance, func_name, function.func_index, |
| static_cast<int>(function.sig->parameter_count()), export_code); |
| js_wrappers_[exp.index] = js_function; |
| } |
| desc.set_value(js_function); |
| Handle<WeakCell> weak_export = |
| isolate_->factory()->NewWeakCell(js_function); |
| DCHECK_GT(weak_exported_functions->length(), export_index); |
| weak_exported_functions->set(export_index, *weak_export); |
| 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_maximum_size ? table.maximum_size |
| : FLAG_wasm_max_table_size; |
| table_instance.table_object = |
| WasmTableObject::New(isolate_, table.initial_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<WasmMemoryObject> memory_object; |
| if (!instance->has_memory_object()) { |
| // If there was no imported WebAssembly.Memory object, create one. |
| memory_object = WasmMemoryObject::New( |
| isolate_, |
| (instance->has_memory_buffer()) |
| ? handle(instance->memory_buffer()) |
| : Handle<JSArrayBuffer>::null(), |
| (module_->maximum_pages != 0) ? module_->maximum_pages : -1); |
| instance->set_memory_object(*memory_object); |
| } else { |
| memory_object = |
| Handle<WasmMemoryObject>(instance->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 kWasmI32: |
| num = *GetRawGlobalPtr<int32_t>(global); |
| break; |
| case kWasmF32: |
| num = *GetRawGlobalPtr<float>(global); |
| break; |
| case kWasmF64: |
| num = *GetRawGlobalPtr<double>(global); |
| break; |
| case kWasmI64: |
| thrower_->LinkError( |
| "export of globals of type I64 is not allowed."); |
| return; |
| default: |
| UNREACHABLE(); |
| } |
| desc.set_value(isolate_->factory()->NewNumber(num)); |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| |
| v8::Maybe<bool> status = JSReceiver::DefineOwnProperty( |
| isolate_, export_to, name, &desc, Object::THROW_ON_ERROR); |
| if (!status.IsJust()) { |
| TruncatedUserString<> trunc_name(name->GetCharVector<uint8_t>()); |
| thrower_->LinkError("export of %.*s failed.", trunc_name.length(), |
| trunc_name.start()); |
| return; |
| } |
| } |
| DCHECK_EQ(export_index, weak_exported_functions->length()); |
| |
| if (module_->is_wasm()) { |
| v8::Maybe<bool> success = JSReceiver::SetIntegrityLevel( |
| exports_object, FROZEN, Object::DONT_THROW); |
| DCHECK(success.FromMaybe(false)); |
| USE(success); |
| } |
| } |
| |
| void InstanceBuilder::InitializeTables( |
| Handle<WasmInstanceObject> instance, |
| CodeSpecialization* code_specialization) { |
| int function_table_count = static_cast<int>(module_->function_tables.size()); |
| Handle<FixedArray> new_function_tables = |
| isolate_->factory()->NewFixedArray(function_table_count, TENURED); |
| Handle<FixedArray> new_signature_tables = |
| isolate_->factory()->NewFixedArray(function_table_count, TENURED); |
| Handle<FixedArray> old_function_tables = compiled_module_->function_tables(); |
| Handle<FixedArray> old_signature_tables = |
| compiled_module_->signature_tables(); |
| |
| // These go on the instance. |
| Handle<FixedArray> rooted_function_tables = |
| isolate_->factory()->NewFixedArray(function_table_count, TENURED); |
| Handle<FixedArray> rooted_signature_tables = |
| isolate_->factory()->NewFixedArray(function_table_count, TENURED); |
| |
| instance->set_function_tables(*rooted_function_tables); |
| instance->set_signature_tables(*rooted_signature_tables); |
| |
| DCHECK_EQ(old_function_tables->length(), new_function_tables->length()); |
| DCHECK_EQ(old_signature_tables->length(), new_signature_tables->length()); |
| |
| for (int index = 0; index < function_table_count; ++index) { |
| WasmIndirectFunctionTable& table = module_->function_tables[index]; |
| TableInstance& table_instance = table_instances_[index]; |
| int table_size = static_cast<int>(table.initial_size); |
| |
| if (table_instance.function_table.is_null()) { |
| // Create a new dispatch table if necessary. |
| table_instance.function_table = |
| isolate_->factory()->NewFixedArray(table_size); |
| table_instance.signature_table = |
| isolate_->factory()->NewFixedArray(table_size); |
| for (int i = 0; i < table_size; ++i) { |
| // Fill the table with invalid signature indexes so that |
| // uninitialized entries will always fail the signature check. |
| table_instance.signature_table->set(i, Smi::FromInt(kInvalidSigIndex)); |
| } |
| } else { |
| // Table is imported, patch table bounds check |
| DCHECK_LE(table_size, table_instance.function_table->length()); |
| code_specialization->PatchTableSize( |
| table_size, table_instance.function_table->length()); |
| } |
| int int_index = static_cast<int>(index); |
| |
| Handle<FixedArray> global_func_table = |
| isolate_->global_handles()->Create(*table_instance.function_table); |
| Handle<FixedArray> global_sig_table = |
| isolate_->global_handles()->Create(*table_instance.signature_table); |
| // Make the handles weak. The table objects are rooted on the instance, as |
| // they belong to it. We need the global handles in order to have stable |
| // pointers to embed in the instance's specialization (wasm compiled code). |
| // The order of finalization doesn't matter, in that the instance finalizer |
| // may be called before each table's finalizer, or vice-versa. |
| // This is because values used for embedding are only interesting should we |
| // {Reset} a specialization, in which case they are interesting as values, |
| // they are not dereferenced. |
| GlobalHandles::MakeWeak( |
| reinterpret_cast<Object**>(global_func_table.location()), |
| global_func_table.location(), &FunctionTableFinalizer, |
| v8::WeakCallbackType::kFinalizer); |
| GlobalHandles::MakeWeak( |
| reinterpret_cast<Object**>(global_sig_table.location()), |
| global_sig_table.location(), &FunctionTableFinalizer, |
| v8::WeakCallbackType::kFinalizer); |
| |
| rooted_function_tables->set(int_index, *global_func_table); |
| rooted_signature_tables->set(int_index, *global_sig_table); |
| |
| GlobalHandleAddress new_func_table_addr = global_func_table.address(); |
| GlobalHandleAddress new_sig_table_addr = global_sig_table.address(); |
| WasmCompiledModule::SetTableValue(isolate_, new_function_tables, int_index, |
| new_func_table_addr); |
| WasmCompiledModule::SetTableValue(isolate_, new_signature_tables, int_index, |
| new_sig_table_addr); |
| |
| GlobalHandleAddress old_func_table_addr = |
| WasmCompiledModule::GetTableValue(*old_function_tables, int_index); |
| GlobalHandleAddress old_sig_table_addr = |
| WasmCompiledModule::GetTableValue(*old_signature_tables, int_index); |
| |
| code_specialization->RelocatePointer(old_func_table_addr, |
| new_func_table_addr); |
| code_specialization->RelocatePointer(old_sig_table_addr, |
| new_sig_table_addr); |
| } |
| |
| compiled_module_->set_function_tables(new_function_tables); |
| compiled_module_->set_signature_tables(new_signature_tables); |
| } |
| |
| void InstanceBuilder::LoadTableSegments(Handle<FixedArray> code_table, |
| Handle<WasmInstanceObject> instance) { |
| int function_table_count = static_cast<int>(module_->function_tables.size()); |
| for (int index = 0; index < function_table_count; ++index) { |
| WasmIndirectFunctionTable& table = module_->function_tables[index]; |
| TableInstance& table_instance = table_instances_[index]; |
| |
| Handle<FixedArray> all_dispatch_tables; |
| if (!table_instance.table_object.is_null()) { |
| // Get the existing dispatch table(s) with the WebAssembly.Table object. |
| all_dispatch_tables = |
| handle(table_instance.table_object->dispatch_tables()); |
| } |
| |
| // Count the number of table exports for each function (needed for lazy |
| // compilation). |
| std::unordered_map<uint32_t, uint32_t> num_table_exports; |
| if (compile_lazy(module_)) { |
| for (auto& table_init : module_->table_inits) { |
| for (uint32_t func_index : table_init.entries) { |
| Code* code = |
| Code::cast(code_table->get(static_cast<int>(func_index))); |
| // Only increase the counter for lazy compile builtins (it's not |
| // needed otherwise). |
| if (code->is_wasm_code()) continue; |
| DCHECK_EQ(Builtins::kWasmCompileLazy, code->builtin_index()); |
| ++num_table_exports[func_index]; |
| } |
| } |
| } |
| |
| // TODO(titzer): this does redundant work if there are multiple tables, |
| // since initializations are not sorted by table index. |
| for (auto& table_init : module_->table_inits) { |
| uint32_t base = EvalUint32InitExpr(table_init.offset); |
| uint32_t num_entries = static_cast<uint32_t>(table_init.entries.size()); |
| DCHECK(in_bounds(base, num_entries, |
| table_instance.function_table->length())); |
| for (uint32_t i = 0; i < num_entries; ++i) { |
| uint32_t func_index = table_init.entries[i]; |
| WasmFunction* function = &module_->functions[func_index]; |
| int table_index = static_cast<int>(i + base); |
| int32_t sig_index = table.map.Find(function->sig); |
| DCHECK_GE(sig_index, 0); |
| table_instance.signature_table->set(table_index, |
| Smi::FromInt(sig_index)); |
| Handle<Code> wasm_code = EnsureTableExportLazyDeoptData( |
| isolate_, instance, code_table, func_index, |
| table_instance.function_table, table_index, num_table_exports); |
| table_instance.function_table->set(table_index, *wasm_code); |
| |
| if (!all_dispatch_tables.is_null()) { |
| if (js_wrappers_[func_index].is_null()) { |
| // No JSFunction entry yet exists for this function. Create one. |
| // TODO(titzer): We compile JS->wasm wrappers for functions are |
| // not exported but are in an exported table. This should be done |
| // at module compile time and cached instead. |
| |
| Handle<Code> wrapper_code = |
| js_to_wasm_cache_.CloneOrCompileJSToWasmWrapper( |
| isolate_, module_, wasm_code, func_index); |
| MaybeHandle<String> func_name; |
| if (module_->is_asm_js()) { |
| // For modules arising from asm.js, honor the names section. |
| func_name = WasmCompiledModule::ExtractUtf8StringFromModuleBytes( |
| isolate_, compiled_module_, function->name) |
| .ToHandleChecked(); |
| } |
| Handle<WasmExportedFunction> js_function = |
| WasmExportedFunction::New( |
| isolate_, instance, func_name, func_index, |
| static_cast<int>(function->sig->parameter_count()), |
| wrapper_code); |
| js_wrappers_[func_index] = js_function; |
| } |
| table_instance.js_wrappers->set(table_index, |
| *js_wrappers_[func_index]); |
| |
| UpdateDispatchTables(isolate_, all_dispatch_tables, table_index, |
| function, wasm_code); |
| } |
| } |
| } |
| |
| #ifdef DEBUG |
| // Check that the count of table exports was accurate. The entries are |
| // decremented on each export, so all should be zero now. |
| for (auto e : num_table_exports) { |
| DCHECK_EQ(0, e.second); |
| } |
| #endif |
| |
| // TODO(titzer): we add the new dispatch table at the end to avoid |
| // redundant work and also because the new instance is not yet fully |
| // initialized. |
| if (!table_instance.table_object.is_null()) { |
| // Add the new dispatch table to the WebAssembly.Table object. |
| all_dispatch_tables = WasmTableObject::AddDispatchTable( |
| isolate_, table_instance.table_object, instance, index, |
| table_instance.function_table, table_instance.signature_table); |
| } |
| } |
| } |
| |
| AsyncCompileJob::AsyncCompileJob(Isolate* isolate, |
| std::unique_ptr<byte[]> bytes_copy, |
| size_t length, Handle<Context> context, |
| Handle<JSPromise> promise) |
| : isolate_(isolate), |
| async_counters_(isolate->async_counters()), |
| bytes_copy_(std::move(bytes_copy)), |
| wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length) { |
| // The handles for the context and promise must be deferred. |
| DeferredHandleScope deferred(isolate); |
| context_ = Handle<Context>(*context); |
| module_promise_ = Handle<JSPromise>(*promise); |
| deferred_handles_.push_back(deferred.Detach()); |
| } |
| |
| void AsyncCompileJob::Start() { |
| DoAsync<DecodeModule>(); // -- |
| } |
| |
| AsyncCompileJob::~AsyncCompileJob() { |
| background_task_manager_.CancelAndWait(); |
| for (auto d : deferred_handles_) delete d; |
| } |
| |
| void AsyncCompileJob::AsyncCompileFailed(ErrorThrower& thrower) { |
| RejectPromise(isolate_, context_, thrower, module_promise_); |
| isolate_->wasm_compilation_manager()->RemoveJob(this); |
| } |
| |
| void AsyncCompileJob::AsyncCompileSucceeded(Handle<Object> result) { |
| ResolvePromise(isolate_, context_, module_promise_, result); |
| isolate_->wasm_compilation_manager()->RemoveJob(this); |
| } |
| |
| // A closure to run a compilation step (either as foreground or background |
| // task) and schedule the next step(s), if any. |
| class AsyncCompileJob::CompileStep { |
| public: |
| explicit CompileStep(size_t num_background_tasks = 0) |
| : num_background_tasks_(num_background_tasks) {} |
| |
| virtual ~CompileStep() {} |
| |
| void Run(bool on_foreground) { |
| if (on_foreground) { |
| DCHECK_EQ(1, job_->num_pending_foreground_tasks_--); |
| SaveContext saved_context(job_->isolate_); |
| job_->isolate_->set_context(*job_->context_); |
| RunInForeground(); |
| } else { |
| RunInBackground(); |
| } |
| } |
| |
| virtual void RunInForeground() { UNREACHABLE(); } |
| virtual void RunInBackground() { UNREACHABLE(); } |
| |
| size_t NumberOfBackgroundTasks() { return num_background_tasks_; } |
| |
| AsyncCompileJob* job_ = nullptr; |
| const size_t num_background_tasks_; |
| }; |
| |
| class AsyncCompileJob::CompileTask : public CancelableTask { |
| public: |
| CompileTask(AsyncCompileJob* job, bool on_foreground) |
| // We only manage the background tasks with the {CancelableTaskManager} of |
| // the {AsyncCompileJob}. Foreground tasks are managed by the system's |
| // {CancelableTaskManager}. Background tasks cannot spawn tasks managed by |
| // their own task manager. |
| : CancelableTask(on_foreground ? job->isolate_->cancelable_task_manager() |
| : &job->background_task_manager_), |
| job_(job), |
| on_foreground_(on_foreground) {} |
| |
| void RunInternal() override { job_->step_->Run(on_foreground_); } |
| |
| private: |
| AsyncCompileJob* job_; |
| bool on_foreground_; |
| }; |
| |
| void AsyncCompileJob::StartForegroundTask() { |
| DCHECK_EQ(0, num_pending_foreground_tasks_++); |
| |
| V8::GetCurrentPlatform()->CallOnForegroundThread( |
| reinterpret_cast<v8::Isolate*>(isolate_), new CompileTask(this, true)); |
| } |
| |
| template <typename State, typename... Args> |
| void AsyncCompileJob::DoSync(Args&&... args) { |
| step_.reset(new State(std::forward<Args>(args)...)); |
| step_->job_ = this; |
| StartForegroundTask(); |
| } |
| |
| void AsyncCompileJob::StartBackgroundTask() { |
| V8::GetCurrentPlatform()->CallOnBackgroundThread( |
| new CompileTask(this, false), v8::Platform::kShortRunningTask); |
| } |
| |
| template <typename State, typename... Args> |
| void AsyncCompileJob::DoAsync(Args&&... args) { |
| step_.reset(new State(std::forward<Args>(args)...)); |
| step_->job_ = this; |
| size_t end = step_->NumberOfBackgroundTasks(); |
| for (size_t i = 0; i < end; ++i) { |
| StartBackgroundTask(); |
| } |
| } |
| |
| //========================================================================== |
| // Step 1: (async) Decode the module. |
| //========================================================================== |
| class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep { |
| public: |
| DecodeModule() : CompileStep(1) {} |
| |
| void RunInBackground() override { |
| ModuleResult result; |
| { |
| DisallowHandleAllocation no_handle; |
| DisallowHeapAllocation no_allocation; |
| // Decode the module bytes. |
| TRACE_COMPILE("(1) Decoding module...\n"); |
| result = AsyncDecodeWasmModule(job_->isolate_, job_->wire_bytes_.start(), |
| job_->wire_bytes_.end(), false, |
| kWasmOrigin, job_->async_counters()); |
| } |
| if (result.failed()) { |
| // Decoding failure; reject the promise and clean up. |
| job_->DoSync<DecodeFail>(std::move(result)); |
| } else { |
| // Decode passed. |
| job_->DoSync<PrepareAndStartCompile>(std::move(result.val)); |
| } |
| } |
| }; |
| |
| //========================================================================== |
| // Step 1b: (sync) Fail decoding the module. |
| //========================================================================== |
| class AsyncCompileJob::DecodeFail : public CompileStep { |
| public: |
| explicit DecodeFail(ModuleResult result) : result_(std::move(result)) {} |
| |
| private: |
| ModuleResult result_; |
| void RunInForeground() override { |
| TRACE_COMPILE("(1b) Decoding failed.\n"); |
| HandleScope scope(job_->isolate_); |
| ErrorThrower thrower(job_->isolate_, "AsyncCompile"); |
| thrower.CompileFailed("Wasm decoding failed", result_); |
| // {job_} is deleted in AsyncCompileFailed, therefore the {return}. |
| return job_->AsyncCompileFailed(thrower); |
| } |
| }; |
| |
| //========================================================================== |
| // Step 2 (sync): Create heap-allocated data and start compile. |
| //========================================================================== |
| class AsyncCompileJob::PrepareAndStartCompile : public CompileStep { |
| public: |
| explicit PrepareAndStartCompile(std::unique_ptr<WasmModule> module) |
| : module_(std::move(module)) {} |
| |
| private: |
| std::unique_ptr<WasmModule> module_; |
| void RunInForeground() override { |
| TRACE_COMPILE("(2) Prepare and start compile...\n"); |
| Isolate* isolate = job_->isolate_; |
| HandleScope scope(isolate); |
| |
| Factory* factory = isolate->factory(); |
| Handle<Code> illegal_builtin = BUILTIN_CODE(isolate, Illegal); |
| job_->module_env_ = |
| CreateDefaultModuleEnv(isolate, module_.get(), illegal_builtin); |
| |
| // The {code_table} array contains import wrappers and functions (which |
| // are both included in {functions.size()}. |
| // The results of compilation will be written into it. |
| // Initialize {code_table_} with the illegal builtin. All call sites |
| // will be patched at instantiation. |
| int code_table_size = static_cast<int>(module_->functions.size()); |
| int export_wrapper_size = static_cast<int>(module_->num_exported_functions); |
| job_->code_table_ = factory->NewFixedArray(code_table_size, TENURED); |
| job_->export_wrappers_ = |
| factory->NewFixedArray(export_wrapper_size, TENURED); |
| |
| for (int i = 0, e = module_->num_imported_functions; i < e; ++i) { |
| job_->code_table_->set(i, *illegal_builtin); |
| } |
| // Transfer ownership of the {WasmModule} to the {ModuleCompiler}, but |
| // keep a pointer. |
| WasmModule* module = module_.get(); |
| Handle<Code> centry_stub = CEntryStub(isolate, 1).GetCode(); |
| |
| { |
| // Now reopen the handles in a deferred scope in order to use |
| // them in the concurrent steps. |
| DeferredHandleScope deferred(isolate); |
| |
| centry_stub = Handle<Code>(*centry_stub, isolate); |
| job_->code_table_ = Handle<FixedArray>(*job_->code_table_, isolate); |
| job_->export_wrappers_ = |
| Handle<FixedArray>(*job_->export_wrappers_, isolate); |
| compiler::ModuleEnv* env = job_->module_env_.get(); |
| ReopenHandles(isolate, env->function_code); |
| Handle<Code>* mut = |
| const_cast<Handle<Code>*>(&env->default_function_code); |
| *mut = Handle<Code>(**mut, isolate); |
| |
| job_->deferred_handles_.push_back(deferred.Detach()); |
| } |
| |
| job_->compiler_.reset( |
| new ModuleCompiler(isolate, std::move(module_), centry_stub)); |
| job_->compiler_->EnableThrottling(); |
| |
| DCHECK_LE(module->num_imported_functions, module->functions.size()); |
| size_t num_functions = |
| module->functions.size() - module->num_imported_functions; |
| if (num_functions == 0) { |
| // Degenerate case of an empty module. |
| job_->DoSync<FinishCompile>(); |
| return; |
| } |
| |
| // Start asynchronous compilation tasks. |
| size_t num_background_tasks = |
| Max(static_cast<size_t>(1), |
| Min(num_functions, |
| Min(static_cast<size_t>(FLAG_wasm_num_compilation_tasks), |
| V8::GetCurrentPlatform() |
| ->NumberOfAvailableBackgroundThreads()))); |
| job_->outstanding_units_ = job_->compiler_->InitializeCompilationUnits( |
| module->functions, job_->wire_bytes_, job_->module_env_.get()); |
| |
| job_->DoAsync<ExecuteAndFinishCompilationUnits>(num_background_tasks); |
| } |
| }; |
| |
| //========================================================================== |
| // Step 3 (async x K tasks): Execute compilation units. |
| //========================================================================== |
| class AsyncCompileJob::ExecuteAndFinishCompilationUnits : public CompileStep { |
| public: |
| explicit ExecuteAndFinishCompilationUnits(size_t num_compile_tasks) |
| : CompileStep(num_compile_tasks) {} |
| |
| void RunInBackground() override { |
| std::function<void()> StartFinishCompilationUnit = [this]() { |
| if (!failed_) job_->StartForegroundTask(); |
| }; |
| |
| TRACE_COMPILE("(3) Compiling...\n"); |
| while (job_->compiler_->CanAcceptWork()) { |
| if (failed_) break; |
| DisallowHandleAllocation no_handle; |
| DisallowHeapAllocation no_allocation; |
| if (!job_->compiler_->FetchAndExecuteCompilationUnit( |
| StartFinishCompilationUnit)) { |
| finished_ = true; |
| break; |
| } |
| } |
| stopped_tasks_.Increment(1); |
| } |
| |
| void RestartCompilationTasks() { |
| size_t num_restarts = stopped_tasks_.Value(); |
| stopped_tasks_.Decrement(num_restarts); |
| |
| for (size_t i = 0; i < num_restarts; ++i) { |
| job_->StartBackgroundTask(); |
| } |
| } |
| |
| void RunInForeground() override { |
| TRACE_COMPILE("(4a) Finishing compilation units...\n"); |
| if (failed_) { |
| // The job failed already, no need to do more work. |
| job_->compiler_->SetFinisherIsRunning(false); |
| return; |
| } |
| HandleScope scope(job_->isolate_); |
| ErrorThrower thrower(job_->isolate_, "AsyncCompile"); |
| |
| // We execute for 1 ms and then reschedule the task, same as the GC. |
| double deadline = MonotonicallyIncreasingTimeInMs() + 1.0; |
| |
| while (true) { |
| if (!finished_ && job_->compiler_->ShouldIncreaseWorkload()) { |
| RestartCompilationTasks(); |
| } |
| |
| int func_index = -1; |
| |
| MaybeHandle<Code> result = |
| job_->compiler_->FinishCompilationUnit(&thrower, &func_index); |
| |
| if (thrower.error()) { |
| // An error was detected, we stop compiling and wait for the |
| // background tasks to finish. |
| failed_ = true; |
| break; |
| } else if (result.is_null()) { |
| // The working queue was empty, we break the loop. If new work units |
| // are enqueued, the background task will start this |
| // FinishCompilationUnits task again. |
| break; |
| } else { |
| DCHECK(func_index >= 0); |
| job_->code_table_->set(func_index, *result.ToHandleChecked()); |
| --job_->outstanding_units_; |
| } |
| |
| if (deadline < MonotonicallyIncreasingTimeInMs()) { |
| // We reached the deadline. We reschedule this task and return |
| // immediately. Since we rescheduled this task already, we do not set |
| // the FinisherIsRunning flat to false. |
| job_->StartForegroundTask(); |
| return; |
| } |
| } |
| // This task finishes without being rescheduled. Therefore we set the |
| // FinisherIsRunning flag to false. |
| job_->compiler_->SetFinisherIsRunning(false); |
| if (thrower.error()) { |
| // Make sure all compilation tasks stopped running. |
| job_->background_task_manager_.CancelAndWait(); |
| return job_->AsyncCompileFailed(thrower); |
| } |
| if (job_->outstanding_units_ == 0) { |
| // Make sure all compilation tasks stopped running. |
| job_->background_task_manager_.CancelAndWait(); |
| job_->DoSync<FinishCompile>(); |
| } |
| } |
| |
| private: |
| std::atomic<bool> failed_{false}; |
| std::atomic<bool> finished_{false}; |
| base::AtomicNumber<size_t> stopped_tasks_{0}; |
| }; |
| |
| //========================================================================== |
| // Step 5 (sync): Finish heap-allocated data structures. |
| //========================================================================== |
| class AsyncCompileJob::FinishCompile : public CompileStep { |
| void RunInForeground() override { |
| TRACE_COMPILE("(5b) Finish compile...\n"); |
| HandleScope scope(job_->isolate_); |
| // At this point, compilation has completed. Update the code table. |
| for (int i = FLAG_skip_compiling_wasm_funcs, |
| e = job_->code_table_->length(); |
| i < e; ++i) { |
| Object* val = job_->code_table_->get(i); |
| if (val->IsCode()) RecordStats(Code::cast(val), job_->counters()); |
| } |
| |
| // Create heap objects for script and module bytes to be stored in the |
| // shared module data. Asm.js is not compiled asynchronously. |
| Handle<Script> script = CreateWasmScript(job_->isolate_, job_->wire_bytes_); |
| Handle<ByteArray> asm_js_offset_table; |
| // TODO(wasm): Improve efficiency of storing module wire bytes. |
| // 1. Only store relevant sections, not function bodies |
| // 2. Don't make a second copy of the bytes here; reuse the copy made |
| // for asynchronous compilation and store it as an external one |
| // byte string for serialization/deserialization. |
| Handle<String> module_bytes = |
| job_->isolate_->factory() |
| ->NewStringFromOneByte( |
| {job_->wire_bytes_.start(), job_->wire_bytes_.length()}, |
| TENURED) |
| .ToHandleChecked(); |
| DCHECK(module_bytes->IsSeqOneByteString()); |
| |
| // The {module_wrapper} will take ownership of the {WasmModule} object, |
| // and it will be destroyed when the GC reclaims the wrapper object. |
| Handle<WasmModuleWrapper> module_wrapper = WasmModuleWrapper::New( |
| job_->isolate_, job_->compiler_->ReleaseModule().release()); |
| |
| // Create the shared module data. |
| // TODO(clemensh): For the same module (same bytes / same hash), we should |
| // only have one WasmSharedModuleData. Otherwise, we might only set |
| // breakpoints on a (potentially empty) subset of the instances. |
| |
| Handle<WasmSharedModuleData> shared = |
| WasmSharedModuleData::New(job_->isolate_, module_wrapper, |
| Handle<SeqOneByteString>::cast(module_bytes), |
| script, asm_js_offset_table); |
| |
| // 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. |
| job_->compiled_module_ = |
| NewCompiledModule(job_->isolate_, shared, job_->code_table_, |
| job_->export_wrappers_, job_->module_env_.get()); |
| // Finish the wasm script now and make it public to the debugger. |
| script->set_wasm_compiled_module(*job_->compiled_module_); |
| job_->isolate_->debug()->OnAfterCompile(script); |
| |
| DeferredHandleScope deferred(job_->isolate_); |
| job_->compiled_module_ = handle(*job_->compiled_module_, job_->isolate_); |
| job_->deferred_handles_.push_back(deferred.Detach()); |
| // TODO(wasm): compiling wrappers should be made async as well. |
| job_->DoSync<CompileWrappers>(); |
| } |
| }; |
| |
| //========================================================================== |
| // Step 6 (sync): Compile JS->wasm wrappers. |
| //========================================================================== |
| class AsyncCompileJob::CompileWrappers : public CompileStep { |
| void RunInForeground() override { |
| TRACE_COMPILE("(6) Compile wrappers...\n"); |
| // Compile JS->wasm wrappers for exported functions. |
| HandleScope scope(job_->isolate_); |
| JSToWasmWrapperCache js_to_wasm_cache; |
| int wrapper_index = 0; |
| WasmModule* module = job_->compiled_module_->module(); |
| for (auto exp : module->export_table) { |
| if (exp.kind != kExternalFunction) continue; |
| Handle<Code> wasm_code(Code::cast(job_->code_table_->get(exp.index)), |
| job_->isolate_); |
| Handle<Code> wrapper_code = |
| js_to_wasm_cache.CloneOrCompileJSToWasmWrapper(job_->isolate_, module, |
| wasm_code, exp.index); |
| job_->export_wrappers_->set(wrapper_index, *wrapper_code); |
| RecordStats(*wrapper_code, job_->counters()); |
| ++wrapper_index; |
| } |
| |
| job_->DoSync<FinishModule>(); |
| } |
| }; |
| |
| //========================================================================== |
| // Step 7 (sync): Finish the module and resolve the promise. |
| //========================================================================== |
| class AsyncCompileJob::FinishModule : public CompileStep { |
| void RunInForeground() override { |
| TRACE_COMPILE("(7) Finish module...\n"); |
| HandleScope scope(job_->isolate_); |
| Handle<WasmModuleObject> result = |
| WasmModuleObject::New(job_->isolate_, job_->compiled_module_); |
| // {job_} is deleted in AsyncCompileSucceeded, therefore the {return}. |
| return job_->AsyncCompileSucceeded(result); |
| } |
| }; |
| |
| #undef TRACE |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |