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