1 // Copyright 2017 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_WASM_WASM_ENGINE_H_ 6 #define V8_WASM_WASM_ENGINE_H_ 7 8 #include <algorithm> 9 #include <map> 10 #include <memory> 11 #include <unordered_map> 12 #include <unordered_set> 13 14 #include "src/base/platform/condition-variable.h" 15 #include "src/base/platform/mutex.h" 16 #include "src/tasks/cancelable-task.h" 17 #include "src/wasm/wasm-code-manager.h" 18 #include "src/wasm/wasm-tier.h" 19 #include "src/zone/accounting-allocator.h" 20 21 namespace v8 { 22 namespace internal { 23 24 class AsmWasmData; 25 class CodeTracer; 26 class CompilationStatistics; 27 class HeapNumber; 28 class WasmInstanceObject; 29 class WasmModuleObject; 30 class JSArrayBuffer; 31 32 namespace wasm { 33 34 #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 35 namespace gdb_server { 36 class GdbServer; 37 } // namespace gdb_server 38 #endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 39 40 class AsyncCompileJob; 41 class ErrorThrower; 42 struct ModuleWireBytes; 43 class WasmFeatures; 44 45 class V8_EXPORT_PRIVATE CompilationResultResolver { 46 public: 47 virtual void OnCompilationSucceeded(Handle<WasmModuleObject> result) = 0; 48 virtual void OnCompilationFailed(Handle<Object> error_reason) = 0; 49 virtual ~CompilationResultResolver() = default; 50 }; 51 52 class V8_EXPORT_PRIVATE InstantiationResultResolver { 53 public: 54 virtual void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) = 0; 55 virtual void OnInstantiationFailed(Handle<Object> error_reason) = 0; 56 virtual ~InstantiationResultResolver() = default; 57 }; 58 59 // Native modules cached by their wire bytes. 60 class NativeModuleCache { 61 public: 62 struct Key { 63 // Store the prefix hash as part of the key for faster lookup, and to 64 // quickly check existing prefixes for streaming compilation. 65 size_t prefix_hash; 66 Vector<const uint8_t> bytes; 67 68 bool operator==(const Key& other) const { 69 bool eq = bytes == other.bytes; 70 DCHECK_IMPLIES(eq, prefix_hash == other.prefix_hash); 71 return eq; 72 } 73 74 bool operator<(const Key& other) const { 75 if (prefix_hash != other.prefix_hash) { 76 DCHECK_IMPLIES(!bytes.empty() && !other.bytes.empty(), 77 bytes != other.bytes); 78 return prefix_hash < other.prefix_hash; 79 } 80 if (bytes.size() != other.bytes.size()) { 81 return bytes.size() < other.bytes.size(); 82 } 83 // Fast path when the base pointers are the same. 84 // Also handles the {nullptr} case which would be UB for memcmp. 85 if (bytes.begin() == other.bytes.begin()) { 86 DCHECK_EQ(prefix_hash, other.prefix_hash); 87 return false; 88 } 89 DCHECK_NOT_NULL(bytes.begin()); 90 DCHECK_NOT_NULL(other.bytes.begin()); 91 return memcmp(bytes.begin(), other.bytes.begin(), bytes.size()) < 0; 92 } 93 }; 94 95 std::shared_ptr<NativeModule> MaybeGetNativeModule( 96 ModuleOrigin origin, Vector<const uint8_t> wire_bytes); 97 bool GetStreamingCompilationOwnership(size_t prefix_hash); 98 void StreamingCompilationFailed(size_t prefix_hash); 99 std::shared_ptr<NativeModule> Update( 100 std::shared_ptr<NativeModule> native_module, bool error); 101 void Erase(NativeModule* native_module); 102 empty()103 bool empty() { return map_.empty(); } 104 105 static size_t WireBytesHash(Vector<const uint8_t> bytes); 106 107 // Hash the wire bytes up to the code section header. Used as a heuristic to 108 // avoid streaming compilation of modules that are likely already in the 109 // cache. See {GetStreamingCompilationOwnership}. Assumes that the bytes have 110 // already been validated. 111 static size_t PrefixHash(Vector<const uint8_t> wire_bytes); 112 113 private: 114 // Each key points to the corresponding native module's wire bytes, so they 115 // should always be valid as long as the native module is alive. When 116 // the native module dies, {FreeNativeModule} deletes the entry from the 117 // map, so that we do not leave any dangling key pointing to an expired 118 // weak_ptr. This also serves as a way to regularly clean up the map, which 119 // would otherwise accumulate expired entries. 120 // A {nullopt} value is inserted to indicate that this native module is 121 // currently being created in some thread, and that other threads should wait 122 // before trying to get it from the cache. 123 // By contrast, an expired {weak_ptr} indicates that the native module died 124 // and will soon be cleaned up from the cache. 125 std::map<Key, base::Optional<std::weak_ptr<NativeModule>>> map_; 126 127 base::Mutex mutex_; 128 129 // This condition variable is used to synchronize threads compiling the same 130 // module. Only one thread will create the {NativeModule}. Other threads 131 // will wait on this variable until the first thread wakes them up. 132 base::ConditionVariable cache_cv_; 133 }; 134 135 // The central data structure that represents an engine instance capable of 136 // loading, instantiating, and executing Wasm code. 137 class V8_EXPORT_PRIVATE WasmEngine { 138 public: 139 WasmEngine(); 140 WasmEngine(const WasmEngine&) = delete; 141 WasmEngine& operator=(const WasmEngine&) = delete; 142 ~WasmEngine(); 143 144 // Synchronously validates the given bytes that represent an encoded Wasm 145 // module. 146 bool SyncValidate(Isolate* isolate, const WasmFeatures& enabled, 147 const ModuleWireBytes& bytes); 148 149 // Synchronously compiles the given bytes that represent a translated 150 // asm.js module. 151 MaybeHandle<AsmWasmData> SyncCompileTranslatedAsmJs( 152 Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes, 153 Vector<const byte> asm_js_offset_table_bytes, 154 Handle<HeapNumber> uses_bitset, LanguageMode language_mode); 155 Handle<WasmModuleObject> FinalizeTranslatedAsmJs( 156 Isolate* isolate, Handle<AsmWasmData> asm_wasm_data, 157 Handle<Script> script); 158 159 // Synchronously compiles the given bytes that represent an encoded Wasm 160 // module. 161 MaybeHandle<WasmModuleObject> SyncCompile(Isolate* isolate, 162 const WasmFeatures& enabled, 163 ErrorThrower* thrower, 164 const ModuleWireBytes& bytes); 165 166 // Synchronously instantiate the given Wasm module with the given imports. 167 // If the module represents an asm.js module, then the supplied {memory} 168 // should be used as the memory of the instance. 169 MaybeHandle<WasmInstanceObject> SyncInstantiate( 170 Isolate* isolate, ErrorThrower* thrower, 171 Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, 172 MaybeHandle<JSArrayBuffer> memory); 173 174 // Begin an asynchronous compilation of the given bytes that represent an 175 // encoded Wasm module. 176 // The {is_shared} flag indicates if the bytes backing the module could 177 // be shared across threads, i.e. could be concurrently modified. 178 void AsyncCompile(Isolate* isolate, const WasmFeatures& enabled, 179 std::shared_ptr<CompilationResultResolver> resolver, 180 const ModuleWireBytes& bytes, bool is_shared, 181 const char* api_method_name_for_errors); 182 183 // Begin an asynchronous instantiation of the given Wasm module. 184 void AsyncInstantiate(Isolate* isolate, 185 std::unique_ptr<InstantiationResultResolver> resolver, 186 Handle<WasmModuleObject> module_object, 187 MaybeHandle<JSReceiver> imports); 188 189 std::shared_ptr<StreamingDecoder> StartStreamingCompilation( 190 Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context, 191 const char* api_method_name, 192 std::shared_ptr<CompilationResultResolver> resolver); 193 194 // Compiles the function with the given index at a specific compilation tier. 195 // Errors are stored internally in the CompilationState. 196 // This is mostly used for testing to force a function into a specific tier. 197 void CompileFunction(Isolate* isolate, NativeModule* native_module, 198 uint32_t function_index, ExecutionTier tier); 199 200 void TierDownAllModulesPerIsolate(Isolate* isolate); 201 void TierUpAllModulesPerIsolate(Isolate* isolate); 202 203 // Exports the sharable parts of the given module object so that they can be 204 // transferred to a different Context/Isolate using the same engine. 205 std::shared_ptr<NativeModule> ExportNativeModule( 206 Handle<WasmModuleObject> module_object); 207 208 // Imports the shared part of a module from a different Context/Isolate using 209 // the the same engine, recreating a full module object in the given Isolate. 210 Handle<WasmModuleObject> ImportNativeModule( 211 Isolate* isolate, std::shared_ptr<NativeModule> shared_module, 212 Vector<const char> source_url); 213 code_manager()214 WasmCodeManager* code_manager() { return &code_manager_; } 215 allocator()216 AccountingAllocator* allocator() { return &allocator_; } 217 218 // Compilation statistics for TurboFan compilations. 219 CompilationStatistics* GetOrCreateTurboStatistics(); 220 221 // Prints the gathered compilation statistics, then resets them. 222 void DumpAndResetTurboStatistics(); 223 224 // Used to redirect tracing output from {stdout} to a file. 225 CodeTracer* GetCodeTracer(); 226 227 // Remove {job} from the list of active compile jobs. 228 std::unique_ptr<AsyncCompileJob> RemoveCompileJob(AsyncCompileJob* job); 229 230 // Returns true if at least one AsyncCompileJob that belongs to the given 231 // Isolate is currently running. 232 bool HasRunningCompileJob(Isolate* isolate); 233 234 // Deletes all AsyncCompileJobs that belong to the given context. All 235 // compilation is aborted, no more callbacks will be triggered. This is used 236 // when a context is disposed, e.g. because of browser navigation. 237 void DeleteCompileJobsOnContext(Handle<Context> context); 238 239 // Deletes all AsyncCompileJobs that belong to the given Isolate. All 240 // compilation is aborted, no more callbacks will be triggered. This is used 241 // for tearing down an isolate, or to clean it up to be reused. 242 void DeleteCompileJobsOnIsolate(Isolate* isolate); 243 244 // Manage the set of Isolates that use this WasmEngine. 245 void AddIsolate(Isolate* isolate); 246 void RemoveIsolate(Isolate* isolate); 247 248 // Trigger code logging for the given code objects in all Isolates which have 249 // access to the NativeModule containing this code. This method can be called 250 // from background threads. 251 void LogCode(Vector<WasmCode*>); 252 253 // Enable code logging for the given Isolate. Initially, code logging is 254 // enabled if {WasmCode::ShouldBeLogged(Isolate*)} returns true during 255 // {AddIsolate}. 256 void EnableCodeLogging(Isolate*); 257 258 // This is called from the foreground thread of the Isolate to log all 259 // outstanding code objects (added via {LogCode}). 260 void LogOutstandingCodesForIsolate(Isolate*); 261 262 // Create a new NativeModule. The caller is responsible for its 263 // lifetime. The native module will be given some memory for code, 264 // which will be page size aligned. The size of the initial memory 265 // is determined by {code_size_estimate}. The native module may later request 266 // more memory. 267 // TODO(wasm): isolate is only required here for CompilationState. 268 std::shared_ptr<NativeModule> NewNativeModule( 269 Isolate* isolate, const WasmFeatures& enabled_features, 270 std::shared_ptr<const WasmModule> module, size_t code_size_estimate); 271 272 // Try getting a cached {NativeModule}, or get ownership for its creation. 273 // Return {nullptr} if no {NativeModule} exists for these bytes. In this case, 274 // a {nullopt} entry is added to let other threads know that a {NativeModule} 275 // for these bytes is currently being created. The caller should eventually 276 // call {UpdateNativeModuleCache} to update the entry and wake up other 277 // threads. The {wire_bytes}' underlying array should be valid at least until 278 // the call to {UpdateNativeModuleCache}. 279 std::shared_ptr<NativeModule> MaybeGetNativeModule( 280 ModuleOrigin origin, Vector<const uint8_t> wire_bytes, Isolate* isolate); 281 282 // Replace the temporary {nullopt} with the new native module, or 283 // erase it if any error occurred. Wake up blocked threads waiting for this 284 // module. 285 // To avoid a deadlock on the main thread between synchronous and streaming 286 // compilation, two compilation jobs might compile the same native module at 287 // the same time. In this case the first call to {UpdateNativeModuleCache} 288 // will insert the native module in the cache, and the last call will discard 289 // its {native_module} argument and replace it with the existing entry. 290 // Return true in the former case, and false in the latter. 291 bool UpdateNativeModuleCache(bool error, 292 std::shared_ptr<NativeModule>* native_module, 293 Isolate* isolate); 294 295 // Register this prefix hash for a streaming compilation job. 296 // If the hash is not in the cache yet, the function returns true and the 297 // caller owns the compilation of this module. 298 // Otherwise another compilation job is currently preparing or has already 299 // prepared a module with the same prefix hash. The caller should wait until 300 // the stream is finished and call {MaybeGetNativeModule} to either get the 301 // module from the cache or get ownership for the compilation of these bytes. 302 bool GetStreamingCompilationOwnership(size_t prefix_hash); 303 304 // Remove the prefix hash from the cache when compilation failed. If 305 // compilation succeeded, {UpdateNativeModuleCache} should be called instead. 306 void StreamingCompilationFailed(size_t prefix_hash); 307 308 void FreeNativeModule(NativeModule*); 309 310 // Sample the code size of the given {NativeModule} in all isolates that have 311 // access to it. Call this after top-tier compilation finished. 312 // This will spawn foreground tasks that do *not* keep the NativeModule alive. 313 void SampleTopTierCodeSizeInAllIsolates(const std::shared_ptr<NativeModule>&); 314 315 // Called by each Isolate to report its live code for a GC cycle. First 316 // version reports an externally determined set of live code (might be empty), 317 // second version gets live code from the execution stack of that isolate. 318 void ReportLiveCodeForGC(Isolate*, Vector<WasmCode*>); 319 void ReportLiveCodeFromStackForGC(Isolate*); 320 321 // Add potentially dead code. The occurrence in the set of potentially dead 322 // code counts as a reference, and is decremented on the next GC. 323 // Returns {true} if the code was added to the set of potentially dead code, 324 // {false} if an entry already exists. The ref count is *unchanged* in any 325 // case. 326 V8_WARN_UNUSED_RESULT bool AddPotentiallyDeadCode(WasmCode*); 327 328 // Free dead code. 329 using DeadCodeMap = std::unordered_map<NativeModule*, std::vector<WasmCode*>>; 330 void FreeDeadCode(const DeadCodeMap&); 331 void FreeDeadCodeLocked(const DeadCodeMap&); 332 333 Handle<Script> GetOrCreateScript(Isolate*, 334 const std::shared_ptr<NativeModule>&, 335 Vector<const char> source_url = {}); 336 337 // Take shared ownership of a compile job handle, such that we can synchronize 338 // on that before the engine dies. 339 void ShepherdCompileJobHandle(std::shared_ptr<JobHandle>); 340 341 // Call on process start and exit. 342 static void InitializeOncePerProcess(); 343 static void GlobalTearDown(); 344 345 // Returns a reference to the WasmEngine shared by the entire process. Try to 346 // use {Isolate::wasm_engine} instead if it is available, which encapsulates 347 // engine lifetime decisions during Isolate bootstrapping. 348 static std::shared_ptr<WasmEngine> GetWasmEngine(); 349 350 private: 351 struct CurrentGCInfo; 352 struct IsolateInfo; 353 struct NativeModuleInfo; 354 355 AsyncCompileJob* CreateAsyncCompileJob( 356 Isolate* isolate, const WasmFeatures& enabled, 357 std::unique_ptr<byte[]> bytes_copy, size_t length, 358 Handle<Context> context, const char* api_method_name, 359 std::shared_ptr<CompilationResultResolver> resolver); 360 361 void TriggerGC(int8_t gc_sequence_index); 362 363 // Remove an isolate from the outstanding isolates of the current GC. Returns 364 // true if the isolate was still outstanding, false otherwise. Hold {mutex_} 365 // when calling this method. 366 bool RemoveIsolateFromCurrentGC(Isolate*); 367 368 // Finish a GC if there are no more outstanding isolates. Hold {mutex_} when 369 // calling this method. 370 void PotentiallyFinishCurrentGC(); 371 372 WasmCodeManager code_manager_; 373 AccountingAllocator allocator_; 374 375 #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 376 // Implements a GDB-remote stub for WebAssembly debugging. 377 std::unique_ptr<gdb_server::GdbServer> gdb_server_; 378 #endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 379 380 // This mutex protects all information which is mutated concurrently or 381 // fields that are initialized lazily on the first access. 382 base::Mutex mutex_; 383 384 ////////////////////////////////////////////////////////////////////////////// 385 // Protected by {mutex_}: 386 387 // We use an AsyncCompileJob as the key for itself so that we can delete the 388 // job from the map when it is finished. 389 std::unordered_map<AsyncCompileJob*, std::unique_ptr<AsyncCompileJob>> 390 async_compile_jobs_; 391 392 std::unique_ptr<CompilationStatistics> compilation_stats_; 393 std::unique_ptr<CodeTracer> code_tracer_; 394 395 // Set of isolates which use this WasmEngine. 396 std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_; 397 398 // Set of native modules managed by this engine. 399 std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>> 400 native_modules_; 401 402 // Background compile jobs that are still running. We need to join them before 403 // the engine gets deleted. Otherwise we don't care when exactly they finish. 404 std::vector<std::shared_ptr<JobHandle>> compile_job_handles_; 405 406 // Size of code that became dead since the last GC. If this exceeds a certain 407 // threshold, a new GC is triggered. 408 size_t new_potentially_dead_code_size_ = 0; 409 410 // If an engine-wide GC is currently running, this pointer stores information 411 // about that. 412 std::unique_ptr<CurrentGCInfo> current_gc_info_; 413 414 NativeModuleCache native_module_cache_; 415 416 // End of fields protected by {mutex_}. 417 ////////////////////////////////////////////////////////////////////////////// 418 }; 419 420 } // namespace wasm 421 } // namespace internal 422 } // namespace v8 423 424 #endif // V8_WASM_WASM_ENGINE_H_ 425