| // 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 "src/api.h" | |
| #include "src/asmjs/asm-js.h" | |
| #include "src/base/optional.h" | |
| #include "src/base/template-utils.h" | |
| #include "src/base/utils/random-number-generator.h" | |
| #include "src/compiler/wasm-compiler.h" | |
| #include "src/counters.h" | |
| #include "src/identity-map.h" | |
| #include "src/property-descriptor.h" | |
| #include "src/tracing/trace-event.h" | |
| #include "src/trap-handler/trap-handler.h" | |
| #include "src/wasm/module-decoder.h" | |
| #include "src/wasm/streaming-decoder.h" | |
| #include "src/wasm/wasm-code-manager.h" | |
| #include "src/wasm/wasm-engine.h" | |
| #include "src/wasm/wasm-js.h" | |
| #include "src/wasm/wasm-limits.h" | |
| #include "src/wasm/wasm-memory.h" | |
| #include "src/wasm/wasm-objects-inl.h" | |
| #include "src/wasm/wasm-result.h" | |
| #define TRACE(...) \ | |
| do { \ | |
| if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \ | |
| } while (false) | |
| #define TRACE_COMPILE(...) \ | |
| do { \ | |
| if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \ | |
| } while (false) | |
| #define TRACE_STREAMING(...) \ | |
| do { \ | |
| if (FLAG_trace_wasm_streaming) PrintF(__VA_ARGS__); \ | |
| } while (false) | |
| #define TRACE_LAZY(...) \ | |
| do { \ | |
| if (FLAG_trace_wasm_lazy_compilation) PrintF(__VA_ARGS__); \ | |
| } while (false) | |
| namespace v8 { | |
| namespace internal { | |
| namespace wasm { | |
| enum class CompilationEvent : uint8_t { | |
| kFinishedBaselineCompilation, | |
| kFinishedTopTierCompilation, | |
| kFailedCompilation | |
| }; | |
| enum class CompileMode : uint8_t { kRegular, kTiering }; | |
| // The CompilationState keeps track of the compilation state of the | |
| // owning NativeModule, i.e. which functions are left to be compiled. | |
| // It contains a task manager to allow parallel and asynchronous background | |
| // compilation of functions. | |
| class CompilationState { | |
| public: | |
| CompilationState(internal::Isolate*, const ModuleEnv&); | |
| ~CompilationState(); | |
| // Set the number of compilations unit expected to be executed. Needs to be | |
| // set before {AddCompilationUnits} is run, which triggers background | |
| // compilation. | |
| void SetNumberOfFunctionsToCompile(size_t num_functions); | |
| // Set the callback function to be called on compilation events. Needs to be | |
| // set before {AddCompilationUnits} is run. | |
| void SetCallback( | |
| std::function<void(CompilationEvent, ErrorThrower*)> callback); | |
| // Inserts new functions to compile and kicks off compilation. | |
| void AddCompilationUnits( | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& baseline_units, | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& tiering_units); | |
| std::unique_ptr<WasmCompilationUnit> GetNextCompilationUnit(); | |
| std::unique_ptr<WasmCompilationUnit> GetNextExecutedUnit(); | |
| bool HasCompilationUnitToFinish(); | |
| void OnError(ErrorThrower* thrower); | |
| void OnFinishedUnit(); | |
| void ScheduleUnitForFinishing(std::unique_ptr<WasmCompilationUnit> unit, | |
| ExecutionTier mode); | |
| void OnBackgroundTaskStopped(const WasmFeatures& detected); | |
| void PublishDetectedFeatures(Isolate* isolate, const WasmFeatures& detected); | |
| void RestartBackgroundTasks(size_t max = std::numeric_limits<size_t>::max()); | |
| // Only one foreground thread (finisher) is allowed to run at a time. | |
| // {SetFinisherIsRunning} returns whether the flag changed its state. | |
| bool SetFinisherIsRunning(bool value); | |
| void ScheduleFinisherTask(); | |
| void Abort(); | |
| Isolate* isolate() const { return isolate_; } | |
| bool failed() const { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| return failed_; | |
| } | |
| bool baseline_compilation_finished() const { | |
| return baseline_compilation_finished_; | |
| } | |
| WasmEngine* wasm_engine() const { return wasm_engine_; } | |
| CompileMode compile_mode() const { return compile_mode_; } | |
| ModuleEnv* module_env() { return &module_env_; } | |
| WasmFeatures* detected_features() { return &detected_features_; } | |
| private: | |
| void NotifyOnEvent(CompilationEvent event, ErrorThrower* thrower); | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& finish_units() { | |
| return baseline_compilation_finished_ ? tiering_finish_units_ | |
| : baseline_finish_units_; | |
| } | |
| // TODO(7423): Get rid of the Isolate field to make sure the CompilationState | |
| // can be shared across multiple Isolates. | |
| Isolate* const isolate_; | |
| WasmEngine* const wasm_engine_; | |
| // TODO(clemensh): Remove ModuleEnv, generate it when needed. | |
| ModuleEnv module_env_; | |
| const CompileMode compile_mode_; | |
| bool baseline_compilation_finished_ = false; | |
| // This mutex protects all information of this CompilationState which is being | |
| // accessed concurrently. | |
| mutable base::Mutex mutex_; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // Protected by {mutex_}: | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_compilation_units_; | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_compilation_units_; | |
| bool finisher_is_running_ = false; | |
| bool failed_ = false; | |
| size_t num_background_tasks_ = 0; | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_finish_units_; | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_finish_units_; | |
| // Features detected to be used in this module. Features can be detected | |
| // as a module is being compiled. | |
| WasmFeatures detected_features_ = kNoWasmFeatures; | |
| // End of fields protected by {mutex_}. | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // Callback function to be called on compilation events. | |
| std::function<void(CompilationEvent, ErrorThrower*)> callback_; | |
| CancelableTaskManager background_task_manager_; | |
| CancelableTaskManager foreground_task_manager_; | |
| std::shared_ptr<v8::TaskRunner> foreground_task_runner_; | |
| const size_t max_background_tasks_ = 0; | |
| size_t outstanding_units_ = 0; | |
| size_t num_tiering_units_ = 0; | |
| }; | |
| namespace { | |
| void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) { | |
| if (detected.threads) { | |
| isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmThreadOpcodes); | |
| } | |
| } | |
| class JSToWasmWrapperCache { | |
| public: | |
| Handle<Code> GetOrCompileJSToWasmWrapper(Isolate* isolate, | |
| const NativeModule* native_module, | |
| uint32_t func_index, | |
| UseTrapHandler use_trap_handler) { | |
| const WasmModule* module = native_module->module(); | |
| const WasmFunction* func = &module->functions[func_index]; | |
| bool is_import = func_index < module->num_imported_functions; | |
| std::pair<bool, FunctionSig> key(is_import, *func->sig); | |
| Handle<Code>& cached = cache_[key]; | |
| if (!cached.is_null()) return cached; | |
| Handle<Code> code = | |
| compiler::CompileJSToWasmWrapper(isolate, native_module, func->sig, | |
| is_import, use_trap_handler) | |
| .ToHandleChecked(); | |
| cached = code; | |
| return code; | |
| } | |
| private: | |
| // We generate different code for calling imports than calling wasm functions | |
| // in this module. Both are cached separately. | |
| using CacheKey = std::pair<bool, FunctionSig>; | |
| std::unordered_map<CacheKey, Handle<Code>, base::hash<CacheKey>> cache_; | |
| }; | |
| // A helper class to simplify instantiating a module from a module object. | |
| // It closes over the {Isolate}, the {ErrorThrower}, etc. | |
| class InstanceBuilder { | |
| public: | |
| InstanceBuilder(Isolate* isolate, ErrorThrower* thrower, | |
| Handle<WasmModuleObject> module_object, | |
| MaybeHandle<JSReceiver> ffi, | |
| MaybeHandle<JSArrayBuffer> memory); | |
| // Build an instance, in all of its glory. | |
| MaybeHandle<WasmInstanceObject> Build(); | |
| // Run the start function, if any. | |
| bool ExecuteStartFunction(); | |
| private: | |
| // Represents the initialized state of a table. | |
| struct TableInstance { | |
| Handle<WasmTableObject> table_object; // WebAssembly.Table instance | |
| Handle<FixedArray> js_wrappers; // JSFunctions exported | |
| size_t table_size; | |
| }; | |
| // A pre-evaluated value to use in import binding. | |
| struct SanitizedImport { | |
| Handle<String> module_name; | |
| Handle<String> import_name; | |
| Handle<Object> value; | |
| }; | |
| Isolate* isolate_; | |
| const WasmFeatures enabled_; | |
| const WasmModule* const module_; | |
| ErrorThrower* thrower_; | |
| Handle<WasmModuleObject> module_object_; | |
| MaybeHandle<JSReceiver> ffi_; | |
| MaybeHandle<JSArrayBuffer> memory_; | |
| Handle<JSArrayBuffer> globals_; | |
| std::vector<TableInstance> table_instances_; | |
| std::vector<Handle<JSFunction>> js_wrappers_; | |
| Handle<WasmExportedFunction> start_function_; | |
| JSToWasmWrapperCache js_to_wasm_cache_; | |
| std::vector<SanitizedImport> sanitized_imports_; | |
| UseTrapHandler use_trap_handler() const { | |
| return module_object_->native_module()->use_trap_handler() ? kUseTrapHandler | |
| : kNoTrapHandler; | |
| } | |
| // Helper routines to print out errors with imports. | |
| #define ERROR_THROWER_WITH_MESSAGE(TYPE) \ | |
| void Report##TYPE(const char* error, uint32_t index, \ | |
| Handle<String> module_name, Handle<String> import_name) { \ | |
| thrower_->TYPE("Import #%d module=\"%s\" function=\"%s\" error: %s", \ | |
| index, module_name->ToCString().get(), \ | |
| import_name->ToCString().get(), error); \ | |
| } \ | |
| \ | |
| MaybeHandle<Object> Report##TYPE(const char* error, uint32_t index, \ | |
| Handle<String> module_name) { \ | |
| thrower_->TYPE("Import #%d module=\"%s\" error: %s", index, \ | |
| module_name->ToCString().get(), error); \ | |
| return MaybeHandle<Object>(); \ | |
| } | |
| ERROR_THROWER_WITH_MESSAGE(LinkError) | |
| ERROR_THROWER_WITH_MESSAGE(TypeError) | |
| #undef ERROR_THROWER_WITH_MESSAGE | |
| // Look up an import value in the {ffi_} object. | |
| MaybeHandle<Object> LookupImport(uint32_t index, Handle<String> module_name, | |
| Handle<String> import_name); | |
| // 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> LookupImportAsm(uint32_t index, | |
| Handle<String> import_name); | |
| uint32_t EvalUint32InitExpr(const WasmInitExpr& expr); | |
| // Load data segments into the memory. | |
| void LoadDataSegments(Handle<WasmInstanceObject> instance); | |
| void WriteGlobalValue(const WasmGlobal& global, double value); | |
| void WriteGlobalValue(const WasmGlobal& global, | |
| Handle<WasmGlobalObject> value); | |
| void SanitizeImports(); | |
| // Find the imported memory buffer if there is one. This is used to see if we | |
| // need to recompile with bounds checks before creating the instance. | |
| MaybeHandle<JSArrayBuffer> FindImportedMemoryBuffer() const; | |
| // Process the imports, including functions, tables, globals, and memory, in | |
| // order, loading them from the {ffi_} object. Returns the number of imported | |
| // functions. | |
| int ProcessImports(Handle<WasmInstanceObject> instance); | |
| template <typename T> | |
| T* GetRawGlobalPtr(const WasmGlobal& global); | |
| // Process initialization of globals. | |
| void InitGlobals(); | |
| // Allocate memory for a module instance as a new JSArrayBuffer. | |
| Handle<JSArrayBuffer> AllocateMemory(uint32_t num_pages); | |
| bool NeedsWrappers() const; | |
| // Process the exports, creating wrappers for functions, tables, memories, | |
| // and globals. | |
| void ProcessExports(Handle<WasmInstanceObject> instance); | |
| void InitializeTables(Handle<WasmInstanceObject> instance); | |
| void LoadTableSegments(Handle<WasmInstanceObject> instance); | |
| }; | |
| } // namespace | |
| MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject( | |
| Isolate* isolate, ErrorThrower* thrower, | |
| Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, | |
| MaybeHandle<JSArrayBuffer> memory) { | |
| InstanceBuilder builder(isolate, thrower, module_object, imports, memory); | |
| auto instance = builder.Build(); | |
| if (!instance.is_null() && builder.ExecuteStartFunction()) { | |
| return instance; | |
| } | |
| return {}; | |
| } | |
| WasmCode* LazyCompileFunction(Isolate* isolate, NativeModule* native_module, | |
| int func_index) { | |
| base::ElapsedTimer compilation_timer; | |
| DCHECK(!native_module->has_code(static_cast<uint32_t>(func_index))); | |
| compilation_timer.Start(); | |
| ModuleEnv* module_env = native_module->compilation_state()->module_env(); | |
| // TODO(wasm): Refactor this to only get the name if it is really needed for | |
| // tracing / debugging. | |
| WasmName func_name; | |
| { | |
| ModuleWireBytes wire_bytes(native_module->wire_bytes()); | |
| WireBytesRef name_ref = | |
| module_env->module->LookupFunctionName(wire_bytes, func_index); | |
| func_name = wire_bytes.GetName(name_ref); | |
| } | |
| TRACE_LAZY("Compiling function '%.*s' (#%d).\n", func_name.length(), | |
| func_name.start(), func_index); | |
| const uint8_t* module_start = native_module->wire_bytes().start(); | |
| const WasmFunction* func = &module_env->module->functions[func_index]; | |
| FunctionBody body{func->sig, func->code.offset(), | |
| module_start + func->code.offset(), | |
| module_start + func->code.end_offset()}; | |
| ErrorThrower thrower(isolate, "WasmLazyCompile"); | |
| WasmCompilationUnit unit(isolate->wasm_engine(), module_env, native_module, | |
| body, func_name, func_index, isolate->counters()); | |
| unit.ExecuteCompilation( | |
| native_module->compilation_state()->detected_features()); | |
| WasmCode* wasm_code = unit.FinishCompilation(&thrower); | |
| if (WasmCode::ShouldBeLogged(isolate)) wasm_code->LogCode(isolate); | |
| // If there is a pending error, something really went wrong. The module was | |
| // verified before starting execution with lazy compilation. | |
| // This might be OOM, but then we cannot continue execution anyway. | |
| // TODO(clemensh): According to the spec, we can actually skip validation at | |
| // module creation time, and return a function that always traps here. | |
| CHECK(!thrower.error()); | |
| int64_t func_size = | |
| static_cast<int64_t>(func->code.end_offset() - func->code.offset()); | |
| int64_t compilation_time = compilation_timer.Elapsed().InMicroseconds(); | |
| auto counters = isolate->counters(); | |
| counters->wasm_lazily_compiled_functions()->Increment(); | |
| counters->wasm_lazy_compilation_throughput()->AddSample( | |
| compilation_time != 0 ? static_cast<int>(func_size / compilation_time) | |
| : 0); | |
| return wasm_code; | |
| } | |
| Address CompileLazy(Isolate* isolate, NativeModule* native_module, | |
| uint32_t func_index) { | |
| HistogramTimerScope lazy_time_scope( | |
| isolate->counters()->wasm_lazy_compilation_time()); | |
| DCHECK(!native_module->lazy_compile_frozen()); | |
| NativeModuleModificationScope native_module_modification_scope(native_module); | |
| WasmCode* result = LazyCompileFunction(isolate, native_module, func_index); | |
| DCHECK_NOT_NULL(result); | |
| DCHECK_EQ(func_index, result->index()); | |
| return result->instruction_start(); | |
| } | |
| namespace { | |
| bool compile_lazy(const WasmModule* module) { | |
| return FLAG_wasm_lazy_compilation || | |
| (FLAG_asm_wasm_lazy_compilation && module->origin == kAsmJsOrigin); | |
| } | |
| byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) { | |
| return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset; | |
| } | |
| void RecordStats(const Code* code, Counters* counters) { | |
| counters->wasm_generated_code_size()->Increment(code->body_size()); | |
| counters->wasm_reloc_size()->Increment(code->relocation_info()->length()); | |
| } | |
| bool in_bounds(uint32_t offset, size_t size, size_t upper) { | |
| return offset + size <= upper && offset + size >= offset; | |
| } | |
| using WasmInstanceMap = | |
| IdentityMap<Handle<WasmInstanceObject>, FreeStoreAllocationPolicy>; | |
| double MonotonicallyIncreasingTimeInMs() { | |
| return V8::GetCurrentPlatform()->MonotonicallyIncreasingTime() * | |
| base::Time::kMillisecondsPerSecond; | |
| } | |
| ModuleEnv CreateDefaultModuleEnv(const WasmModule* module, | |
| bool allow_trap_handler = true) { | |
| UseTrapHandler use_trap_handler = | |
| trap_handler::IsTrapHandlerEnabled() && allow_trap_handler | |
| ? kUseTrapHandler | |
| : kNoTrapHandler; | |
| return ModuleEnv(module, use_trap_handler, kRuntimeExceptionSupport); | |
| } | |
| // The CompilationUnitBuilder builds compilation units and stores them in an | |
| // internal buffer. The buffer is moved into the working queue of the | |
| // CompilationState when {Commit} is called. | |
| class CompilationUnitBuilder { | |
| public: | |
| explicit CompilationUnitBuilder(NativeModule* native_module) | |
| : native_module_(native_module), | |
| compilation_state_(native_module->compilation_state()) {} | |
| void AddUnit(const WasmFunction* function, uint32_t buffer_offset, | |
| Vector<const uint8_t> bytes, WasmName name) { | |
| switch (compilation_state_->compile_mode()) { | |
| case CompileMode::kTiering: | |
| tiering_units_.emplace_back(CreateUnit( | |
| function, buffer_offset, bytes, name, ExecutionTier::kOptimized)); | |
| baseline_units_.emplace_back(CreateUnit( | |
| function, buffer_offset, bytes, name, ExecutionTier::kBaseline)); | |
| return; | |
| case CompileMode::kRegular: | |
| baseline_units_.emplace_back( | |
| CreateUnit(function, buffer_offset, bytes, name, | |
| WasmCompilationUnit::GetDefaultExecutionTier())); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| bool Commit() { | |
| if (baseline_units_.empty() && tiering_units_.empty()) return false; | |
| compilation_state_->AddCompilationUnits(baseline_units_, tiering_units_); | |
| Clear(); | |
| return true; | |
| } | |
| void Clear() { | |
| baseline_units_.clear(); | |
| tiering_units_.clear(); | |
| } | |
| private: | |
| std::unique_ptr<WasmCompilationUnit> CreateUnit(const WasmFunction* function, | |
| uint32_t buffer_offset, | |
| Vector<const uint8_t> bytes, | |
| WasmName name, | |
| ExecutionTier mode) { | |
| return base::make_unique<WasmCompilationUnit>( | |
| compilation_state_->wasm_engine(), compilation_state_->module_env(), | |
| native_module_, | |
| FunctionBody{function->sig, buffer_offset, bytes.begin(), bytes.end()}, | |
| name, function->func_index, | |
| compilation_state_->isolate()->async_counters().get(), mode); | |
| } | |
| NativeModule* native_module_; | |
| CompilationState* compilation_state_; | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> baseline_units_; | |
| std::vector<std::unique_ptr<WasmCompilationUnit>> tiering_units_; | |
| }; | |
| // Run by each compilation task and by the main thread (i.e. in both | |
| // foreground and background threads). 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 FetchAndExecuteCompilationUnit(CompilationState* compilation_state, | |
| WasmFeatures* detected) { | |
| DisallowHeapAccess no_heap_access; | |
| std::unique_ptr<WasmCompilationUnit> unit = | |
| compilation_state->GetNextCompilationUnit(); | |
| if (unit == nullptr) return false; | |
| // TODO(kimanh): We need to find out in which mode the unit | |
| // should be compiled in before compiling it, as it might fallback | |
| // to Turbofan if it cannot be compiled using Liftoff. This can be removed | |
| // later as soon as Liftoff can compile any function. Then, we can directly | |
| // access {unit->mode()} within {ScheduleUnitForFinishing()}. | |
| ExecutionTier mode = unit->mode(); | |
| unit->ExecuteCompilation(detected); | |
| compilation_state->ScheduleUnitForFinishing(std::move(unit), mode); | |
| return true; | |
| } | |
| void InitializeCompilationUnits(NativeModule* native_module) { | |
| ModuleWireBytes wire_bytes(native_module->wire_bytes()); | |
| const WasmModule* module = native_module->module(); | |
| CompilationUnitBuilder builder(native_module); | |
| uint32_t start = module->num_imported_functions; | |
| uint32_t end = start + module->num_declared_functions; | |
| for (uint32_t i = start; i < end; ++i) { | |
| const WasmFunction* func = &module->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, module); | |
| DCHECK_NOT_NULL(native_module); | |
| builder.AddUnit(func, buffer_offset, bytes, name); | |
| } | |
| builder.Commit(); | |
| } | |
| void FinishCompilationUnits(CompilationState* compilation_state, | |
| ErrorThrower* thrower) { | |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "FinishCompilationUnits"); | |
| while (true) { | |
| if (compilation_state->failed()) break; | |
| std::unique_ptr<WasmCompilationUnit> unit = | |
| compilation_state->GetNextExecutedUnit(); | |
| if (unit == nullptr) break; | |
| WasmCode* result = unit->FinishCompilation(thrower); | |
| if (thrower->error()) { | |
| compilation_state->Abort(); | |
| break; | |
| } | |
| // Update the compilation state. | |
| compilation_state->OnFinishedUnit(); | |
| DCHECK_IMPLIES(result == nullptr, thrower->error()); | |
| if (result == nullptr) break; | |
| } | |
| if (!compilation_state->failed()) { | |
| compilation_state->RestartBackgroundTasks(); | |
| } | |
| } | |
| void CompileInParallel(Isolate* isolate, NativeModule* native_module, | |
| Handle<WasmModuleObject> module_object, | |
| ErrorThrower* thrower) { | |
| // 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} within the | |
| // {compilation_state}. By adding units to the {compilation_state}, new | |
| // {BackgroundCompileTasks} instances are spawned which run on | |
| // the background threads. | |
| // 2.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 {baseline_finish_units_}. | |
| // 2.b) If {baseline_finish_units_} contains a compilation unit, the main | |
| // thread dequeues it and finishes the compilation. | |
| // 3) After the parallel phase of all compilation units has started, the | |
| // main thread continues to finish all compilation units as long as | |
| // baseline-compilation units are left to be processed. | |
| // 4) If tier-up is enabled, the main thread restarts background tasks | |
| // that take care of compiling and finishing the top-tier compilation | |
| // units. | |
| // Turn on the {CanonicalHandleScope} so that the background threads can | |
| // use the node cache. | |
| CanonicalHandleScope canonical(isolate); | |
| CompilationState* compilation_state = native_module->compilation_state(); | |
| // Make sure that no foreground task is spawned for finishing | |
| // the compilation units. This foreground thread will be | |
| // responsible for finishing compilation. | |
| compilation_state->SetFinisherIsRunning(true); | |
| uint32_t num_wasm_functions = | |
| native_module->num_functions() - native_module->num_imported_functions(); | |
| compilation_state->SetNumberOfFunctionsToCompile(num_wasm_functions); | |
| // 1) The main thread allocates a compilation unit for each wasm function | |
| // and stores them in the vector {compilation_units} within the | |
| // {compilation_state}. By adding units to the {compilation_state}, new | |
| // {BackgroundCompileTask} instances are spawned which run on | |
| // background threads. | |
| InitializeCompilationUnits(native_module); | |
| // 2.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 {baseline_finish_units_}. | |
| // The foreground task bypasses waiting on memory threshold, because | |
| // its results will immediately be converted to code (below). | |
| WasmFeatures detected_features; | |
| while ( | |
| FetchAndExecuteCompilationUnit(compilation_state, &detected_features) && | |
| !compilation_state->baseline_compilation_finished()) { | |
| // 2.b) If {baseline_finish_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(compilation_state, thrower); | |
| if (compilation_state->failed()) break; | |
| } | |
| while (!compilation_state->failed()) { | |
| // 3) After the parallel phase of all compilation units has started, the | |
| // main thread continues to finish compilation units as long as | |
| // baseline compilation units are left to be processed. If compilation | |
| // already failed, all background tasks have already been canceled | |
| // in {FinishCompilationUnits}, and there are no units to finish. | |
| FinishCompilationUnits(compilation_state, thrower); | |
| if (compilation_state->baseline_compilation_finished()) break; | |
| } | |
| // Publish features from the foreground and background tasks. | |
| compilation_state->PublishDetectedFeatures(isolate, detected_features); | |
| // 4) If tiering-compilation is enabled, we need to set the finisher | |
| // to false, such that the background threads will spawn a foreground | |
| // thread to finish the top-tier compilation units. | |
| if (!compilation_state->failed() && | |
| compilation_state->compile_mode() == CompileMode::kTiering) { | |
| compilation_state->SetFinisherIsRunning(false); | |
| compilation_state->RestartBackgroundTasks(); | |
| } | |
| } | |
| void CompileSequentially(Isolate* isolate, NativeModule* native_module, | |
| ModuleEnv* module_env, ErrorThrower* thrower) { | |
| DCHECK(!thrower->error()); | |
| ModuleWireBytes wire_bytes(native_module->wire_bytes()); | |
| const WasmModule* module = module_env->module; | |
| WasmFeatures detected = kNoWasmFeatures; | |
| for (uint32_t i = 0; i < module->functions.size(); ++i) { | |
| const WasmFunction& func = module->functions[i]; | |
| if (func.imported) continue; // Imports are compiled at instantiation time. | |
| // Compile the function. | |
| WasmCode* code = WasmCompilationUnit::CompileWasmFunction( | |
| isolate, native_module, &detected, thrower, module_env, &func); | |
| if (code == nullptr) { | |
| TruncatedUserString<> name(wire_bytes.GetName(&func, module)); | |
| thrower->CompileError("Compilation of #%d:%.*s failed.", i, name.length(), | |
| name.start()); | |
| break; | |
| } | |
| } | |
| UpdateFeatureUseCounts(isolate, detected); | |
| } | |
| void ValidateSequentially(Isolate* isolate, NativeModule* native_module, | |
| ErrorThrower* thrower) { | |
| DCHECK(!thrower->error()); | |
| ModuleWireBytes wire_bytes(native_module->wire_bytes()); | |
| const WasmModule* module = native_module->module(); | |
| uint32_t start = module->num_imported_functions; | |
| uint32_t end = start + module->num_declared_functions; | |
| for (uint32_t i = start; i < end; ++i) { | |
| const WasmFunction& func = module->functions[i]; | |
| const byte* base = wire_bytes.start(); | |
| FunctionBody body{func.sig, func.code.offset(), base + func.code.offset(), | |
| base + func.code.end_offset()}; | |
| DecodeResult result; | |
| { | |
| auto time_counter = | |
| SELECT_WASM_COUNTER(isolate->async_counters(), module->origin, | |
| wasm_decode, function_time); | |
| TimedHistogramScope wasm_decode_function_time_scope(time_counter); | |
| WasmFeatures detected; | |
| result = VerifyWasmCode(isolate->allocator(), | |
| native_module->enabled_features(), module, | |
| &detected, body); | |
| } | |
| if (result.failed()) { | |
| TruncatedUserString<> name(wire_bytes.GetName(&func, module)); | |
| thrower->CompileError("Compiling function #%d:%.*s failed: %s @+%u", i, | |
| name.length(), name.start(), | |
| result.error_msg().c_str(), result.error_offset()); | |
| break; | |
| } | |
| } | |
| } | |
| void CompileNativeModule(Isolate* isolate, ErrorThrower* thrower, | |
| Handle<WasmModuleObject> module_object, | |
| const WasmModule* wasm_module, ModuleEnv* env) { | |
| NativeModule* const native_module = module_object->native_module(); | |
| ModuleWireBytes wire_bytes(native_module->wire_bytes()); | |
| if (compile_lazy(wasm_module)) { | |
| if (wasm_module->origin == kWasmOrigin) { | |
| // 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(isolate, native_module, thrower); | |
| if (thrower->error()) return; | |
| } | |
| native_module->SetLazyBuiltin(BUILTIN_CODE(isolate, WasmCompileLazy)); | |
| } else { | |
| size_t funcs_to_compile = | |
| wasm_module->functions.size() - wasm_module->num_imported_functions; | |
| bool compile_parallel = | |
| !FLAG_trace_wasm_decoder && FLAG_wasm_num_compilation_tasks > 0 && | |
| funcs_to_compile > 1 && | |
| V8::GetCurrentPlatform()->NumberOfWorkerThreads() > 0; | |
| if (compile_parallel) { | |
| CompileInParallel(isolate, native_module, module_object, thrower); | |
| } else { | |
| CompileSequentially(isolate, native_module, env, thrower); | |
| } | |
| if (thrower->error()) return; | |
| } | |
| } | |
| // The runnable task that finishes compilation in foreground (e.g. updating | |
| // the NativeModule, the code table, etc.). | |
| class FinishCompileTask : public CancelableTask { | |
| public: | |
| explicit FinishCompileTask(CompilationState* compilation_state, | |
| CancelableTaskManager* task_manager) | |
| : CancelableTask(task_manager), compilation_state_(compilation_state) {} | |
| void RunInternal() override { | |
| Isolate* isolate = compilation_state_->isolate(); | |
| HandleScope scope(isolate); | |
| SaveContext saved_context(isolate); | |
| isolate->set_context(nullptr); | |
| TRACE_COMPILE("(4a) Finishing compilation units...\n"); | |
| if (compilation_state_->failed()) { | |
| compilation_state_->SetFinisherIsRunning(false); | |
| return; | |
| } | |
| // We execute for 1 ms and then reschedule the task, same as the GC. | |
| double deadline = MonotonicallyIncreasingTimeInMs() + 1.0; | |
| while (true) { | |
| compilation_state_->RestartBackgroundTasks(); | |
| std::unique_ptr<WasmCompilationUnit> unit = | |
| compilation_state_->GetNextExecutedUnit(); | |
| if (unit == nullptr) { | |
| // It might happen that a background task just scheduled a unit to be | |
| // finished, but did not start a finisher task since the flag was still | |
| // set. Check for this case, and continue if there is more work. | |
| compilation_state_->SetFinisherIsRunning(false); | |
| if (compilation_state_->HasCompilationUnitToFinish() && | |
| compilation_state_->SetFinisherIsRunning(true)) { | |
| continue; | |
| } | |
| break; | |
| } | |
| ErrorThrower thrower(compilation_state_->isolate(), "AsyncCompile"); | |
| WasmCode* result = unit->FinishCompilation(&thrower); | |
| if (thrower.error()) { | |
| DCHECK_NULL(result); | |
| compilation_state_->OnError(&thrower); | |
| compilation_state_->SetFinisherIsRunning(false); | |
| thrower.Reset(); | |
| break; | |
| } | |
| if (compilation_state_->baseline_compilation_finished()) { | |
| // If Liftoff compilation finishes it will directly start executing. | |
| // As soon as we have Turbofan-compiled code available, it will | |
| // directly be used by Liftoff-compiled code via the jump table. | |
| DCHECK_EQ(CompileMode::kTiering, compilation_state_->compile_mode()); | |
| DCHECK(!result->is_liftoff()); | |
| if (WasmCode::ShouldBeLogged(isolate)) result->LogCode(isolate); | |
| } | |
| // Update the compilation state, and possibly notify | |
| // threads waiting for events. | |
| compilation_state_->OnFinishedUnit(); | |
| 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 flag to false. | |
| compilation_state_->ScheduleFinisherTask(); | |
| return; | |
| } | |
| } | |
| } | |
| private: | |
| CompilationState* compilation_state_; | |
| }; | |
| // The runnable task that performs compilations in the background. | |
| class BackgroundCompileTask : public CancelableTask { | |
| public: | |
| explicit BackgroundCompileTask(CompilationState* compilation_state, | |
| CancelableTaskManager* task_manager) | |
| : CancelableTask(task_manager), compilation_state_(compilation_state) {} | |
| void RunInternal() override { | |
| TRACE_COMPILE("(3b) Compiling...\n"); | |
| // The number of currently running background tasks is reduced in | |
| // {OnBackgroundTaskStopped}. | |
| while (!compilation_state_->failed()) { | |
| if (!FetchAndExecuteCompilationUnit(compilation_state_, | |
| &detected_features_)) { | |
| break; | |
| } | |
| } | |
| compilation_state_->OnBackgroundTaskStopped(detected_features_); | |
| } | |
| private: | |
| CompilationState* compilation_state_; | |
| WasmFeatures detected_features_ = kNoWasmFeatures; | |
| }; | |
| } // namespace | |
| MaybeHandle<WasmModuleObject> CompileToModuleObject( | |
| Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower, | |
| std::shared_ptr<const WasmModule> module, const ModuleWireBytes& wire_bytes, | |
| Handle<Script> asm_js_script, | |
| Vector<const byte> asm_js_offset_table_bytes) { | |
| const WasmModule* wasm_module = module.get(); | |
| TimedHistogramScope wasm_compile_module_time_scope(SELECT_WASM_COUNTER( | |
| isolate->counters(), wasm_module->origin, wasm_compile, module_time)); | |
| // Embedder usage count for declared shared memories. | |
| if (wasm_module->has_shared_memory) { | |
| isolate->CountUsage(v8::Isolate::UseCounterFeature::kWasmSharedMemory); | |
| } | |
| // TODO(6792): No longer needed once WebAssembly code is off heap. Use | |
| // base::Optional to be able to close the scope before notifying the debugger. | |
| base::Optional<CodeSpaceMemoryModificationScope> modification_scope( | |
| base::in_place_t(), isolate->heap()); | |
| // Create heap objects for script, module bytes and asm.js offset table to | |
| // be stored in the module object. | |
| 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. | |
| OwnedVector<uint8_t> wire_bytes_copy = | |
| OwnedVector<uint8_t>::Of(wire_bytes.module_bytes()); | |
| // Create the module object. | |
| // TODO(clemensh): For the same module (same bytes / same hash), we should | |
| // only have one WasmModuleObject. Otherwise, we might only set | |
| // breakpoints on a (potentially empty) subset of the instances. | |
| ModuleEnv env = CreateDefaultModuleEnv(wasm_module); | |
| // 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<WasmModuleObject> module_object = WasmModuleObject::New( | |
| isolate, enabled, std::move(module), env, std::move(wire_bytes_copy), | |
| script, asm_js_offset_table); | |
| CompileNativeModule(isolate, thrower, module_object, wasm_module, &env); | |
| if (thrower->error()) return {}; | |
| // Compile JS->wasm wrappers for exported functions. | |
| CompileJsToWasmWrappers(isolate, module_object); | |
| // If we created a wasm script, finish it now and make it public to the | |
| // debugger. | |
| if (asm_js_script.is_null()) { | |
| // Close the CodeSpaceMemoryModificationScope before calling into the | |
| // debugger. | |
| modification_scope.reset(); | |
| isolate->debug()->OnAfterCompile(script); | |
| } | |
| // Log the code within the generated module for profiling. | |
| module_object->native_module()->LogWasmCodes(isolate); | |
| return module_object; | |
| } | |
| InstanceBuilder::InstanceBuilder(Isolate* isolate, ErrorThrower* thrower, | |
| Handle<WasmModuleObject> module_object, | |
| MaybeHandle<JSReceiver> ffi, | |
| MaybeHandle<JSArrayBuffer> memory) | |
| : isolate_(isolate), | |
| enabled_(module_object->native_module()->enabled_features()), | |
| module_(module_object->module()), | |
| thrower_(thrower), | |
| module_object_(module_object), | |
| ffi_(ffi), | |
| memory_(memory) { | |
| sanitized_imports_.reserve(module_->import_table.size()); | |
| } | |
| // Build an instance, in all of its glory. | |
| MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() { | |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "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 {}; | |
| // TODO(6792): No longer needed once WebAssembly code is off heap. | |
| CodeSpaceMemoryModificationScope modification_scope(isolate_->heap()); | |
| // From here on, we expect the build pipeline to run without exiting to JS. | |
| DisallowJavascriptExecution no_js(isolate_); | |
| // Record build time into correct bucket, then build instance. | |
| TimedHistogramScope wasm_instantiate_module_time_scope(SELECT_WASM_COUNTER( | |
| isolate_->counters(), module_->origin, wasm_instantiate, module_time)); | |
| //-------------------------------------------------------------------------- | |
| // Allocate the memory array buffer. | |
| //-------------------------------------------------------------------------- | |
| // We allocate the memory buffer before cloning or reusing the compiled module | |
| // so we will know whether we need to recompile with bounds checks. | |
| uint32_t initial_pages = module_->initial_pages; | |
| auto initial_pages_counter = SELECT_WASM_COUNTER( | |
| isolate_->counters(), module_->origin, wasm, min_mem_pages_count); | |
| initial_pages_counter->AddSample(initial_pages); | |
| // Asm.js has memory_ already set at this point, so we don't want to | |
| // overwrite it. | |
| if (memory_.is_null()) { | |
| memory_ = FindImportedMemoryBuffer(); | |
| } | |
| if (!memory_.is_null()) { | |
| // Set externally passed ArrayBuffer non neuterable. | |
| Handle<JSArrayBuffer> memory = memory_.ToHandleChecked(); | |
| memory->set_is_neuterable(false); | |
| DCHECK_IMPLIES(use_trap_handler(), module_->origin == kAsmJsOrigin || | |
| memory->is_wasm_memory() || | |
| memory->backing_store() == nullptr); | |
| } else if (initial_pages > 0 || use_trap_handler()) { | |
| // We need to unconditionally create a guard region if using trap handlers, | |
| // even when the size is zero to prevent null-dereference issues | |
| // (e.g. https://crbug.com/769637). | |
| // Allocate memory if the initial size is more than 0 pages. | |
| memory_ = AllocateMemory(initial_pages); | |
| if (memory_.is_null()) return {}; // failed to allocate memory | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Recompile module if using trap handlers but could not get guarded memory | |
| //-------------------------------------------------------------------------- | |
| if (module_->origin == kWasmOrigin && use_trap_handler()) { | |
| // Make sure the memory has suitable guard regions. | |
| WasmMemoryTracker* const memory_tracker = | |
| isolate_->wasm_engine()->memory_tracker(); | |
| if (!memory_tracker->HasFullGuardRegions( | |
| memory_.ToHandleChecked()->backing_store())) { | |
| if (!FLAG_wasm_trap_handler_fallback) { | |
| return {}; | |
| } | |
| TRACE("Recompiling module without bounds checks\n"); | |
| constexpr bool allow_trap_handler = false; | |
| ModuleEnv env = CreateDefaultModuleEnv(module_, allow_trap_handler); | |
| // Disable trap handlers on this native module. | |
| NativeModule* native_module = module_object_->native_module(); | |
| native_module->DisableTrapHandler(); | |
| // Recompile all functions in this native module. | |
| ErrorThrower thrower(isolate_, "recompile"); | |
| CompileNativeModule(isolate_, &thrower, module_object_, module_, &env); | |
| if (thrower.error()) { | |
| return {}; | |
| } | |
| DCHECK(!native_module->use_trap_handler()); | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Create the WebAssembly.Instance object. | |
| //-------------------------------------------------------------------------- | |
| NativeModule* native_module = module_object_->native_module(); | |
| TRACE("New module instantiation for %p\n", native_module); | |
| Handle<WasmInstanceObject> instance = | |
| WasmInstanceObject::New(isolate_, module_object_); | |
| NativeModuleModificationScope native_modification_scope(native_module); | |
| //-------------------------------------------------------------------------- | |
| // Set up the globals for the new instance. | |
| //-------------------------------------------------------------------------- | |
| MaybeHandle<JSArrayBuffer> old_globals; | |
| uint32_t globals_buffer_size = module_->globals_buffer_size; | |
| if (globals_buffer_size > 0) { | |
| void* backing_store = | |
| isolate_->array_buffer_allocator()->Allocate(globals_buffer_size); | |
| if (backing_store == nullptr) { | |
| thrower_->RangeError("Out of memory: wasm globals"); | |
| return {}; | |
| } | |
| globals_ = | |
| isolate_->factory()->NewJSArrayBuffer(SharedFlag::kNotShared, TENURED); | |
| constexpr bool is_external = false; | |
| constexpr bool is_wasm_memory = false; | |
| JSArrayBuffer::Setup(globals_, isolate_, is_external, backing_store, | |
| globals_buffer_size, SharedFlag::kNotShared, | |
| is_wasm_memory); | |
| if (globals_.is_null()) { | |
| thrower_->RangeError("Out of memory: wasm globals"); | |
| return {}; | |
| } | |
| instance->set_globals_start( | |
| reinterpret_cast<byte*>(globals_->backing_store())); | |
| instance->set_globals_buffer(*globals_); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Set up the array of references to imported globals' array buffers. | |
| //-------------------------------------------------------------------------- | |
| if (module_->num_imported_mutable_globals > 0) { | |
| // TODO(binji): This allocates one slot for each mutable global, which is | |
| // more than required if multiple globals are imported from the same | |
| // module. | |
| Handle<FixedArray> buffers_array = isolate_->factory()->NewFixedArray( | |
| module_->num_imported_mutable_globals, TENURED); | |
| instance->set_imported_mutable_globals_buffers(*buffers_array); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Reserve the metadata for indirect function tables. | |
| //-------------------------------------------------------------------------- | |
| int table_count = static_cast<int>(module_->tables.size()); | |
| table_instances_.resize(table_count); | |
| //-------------------------------------------------------------------------- | |
| // Process the imports for the module. | |
| //-------------------------------------------------------------------------- | |
| int num_imported_functions = ProcessImports(instance); | |
| if (num_imported_functions < 0) return {}; | |
| //-------------------------------------------------------------------------- | |
| // Process the initialization for the module's globals. | |
| //-------------------------------------------------------------------------- | |
| InitGlobals(); | |
| //-------------------------------------------------------------------------- | |
| // Initialize the indirect tables. | |
| //-------------------------------------------------------------------------- | |
| if (table_count > 0) { | |
| InitializeTables(instance); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Create the WebAssembly.Memory object. | |
| //-------------------------------------------------------------------------- | |
| if (module_->has_memory) { | |
| if (!instance->has_memory_object()) { | |
| // No memory object exists. Create one. | |
| Handle<WasmMemoryObject> memory_object = WasmMemoryObject::New( | |
| isolate_, memory_, | |
| module_->maximum_pages != 0 ? module_->maximum_pages : -1); | |
| instance->set_memory_object(*memory_object); | |
| } | |
| // Add the instance object to the list of instances for this memory. | |
| Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate_); | |
| WasmMemoryObject::AddInstance(isolate_, memory_object, instance); | |
| if (!memory_.is_null()) { | |
| // Double-check the {memory} array buffer matches the instance. | |
| Handle<JSArrayBuffer> memory = memory_.ToHandleChecked(); | |
| CHECK_EQ(instance->memory_size(), memory->byte_length()->Number()); | |
| CHECK_EQ(instance->memory_start(), memory->backing_store()); | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Check that indirect function table segments are within bounds. | |
| //-------------------------------------------------------------------------- | |
| for (const WasmTableInit& table_init : module_->table_inits) { | |
| DCHECK(table_init.table_index < table_instances_.size()); | |
| uint32_t base = EvalUint32InitExpr(table_init.offset); | |
| size_t table_size = table_instances_[table_init.table_index].table_size; | |
| if (!in_bounds(base, table_init.entries.size(), table_size)) { | |
| thrower_->LinkError("table initializer is out of bounds"); | |
| return {}; | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Check that memory segments are within bounds. | |
| //-------------------------------------------------------------------------- | |
| for (const WasmDataSegment& seg : module_->data_segments) { | |
| uint32_t base = EvalUint32InitExpr(seg.dest_addr); | |
| if (!in_bounds(base, seg.source.length(), instance->memory_size())) { | |
| thrower_->LinkError("data segment is out of bounds"); | |
| return {}; | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Set up the exports object for the new instance. | |
| //-------------------------------------------------------------------------- | |
| ProcessExports(instance); | |
| if (thrower_->error()) return {}; | |
| //-------------------------------------------------------------------------- | |
| // Initialize the indirect function tables. | |
| //-------------------------------------------------------------------------- | |
| if (table_count > 0) { | |
| LoadTableSegments(instance); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Initialize the memory by loading data segments. | |
| //-------------------------------------------------------------------------- | |
| if (module_->data_segments.size() > 0) { | |
| LoadDataSegments(instance); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Install a finalizer on the new instance object. | |
| //-------------------------------------------------------------------------- | |
| WasmInstanceObject::InstallFinalizer(isolate_, instance); | |
| //-------------------------------------------------------------------------- | |
| // Debugging support. | |
| //-------------------------------------------------------------------------- | |
| // Set all breakpoints that were set on the shared module. | |
| WasmModuleObject::SetBreakpointsOnNewInstance(module_object_, instance); | |
| if (FLAG_wasm_interpret_all && module_->origin == kWasmOrigin) { | |
| 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()))); | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Create a wrapper for the start function. | |
| //-------------------------------------------------------------------------- | |
| if (module_->start_function_index >= 0) { | |
| int start_index = module_->start_function_index; | |
| FunctionSig* sig = module_->functions[start_index].sig; | |
| Handle<Code> wrapper_code = js_to_wasm_cache_.GetOrCompileJSToWasmWrapper( | |
| isolate_, native_module, start_index, use_trap_handler()); | |
| // TODO(clemensh): Don't generate an exported function for the start | |
| // function. Use CWasmEntry instead. | |
| start_function_ = WasmExportedFunction::New( | |
| isolate_, instance, MaybeHandle<String>(), start_index, | |
| static_cast<int>(sig->parameter_count()), wrapper_code); | |
| } | |
| DCHECK(!isolate_->has_pending_exception()); | |
| TRACE("Successfully built instance for module %p\n", | |
| module_object_->native_module()); | |
| return instance; | |
| } | |
| bool InstanceBuilder::ExecuteStartFunction() { | |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), | |
| "InstanceBuilder::ExecuteStartFunction"); | |
| if (start_function_.is_null()) return true; // No start function. | |
| HandleScope scope(isolate_); | |
| // Call the JS function. | |
| Handle<Object> undefined = isolate_->factory()->undefined_value(); | |
| MaybeHandle<Object> retval = | |
| Execution::Call(isolate_, start_function_, undefined, 0, nullptr); | |
| if (retval.is_null()) { | |
| DCHECK(isolate_->has_pending_exception()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // 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( | |
| isolate_, 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(isolate_, 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 ReadLittleEndianValue<uint32_t>( | |
| reinterpret_cast<Address>(raw_buffer_ptr(globals_, offset))); | |
| } | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| // Load data segments into the memory. | |
| void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) { | |
| Vector<const uint8_t> wire_bytes = | |
| module_object_->native_module()->wire_bytes(); | |
| 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, instance->memory_size())); | |
| byte* dest = instance->memory_start() + dest_offset; | |
| const byte* src = wire_bytes.start() + segment.source.offset(); | |
| memcpy(dest, src, source_size); | |
| } | |
| } | |
| void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, double num) { | |
| TRACE("init [globals_start=%p + %u] = %lf, type = %s\n", | |
| reinterpret_cast<void*>(raw_buffer_ptr(globals_, 0)), global.offset, | |
| num, ValueTypes::TypeName(global.type)); | |
| switch (global.type) { | |
| case kWasmI32: | |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), | |
| static_cast<int32_t>(num)); | |
| break; | |
| case kWasmI64: | |
| // TODO(titzer): initialization of imported i64 globals. | |
| UNREACHABLE(); | |
| break; | |
| case kWasmF32: | |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), | |
| static_cast<float>(num)); | |
| break; | |
| case kWasmF64: | |
| WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), | |
| static_cast<double>(num)); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| void InstanceBuilder::WriteGlobalValue(const WasmGlobal& global, | |
| Handle<WasmGlobalObject> value) { | |
| TRACE("init [globals_start=%p + %u] = ", | |
| reinterpret_cast<void*>(raw_buffer_ptr(globals_, 0)), global.offset); | |
| switch (global.type) { | |
| case kWasmI32: { | |
| int32_t num = value->GetI32(); | |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), num); | |
| TRACE("%d", num); | |
| break; | |
| } | |
| case kWasmI64: { | |
| int64_t num = value->GetI64(); | |
| WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), num); | |
| TRACE("%" PRId64, num); | |
| break; | |
| } | |
| case kWasmF32: { | |
| float num = value->GetF32(); | |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), num); | |
| TRACE("%f", num); | |
| break; | |
| } | |
| case kWasmF64: { | |
| double num = value->GetF64(); | |
| WriteLittleEndianValue<double>(GetRawGlobalPtr<double>(global), num); | |
| TRACE("%lf", num); | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| } | |
| TRACE(", type = %s (from WebAssembly.Global)\n", | |
| ValueTypes::TypeName(global.type)); | |
| } | |
| void InstanceBuilder::SanitizeImports() { | |
| Vector<const uint8_t> wire_bytes = | |
| module_object_->native_module()->wire_bytes(); | |
| for (size_t index = 0; index < module_->import_table.size(); ++index) { | |
| const WasmImport& import = module_->import_table[index]; | |
| Handle<String> module_name; | |
| MaybeHandle<String> maybe_module_name = | |
| WasmModuleObject::ExtractUtf8StringFromModuleBytes(isolate_, wire_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 = | |
| WasmModuleObject::ExtractUtf8StringFromModuleBytes(isolate_, wire_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_->origin == kAsmJsOrigin | |
| ? 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}); | |
| } | |
| } | |
| MaybeHandle<JSArrayBuffer> InstanceBuilder::FindImportedMemoryBuffer() const { | |
| DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size()); | |
| for (size_t index = 0; index < module_->import_table.size(); index++) { | |
| const WasmImport& import = module_->import_table[index]; | |
| if (import.kind == kExternalMemory) { | |
| const auto& value = sanitized_imports_[index].value; | |
| if (!value->IsWasmMemoryObject()) { | |
| return {}; | |
| } | |
| auto memory = Handle<WasmMemoryObject>::cast(value); | |
| Handle<JSArrayBuffer> buffer(memory->array_buffer(), isolate_); | |
| return buffer; | |
| } | |
| } | |
| return {}; | |
| } | |
| // 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<WasmInstanceObject> instance) { | |
| int num_imported_functions = 0; | |
| int num_imported_tables = 0; | |
| int num_imported_mutable_globals = 0; | |
| DCHECK_EQ(module_->import_table.size(), sanitized_imports_.size()); | |
| int num_imports = static_cast<int>(module_->import_table.size()); | |
| NativeModule* native_module = instance->module_object()->native_module(); | |
| for (int index = 0; index < num_imports; ++index) { | |
| const 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; | |
| } | |
| uint32_t func_index = import.index; | |
| DCHECK_EQ(num_imported_functions, func_index); | |
| FunctionSig* expected_sig = module_->functions[func_index].sig; | |
| if (WasmExportedFunction::IsWasmExportedFunction(*value)) { | |
| // The imported function is a WASM function from another instance. | |
| Handle<WasmExportedFunction> imported_function( | |
| WasmExportedFunction::cast(*value), isolate_); | |
| Handle<WasmInstanceObject> imported_instance( | |
| imported_function->instance(), isolate_); | |
| FunctionSig* imported_sig = | |
| imported_instance->module() | |
| ->functions[imported_function->function_index()] | |
| .sig; | |
| if (*imported_sig != *expected_sig) { | |
| ReportLinkError( | |
| "imported function does not match the expected type", index, | |
| module_name, import_name); | |
| return -1; | |
| } | |
| // The import reference is the instance object itself. | |
| Address imported_target = imported_function->GetWasmCallTarget(); | |
| ImportedFunctionEntry entry(instance, func_index); | |
| entry.set_wasm_to_wasm(*imported_instance, imported_target); | |
| } else { | |
| // The imported function is a callable. | |
| Handle<JSReceiver> js_receiver(JSReceiver::cast(*value), isolate_); | |
| Handle<Code> wrapper_code = | |
| compiler::CompileWasmToJSWrapper( | |
| isolate_, js_receiver, expected_sig, func_index, | |
| module_->origin, use_trap_handler()) | |
| .ToHandleChecked(); | |
| RecordStats(*wrapper_code, isolate_->counters()); | |
| WasmCode* wasm_code = native_module->AddCodeCopy( | |
| wrapper_code, WasmCode::kWasmToJsWrapper, func_index); | |
| ImportedFunctionEntry entry(instance, func_index); | |
| entry.set_wasm_to_js(*js_receiver, wasm_code); | |
| } | |
| num_imported_functions++; | |
| break; | |
| } | |
| case kExternalTable: { | |
| if (!value->IsWasmTableObject()) { | |
| ReportLinkError("table import requires a WebAssembly.Table", index, | |
| module_name, import_name); | |
| return -1; | |
| } | |
| uint32_t table_num = import.index; | |
| DCHECK_EQ(table_num, num_imported_tables); | |
| const WasmTable& table = module_->tables[table_num]; | |
| TableInstance& table_instance = table_instances_[table_num]; | |
| table_instance.table_object = Handle<WasmTableObject>::cast(value); | |
| instance->set_table_object(*table_instance.table_object); | |
| table_instance.js_wrappers = Handle<FixedArray>( | |
| table_instance.table_object->functions(), isolate_); | |
| int imported_table_size = table_instance.js_wrappers->length(); | |
| if (imported_table_size < static_cast<int>(table.initial_size)) { | |
| thrower_->LinkError( | |
| "table import %d is smaller than initial %d, got %u", index, | |
| table.initial_size, imported_table_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. | |
| if (!instance->has_indirect_function_table()) { | |
| WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( | |
| instance, imported_table_size); | |
| table_instances_[table_num].table_size = imported_table_size; | |
| } | |
| // Initialize the dispatch table with the (foreign) JS functions | |
| // that are already in the table. | |
| for (int i = 0; i < imported_table_size; ++i) { | |
| Handle<Object> val(table_instance.js_wrappers->get(i), isolate_); | |
| // TODO(mtrofin): this is the same logic as WasmTableObject::Set: | |
| // insert in the local table a wrapper from the other module, and add | |
| // a reference to the owning instance of the other module. | |
| if (!val->IsJSFunction()) continue; | |
| if (!WasmExportedFunction::IsWasmExportedFunction(*val)) { | |
| thrower_->LinkError("table import %d[%d] is not a wasm function", | |
| index, i); | |
| return -1; | |
| } | |
| // Look up the signature's canonical id. If there is no canonical | |
| // id, then the signature does not appear at all in this module, | |
| // so putting {-1} in the table will cause checks to always fail. | |
| auto target = Handle<WasmExportedFunction>::cast(val); | |
| Handle<WasmInstanceObject> imported_instance = | |
| handle(target->instance(), isolate_); | |
| Address exported_call_target = target->GetWasmCallTarget(); | |
| FunctionSig* sig = imported_instance->module() | |
| ->functions[target->function_index()] | |
| .sig; | |
| IndirectFunctionTableEntry(instance, i) | |
| .set(module_->signature_map.Find(*sig), *imported_instance, | |
| exported_call_target); | |
| } | |
| 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_ should have already been assigned in Build(). | |
| DCHECK_EQ(*memory_.ToHandleChecked(), *buffer); | |
| uint32_t imported_cur_pages = static_cast<uint32_t>( | |
| buffer->byte_length()->Number() / kWasmPageSize); | |
| 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; | |
| } | |
| } | |
| if (module_->has_shared_memory != buffer->is_shared()) { | |
| thrower_->LinkError( | |
| "mismatch in shared state of memory, declared = %d, imported = " | |
| "%d", | |
| module_->has_shared_memory, buffer->is_shared()); | |
| return -1; | |
| } | |
| break; | |
| } | |
| case kExternalGlobal: { | |
| // Immutable global imports are converted to numbers and written into | |
| // the {globals_} array buffer. | |
| // | |
| // Mutable global imports instead have their backing array buffers | |
| // referenced by this instance, and store the address of the imported | |
| // global in the {imported_mutable_globals_} array. | |
| const WasmGlobal& global = module_->globals[import.index]; | |
| // The mutable-global proposal allows importing i64 values, but only if | |
| // they are passed as a WebAssembly.Global object. | |
| if (global.type == kWasmI64 && | |
| !(enabled_.mut_global && value->IsWasmGlobalObject())) { | |
| ReportLinkError("global import cannot have type i64", index, | |
| module_name, import_name); | |
| return -1; | |
| } | |
| if (module_->origin == kAsmJsOrigin) { | |
| // 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 (global.type == kWasmI32) { | |
| value = Object::ToInt32(isolate_, value).ToHandleChecked(); | |
| } else { | |
| value = Object::ToNumber(isolate_, value).ToHandleChecked(); | |
| } | |
| } | |
| } | |
| if (enabled_.mut_global) { | |
| if (value->IsWasmGlobalObject()) { | |
| auto global_object = Handle<WasmGlobalObject>::cast(value); | |
| if (global_object->type() != global.type) { | |
| ReportLinkError( | |
| "imported global does not match the expected type", index, | |
| module_name, import_name); | |
| return -1; | |
| } | |
| if (global_object->is_mutable() != global.mutability) { | |
| ReportLinkError( | |
| "imported global does not match the expected mutability", | |
| index, module_name, import_name); | |
| return -1; | |
| } | |
| if (global.mutability) { | |
| Handle<JSArrayBuffer> buffer(global_object->array_buffer(), | |
| isolate_); | |
| int index = num_imported_mutable_globals++; | |
| instance->imported_mutable_globals_buffers()->set(index, *buffer); | |
| // It is safe in this case to store the raw pointer to the buffer | |
| // since the backing store of the JSArrayBuffer will not be | |
| // relocated. | |
| instance->imported_mutable_globals()[index] = | |
| reinterpret_cast<Address>( | |
| raw_buffer_ptr(buffer, global_object->offset())); | |
| } else { | |
| WriteGlobalValue(global, global_object); | |
| } | |
| } else if (value->IsNumber()) { | |
| if (global.mutability) { | |
| ReportLinkError( | |
| "imported mutable global must be a WebAssembly.Global object", | |
| index, module_name, import_name); | |
| return -1; | |
| } | |
| WriteGlobalValue(global, value->Number()); | |
| } else { | |
| ReportLinkError( | |
| "global import must be a number or WebAssembly.Global object", | |
| index, module_name, import_name); | |
| return -1; | |
| } | |
| } else { | |
| if (value->IsNumber()) { | |
| WriteGlobalValue(global, value->Number()); | |
| } else { | |
| ReportLinkError("global import must be a number", index, | |
| module_name, import_name); | |
| return -1; | |
| } | |
| } | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| DCHECK_EQ(module_->num_imported_mutable_globals, | |
| num_imported_mutable_globals); | |
| return num_imported_functions; | |
| } | |
| template <typename T> | |
| T* InstanceBuilder::GetRawGlobalPtr(const WasmGlobal& global) { | |
| return reinterpret_cast<T*>(raw_buffer_ptr(globals_, global.offset)); | |
| } | |
| // Process initialization of globals. | |
| void InstanceBuilder::InitGlobals() { | |
| for (auto global : module_->globals) { | |
| if (global.mutability && global.imported) { | |
| continue; | |
| } | |
| switch (global.init.kind) { | |
| case WasmInitExpr::kI32Const: | |
| WriteLittleEndianValue<int32_t>(GetRawGlobalPtr<int32_t>(global), | |
| global.init.val.i32_const); | |
| break; | |
| case WasmInitExpr::kI64Const: | |
| WriteLittleEndianValue<int64_t>(GetRawGlobalPtr<int64_t>(global), | |
| global.init.val.i64_const); | |
| break; | |
| case WasmInitExpr::kF32Const: | |
| WriteLittleEndianValue<float>(GetRawGlobalPtr<float>(global), | |
| global.init.val.f32_const); | |
| break; | |
| case WasmInitExpr::kF64Const: | |
| WriteLittleEndianValue<double>(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 is_shared_memory = module_->has_shared_memory && enabled_.threads; | |
| i::SharedFlag shared_flag = | |
| is_shared_memory ? i::SharedFlag::kShared : i::SharedFlag::kNotShared; | |
| Handle<JSArrayBuffer> mem_buffer; | |
| if (!NewArrayBuffer(isolate_, num_pages * kWasmPageSize, shared_flag) | |
| .ToHandle(&mem_buffer)) { | |
| 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_->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<FixedArray> export_wrappers(module_object_->export_wrappers(), | |
| isolate_); | |
| if (NeedsWrappers()) { | |
| // Fill the table to cache the exported JSFunction wrappers. | |
| js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(), | |
| Handle<JSFunction>::null()); | |
| // If an imported WebAssembly function gets exported, the exported function | |
| // has to be identical to to imported function. Therefore we put all | |
| // imported WebAssembly functions into the js_wrappers_ list. | |
| for (int index = 0, end = static_cast<int>(module_->import_table.size()); | |
| index < end; ++index) { | |
| const WasmImport& import = module_->import_table[index]; | |
| if (import.kind == kExternalFunction) { | |
| Handle<Object> value = sanitized_imports_[index].value; | |
| if (WasmExportedFunction::IsWasmExportedFunction(*value)) { | |
| js_wrappers_[import.index] = Handle<JSFunction>::cast(value); | |
| } | |
| } | |
| } | |
| } | |
| Handle<JSObject> exports_object; | |
| bool is_asm_js = false; | |
| switch (module_->origin) { | |
| case kWasmOrigin: { | |
| // Create the "exports" object. | |
| exports_object = isolate_->factory()->NewJSObjectWithNullProto(); | |
| break; | |
| } | |
| case kAsmJsOrigin: { | |
| Handle<JSFunction> object_function = Handle<JSFunction>( | |
| isolate_->native_context()->object_function(), isolate_); | |
| exports_object = isolate_->factory()->NewJSObject(object_function); | |
| is_asm_js = true; | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| } | |
| instance->set_exports_object(*exports_object); | |
| Handle<String> single_function_name = | |
| isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName); | |
| PropertyDescriptor desc; | |
| desc.set_writable(is_asm_js); | |
| desc.set_enumerable(true); | |
| desc.set_configurable(is_asm_js); | |
| // Process each export in the export table. | |
| int export_index = 0; // Index into {export_wrappers}. | |
| for (const WasmExport& exp : module_->export_table) { | |
| Handle<String> name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( | |
| isolate_, module_object_, exp.name) | |
| .ToHandleChecked(); | |
| Handle<JSObject> export_to; | |
| if (is_asm_js && exp.kind == kExternalFunction && | |
| String::Equals(isolate_, 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. | |
| const 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 = | |
| export_wrappers->GetValueChecked<Code>(isolate_, export_index); | |
| MaybeHandle<String> func_name; | |
| if (is_asm_js) { | |
| // For modules arising from asm.js, honor the names section. | |
| WireBytesRef func_name_ref = module_->LookupFunctionName( | |
| module_object_->native_module()->wire_bytes(), | |
| function.func_index); | |
| func_name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( | |
| isolate_, module_object_, func_name_ref) | |
| .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); | |
| export_index++; | |
| break; | |
| } | |
| case kExternalTable: { | |
| // Export a table as a WebAssembly.Table object. | |
| TableInstance& table_instance = table_instances_[exp.index]; | |
| const WasmTable& table = module_->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. A WasmMemoryObject | |
| // should already be available if the module has memory, since we always | |
| // create or import it when building an WasmInstanceObject. | |
| DCHECK(instance->has_memory_object()); | |
| desc.set_value( | |
| Handle<WasmMemoryObject>(instance->memory_object(), isolate_)); | |
| break; | |
| } | |
| case kExternalGlobal: { | |
| const WasmGlobal& global = module_->globals[exp.index]; | |
| if (enabled_.mut_global) { | |
| Handle<JSArrayBuffer> buffer; | |
| uint32_t offset; | |
| if (global.mutability && global.imported) { | |
| Handle<FixedArray> buffers_array( | |
| instance->imported_mutable_globals_buffers(), isolate_); | |
| buffer = buffers_array->GetValueChecked<JSArrayBuffer>( | |
| isolate_, global.index); | |
| Address global_addr = | |
| instance->imported_mutable_globals()[global.index]; | |
| uint32_t buffer_size = 0; | |
| CHECK(buffer->byte_length()->ToUint32(&buffer_size)); | |
| Address backing_store = | |
| reinterpret_cast<Address>(buffer->backing_store()); | |
| CHECK(global_addr >= backing_store && | |
| global_addr < backing_store + buffer_size); | |
| offset = static_cast<uint32_t>(global_addr - backing_store); | |
| } else { | |
| buffer = handle(instance->globals_buffer(), isolate_); | |
| offset = global.offset; | |
| } | |
| // Since the global's array buffer is always provided, allocation | |
| // should never fail. | |
| Handle<WasmGlobalObject> global_obj = | |
| WasmGlobalObject::New(isolate_, buffer, global.type, offset, | |
| global.mutability) | |
| .ToHandleChecked(); | |
| desc.set_value(global_obj); | |
| } else { | |
| // Export the value of the global variable as a number. | |
| double num = 0; | |
| switch (global.type) { | |
| case kWasmI32: | |
| num = ReadLittleEndianValue<int32_t>( | |
| GetRawGlobalPtr<int32_t>(global)); | |
| break; | |
| case kWasmF32: | |
| num = | |
| ReadLittleEndianValue<float>(GetRawGlobalPtr<float>(global)); | |
| break; | |
| case kWasmF64: | |
| num = ReadLittleEndianValue<double>( | |
| 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, kThrowOnError); | |
| 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, export_wrappers->length()); | |
| if (module_->origin == kWasmOrigin) { | |
| v8::Maybe<bool> success = | |
| JSReceiver::SetIntegrityLevel(exports_object, FROZEN, kDontThrow); | |
| DCHECK(success.FromMaybe(false)); | |
| USE(success); | |
| } | |
| } | |
| void InstanceBuilder::InitializeTables(Handle<WasmInstanceObject> instance) { | |
| size_t table_count = module_->tables.size(); | |
| for (size_t index = 0; index < table_count; ++index) { | |
| const WasmTable& table = module_->tables[index]; | |
| TableInstance& table_instance = table_instances_[index]; | |
| if (!instance->has_indirect_function_table() && | |
| table.type == kWasmAnyFunc) { | |
| WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( | |
| instance, table.initial_size); | |
| table_instance.table_size = table.initial_size; | |
| } | |
| } | |
| } | |
| void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) { | |
| NativeModule* native_module = module_object_->native_module(); | |
| 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()); | |
| uint32_t index = table_init.table_index; | |
| TableInstance& table_instance = table_instances_[index]; | |
| DCHECK(in_bounds(base, num_entries, table_instance.table_size)); | |
| for (uint32_t i = 0; i < num_entries; ++i) { | |
| uint32_t func_index = table_init.entries[i]; | |
| const WasmFunction* function = &module_->functions[func_index]; | |
| int table_index = static_cast<int>(i + base); | |
| // Update the local dispatch table first. | |
| uint32_t sig_id = module_->signature_ids[function->sig_index]; | |
| Handle<WasmInstanceObject> target_instance = instance; | |
| Address call_target; | |
| const bool is_import = func_index < module_->num_imported_functions; | |
| if (is_import) { | |
| // For imported calls, take target instance and address from the | |
| // import table. | |
| ImportedFunctionEntry entry(instance, func_index); | |
| target_instance = handle(entry.instance(), isolate_); | |
| call_target = entry.target(); | |
| } else { | |
| call_target = native_module->GetCallTargetForFunction(func_index); | |
| } | |
| IndirectFunctionTableEntry(instance, table_index) | |
| .set(sig_id, *target_instance, call_target); | |
| if (!table_instance.table_object.is_null()) { | |
| // Update the table object's other dispatch tables. | |
| 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_.GetOrCompileJSToWasmWrapper( | |
| isolate_, native_module, func_index, use_trap_handler()); | |
| MaybeHandle<String> func_name; | |
| if (module_->origin == kAsmJsOrigin) { | |
| // For modules arising from asm.js, honor the names section. | |
| WireBytesRef func_name_ref = module_->LookupFunctionName( | |
| native_module->wire_bytes(), func_index); | |
| func_name = WasmModuleObject::ExtractUtf8StringFromModuleBytes( | |
| isolate_, module_object_, func_name_ref) | |
| .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() updates all other dispatch tables, since | |
| // we have not yet added the dispatch table we are currently building. | |
| WasmTableObject::UpdateDispatchTables( | |
| isolate_, table_instance.table_object, table_index, function->sig, | |
| target_instance, call_target); | |
| } | |
| } | |
| } | |
| int table_count = static_cast<int>(module_->tables.size()); | |
| for (int index = 0; index < table_count; ++index) { | |
| TableInstance& table_instance = table_instances_[index]; | |
| // Add the new dispatch table at the end to avoid redundant lookups. | |
| if (!table_instance.table_object.is_null()) { | |
| WasmTableObject::AddDispatchTable(isolate_, table_instance.table_object, | |
| instance, index); | |
| } | |
| } | |
| } | |
| AsyncCompileJob::AsyncCompileJob( | |
| Isolate* isolate, const WasmFeatures& enabled, | |
| std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context, | |
| std::shared_ptr<CompilationResultResolver> resolver) | |
| : isolate_(isolate), | |
| enabled_features_(enabled), | |
| async_counters_(isolate->async_counters()), | |
| bytes_copy_(std::move(bytes_copy)), | |
| wire_bytes_(bytes_copy_.get(), bytes_copy_.get() + length), | |
| resolver_(std::move(resolver)) { | |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); | |
| v8::Platform* platform = V8::GetCurrentPlatform(); | |
| foreground_task_runner_ = platform->GetForegroundTaskRunner(v8_isolate); | |
| // The handle for the context must be deferred. | |
| DeferredHandleScope deferred(isolate); | |
| native_context_ = Handle<Context>(context->native_context(), isolate); | |
| DCHECK(native_context_->IsNativeContext()); | |
| deferred_handles_.push_back(deferred.Detach()); | |
| } | |
| void AsyncCompileJob::Start() { | |
| DoAsync<DecodeModule>(); // -- | |
| } | |
| void AsyncCompileJob::Abort() { | |
| // Removing this job will trigger the destructor, which will cancel all | |
| // compilation. | |
| isolate_->wasm_engine()->RemoveCompileJob(this); | |
| } | |
| class AsyncStreamingProcessor final : public StreamingProcessor { | |
| public: | |
| explicit AsyncStreamingProcessor(AsyncCompileJob* job); | |
| bool ProcessModuleHeader(Vector<const uint8_t> bytes, | |
| uint32_t offset) override; | |
| bool ProcessSection(SectionCode section_code, Vector<const uint8_t> bytes, | |
| uint32_t offset) override; | |
| bool ProcessCodeSectionHeader(size_t functions_count, | |
| uint32_t offset) override; | |
| bool ProcessFunctionBody(Vector<const uint8_t> bytes, | |
| uint32_t offset) override; | |
| void OnFinishedChunk() override; | |
| void OnFinishedStream(OwnedVector<uint8_t> bytes) override; | |
| void OnError(DecodeResult result) override; | |
| void OnAbort() override; | |
| private: | |
| // Finishes the AsyncCompileJob with an error. | |
| void FinishAsyncCompileJobWithError(ResultBase result); | |
| void CommitCompilationUnits(); | |
| ModuleDecoder decoder_; | |
| AsyncCompileJob* job_; | |
| std::unique_ptr<CompilationUnitBuilder> compilation_unit_builder_; | |
| uint32_t next_function_ = 0; | |
| }; | |
| std::shared_ptr<StreamingDecoder> AsyncCompileJob::CreateStreamingDecoder() { | |
| DCHECK_NULL(stream_); | |
| stream_.reset( | |
| new StreamingDecoder(base::make_unique<AsyncStreamingProcessor>(this))); | |
| return stream_; | |
| } | |
| AsyncCompileJob::~AsyncCompileJob() { | |
| background_task_manager_.CancelAndWait(); | |
| if (native_module_) native_module_->compilation_state()->Abort(); | |
| // Tell the streaming decoder that the AsyncCompileJob is not available | |
| // anymore. | |
| // TODO(ahaas): Is this notification really necessary? Check | |
| // https://crbug.com/888170. | |
| if (stream_) stream_->NotifyCompilationEnded(); | |
| CancelPendingForegroundTask(); | |
| for (auto d : deferred_handles_) delete d; | |
| } | |
| // This function assumes that it is executed in a HandleScope, and that a | |
| // context is set on the isolate. | |
| void AsyncCompileJob::FinishCompile() { | |
| DCHECK_NOT_NULL(isolate_->context()); | |
| // Finish the wasm script now and make it public to the debugger. | |
| Handle<Script> script(module_object_->script(), isolate_); | |
| isolate_->debug()->OnAfterCompile(script); | |
| // Log the code within the generated module for profiling. | |
| native_module_->LogWasmCodes(isolate_); | |
| // We can only update the feature counts once the entire compile is done. | |
| auto compilation_state = native_module_->compilation_state(); | |
| compilation_state->PublishDetectedFeatures( | |
| isolate_, *compilation_state->detected_features()); | |
| // TODO(wasm): compiling wrappers should be made async as well. | |
| DoSync<CompileWrappers>(); | |
| } | |
| void AsyncCompileJob::AsyncCompileFailed(Handle<Object> error_reason) { | |
| // {job} keeps the {this} pointer alive. | |
| std::shared_ptr<AsyncCompileJob> job = | |
| isolate_->wasm_engine()->RemoveCompileJob(this); | |
| resolver_->OnCompilationFailed(error_reason); | |
| } | |
| void AsyncCompileJob::AsyncCompileSucceeded(Handle<WasmModuleObject> result) { | |
| resolver_->OnCompilationSucceeded(result); | |
| } | |
| // 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: | |
| virtual ~CompileStep() {} | |
| void Run(bool on_foreground) { | |
| if (on_foreground) { | |
| HandleScope scope(job_->isolate_); | |
| SaveContext saved_context(job_->isolate_); | |
| job_->isolate_->set_context(*job_->native_context_); | |
| RunInForeground(); | |
| } else { | |
| RunInBackground(); | |
| } | |
| } | |
| virtual void RunInForeground() { UNREACHABLE(); } | |
| virtual void RunInBackground() { UNREACHABLE(); } | |
| AsyncCompileJob* job_ = nullptr; | |
| }; | |
| 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) {} | |
| ~CompileTask() { | |
| if (job_ != nullptr && on_foreground_) ResetPendingForegroundTask(); | |
| } | |
| void RunInternal() final { | |
| if (!job_) return; | |
| if (on_foreground_) ResetPendingForegroundTask(); | |
| job_->step_->Run(on_foreground_); | |
| // After execution, reset {job_} such that we don't try to reset the pending | |
| // foreground task when the task is deleted. | |
| job_ = nullptr; | |
| } | |
| void Cancel() { | |
| DCHECK_NOT_NULL(job_); | |
| job_ = nullptr; | |
| } | |
| private: | |
| // {job_} will be cleared to cancel a pending task. | |
| AsyncCompileJob* job_; | |
| bool on_foreground_; | |
| void ResetPendingForegroundTask() const { | |
| DCHECK_EQ(this, job_->pending_foreground_task_); | |
| job_->pending_foreground_task_ = nullptr; | |
| } | |
| }; | |
| void AsyncCompileJob::StartForegroundTask() { | |
| DCHECK_NULL(pending_foreground_task_); | |
| auto new_task = base::make_unique<CompileTask>(this, true); | |
| pending_foreground_task_ = new_task.get(); | |
| foreground_task_runner_->PostTask(std::move(new_task)); | |
| } | |
| void AsyncCompileJob::ExecuteForegroundTaskImmediately() { | |
| DCHECK_NULL(pending_foreground_task_); | |
| auto new_task = base::make_unique<CompileTask>(this, true); | |
| pending_foreground_task_ = new_task.get(); | |
| new_task->Run(); | |
| } | |
| void AsyncCompileJob::CancelPendingForegroundTask() { | |
| if (!pending_foreground_task_) return; | |
| pending_foreground_task_->Cancel(); | |
| pending_foreground_task_ = nullptr; | |
| } | |
| void AsyncCompileJob::StartBackgroundTask() { | |
| auto task = base::make_unique<CompileTask>(this, false); | |
| // If --wasm-num-compilation-tasks=0 is passed, do only spawn foreground | |
| // tasks. This is used to make timing deterministic. | |
| if (FLAG_wasm_num_compilation_tasks > 0) { | |
| V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task)); | |
| } else { | |
| foreground_task_runner_->PostTask(std::move(task)); | |
| } | |
| } | |
| template <typename Step, typename... Args> | |
| void AsyncCompileJob::DoSync(Args&&... args) { | |
| NextStep<Step>(std::forward<Args>(args)...); | |
| StartForegroundTask(); | |
| } | |
| template <typename Step, typename... Args> | |
| void AsyncCompileJob::DoImmediately(Args&&... args) { | |
| NextStep<Step>(std::forward<Args>(args)...); | |
| ExecuteForegroundTaskImmediately(); | |
| } | |
| template <typename Step, typename... Args> | |
| void AsyncCompileJob::DoAsync(Args&&... args) { | |
| NextStep<Step>(std::forward<Args>(args)...); | |
| StartBackgroundTask(); | |
| } | |
| template <typename Step, typename... Args> | |
| void AsyncCompileJob::NextStep(Args&&... args) { | |
| step_.reset(new Step(std::forward<Args>(args)...)); | |
| step_->job_ = this; | |
| } | |
| //========================================================================== | |
| // Step 1: (async) Decode the module. | |
| //========================================================================== | |
| class AsyncCompileJob::DecodeModule : public AsyncCompileJob::CompileStep { | |
| public: | |
| void RunInBackground() override { | |
| ModuleResult result; | |
| { | |
| DisallowHandleAllocation no_handle; | |
| DisallowHeapAllocation no_allocation; | |
| // Decode the module bytes. | |
| TRACE_COMPILE("(1) Decoding module...\n"); | |
| result = | |
| DecodeWasmModule(job_->enabled_features_, job_->wire_bytes_.start(), | |
| job_->wire_bytes_.end(), false, kWasmOrigin, | |
| job_->async_counters().get(), | |
| job_->isolate()->wasm_engine()->allocator()); | |
| } | |
| 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), true); | |
| } | |
| } | |
| }; | |
| //========================================================================== | |
| // 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"); | |
| ErrorThrower thrower(job_->isolate_, "AsyncCompile"); | |
| thrower.CompileFailed("Wasm decoding failed", result_); | |
| // {job_} is deleted in AsyncCompileFailed, therefore the {return}. | |
| return job_->AsyncCompileFailed(thrower.Reify()); | |
| } | |
| }; | |
| //========================================================================== | |
| // Step 2 (sync): Create heap-allocated data and start compile. | |
| //========================================================================== | |
| class AsyncCompileJob::PrepareAndStartCompile : public CompileStep { | |
| public: | |
| PrepareAndStartCompile(std::shared_ptr<const WasmModule> module, | |
| bool start_compilation) | |
| : module_(module), start_compilation_(start_compilation) {} | |
| private: | |
| std::shared_ptr<const WasmModule> module_; | |
| bool start_compilation_; | |
| void RunInForeground() override { | |
| TRACE_COMPILE("(2) Prepare and start compile...\n"); | |
| // Make sure all compilation tasks stopped running. Decoding (async step) | |
| // is done. | |
| job_->background_task_manager_.CancelAndWait(); | |
| // Embedder usage count for declared shared memories. | |
| if (module_->has_shared_memory) { | |
| job_->isolate_->CountUsage( | |
| v8::Isolate::UseCounterFeature::kWasmSharedMemory); | |
| } | |
| // Create heap objects for script and module bytes to be stored in the | |
| // module object. Asm.js is not compiled asynchronously. | |
| Handle<Script> script = CreateWasmScript(job_->isolate_, job_->wire_bytes_); | |
| Handle<ByteArray> asm_js_offset_table; | |
| const WasmModule* module = module_.get(); | |
| ModuleEnv env = CreateDefaultModuleEnv(module); | |
| // TODO(wasm): Improve efficiency of storing module wire bytes. Only store | |
| // relevant sections, not function bodies | |
| // Create the module object and populate with compiled functions and | |
| // information needed at instantiation time. | |
| // TODO(clemensh): For the same module (same bytes / same hash), we should | |
| // only have one {WasmModuleObject}. Otherwise, we might only set | |
| // breakpoints on a (potentially empty) subset of the instances. | |
| // Create the module object. | |
| job_->module_object_ = WasmModuleObject::New( | |
| job_->isolate_, job_->enabled_features_, module_, env, | |
| {std::move(job_->bytes_copy_), job_->wire_bytes_.length()}, script, | |
| asm_js_offset_table); | |
| job_->native_module_ = job_->module_object_->native_module(); | |
| { | |
| DeferredHandleScope deferred(job_->isolate_); | |
| job_->module_object_ = handle(*job_->module_object_, job_->isolate_); | |
| job_->deferred_handles_.push_back(deferred.Detach()); | |
| } | |
| size_t num_functions = | |
| module->functions.size() - module->num_imported_functions; | |
| if (num_functions == 0) { | |
| // Tiering has nothing to do if module is empty. | |
| job_->tiering_completed_ = true; | |
| // Degenerate case of an empty module. | |
| job_->FinishCompile(); | |
| return; | |
| } | |
| CompilationState* compilation_state = | |
| job_->native_module_->compilation_state(); | |
| { | |
| // Instance field {job_} cannot be captured by copy, therefore | |
| // we need to add a local helper variable {job}. We want to | |
| // capture the {job} pointer by copy, as it otherwise is dependent | |
| // on the current step we are in. | |
| AsyncCompileJob* job = job_; | |
| compilation_state->SetCallback( | |
| [job](CompilationEvent event, ErrorThrower* thrower) { | |
| // Callback is called from a foreground thread. | |
| switch (event) { | |
| case CompilationEvent::kFinishedBaselineCompilation: | |
| if (job->DecrementAndCheckFinisherCount()) { | |
| SaveContext saved_context(job->isolate()); | |
| job->isolate()->set_context(*job->native_context_); | |
| job->FinishCompile(); | |
| } | |
| return; | |
| case CompilationEvent::kFinishedTopTierCompilation: | |
| // If a foreground task or a finisher is pending, we rely on | |
| // FinishModule to remove the job. | |
| if (job->pending_foreground_task_ || | |
| job->outstanding_finishers_.load() > 0) { | |
| job->tiering_completed_ = true; | |
| return; | |
| } | |
| job->isolate_->wasm_engine()->RemoveCompileJob(job); | |
| return; | |
| case CompilationEvent::kFailedCompilation: { | |
| // Tier-up compilation should not fail if baseline compilation | |
| // did not fail. | |
| DCHECK(!job->native_module_->compilation_state() | |
| ->baseline_compilation_finished()); | |
| SaveContext saved_context(job->isolate()); | |
| job->isolate()->set_context(*job->native_context_); | |
| Handle<Object> error = thrower->Reify(); | |
| DeferredHandleScope deferred(job->isolate()); | |
| error = handle(*error, job->isolate()); | |
| job->deferred_handles_.push_back(deferred.Detach()); | |
| job->DoSync<CompileFailed>(error); | |
| return; | |
| } | |
| } | |
| UNREACHABLE(); | |
| }); | |
| } | |
| if (start_compilation_) { | |
| // TODO(ahaas): Try to remove the {start_compilation_} check when | |
| // streaming decoding is done in the background. If | |
| // InitializeCompilationUnits always returns 0 for streaming compilation, | |
| // then DoAsync would do the same as NextStep already. | |
| compilation_state->SetNumberOfFunctionsToCompile( | |
| module->num_declared_functions); | |
| // Add compilation units and kick off compilation. | |
| InitializeCompilationUnits(job_->native_module_); | |
| } | |
| } | |
| }; | |
| //========================================================================== | |
| // Step 4b (sync): Compilation failed. Reject Promise. | |
| //========================================================================== | |
| class AsyncCompileJob::CompileFailed : public CompileStep { | |
| public: | |
| explicit CompileFailed(Handle<Object> error_reason) | |
| : error_reason_(error_reason) {} | |
| void RunInForeground() override { | |
| TRACE_COMPILE("(4b) Compilation Failed...\n"); | |
| return job_->AsyncCompileFailed(error_reason_); | |
| } | |
| private: | |
| Handle<Object> error_reason_; | |
| }; | |
| //========================================================================== | |
| // Step 5 (sync): Compile JS->wasm wrappers. | |
| //========================================================================== | |
| class AsyncCompileJob::CompileWrappers : public CompileStep { | |
| // TODO(wasm): Compile all wrappers here, including the start function wrapper | |
| // and the wrappers for the function table elements. | |
| void RunInForeground() override { | |
| TRACE_COMPILE("(5) Compile wrappers...\n"); | |
| // TODO(6792): No longer needed once WebAssembly code is off heap. | |
| CodeSpaceMemoryModificationScope modification_scope(job_->isolate_->heap()); | |
| // Compile JS->wasm wrappers for exported functions. | |
| CompileJsToWasmWrappers(job_->isolate_, job_->module_object_); | |
| job_->DoSync<FinishModule>(); | |
| } | |
| }; | |
| //========================================================================== | |
| // Step 6 (sync): Finish the module and resolve the promise. | |
| //========================================================================== | |
| class AsyncCompileJob::FinishModule : public CompileStep { | |
| void RunInForeground() override { | |
| TRACE_COMPILE("(6) Finish module...\n"); | |
| job_->AsyncCompileSucceeded(job_->module_object_); | |
| size_t num_functions = job_->native_module_->num_functions() - | |
| job_->native_module_->num_imported_functions(); | |
| if (job_->native_module_->compilation_state()->compile_mode() == | |
| CompileMode::kRegular || | |
| num_functions == 0) { | |
| // If we do not tier up, the async compile job is done here and | |
| // can be deleted. | |
| job_->isolate_->wasm_engine()->RemoveCompileJob(job_); | |
| return; | |
| } | |
| // If background tiering compilation finished before we resolved the | |
| // promise, switch to patching now. Otherwise, patching will be scheduled | |
| // by a callback. | |
| DCHECK_EQ(CompileMode::kTiering, | |
| job_->native_module_->compilation_state()->compile_mode()); | |
| if (job_->tiering_completed_) { | |
| job_->isolate_->wasm_engine()->RemoveCompileJob(job_); | |
| } | |
| } | |
| }; | |
| AsyncStreamingProcessor::AsyncStreamingProcessor(AsyncCompileJob* job) | |
| : decoder_(job->enabled_features_), | |
| job_(job), | |
| compilation_unit_builder_(nullptr) {} | |
| void AsyncStreamingProcessor::FinishAsyncCompileJobWithError(ResultBase error) { | |
| // Make sure all background tasks stopped executing before we change the state | |
| // of the AsyncCompileJob to DecodeFail. | |
| job_->background_task_manager_.CancelAndWait(); | |
| // Create a ModuleResult from the result we got as parameter. Since there was | |
| // no error, we don't have to provide a real wasm module to the ModuleResult. | |
| ModuleResult result(nullptr); | |
| result.MoveErrorFrom(error); | |
| // Check if there is already a CompiledModule, in which case we have to clean | |
| // up the CompilationState as well. | |
| if (job_->native_module_) { | |
| job_->native_module_->compilation_state()->Abort(); | |
| if (job_->pending_foreground_task_ == nullptr) { | |
| job_->DoSync<AsyncCompileJob::DecodeFail>(std::move(result)); | |
| } else { | |
| job_->NextStep<AsyncCompileJob::DecodeFail>(std::move(result)); | |
| } | |
| // Clear the {compilation_unit_builder_} if it exists. This is needed | |
| // because there is a check in the destructor of the | |
| // {CompilationUnitBuilder} that it is empty. | |
| if (compilation_unit_builder_) compilation_unit_builder_->Clear(); | |
| } else { | |
| job_->DoSync<AsyncCompileJob::DecodeFail>(std::move(result)); | |
| } | |
| } | |
| // Process the module header. | |
| bool AsyncStreamingProcessor::ProcessModuleHeader(Vector<const uint8_t> bytes, | |
| uint32_t offset) { | |
| TRACE_STREAMING("Process module header...\n"); | |
| decoder_.StartDecoding(job_->async_counters().get(), | |
| job_->isolate()->wasm_engine()->allocator()); | |
| decoder_.DecodeModuleHeader(bytes, offset); | |
| if (!decoder_.ok()) { | |
| FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false)); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Process all sections except for the code section. | |
| bool AsyncStreamingProcessor::ProcessSection(SectionCode section_code, | |
| Vector<const uint8_t> bytes, | |
| uint32_t offset) { | |
| TRACE_STREAMING("Process section %d ...\n", section_code); | |
| if (compilation_unit_builder_) { | |
| // We reached a section after the code section, we do not need the | |
| // compilation_unit_builder_ anymore. | |
| CommitCompilationUnits(); | |
| compilation_unit_builder_.reset(); | |
| } | |
| if (section_code == SectionCode::kUnknownSectionCode) { | |
| Decoder decoder(bytes, offset); | |
| section_code = ModuleDecoder::IdentifyUnknownSection( | |
| decoder, bytes.start() + bytes.length()); | |
| if (section_code == SectionCode::kUnknownSectionCode) { | |
| // Skip unknown sections that we do not know how to handle. | |
| return true; | |
| } | |
| // Remove the unknown section tag from the payload bytes. | |
| offset += decoder.position(); | |
| bytes = bytes.SubVector(decoder.position(), bytes.size()); | |
| } | |
| constexpr bool verify_functions = false; | |
| decoder_.DecodeSection(section_code, bytes, offset, verify_functions); | |
| if (!decoder_.ok()) { | |
| FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false)); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Start the code section. | |
| bool AsyncStreamingProcessor::ProcessCodeSectionHeader(size_t functions_count, | |
| uint32_t offset) { | |
| TRACE_STREAMING("Start the code section with %zu functions...\n", | |
| functions_count); | |
| if (!decoder_.CheckFunctionsCount(static_cast<uint32_t>(functions_count), | |
| offset)) { | |
| FinishAsyncCompileJobWithError(decoder_.FinishDecoding(false)); | |
| return false; | |
| } | |
| // Execute the PrepareAndStartCompile step immediately and not in a separate | |
| // task. | |
| job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>( | |
| decoder_.shared_module(), false); | |
| job_->native_module_->compilation_state()->SetNumberOfFunctionsToCompile( | |
| functions_count); | |
| // Set outstanding_finishers_ to 2, because both the AsyncCompileJob and the | |
| // AsyncStreamingProcessor have to finish. | |
| job_->outstanding_finishers_.store(2); | |
| compilation_unit_builder_.reset( | |
| new CompilationUnitBuilder(job_->native_module_)); | |
| return true; | |
| } | |
| // Process a function body. | |
| bool AsyncStreamingProcessor::ProcessFunctionBody(Vector<const uint8_t> bytes, | |
| uint32_t offset) { | |
| TRACE_STREAMING("Process function body %d ...\n", next_function_); | |
| decoder_.DecodeFunctionBody( | |
| next_function_, static_cast<uint32_t>(bytes.length()), offset, false); | |
| uint32_t index = next_function_ + decoder_.module()->num_imported_functions; | |
| const WasmFunction* func = &decoder_.module()->functions[index]; | |
| WasmName name = {nullptr, 0}; | |
| compilation_unit_builder_->AddUnit(func, offset, bytes, name); | |
| ++next_function_; | |
| // This method always succeeds. The return value is necessary to comply with | |
| // the StreamingProcessor interface. | |
| return true; | |
| } | |
| void AsyncStreamingProcessor::CommitCompilationUnits() { | |
| DCHECK(compilation_unit_builder_); | |
| compilation_unit_builder_->Commit(); | |
| } | |
| void AsyncStreamingProcessor::OnFinishedChunk() { | |
| TRACE_STREAMING("FinishChunk...\n"); | |
| if (compilation_unit_builder_) CommitCompilationUnits(); | |
| } | |
| // Finish the processing of the stream. | |
| void AsyncStreamingProcessor::OnFinishedStream(OwnedVector<uint8_t> bytes) { | |
| TRACE_STREAMING("Finish stream...\n"); | |
| ModuleResult result = decoder_.FinishDecoding(false); | |
| DCHECK(result.ok()); | |
| bool needs_finish = job_->DecrementAndCheckFinisherCount(); | |
| if (job_->native_module_ == nullptr) { | |
| // We are processing a WebAssembly module without code section. We need to | |
| // prepare compilation first before we can finish it. | |
| // {PrepareAndStartCompile} will call {FinishCompile} by itself if there | |
| // is no code section. | |
| DCHECK(needs_finish); | |
| needs_finish = false; | |
| job_->DoImmediately<AsyncCompileJob::PrepareAndStartCompile>(result.val, | |
| true); | |
| } | |
| job_->wire_bytes_ = ModuleWireBytes(bytes.as_vector()); | |
| job_->native_module_->set_wire_bytes(std::move(bytes)); | |
| if (needs_finish) { | |
| HandleScope scope(job_->isolate_); | |
| SaveContext saved_context(job_->isolate_); | |
| job_->isolate_->set_context(*job_->native_context_); | |
| job_->FinishCompile(); | |
| } | |
| } | |
| // Report an error detected in the StreamingDecoder. | |
| void AsyncStreamingProcessor::OnError(DecodeResult result) { | |
| TRACE_STREAMING("Stream error...\n"); | |
| FinishAsyncCompileJobWithError(std::move(result)); | |
| } | |
| void AsyncStreamingProcessor::OnAbort() { | |
| TRACE_STREAMING("Abort stream...\n"); | |
| job_->Abort(); | |
| } | |
| void CompilationStateDeleter::operator()( | |
| CompilationState* compilation_state) const { | |
| delete compilation_state; | |
| } | |
| std::unique_ptr<CompilationState, CompilationStateDeleter> NewCompilationState( | |
| Isolate* isolate, const ModuleEnv& env) { | |
| return std::unique_ptr<CompilationState, CompilationStateDeleter>( | |
| new CompilationState(isolate, env)); | |
| } | |
| ModuleEnv* GetModuleEnv(CompilationState* compilation_state) { | |
| return compilation_state->module_env(); | |
| } | |
| CompilationState::CompilationState(internal::Isolate* isolate, | |
| const ModuleEnv& env) | |
| : isolate_(isolate), | |
| wasm_engine_(isolate->wasm_engine()), | |
| module_env_(env), | |
| compile_mode_(FLAG_wasm_tier_up && env.module->origin == kWasmOrigin | |
| ? CompileMode::kTiering | |
| : CompileMode::kRegular), | |
| max_background_tasks_(std::max( | |
| 1, std::min(FLAG_wasm_num_compilation_tasks, | |
| V8::GetCurrentPlatform()->NumberOfWorkerThreads()))) { | |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); | |
| v8::Platform* platform = V8::GetCurrentPlatform(); | |
| foreground_task_runner_ = platform->GetForegroundTaskRunner(v8_isolate); | |
| } | |
| CompilationState::~CompilationState() { | |
| background_task_manager_.CancelAndWait(); | |
| foreground_task_manager_.CancelAndWait(); | |
| } | |
| void CompilationState::SetNumberOfFunctionsToCompile(size_t num_functions) { | |
| DCHECK(!failed()); | |
| outstanding_units_ = num_functions; | |
| if (compile_mode_ == CompileMode::kTiering) { | |
| outstanding_units_ += num_functions; | |
| num_tiering_units_ = num_functions; | |
| } | |
| } | |
| void CompilationState::SetCallback( | |
| std::function<void(CompilationEvent, ErrorThrower*)> callback) { | |
| DCHECK_NULL(callback_); | |
| callback_ = callback; | |
| } | |
| void CompilationState::AddCompilationUnits( | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& baseline_units, | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& tiering_units) { | |
| { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| if (compile_mode_ == CompileMode::kTiering) { | |
| DCHECK_EQ(baseline_units.size(), tiering_units.size()); | |
| DCHECK_EQ(tiering_units.back()->mode(), ExecutionTier::kOptimized); | |
| tiering_compilation_units_.insert( | |
| tiering_compilation_units_.end(), | |
| std::make_move_iterator(tiering_units.begin()), | |
| std::make_move_iterator(tiering_units.end())); | |
| } else { | |
| DCHECK(tiering_compilation_units_.empty()); | |
| } | |
| baseline_compilation_units_.insert( | |
| baseline_compilation_units_.end(), | |
| std::make_move_iterator(baseline_units.begin()), | |
| std::make_move_iterator(baseline_units.end())); | |
| } | |
| RestartBackgroundTasks(); | |
| } | |
| std::unique_ptr<WasmCompilationUnit> | |
| CompilationState::GetNextCompilationUnit() { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& units = | |
| baseline_compilation_units_.empty() ? tiering_compilation_units_ | |
| : baseline_compilation_units_; | |
| if (!units.empty()) { | |
| std::unique_ptr<WasmCompilationUnit> unit = std::move(units.back()); | |
| units.pop_back(); | |
| return unit; | |
| } | |
| return std::unique_ptr<WasmCompilationUnit>(); | |
| } | |
| std::unique_ptr<WasmCompilationUnit> CompilationState::GetNextExecutedUnit() { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| std::vector<std::unique_ptr<WasmCompilationUnit>>& units = finish_units(); | |
| if (units.empty()) return {}; | |
| std::unique_ptr<WasmCompilationUnit> ret = std::move(units.back()); | |
| units.pop_back(); | |
| return ret; | |
| } | |
| bool CompilationState::HasCompilationUnitToFinish() { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| return !finish_units().empty(); | |
| } | |
| void CompilationState::OnError(ErrorThrower* thrower) { | |
| Abort(); | |
| DCHECK(thrower->error()); | |
| NotifyOnEvent(CompilationEvent::kFailedCompilation, thrower); | |
| } | |
| void CompilationState::OnFinishedUnit() { | |
| DCHECK_GT(outstanding_units_, 0); | |
| --outstanding_units_; | |
| if (outstanding_units_ == 0) { | |
| background_task_manager_.CancelAndWait(); | |
| baseline_compilation_finished_ = true; | |
| DCHECK(compile_mode_ == CompileMode::kRegular || | |
| compile_mode_ == CompileMode::kTiering); | |
| NotifyOnEvent(compile_mode_ == CompileMode::kRegular | |
| ? CompilationEvent::kFinishedBaselineCompilation | |
| : CompilationEvent::kFinishedTopTierCompilation, | |
| nullptr); | |
| } else if (outstanding_units_ == num_tiering_units_) { | |
| DCHECK_EQ(compile_mode_, CompileMode::kTiering); | |
| baseline_compilation_finished_ = true; | |
| // TODO(wasm): For streaming compilation, we want to start top tier | |
| // compilation before all functions have been compiled with Liftoff, e.g. | |
| // in the case when all received functions have been compiled with Liftoff | |
| // and we are waiting for new functions to compile. | |
| // If we are in {kRegular} mode, {num_tiering_units_} is 0, therefore | |
| // this case is already caught by the previous check. | |
| NotifyOnEvent(CompilationEvent::kFinishedBaselineCompilation, nullptr); | |
| RestartBackgroundTasks(); | |
| } | |
| } | |
| void CompilationState::ScheduleUnitForFinishing( | |
| std::unique_ptr<WasmCompilationUnit> unit, ExecutionTier mode) { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| if (compile_mode_ == CompileMode::kTiering && | |
| mode == ExecutionTier::kOptimized) { | |
| tiering_finish_units_.push_back(std::move(unit)); | |
| } else { | |
| baseline_finish_units_.push_back(std::move(unit)); | |
| } | |
| if (!finisher_is_running_ && !failed_) { | |
| ScheduleFinisherTask(); | |
| // We set the flag here so that not more than one finisher is started. | |
| finisher_is_running_ = true; | |
| } | |
| } | |
| void CompilationState::OnBackgroundTaskStopped(const WasmFeatures& detected) { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| DCHECK_LE(1, num_background_tasks_); | |
| --num_background_tasks_; | |
| UnionFeaturesInto(&detected_features_, detected); | |
| } | |
| void CompilationState::PublishDetectedFeatures(Isolate* isolate, | |
| const WasmFeatures& detected) { | |
| // Notifying the isolate of the feature counts must take place under | |
| // the mutex, because even if we have finished baseline compilation, | |
| // tiering compilations may still occur in the background. | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| UnionFeaturesInto(&detected_features_, detected); | |
| UpdateFeatureUseCounts(isolate, detected_features_); | |
| } | |
| void CompilationState::RestartBackgroundTasks(size_t max) { | |
| size_t num_restart; | |
| { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| // No need to restart tasks if compilation already failed. | |
| if (failed_) return; | |
| DCHECK_LE(num_background_tasks_, max_background_tasks_); | |
| if (num_background_tasks_ == max_background_tasks_) return; | |
| size_t num_compilation_units = | |
| baseline_compilation_units_.size() + tiering_compilation_units_.size(); | |
| size_t stopped_tasks = max_background_tasks_ - num_background_tasks_; | |
| num_restart = std::min(max, std::min(num_compilation_units, stopped_tasks)); | |
| num_background_tasks_ += num_restart; | |
| } | |
| for (; num_restart > 0; --num_restart) { | |
| auto task = base::make_unique<BackgroundCompileTask>( | |
| this, &background_task_manager_); | |
| // If --wasm-num-compilation-tasks=0 is passed, do only spawn foreground | |
| // tasks. This is used to make timing deterministic. | |
| if (FLAG_wasm_num_compilation_tasks > 0) { | |
| V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task)); | |
| } else { | |
| foreground_task_runner_->PostTask(std::move(task)); | |
| } | |
| } | |
| } | |
| bool CompilationState::SetFinisherIsRunning(bool value) { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| if (finisher_is_running_ == value) return false; | |
| finisher_is_running_ = value; | |
| return true; | |
| } | |
| void CompilationState::ScheduleFinisherTask() { | |
| foreground_task_runner_->PostTask( | |
| base::make_unique<FinishCompileTask>(this, &foreground_task_manager_)); | |
| } | |
| void CompilationState::Abort() { | |
| { | |
| base::LockGuard<base::Mutex> guard(&mutex_); | |
| failed_ = true; | |
| } | |
| background_task_manager_.CancelAndWait(); | |
| } | |
| void CompilationState::NotifyOnEvent(CompilationEvent event, | |
| ErrorThrower* thrower) { | |
| if (callback_) callback_(event, thrower); | |
| } | |
| void CompileJsToWasmWrappers(Isolate* isolate, | |
| Handle<WasmModuleObject> module_object) { | |
| JSToWasmWrapperCache js_to_wasm_cache; | |
| int wrapper_index = 0; | |
| Handle<FixedArray> export_wrappers(module_object->export_wrappers(), isolate); | |
| NativeModule* native_module = module_object->native_module(); | |
| UseTrapHandler use_trap_handler = | |
| native_module->use_trap_handler() ? kUseTrapHandler : kNoTrapHandler; | |
| const WasmModule* module = native_module->module(); | |
| for (auto exp : module->export_table) { | |
| if (exp.kind != kExternalFunction) continue; | |
| Handle<Code> wrapper_code = js_to_wasm_cache.GetOrCompileJSToWasmWrapper( | |
| isolate, native_module, exp.index, use_trap_handler); | |
| export_wrappers->set(wrapper_index, *wrapper_code); | |
| RecordStats(*wrapper_code, isolate->counters()); | |
| ++wrapper_index; | |
| } | |
| } | |
| 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; | |
| } | |
| } // namespace wasm | |
| } // namespace internal | |
| } // namespace v8 | |
| #undef TRACE | |
| #undef TRACE_COMPILE | |
| #undef TRACE_STREAMING | |
| #undef TRACE_LAZY |