| // 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_ |