| // 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. |
| |
| #ifndef V8_WASM_WASM_ENGINE_H_ |
| #define V8_WASM_WASM_ENGINE_H_ |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| #include "src/base/platform/condition-variable.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/tasks/cancelable-task.h" |
| #include "src/wasm/wasm-code-manager.h" |
| #include "src/wasm/wasm-tier.h" |
| #include "src/zone/accounting-allocator.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class AsmWasmData; |
| class CodeTracer; |
| class CompilationStatistics; |
| class HeapNumber; |
| class WasmInstanceObject; |
| class WasmModuleObject; |
| class JSArrayBuffer; |
| |
| namespace wasm { |
| |
| #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING |
| namespace gdb_server { |
| class GdbServer; |
| } // namespace gdb_server |
| #endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING |
| |
| class AsyncCompileJob; |
| class ErrorThrower; |
| struct ModuleWireBytes; |
| class WasmFeatures; |
| |
| class V8_EXPORT_PRIVATE CompilationResultResolver { |
| public: |
| virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0; |
| virtual void OnCompilationFailed(Handle<Object> error_reason) = 0; |
| virtual ~CompilationResultResolver() = default; |
| }; |
| |
| class V8_EXPORT_PRIVATE InstantiationResultResolver { |
| public: |
| virtual void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) = 0; |
| virtual void OnInstantiationFailed(Handle<Object> error_reason) = 0; |
| virtual ~InstantiationResultResolver() = default; |
| }; |
| |
| // Native modules cached by their wire bytes. |
| class NativeModuleCache { |
| public: |
| struct Key { |
| // Store the prefix hash as part of the key for faster lookup, and to |
| // quickly check existing prefixes for streaming compilation. |
| size_t prefix_hash; |
| Vector<const uint8_t> bytes; |
| |
| bool operator==(const Key& other) const { |
| bool eq = bytes == other.bytes; |
| DCHECK_IMPLIES(eq, prefix_hash == other.prefix_hash); |
| return eq; |
| } |
| |
| bool operator<(const Key& other) const { |
| if (prefix_hash != other.prefix_hash) { |
| DCHECK_IMPLIES(!bytes.empty() && !other.bytes.empty(), |
| bytes != other.bytes); |
| return prefix_hash < other.prefix_hash; |
| } |
| if (bytes.size() != other.bytes.size()) { |
| return bytes.size() < other.bytes.size(); |
| } |
| // Fast path when the base pointers are the same. |
| // Also handles the {nullptr} case which would be UB for memcmp. |
| if (bytes.begin() == other.bytes.begin()) { |
| DCHECK_EQ(prefix_hash, other.prefix_hash); |
| return false; |
| } |
| DCHECK_NOT_NULL(bytes.begin()); |
| DCHECK_NOT_NULL(other.bytes.begin()); |
| return memcmp(bytes.begin(), other.bytes.begin(), bytes.size()) < 0; |
| } |
| }; |
| |
| std::shared_ptr<NativeModule> MaybeGetNativeModule( |
| ModuleOrigin origin, Vector<const uint8_t> wire_bytes); |
| bool GetStreamingCompilationOwnership(size_t prefix_hash); |
| void StreamingCompilationFailed(size_t prefix_hash); |
| std::shared_ptr<NativeModule> Update( |
| std::shared_ptr<NativeModule> native_module, bool error); |
| void Erase(NativeModule* native_module); |
| |
| bool empty() { return map_.empty(); } |
| |
| static size_t WireBytesHash(Vector<const uint8_t> bytes); |
| |
| // Hash the wire bytes up to the code section header. Used as a heuristic to |
| // avoid streaming compilation of modules that are likely already in the |
| // cache. See {GetStreamingCompilationOwnership}. Assumes that the bytes have |
| // already been validated. |
| static size_t PrefixHash(Vector<const uint8_t> wire_bytes); |
| |
| private: |
| // Each key points to the corresponding native module's wire bytes, so they |
| // should always be valid as long as the native module is alive. When |
| // the native module dies, {FreeNativeModule} deletes the entry from the |
| // map, so that we do not leave any dangling key pointing to an expired |
| // weak_ptr. This also serves as a way to regularly clean up the map, which |
| // would otherwise accumulate expired entries. |
| // A {nullopt} value is inserted to indicate that this native module is |
| // currently being created in some thread, and that other threads should wait |
| // before trying to get it from the cache. |
| // By contrast, an expired {weak_ptr} indicates that the native module died |
| // and will soon be cleaned up from the cache. |
| std::map<Key, base::Optional<std::weak_ptr<NativeModule>>> map_; |
| |
| base::Mutex mutex_; |
| |
| // This condition variable is used to synchronize threads compiling the same |
| // module. Only one thread will create the {NativeModule}. Other threads |
| // will wait on this variable until the first thread wakes them up. |
| base::ConditionVariable cache_cv_; |
| }; |
| |
| // The central data structure that represents an engine instance capable of |
| // loading, instantiating, and executing Wasm code. |
| class V8_EXPORT_PRIVATE WasmEngine { |
| public: |
| WasmEngine(); |
| WasmEngine(const WasmEngine&) = delete; |
| WasmEngine& operator=(const WasmEngine&) = delete; |
| ~WasmEngine(); |
| |
| // Synchronously validates the given bytes that represent an encoded Wasm |
| // module. |
| bool SyncValidate(Isolate* isolate, const WasmFeatures& enabled, |
| const ModuleWireBytes& bytes); |
| |
| // Synchronously compiles the given bytes that represent a translated |
| // asm.js module. |
| MaybeHandle<AsmWasmData> SyncCompileTranslatedAsmJs( |
| Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes, |
| Vector<const byte> asm_js_offset_table_bytes, |
| Handle<HeapNumber> uses_bitset, LanguageMode language_mode); |
| Handle<WasmModuleObject> FinalizeTranslatedAsmJs( |
| Isolate* isolate, Handle<AsmWasmData> asm_wasm_data, |
| Handle<Script> script); |
| |
| // Synchronously compiles the given bytes that represent an encoded Wasm |
| // module. |
| MaybeHandle<WasmModuleObject> SyncCompile(Isolate* isolate, |
| const WasmFeatures& enabled, |
| ErrorThrower* thrower, |
| const ModuleWireBytes& bytes); |
| |
| // Synchronously instantiate the given Wasm module with the given imports. |
| // If the module represents an asm.js module, then the supplied {memory} |
| // should be used as the memory of the instance. |
| MaybeHandle<WasmInstanceObject> SyncInstantiate( |
| Isolate* isolate, ErrorThrower* thrower, |
| Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, |
| MaybeHandle<JSArrayBuffer> memory); |
| |
| // Begin an asynchronous compilation of the given bytes that represent an |
| // encoded Wasm module. |
| // The {is_shared} flag indicates if the bytes backing the module could |
| // be shared across threads, i.e. could be concurrently modified. |
| void AsyncCompile(Isolate* isolate, const WasmFeatures& enabled, |
| std::shared_ptr<CompilationResultResolver> resolver, |
| const ModuleWireBytes& bytes, bool is_shared, |
| const char* api_method_name_for_errors); |
| |
| // Begin an asynchronous instantiation of the given Wasm module. |
| void AsyncInstantiate(Isolate* isolate, |
| std::unique_ptr<InstantiationResultResolver> resolver, |
| Handle<WasmModuleObject> module_object, |
| MaybeHandle<JSReceiver> imports); |
| |
| std::shared_ptr<StreamingDecoder> StartStreamingCompilation( |
| Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context, |
| const char* api_method_name, |
| std::shared_ptr<CompilationResultResolver> resolver); |
| |
| // Compiles the function with the given index at a specific compilation tier. |
| // Errors are stored internally in the CompilationState. |
| // This is mostly used for testing to force a function into a specific tier. |
| void CompileFunction(Isolate* isolate, NativeModule* native_module, |
| uint32_t function_index, ExecutionTier tier); |
| |
| void TierDownAllModulesPerIsolate(Isolate* isolate); |
| void TierUpAllModulesPerIsolate(Isolate* isolate); |
| |
| // Exports the sharable parts of the given module object so that they can be |
| // transferred to a different Context/Isolate using the same engine. |
| std::shared_ptr<NativeModule> ExportNativeModule( |
| Handle<WasmModuleObject> module_object); |
| |
| // Imports the shared part of a module from a different Context/Isolate using |
| // the the same engine, recreating a full module object in the given Isolate. |
| Handle<WasmModuleObject> ImportNativeModule( |
| Isolate* isolate, std::shared_ptr<NativeModule> shared_module, |
| Vector<const char> source_url); |
| |
| WasmCodeManager* code_manager() { return &code_manager_; } |
| |
| AccountingAllocator* allocator() { return &allocator_; } |
| |
| // Compilation statistics for TurboFan compilations. |
| CompilationStatistics* GetOrCreateTurboStatistics(); |
| |
| // Prints the gathered compilation statistics, then resets them. |
| void DumpAndResetTurboStatistics(); |
| |
| // Used to redirect tracing output from {stdout} to a file. |
| CodeTracer* GetCodeTracer(); |
| |
| // Remove {job} from the list of active compile jobs. |
| std::unique_ptr<AsyncCompileJob> RemoveCompileJob(AsyncCompileJob* job); |
| |
| // Returns true if at least one AsyncCompileJob that belongs to the given |
| // Isolate is currently running. |
| bool HasRunningCompileJob(Isolate* isolate); |
| |
| // Deletes all AsyncCompileJobs that belong to the given context. All |
| // compilation is aborted, no more callbacks will be triggered. This is used |
| // when a context is disposed, e.g. because of browser navigation. |
| void DeleteCompileJobsOnContext(Handle<Context> context); |
| |
| // Deletes all AsyncCompileJobs that belong to the given Isolate. All |
| // compilation is aborted, no more callbacks will be triggered. This is used |
| // for tearing down an isolate, or to clean it up to be reused. |
| void DeleteCompileJobsOnIsolate(Isolate* isolate); |
| |
| // Manage the set of Isolates that use this WasmEngine. |
| void AddIsolate(Isolate* isolate); |
| void RemoveIsolate(Isolate* isolate); |
| |
| // Trigger code logging for the given code objects in all Isolates which have |
| // access to the NativeModule containing this code. This method can be called |
| // from background threads. |
| void LogCode(Vector<WasmCode*>); |
| |
| // Enable code logging for the given Isolate. Initially, code logging is |
| // enabled if {WasmCode::ShouldBeLogged(Isolate*)} returns true during |
| // {AddIsolate}. |
| void EnableCodeLogging(Isolate*); |
| |
| // This is called from the foreground thread of the Isolate to log all |
| // outstanding code objects (added via {LogCode}). |
| void LogOutstandingCodesForIsolate(Isolate*); |
| |
| // Create a new NativeModule. The caller is responsible for its |
| // lifetime. The native module will be given some memory for code, |
| // which will be page size aligned. The size of the initial memory |
| // is determined by {code_size_estimate}. The native module may later request |
| // more memory. |
| // TODO(wasm): isolate is only required here for CompilationState. |
| std::shared_ptr<NativeModule> NewNativeModule( |
| Isolate* isolate, const WasmFeatures& enabled_features, |
| std::shared_ptr<const WasmModule> module, size_t code_size_estimate); |
| |
| // Try getting a cached {NativeModule}, or get ownership for its creation. |
| // Return {nullptr} if no {NativeModule} exists for these bytes. In this case, |
| // a {nullopt} entry is added to let other threads know that a {NativeModule} |
| // for these bytes is currently being created. The caller should eventually |
| // call {UpdateNativeModuleCache} to update the entry and wake up other |
| // threads. The {wire_bytes}' underlying array should be valid at least until |
| // the call to {UpdateNativeModuleCache}. |
| std::shared_ptr<NativeModule> MaybeGetNativeModule( |
| ModuleOrigin origin, Vector<const uint8_t> wire_bytes, Isolate* isolate); |
| |
| // Replace the temporary {nullopt} with the new native module, or |
| // erase it if any error occurred. Wake up blocked threads waiting for this |
| // module. |
| // To avoid a deadlock on the main thread between synchronous and streaming |
| // compilation, two compilation jobs might compile the same native module at |
| // the same time. In this case the first call to {UpdateNativeModuleCache} |
| // will insert the native module in the cache, and the last call will discard |
| // its {native_module} argument and replace it with the existing entry. |
| // Return true in the former case, and false in the latter. |
| bool UpdateNativeModuleCache(bool error, |
| std::shared_ptr<NativeModule>* native_module, |
| Isolate* isolate); |
| |
| // Register this prefix hash for a streaming compilation job. |
| // If the hash is not in the cache yet, the function returns true and the |
| // caller owns the compilation of this module. |
| // Otherwise another compilation job is currently preparing or has already |
| // prepared a module with the same prefix hash. The caller should wait until |
| // the stream is finished and call {MaybeGetNativeModule} to either get the |
| // module from the cache or get ownership for the compilation of these bytes. |
| bool GetStreamingCompilationOwnership(size_t prefix_hash); |
| |
| // Remove the prefix hash from the cache when compilation failed. If |
| // compilation succeeded, {UpdateNativeModuleCache} should be called instead. |
| void StreamingCompilationFailed(size_t prefix_hash); |
| |
| void FreeNativeModule(NativeModule*); |
| |
| // Sample the code size of the given {NativeModule} in all isolates that have |
| // access to it. Call this after top-tier compilation finished. |
| // This will spawn foreground tasks that do *not* keep the NativeModule alive. |
| void SampleTopTierCodeSizeInAllIsolates(const std::shared_ptr<NativeModule>&); |
| |
| // Called by each Isolate to report its live code for a GC cycle. First |
| // version reports an externally determined set of live code (might be empty), |
| // second version gets live code from the execution stack of that isolate. |
| void ReportLiveCodeForGC(Isolate*, Vector<WasmCode*>); |
| void ReportLiveCodeFromStackForGC(Isolate*); |
| |
| // Add potentially dead code. The occurrence in the set of potentially dead |
| // code counts as a reference, and is decremented on the next GC. |
| // Returns {true} if the code was added to the set of potentially dead code, |
| // {false} if an entry already exists. The ref count is *unchanged* in any |
| // case. |
| V8_WARN_UNUSED_RESULT bool AddPotentiallyDeadCode(WasmCode*); |
| |
| // Free dead code. |
| using DeadCodeMap = std::unordered_map<NativeModule*, std::vector<WasmCode*>>; |
| void FreeDeadCode(const DeadCodeMap&); |
| void FreeDeadCodeLocked(const DeadCodeMap&); |
| |
| Handle<Script> GetOrCreateScript(Isolate*, |
| const std::shared_ptr<NativeModule>&, |
| Vector<const char> source_url = {}); |
| |
| // Take shared ownership of a compile job handle, such that we can synchronize |
| // on that before the engine dies. |
| void ShepherdCompileJobHandle(std::shared_ptr<JobHandle>); |
| |
| // Call on process start and exit. |
| static void InitializeOncePerProcess(); |
| static void GlobalTearDown(); |
| |
| // Returns a reference to the WasmEngine shared by the entire process. Try to |
| // use {Isolate::wasm_engine} instead if it is available, which encapsulates |
| // engine lifetime decisions during Isolate bootstrapping. |
| static std::shared_ptr<WasmEngine> GetWasmEngine(); |
| |
| private: |
| struct CurrentGCInfo; |
| struct IsolateInfo; |
| struct NativeModuleInfo; |
| |
| AsyncCompileJob* CreateAsyncCompileJob( |
| Isolate* isolate, const WasmFeatures& enabled, |
| std::unique_ptr<byte[]> bytes_copy, size_t length, |
| Handle<Context> context, const char* api_method_name, |
| std::shared_ptr<CompilationResultResolver> resolver); |
| |
| void TriggerGC(int8_t gc_sequence_index); |
| |
| // Remove an isolate from the outstanding isolates of the current GC. Returns |
| // true if the isolate was still outstanding, false otherwise. Hold {mutex_} |
| // when calling this method. |
| bool RemoveIsolateFromCurrentGC(Isolate*); |
| |
| // Finish a GC if there are no more outstanding isolates. Hold {mutex_} when |
| // calling this method. |
| void PotentiallyFinishCurrentGC(); |
| |
| WasmCodeManager code_manager_; |
| AccountingAllocator allocator_; |
| |
| #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING |
| // Implements a GDB-remote stub for WebAssembly debugging. |
| std::unique_ptr<gdb_server::GdbServer> gdb_server_; |
| #endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING |
| |
| // This mutex protects all information which is mutated concurrently or |
| // fields that are initialized lazily on the first access. |
| base::Mutex mutex_; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Protected by {mutex_}: |
| |
| // We use an AsyncCompileJob as the key for itself so that we can delete the |
| // job from the map when it is finished. |
| std::unordered_map<AsyncCompileJob*, std::unique_ptr<AsyncCompileJob>> |
| async_compile_jobs_; |
| |
| std::unique_ptr<CompilationStatistics> compilation_stats_; |
| std::unique_ptr<CodeTracer> code_tracer_; |
| |
| // Set of isolates which use this WasmEngine. |
| std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_; |
| |
| // Set of native modules managed by this engine. |
| std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>> |
| native_modules_; |
| |
| // Background compile jobs that are still running. We need to join them before |
| // the engine gets deleted. Otherwise we don't care when exactly they finish. |
| std::vector<std::shared_ptr<JobHandle>> compile_job_handles_; |
| |
| // Size of code that became dead since the last GC. If this exceeds a certain |
| // threshold, a new GC is triggered. |
| size_t new_potentially_dead_code_size_ = 0; |
| |
| // If an engine-wide GC is currently running, this pointer stores information |
| // about that. |
| std::unique_ptr<CurrentGCInfo> current_gc_info_; |
| |
| NativeModuleCache native_module_cache_; |
| |
| // End of fields protected by {mutex_}. |
| ////////////////////////////////////////////////////////////////////////////// |
| }; |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_WASM_WASM_ENGINE_H_ |