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