1// Copyright 2018 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#include "src/wasm/wasm-engine.h" 6 7#include "src/base/functional.h" 8#include "src/base/platform/time.h" 9#include "src/common/globals.h" 10#include "src/diagnostics/code-tracer.h" 11#include "src/diagnostics/compilation-statistics.h" 12#include "src/execution/frames.h" 13#include "src/execution/v8threads.h" 14#include "src/handles/global-handles-inl.h" 15#include "src/logging/counters.h" 16#include "src/logging/metrics.h" 17#include "src/objects/heap-number.h" 18#include "src/objects/js-promise.h" 19#include "src/objects/managed-inl.h" 20#include "src/objects/objects-inl.h" 21#include "src/strings/string-hasher-inl.h" 22#include "src/utils/ostreams.h" 23#include "src/wasm/function-compiler.h" 24#include "src/wasm/memory-protection-key.h" 25#include "src/wasm/module-compiler.h" 26#include "src/wasm/module-decoder.h" 27#include "src/wasm/module-instantiate.h" 28#include "src/wasm/streaming-decoder.h" 29#include "src/wasm/wasm-debug.h" 30#include "src/wasm/wasm-limits.h" 31#include "src/wasm/wasm-objects-inl.h" 32 33#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 34#include "src/base/platform/wrappers.h" 35#include "src/debug/wasm/gdb-server/gdb-server.h" 36#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 37 38namespace v8 { 39namespace internal { 40namespace wasm { 41 42#define TRACE_CODE_GC(...) \ 43 do { \ 44 if (FLAG_trace_wasm_code_gc) PrintF("[wasm-gc] " __VA_ARGS__); \ 45 } while (false) 46 47namespace { 48// A task to log a set of {WasmCode} objects in an isolate. It does not own any 49// data itself, since it is owned by the platform, so lifetime is not really 50// bound to the wasm engine. 51class LogCodesTask : public Task { 52 public: 53 LogCodesTask(base::Mutex* mutex, LogCodesTask** task_slot, Isolate* isolate, 54 WasmEngine* engine) 55 : mutex_(mutex), 56 task_slot_(task_slot), 57 isolate_(isolate), 58 engine_(engine) { 59 DCHECK_NOT_NULL(task_slot); 60 DCHECK_NOT_NULL(isolate); 61 } 62 63 ~LogCodesTask() override { 64 // If the platform deletes this task before executing it, we also deregister 65 // it to avoid use-after-free from still-running background threads. 66 if (!cancelled()) DeregisterTask(); 67 } 68 69 void Run() override { 70 if (cancelled()) return; 71 DeregisterTask(); 72 engine_->LogOutstandingCodesForIsolate(isolate_); 73 } 74 75 void Cancel() { 76 // Cancel will only be called on Isolate shutdown, which happens on the 77 // Isolate's foreground thread. Thus no synchronization needed. 78 isolate_ = nullptr; 79 } 80 81 bool cancelled() const { return isolate_ == nullptr; } 82 83 void DeregisterTask() { 84 // The task will only be deregistered from the foreground thread (executing 85 // this task or calling its destructor), thus we do not need synchronization 86 // on this field access. 87 if (task_slot_ == nullptr) return; // already deregistered. 88 // Remove this task from the {IsolateInfo} in the engine. The next 89 // logging request will allocate and schedule a new task. 90 base::MutexGuard guard(mutex_); 91 DCHECK_EQ(this, *task_slot_); 92 *task_slot_ = nullptr; 93 task_slot_ = nullptr; 94 } 95 96 private: 97 // The mutex of the WasmEngine. 98 base::Mutex* const mutex_; 99 // The slot in the WasmEngine where this LogCodesTask is stored. This is 100 // cleared by this task before execution or on task destruction. 101 LogCodesTask** task_slot_; 102 Isolate* isolate_; 103 WasmEngine* const engine_; 104}; 105 106void CheckNoArchivedThreads(Isolate* isolate) { 107 class ArchivedThreadsVisitor : public ThreadVisitor { 108 void VisitThread(Isolate* isolate, ThreadLocalTop* top) override { 109 // Archived threads are rarely used, and not combined with Wasm at the 110 // moment. Implement this and test it properly once we have a use case for 111 // that. 112 FATAL("archived threads in combination with wasm not supported"); 113 } 114 } archived_threads_visitor; 115 isolate->thread_manager()->IterateArchivedThreads(&archived_threads_visitor); 116} 117 118class WasmGCForegroundTask : public CancelableTask { 119 public: 120 explicit WasmGCForegroundTask(Isolate* isolate) 121 : CancelableTask(isolate->cancelable_task_manager()), isolate_(isolate) {} 122 123 void RunInternal() final { 124 // The stack can contain live frames, for instance when this is invoked 125 // during a pause or a breakpoint. 126 GetWasmEngine()->ReportLiveCodeFromStackForGC(isolate_); 127 } 128 129 private: 130 Isolate* isolate_; 131}; 132 133class WeakScriptHandle { 134 public: 135 explicit WeakScriptHandle(Handle<Script> script) : script_id_(script->id()) { 136 DCHECK(script->name().IsString() || script->name().IsUndefined()); 137 if (script->name().IsString()) { 138 std::unique_ptr<char[]> source_url = 139 String::cast(script->name()).ToCString(); 140 // Convert from {unique_ptr} to {shared_ptr}. 141 source_url_ = {source_url.release(), source_url.get_deleter()}; 142 } 143 auto global_handle = 144 script->GetIsolate()->global_handles()->Create(*script); 145 location_ = std::make_unique<Address*>(global_handle.location()); 146 GlobalHandles::MakeWeak(location_.get()); 147 } 148 149 // Usually the destructor of this class should always be called after the weak 150 // callback because the Script keeps the NativeModule alive. So we expect the 151 // handle to be destroyed and the location to be reset already. 152 // We cannot check this because of one exception. When the native module is 153 // freed during isolate shutdown, the destructor will be called 154 // first, and the callback will never be called. 155 ~WeakScriptHandle() = default; 156 157 WeakScriptHandle(WeakScriptHandle&&) V8_NOEXCEPT = default; 158 159 Handle<Script> handle() const { return Handle<Script>(*location_); } 160 161 int script_id() const { return script_id_; } 162 163 const std::shared_ptr<const char>& source_url() const { return source_url_; } 164 165 private: 166 // Store the location in a unique_ptr so that its address stays the same even 167 // when this object is moved/copied. 168 std::unique_ptr<Address*> location_; 169 170 // Store the script ID independent of the weak handle, such that it's always 171 // available. 172 int script_id_; 173 174 // Similar for the source URL. We cannot dereference the Handle from arbitrary 175 // threads, but we need the URL available for code logging. 176 // The shared pointer is kept alive by unlogged code, even if this entry is 177 // collected in the meantime. 178 // TODO(chromium:1132260): Revisit this for huge URLs. 179 std::shared_ptr<const char> source_url_; 180}; 181 182} // namespace 183 184std::shared_ptr<NativeModule> NativeModuleCache::MaybeGetNativeModule( 185 ModuleOrigin origin, base::Vector<const uint8_t> wire_bytes) { 186 if (origin != kWasmOrigin) return nullptr; 187 base::MutexGuard lock(&mutex_); 188 size_t prefix_hash = PrefixHash(wire_bytes); 189 NativeModuleCache::Key key{prefix_hash, wire_bytes}; 190 while (true) { 191 auto it = map_.find(key); 192 if (it == map_.end()) { 193 // Even though this exact key is not in the cache, there might be a 194 // matching prefix hash indicating that a streaming compilation is 195 // currently compiling a module with the same prefix. {OnFinishedStream} 196 // happens on the main thread too, so waiting for streaming compilation to 197 // finish would create a deadlock. Instead, compile the module twice and 198 // handle the conflict in {UpdateNativeModuleCache}. 199 200 // Insert a {nullopt} entry to let other threads know that this 201 // {NativeModule} is already being created on another thread. 202 auto p = map_.emplace(key, base::nullopt); 203 USE(p); 204 DCHECK(p.second); 205 return nullptr; 206 } 207 if (it->second.has_value()) { 208 if (auto shared_native_module = it->second.value().lock()) { 209 DCHECK_EQ(shared_native_module->wire_bytes(), wire_bytes); 210 return shared_native_module; 211 } 212 } 213 // TODO(11858): This deadlocks in predictable mode, because there is only a 214 // single thread. 215 cache_cv_.Wait(&mutex_); 216 } 217} 218 219bool NativeModuleCache::GetStreamingCompilationOwnership(size_t prefix_hash) { 220 base::MutexGuard lock(&mutex_); 221 auto it = map_.lower_bound(Key{prefix_hash, {}}); 222 if (it != map_.end() && it->first.prefix_hash == prefix_hash) { 223 DCHECK_IMPLIES(!it->first.bytes.empty(), 224 PrefixHash(it->first.bytes) == prefix_hash); 225 return false; 226 } 227 Key key{prefix_hash, {}}; 228 DCHECK_EQ(0, map_.count(key)); 229 map_.emplace(key, base::nullopt); 230 return true; 231} 232 233void NativeModuleCache::StreamingCompilationFailed(size_t prefix_hash) { 234 base::MutexGuard lock(&mutex_); 235 Key key{prefix_hash, {}}; 236 DCHECK_EQ(1, map_.count(key)); 237 map_.erase(key); 238 cache_cv_.NotifyAll(); 239} 240 241std::shared_ptr<NativeModule> NativeModuleCache::Update( 242 std::shared_ptr<NativeModule> native_module, bool error) { 243 DCHECK_NOT_NULL(native_module); 244 if (native_module->module()->origin != kWasmOrigin) return native_module; 245 base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes(); 246 DCHECK(!wire_bytes.empty()); 247 size_t prefix_hash = PrefixHash(native_module->wire_bytes()); 248 base::MutexGuard lock(&mutex_); 249 map_.erase(Key{prefix_hash, {}}); 250 const Key key{prefix_hash, wire_bytes}; 251 auto it = map_.find(key); 252 if (it != map_.end()) { 253 if (it->second.has_value()) { 254 auto conflicting_module = it->second.value().lock(); 255 if (conflicting_module != nullptr) { 256 DCHECK_EQ(conflicting_module->wire_bytes(), wire_bytes); 257 return conflicting_module; 258 } 259 } 260 map_.erase(it); 261 } 262 if (!error) { 263 // The key now points to the new native module's owned copy of the bytes, 264 // so that it stays valid until the native module is freed and erased from 265 // the map. 266 auto p = map_.emplace( 267 key, base::Optional<std::weak_ptr<NativeModule>>(native_module)); 268 USE(p); 269 DCHECK(p.second); 270 } 271 cache_cv_.NotifyAll(); 272 return native_module; 273} 274 275void NativeModuleCache::Erase(NativeModule* native_module) { 276 if (native_module->module()->origin != kWasmOrigin) return; 277 // Happens in some tests where bytes are set directly. 278 if (native_module->wire_bytes().empty()) return; 279 base::MutexGuard lock(&mutex_); 280 size_t prefix_hash = PrefixHash(native_module->wire_bytes()); 281 map_.erase(Key{prefix_hash, native_module->wire_bytes()}); 282 cache_cv_.NotifyAll(); 283} 284 285// static 286size_t NativeModuleCache::WireBytesHash(base::Vector<const uint8_t> bytes) { 287 return StringHasher::HashSequentialString( 288 reinterpret_cast<const char*>(bytes.begin()), bytes.length(), 289 kZeroHashSeed); 290} 291 292// static 293size_t NativeModuleCache::PrefixHash(base::Vector<const uint8_t> wire_bytes) { 294 // Compute the hash as a combined hash of the sections up to the code section 295 // header, to mirror the way streaming compilation does it. 296 Decoder decoder(wire_bytes.begin(), wire_bytes.end()); 297 decoder.consume_bytes(8, "module header"); 298 size_t hash = NativeModuleCache::WireBytesHash(wire_bytes.SubVector(0, 8)); 299 SectionCode section_id = SectionCode::kUnknownSectionCode; 300 while (decoder.ok() && decoder.more()) { 301 section_id = static_cast<SectionCode>(decoder.consume_u8()); 302 uint32_t section_size = decoder.consume_u32v("section size"); 303 if (section_id == SectionCode::kCodeSectionCode) { 304 uint32_t num_functions = decoder.consume_u32v("num functions"); 305 // If {num_functions} is 0, the streaming decoder skips the section. Do 306 // the same here to ensure hashes are consistent. 307 if (num_functions != 0) { 308 hash = base::hash_combine(hash, section_size); 309 } 310 break; 311 } 312 const uint8_t* payload_start = decoder.pc(); 313 decoder.consume_bytes(section_size, "section payload"); 314 size_t section_hash = NativeModuleCache::WireBytesHash( 315 base::Vector<const uint8_t>(payload_start, section_size)); 316 hash = base::hash_combine(hash, section_hash); 317 } 318 return hash; 319} 320 321struct WasmEngine::CurrentGCInfo { 322 explicit CurrentGCInfo(int8_t gc_sequence_index) 323 : gc_sequence_index(gc_sequence_index) { 324 DCHECK_NE(0, gc_sequence_index); 325 } 326 327 // Set of isolates that did not scan their stack yet for used WasmCode, and 328 // their scheduled foreground task. 329 std::unordered_map<Isolate*, WasmGCForegroundTask*> outstanding_isolates; 330 331 // Set of dead code. Filled with all potentially dead code on initialization. 332 // Code that is still in-use is removed by the individual isolates. 333 std::unordered_set<WasmCode*> dead_code; 334 335 // The number of GCs triggered in the native module that triggered this GC. 336 // This is stored in the histogram for each participating isolate during 337 // execution of that isolate's foreground task. 338 const int8_t gc_sequence_index; 339 340 // If during this GC, another GC was requested, we skipped that other GC (we 341 // only run one GC at a time). Remember though to trigger another one once 342 // this one finishes. {next_gc_sequence_index} is 0 if no next GC is needed, 343 // and >0 otherwise. It stores the {num_code_gcs_triggered} of the native 344 // module which triggered the next GC. 345 int8_t next_gc_sequence_index = 0; 346 347 // The start time of this GC; used for tracing and sampled via {Counters}. 348 // Can be null ({TimeTicks::IsNull()}) if timer is not high resolution. 349 base::TimeTicks start_time; 350}; 351 352struct WasmEngine::IsolateInfo { 353 explicit IsolateInfo(Isolate* isolate) 354 : log_codes(WasmCode::ShouldBeLogged(isolate)), 355 async_counters(isolate->async_counters()), 356 wrapper_compilation_barrier_(std::make_shared<OperationsBarrier>()) { 357 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); 358 v8::Platform* platform = V8::GetCurrentPlatform(); 359 foreground_task_runner = platform->GetForegroundTaskRunner(v8_isolate); 360 } 361 362#ifdef DEBUG 363 ~IsolateInfo() { 364 // Before destructing, the {WasmEngine} must have cleared outstanding code 365 // to log. 366 DCHECK_EQ(0, code_to_log.size()); 367 } 368#endif 369 370 // All native modules that are being used by this Isolate. 371 std::unordered_set<NativeModule*> native_modules; 372 373 // Scripts created for each native module in this isolate. 374 std::unordered_map<NativeModule*, WeakScriptHandle> scripts; 375 376 // Caches whether code needs to be logged on this isolate. 377 bool log_codes; 378 379 // The currently scheduled LogCodesTask. 380 LogCodesTask* log_codes_task = nullptr; 381 382 // Maps script ID to vector of code objects that still need to be logged, and 383 // the respective source URL. 384 struct CodeToLogPerScript { 385 std::vector<WasmCode*> code; 386 std::shared_ptr<const char> source_url; 387 }; 388 std::unordered_map<int, CodeToLogPerScript> code_to_log; 389 390 // The foreground task runner of the isolate (can be called from background). 391 std::shared_ptr<v8::TaskRunner> foreground_task_runner; 392 393 const std::shared_ptr<Counters> async_counters; 394 395 // Keep new modules in tiered down state. 396 bool keep_tiered_down = false; 397 398 // Keep track whether we already added a sample for PKU support (we only want 399 // one sample per Isolate). 400 bool pku_support_sampled = false; 401 402 // Elapsed time since last throw/rethrow/catch event. 403 base::ElapsedTimer throw_timer; 404 base::ElapsedTimer rethrow_timer; 405 base::ElapsedTimer catch_timer; 406 407 // Total number of exception events in this isolate. 408 int throw_count = 0; 409 int rethrow_count = 0; 410 int catch_count = 0; 411 412 // Operations barrier to synchronize on wrapper compilation on isolate 413 // shutdown. 414 // TODO(wasm): Remove this once we can use the generic js-to-wasm wrapper 415 // everywhere. 416 std::shared_ptr<OperationsBarrier> wrapper_compilation_barrier_; 417}; 418 419struct WasmEngine::NativeModuleInfo { 420 explicit NativeModuleInfo(std::weak_ptr<NativeModule> native_module) 421 : weak_ptr(std::move(native_module)) {} 422 423 // Weak pointer, to gain back a shared_ptr if needed. 424 std::weak_ptr<NativeModule> weak_ptr; 425 426 // Set of isolates using this NativeModule. 427 std::unordered_set<Isolate*> isolates; 428 429 // Set of potentially dead code. This set holds one ref for each code object, 430 // until code is detected to be really dead. At that point, the ref count is 431 // decremented and code is move to the {dead_code} set. If the code is finally 432 // deleted, it is also removed from {dead_code}. 433 std::unordered_set<WasmCode*> potentially_dead_code; 434 435 // Code that is not being executed in any isolate any more, but the ref count 436 // did not drop to zero yet. 437 std::unordered_set<WasmCode*> dead_code; 438 439 // Number of code GCs triggered because code in this native module became 440 // potentially dead. 441 int8_t num_code_gcs_triggered = 0; 442}; 443 444WasmEngine::WasmEngine() = default; 445 446WasmEngine::~WasmEngine() { 447#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 448 // Synchronize on the GDB-remote thread, if running. 449 gdb_server_.reset(); 450#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 451 452 operations_barrier_->CancelAndWait(); 453 454 // All AsyncCompileJobs have been canceled. 455 DCHECK(async_compile_jobs_.empty()); 456 // All Isolates have been deregistered. 457 DCHECK(isolates_.empty()); 458 // All NativeModules did die. 459 DCHECK(native_modules_.empty()); 460 // Native module cache does not leak. 461 DCHECK(native_module_cache_.empty()); 462} 463 464bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled, 465 const ModuleWireBytes& bytes, 466 std::string* error_message) { 467 TRACE_EVENT0("v8.wasm", "wasm.SyncValidate"); 468 // TODO(titzer): remove dependency on the isolate. 469 if (bytes.start() == nullptr || bytes.length() == 0) { 470 if (error_message) *error_message = "empty module wire bytes"; 471 return false; 472 } 473 auto result = DecodeWasmModule( 474 enabled, bytes.start(), bytes.end(), true, kWasmOrigin, 475 isolate->counters(), isolate->metrics_recorder(), 476 isolate->GetOrRegisterRecorderContextId(isolate->native_context()), 477 DecodingMethod::kSync, allocator()); 478 if (result.failed() && error_message) { 479 *error_message = result.error().message(); 480 } 481 return result.ok(); 482} 483 484MaybeHandle<AsmWasmData> WasmEngine::SyncCompileTranslatedAsmJs( 485 Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes, 486 base::Vector<const byte> asm_js_offset_table_bytes, 487 Handle<HeapNumber> uses_bitset, LanguageMode language_mode) { 488 int compilation_id = next_compilation_id_.fetch_add(1); 489 TRACE_EVENT1("v8.wasm", "wasm.SyncCompileTranslatedAsmJs", "id", 490 compilation_id); 491 ModuleOrigin origin = language_mode == LanguageMode::kSloppy 492 ? kAsmJsSloppyOrigin 493 : kAsmJsStrictOrigin; 494 // TODO(leszeks): If we want asm.js in UKM, we should figure out a way to pass 495 // the context id in here. 496 v8::metrics::Recorder::ContextId context_id = 497 v8::metrics::Recorder::ContextId::Empty(); 498 ModuleResult result = DecodeWasmModule( 499 WasmFeatures::ForAsmjs(), bytes.start(), bytes.end(), false, origin, 500 isolate->counters(), isolate->metrics_recorder(), context_id, 501 DecodingMethod::kSync, allocator()); 502 if (result.failed()) { 503 // This happens once in a while when we have missed some limit check 504 // in the asm parser. Output an error message to help diagnose, but crash. 505 std::cout << result.error().message(); 506 UNREACHABLE(); 507 } 508 509 result.value()->asm_js_offset_information = 510 std::make_unique<AsmJsOffsetInformation>(asm_js_offset_table_bytes); 511 512 // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated 513 // in {CompileToNativeModule}. 514 Handle<FixedArray> export_wrappers; 515 std::shared_ptr<NativeModule> native_module = CompileToNativeModule( 516 isolate, WasmFeatures::ForAsmjs(), thrower, std::move(result).value(), 517 bytes, &export_wrappers, compilation_id, context_id); 518 if (!native_module) return {}; 519 520 return AsmWasmData::New(isolate, std::move(native_module), export_wrappers, 521 uses_bitset); 522} 523 524Handle<WasmModuleObject> WasmEngine::FinalizeTranslatedAsmJs( 525 Isolate* isolate, Handle<AsmWasmData> asm_wasm_data, 526 Handle<Script> script) { 527 std::shared_ptr<NativeModule> native_module = 528 asm_wasm_data->managed_native_module().get(); 529 Handle<FixedArray> export_wrappers = 530 handle(asm_wasm_data->export_wrappers(), isolate); 531 Handle<WasmModuleObject> module_object = WasmModuleObject::New( 532 isolate, std::move(native_module), script, export_wrappers); 533 return module_object; 534} 535 536MaybeHandle<WasmModuleObject> WasmEngine::SyncCompile( 537 Isolate* isolate, const WasmFeatures& enabled, ErrorThrower* thrower, 538 const ModuleWireBytes& bytes) { 539 int compilation_id = next_compilation_id_.fetch_add(1); 540 TRACE_EVENT1("v8.wasm", "wasm.SyncCompile", "id", compilation_id); 541 v8::metrics::Recorder::ContextId context_id = 542 isolate->GetOrRegisterRecorderContextId(isolate->native_context()); 543 ModuleResult result = 544 DecodeWasmModule(enabled, bytes.start(), bytes.end(), false, kWasmOrigin, 545 isolate->counters(), isolate->metrics_recorder(), 546 context_id, DecodingMethod::kSync, allocator()); 547 if (result.failed()) { 548 thrower->CompileFailed(result.error()); 549 return {}; 550 } 551 552 // Transfer ownership of the WasmModule to the {Managed<WasmModule>} generated 553 // in {CompileToNativeModule}. 554 Handle<FixedArray> export_wrappers; 555 std::shared_ptr<NativeModule> native_module = CompileToNativeModule( 556 isolate, enabled, thrower, std::move(result).value(), bytes, 557 &export_wrappers, compilation_id, context_id); 558 if (!native_module) return {}; 559 560#ifdef DEBUG 561 // Ensure that code GC will check this isolate for live code. 562 { 563 base::MutexGuard lock(&mutex_); 564 DCHECK_EQ(1, isolates_.count(isolate)); 565 DCHECK_EQ(1, isolates_[isolate]->native_modules.count(native_module.get())); 566 DCHECK_EQ(1, native_modules_.count(native_module.get())); 567 DCHECK_EQ(1, native_modules_[native_module.get()]->isolates.count(isolate)); 568 } 569#endif 570 571 constexpr base::Vector<const char> kNoSourceUrl; 572 Handle<Script> script = 573 GetOrCreateScript(isolate, native_module, kNoSourceUrl); 574 575 native_module->LogWasmCodes(isolate, *script); 576 577 // Create the compiled module object and populate with compiled functions 578 // and information needed at instantiation time. This object needs to be 579 // serializable. Instantiation may occur off a deserialized version of this 580 // object. 581 Handle<WasmModuleObject> module_object = WasmModuleObject::New( 582 isolate, std::move(native_module), script, export_wrappers); 583 584 // Finish the Wasm script now and make it public to the debugger. 585 isolate->debug()->OnAfterCompile(script); 586 return module_object; 587} 588 589MaybeHandle<WasmInstanceObject> WasmEngine::SyncInstantiate( 590 Isolate* isolate, ErrorThrower* thrower, 591 Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, 592 MaybeHandle<JSArrayBuffer> memory) { 593 TRACE_EVENT0("v8.wasm", "wasm.SyncInstantiate"); 594 return InstantiateToInstanceObject(isolate, thrower, module_object, imports, 595 memory); 596} 597 598void WasmEngine::AsyncInstantiate( 599 Isolate* isolate, std::unique_ptr<InstantiationResultResolver> resolver, 600 Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports) { 601 ErrorThrower thrower(isolate, "WebAssembly.instantiate()"); 602 TRACE_EVENT0("v8.wasm", "wasm.AsyncInstantiate"); 603 // Instantiate a TryCatch so that caught exceptions won't progagate out. 604 // They will still be set as pending exceptions on the isolate. 605 // TODO(clemensb): Avoid TryCatch, use Execution::TryCall internally to invoke 606 // start function and report thrown exception explicitly via out argument. 607 v8::TryCatch catcher(reinterpret_cast<v8::Isolate*>(isolate)); 608 catcher.SetVerbose(false); 609 catcher.SetCaptureMessage(false); 610 611 MaybeHandle<WasmInstanceObject> instance_object = SyncInstantiate( 612 isolate, &thrower, module_object, imports, Handle<JSArrayBuffer>::null()); 613 614 if (!instance_object.is_null()) { 615 resolver->OnInstantiationSucceeded(instance_object.ToHandleChecked()); 616 return; 617 } 618 619 if (isolate->has_pending_exception()) { 620 // The JS code executed during instantiation has thrown an exception. 621 // We have to move the exception to the promise chain. 622 Handle<Object> exception(isolate->pending_exception(), isolate); 623 isolate->clear_pending_exception(); 624 *isolate->external_caught_exception_address() = false; 625 resolver->OnInstantiationFailed(exception); 626 thrower.Reset(); 627 } else { 628 DCHECK(thrower.error()); 629 resolver->OnInstantiationFailed(thrower.Reify()); 630 } 631} 632 633void WasmEngine::AsyncCompile( 634 Isolate* isolate, const WasmFeatures& enabled, 635 std::shared_ptr<CompilationResultResolver> resolver, 636 const ModuleWireBytes& bytes, bool is_shared, 637 const char* api_method_name_for_errors) { 638 int compilation_id = next_compilation_id_.fetch_add(1); 639 TRACE_EVENT1("v8.wasm", "wasm.AsyncCompile", "id", compilation_id); 640 if (!FLAG_wasm_async_compilation) { 641 // Asynchronous compilation disabled; fall back on synchronous compilation. 642 ErrorThrower thrower(isolate, api_method_name_for_errors); 643 MaybeHandle<WasmModuleObject> module_object; 644 if (is_shared) { 645 // Make a copy of the wire bytes to avoid concurrent modification. 646 std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]); 647 memcpy(copy.get(), bytes.start(), bytes.length()); 648 ModuleWireBytes bytes_copy(copy.get(), copy.get() + bytes.length()); 649 module_object = SyncCompile(isolate, enabled, &thrower, bytes_copy); 650 } else { 651 // The wire bytes are not shared, OK to use them directly. 652 module_object = SyncCompile(isolate, enabled, &thrower, bytes); 653 } 654 if (thrower.error()) { 655 resolver->OnCompilationFailed(thrower.Reify()); 656 return; 657 } 658 Handle<WasmModuleObject> module = module_object.ToHandleChecked(); 659 resolver->OnCompilationSucceeded(module); 660 return; 661 } 662 663 if (FLAG_wasm_test_streaming) { 664 std::shared_ptr<StreamingDecoder> streaming_decoder = 665 StartStreamingCompilation( 666 isolate, enabled, handle(isolate->context(), isolate), 667 api_method_name_for_errors, std::move(resolver)); 668 streaming_decoder->OnBytesReceived(bytes.module_bytes()); 669 streaming_decoder->Finish(); 670 return; 671 } 672 // Make a copy of the wire bytes in case the user program changes them 673 // during asynchronous compilation. 674 std::unique_ptr<byte[]> copy(new byte[bytes.length()]); 675 memcpy(copy.get(), bytes.start(), bytes.length()); 676 677 AsyncCompileJob* job = CreateAsyncCompileJob( 678 isolate, enabled, std::move(copy), bytes.length(), 679 handle(isolate->context(), isolate), api_method_name_for_errors, 680 std::move(resolver), compilation_id); 681 job->Start(); 682} 683 684std::shared_ptr<StreamingDecoder> WasmEngine::StartStreamingCompilation( 685 Isolate* isolate, const WasmFeatures& enabled, Handle<Context> context, 686 const char* api_method_name, 687 std::shared_ptr<CompilationResultResolver> resolver) { 688 int compilation_id = next_compilation_id_.fetch_add(1); 689 TRACE_EVENT1("v8.wasm", "wasm.StartStreamingCompilation", "id", 690 compilation_id); 691 if (FLAG_wasm_async_compilation) { 692 AsyncCompileJob* job = CreateAsyncCompileJob( 693 isolate, enabled, std::unique_ptr<byte[]>(nullptr), 0, context, 694 api_method_name, std::move(resolver), compilation_id); 695 return job->CreateStreamingDecoder(); 696 } 697 return StreamingDecoder::CreateSyncStreamingDecoder( 698 isolate, enabled, context, api_method_name, std::move(resolver)); 699} 700 701void WasmEngine::CompileFunction(Isolate* isolate, NativeModule* native_module, 702 uint32_t function_index, ExecutionTier tier) { 703 // Note we assume that "one-off" compilations can discard detected features. 704 WasmFeatures detected = WasmFeatures::None(); 705 WasmCompilationUnit::CompileWasmFunction( 706 isolate, native_module, &detected, 707 &native_module->module()->functions[function_index], tier); 708} 709 710void WasmEngine::TierDownAllModulesPerIsolate(Isolate* isolate) { 711 std::vector<std::shared_ptr<NativeModule>> native_modules; 712 { 713 base::MutexGuard lock(&mutex_); 714 if (isolates_[isolate]->keep_tiered_down) return; 715 isolates_[isolate]->keep_tiered_down = true; 716 for (auto* native_module : isolates_[isolate]->native_modules) { 717 native_module->SetTieringState(kTieredDown); 718 DCHECK_EQ(1, native_modules_.count(native_module)); 719 if (auto shared_ptr = native_modules_[native_module]->weak_ptr.lock()) { 720 native_modules.emplace_back(std::move(shared_ptr)); 721 } 722 } 723 } 724 for (auto& native_module : native_modules) { 725 native_module->RecompileForTiering(); 726 } 727} 728 729void WasmEngine::TierUpAllModulesPerIsolate(Isolate* isolate) { 730 // Only trigger recompilation after releasing the mutex, otherwise we risk 731 // deadlocks because of lock inversion. The bool tells whether the module 732 // needs recompilation for tier up. 733 std::vector<std::pair<std::shared_ptr<NativeModule>, bool>> native_modules; 734 { 735 base::MutexGuard lock(&mutex_); 736 isolates_[isolate]->keep_tiered_down = false; 737 auto test_can_tier_up = [this](NativeModule* native_module) { 738 DCHECK_EQ(1, native_modules_.count(native_module)); 739 for (auto* isolate : native_modules_[native_module]->isolates) { 740 DCHECK_EQ(1, isolates_.count(isolate)); 741 if (isolates_[isolate]->keep_tiered_down) return false; 742 } 743 return true; 744 }; 745 for (auto* native_module : isolates_[isolate]->native_modules) { 746 DCHECK_EQ(1, native_modules_.count(native_module)); 747 auto shared_ptr = native_modules_[native_module]->weak_ptr.lock(); 748 if (!shared_ptr) continue; // The module is not used any more. 749 if (!native_module->IsTieredDown()) continue; 750 // Only start tier-up if no other isolate needs this module in tiered 751 // down state. 752 bool tier_up = test_can_tier_up(native_module); 753 if (tier_up) native_module->SetTieringState(kTieredUp); 754 native_modules.emplace_back(std::move(shared_ptr), tier_up); 755 } 756 } 757 for (auto& entry : native_modules) { 758 auto& native_module = entry.first; 759 bool tier_up = entry.second; 760 // Remove all breakpoints set by this isolate. 761 if (native_module->HasDebugInfo()) { 762 native_module->GetDebugInfo()->RemoveIsolate(isolate); 763 } 764 if (tier_up) native_module->RecompileForTiering(); 765 } 766} 767 768std::shared_ptr<NativeModule> WasmEngine::ExportNativeModule( 769 Handle<WasmModuleObject> module_object) { 770 return module_object->shared_native_module(); 771} 772 773namespace { 774Handle<Script> CreateWasmScript(Isolate* isolate, 775 std::shared_ptr<NativeModule> native_module, 776 base::Vector<const char> source_url) { 777 Handle<Script> script = 778 isolate->factory()->NewScript(isolate->factory()->undefined_value()); 779 script->set_compilation_state(Script::COMPILATION_STATE_COMPILED); 780 script->set_context_data(isolate->native_context()->debug_context_id()); 781 script->set_type(Script::TYPE_WASM); 782 783 base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes(); 784 785 // The source URL of the script is 786 // - the original source URL if available (from the streaming API), 787 // - wasm://wasm/<module name>-<hash> if a module name has been set, or 788 // - wasm://wasm/<hash> otherwise. 789 const WasmModule* module = native_module->module(); 790 Handle<String> url_str; 791 if (!source_url.empty()) { 792 url_str = isolate->factory() 793 ->NewStringFromUtf8(source_url, AllocationType::kOld) 794 .ToHandleChecked(); 795 } else { 796 int hash = StringHasher::HashSequentialString( 797 reinterpret_cast<const char*>(wire_bytes.begin()), wire_bytes.length(), 798 kZeroHashSeed); 799 800 base::EmbeddedVector<char, 32> buffer; 801 if (module->name.is_empty()) { 802 // Build the URL in the form "wasm://wasm/<hash>". 803 int url_len = SNPrintF(buffer, "wasm://wasm/%08x", hash); 804 DCHECK(url_len >= 0 && url_len < buffer.length()); 805 url_str = isolate->factory() 806 ->NewStringFromUtf8(buffer.SubVector(0, url_len), 807 AllocationType::kOld) 808 .ToHandleChecked(); 809 } else { 810 // Build the URL in the form "wasm://wasm/<module name>-<hash>". 811 int hash_len = SNPrintF(buffer, "-%08x", hash); 812 DCHECK(hash_len >= 0 && hash_len < buffer.length()); 813 Handle<String> prefix = 814 isolate->factory()->NewStringFromStaticChars("wasm://wasm/"); 815 Handle<String> module_name = 816 WasmModuleObject::ExtractUtf8StringFromModuleBytes( 817 isolate, wire_bytes, module->name, kNoInternalize); 818 Handle<String> hash_str = 819 isolate->factory() 820 ->NewStringFromUtf8(buffer.SubVector(0, hash_len)) 821 .ToHandleChecked(); 822 // Concatenate the three parts. 823 url_str = isolate->factory() 824 ->NewConsString(prefix, module_name) 825 .ToHandleChecked(); 826 url_str = isolate->factory() 827 ->NewConsString(url_str, hash_str) 828 .ToHandleChecked(); 829 } 830 } 831 script->set_name(*url_str); 832 833 const WasmDebugSymbols& debug_symbols = module->debug_symbols; 834 if (debug_symbols.type == WasmDebugSymbols::Type::SourceMap && 835 !debug_symbols.external_url.is_empty()) { 836 base::Vector<const char> external_url = 837 ModuleWireBytes(wire_bytes).GetNameOrNull(debug_symbols.external_url); 838 MaybeHandle<String> src_map_str = isolate->factory()->NewStringFromUtf8( 839 external_url, AllocationType::kOld); 840 script->set_source_mapping_url(*src_map_str.ToHandleChecked()); 841 } 842 843 // Use the given shared {NativeModule}, but increase its reference count by 844 // allocating a new {Managed<T>} that the {Script} references. 845 size_t code_size_estimate = native_module->committed_code_space(); 846 size_t memory_estimate = 847 code_size_estimate + 848 wasm::WasmCodeManager::EstimateNativeModuleMetaDataSize(module); 849 Handle<Managed<wasm::NativeModule>> managed_native_module = 850 Managed<wasm::NativeModule>::FromSharedPtr(isolate, memory_estimate, 851 std::move(native_module)); 852 script->set_wasm_managed_native_module(*managed_native_module); 853 script->set_wasm_breakpoint_infos(ReadOnlyRoots(isolate).empty_fixed_array()); 854 script->set_wasm_weak_instance_list( 855 ReadOnlyRoots(isolate).empty_weak_array_list()); 856 return script; 857} 858} // namespace 859 860Handle<WasmModuleObject> WasmEngine::ImportNativeModule( 861 Isolate* isolate, std::shared_ptr<NativeModule> shared_native_module, 862 base::Vector<const char> source_url) { 863 NativeModule* native_module = shared_native_module.get(); 864 ModuleWireBytes wire_bytes(native_module->wire_bytes()); 865 Handle<Script> script = 866 GetOrCreateScript(isolate, shared_native_module, source_url); 867 Handle<FixedArray> export_wrappers; 868 CompileJsToWasmWrappers(isolate, native_module->module(), &export_wrappers); 869 Handle<WasmModuleObject> module_object = WasmModuleObject::New( 870 isolate, std::move(shared_native_module), script, export_wrappers); 871 { 872 base::MutexGuard lock(&mutex_); 873 DCHECK_EQ(1, isolates_.count(isolate)); 874 isolates_[isolate]->native_modules.insert(native_module); 875 DCHECK_EQ(1, native_modules_.count(native_module)); 876 native_modules_[native_module]->isolates.insert(isolate); 877 } 878 879 // Finish the Wasm script now and make it public to the debugger. 880 isolate->debug()->OnAfterCompile(script); 881 return module_object; 882} 883 884CompilationStatistics* WasmEngine::GetOrCreateTurboStatistics() { 885 base::MutexGuard guard(&mutex_); 886 if (compilation_stats_ == nullptr) { 887 compilation_stats_.reset(new CompilationStatistics()); 888 } 889 return compilation_stats_.get(); 890} 891 892void WasmEngine::DumpAndResetTurboStatistics() { 893 base::MutexGuard guard(&mutex_); 894 if (compilation_stats_ != nullptr) { 895 StdoutStream os; 896 os << AsPrintableStatistics{*compilation_stats_.get(), false} << std::endl; 897 } 898 compilation_stats_.reset(); 899} 900 901void WasmEngine::DumpTurboStatistics() { 902 base::MutexGuard guard(&mutex_); 903 if (compilation_stats_ != nullptr) { 904 StdoutStream os; 905 os << AsPrintableStatistics{*compilation_stats_.get(), false} << std::endl; 906 } 907} 908 909CodeTracer* WasmEngine::GetCodeTracer() { 910 base::MutexGuard guard(&mutex_); 911 if (code_tracer_ == nullptr) code_tracer_.reset(new CodeTracer(-1)); 912 return code_tracer_.get(); 913} 914 915AsyncCompileJob* WasmEngine::CreateAsyncCompileJob( 916 Isolate* isolate, const WasmFeatures& enabled, 917 std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context, 918 const char* api_method_name, 919 std::shared_ptr<CompilationResultResolver> resolver, int compilation_id) { 920 Handle<Context> incumbent_context = isolate->GetIncumbentContext(); 921 AsyncCompileJob* job = new AsyncCompileJob( 922 isolate, enabled, std::move(bytes_copy), length, context, 923 incumbent_context, api_method_name, std::move(resolver), compilation_id); 924 // Pass ownership to the unique_ptr in {async_compile_jobs_}. 925 base::MutexGuard guard(&mutex_); 926 async_compile_jobs_[job] = std::unique_ptr<AsyncCompileJob>(job); 927 return job; 928} 929 930std::unique_ptr<AsyncCompileJob> WasmEngine::RemoveCompileJob( 931 AsyncCompileJob* job) { 932 base::MutexGuard guard(&mutex_); 933 auto item = async_compile_jobs_.find(job); 934 DCHECK(item != async_compile_jobs_.end()); 935 std::unique_ptr<AsyncCompileJob> result = std::move(item->second); 936 async_compile_jobs_.erase(item); 937 return result; 938} 939 940bool WasmEngine::HasRunningCompileJob(Isolate* isolate) { 941 base::MutexGuard guard(&mutex_); 942 DCHECK_EQ(1, isolates_.count(isolate)); 943 for (auto& entry : async_compile_jobs_) { 944 if (entry.first->isolate() == isolate) return true; 945 } 946 return false; 947} 948 949void WasmEngine::DeleteCompileJobsOnContext(Handle<Context> context) { 950 // Under the mutex get all jobs to delete. Then delete them without holding 951 // the mutex, such that deletion can reenter the WasmEngine. 952 std::vector<std::unique_ptr<AsyncCompileJob>> jobs_to_delete; 953 { 954 base::MutexGuard guard(&mutex_); 955 for (auto it = async_compile_jobs_.begin(); 956 it != async_compile_jobs_.end();) { 957 if (!it->first->context().is_identical_to(context)) { 958 ++it; 959 continue; 960 } 961 jobs_to_delete.push_back(std::move(it->second)); 962 it = async_compile_jobs_.erase(it); 963 } 964 } 965} 966 967void WasmEngine::DeleteCompileJobsOnIsolate(Isolate* isolate) { 968 // Under the mutex get all jobs to delete. Then delete them without holding 969 // the mutex, such that deletion can reenter the WasmEngine. 970 std::vector<std::unique_ptr<AsyncCompileJob>> jobs_to_delete; 971 std::vector<std::weak_ptr<NativeModule>> modules_in_isolate; 972 std::shared_ptr<OperationsBarrier> wrapper_compilation_barrier; 973 { 974 base::MutexGuard guard(&mutex_); 975 for (auto it = async_compile_jobs_.begin(); 976 it != async_compile_jobs_.end();) { 977 if (it->first->isolate() != isolate) { 978 ++it; 979 continue; 980 } 981 jobs_to_delete.push_back(std::move(it->second)); 982 it = async_compile_jobs_.erase(it); 983 } 984 DCHECK_EQ(1, isolates_.count(isolate)); 985 auto* isolate_info = isolates_[isolate].get(); 986 wrapper_compilation_barrier = isolate_info->wrapper_compilation_barrier_; 987 for (auto* native_module : isolate_info->native_modules) { 988 DCHECK_EQ(1, native_modules_.count(native_module)); 989 modules_in_isolate.emplace_back(native_modules_[native_module]->weak_ptr); 990 } 991 } 992 993 // All modules that have not finished initial compilation yet cannot be 994 // shared with other isolates. Hence we cancel their compilation. In 995 // particular, this will cancel wrapper compilation which is bound to this 996 // isolate (this would be a UAF otherwise). 997 for (auto& weak_module : modules_in_isolate) { 998 if (auto shared_module = weak_module.lock()) { 999 shared_module->compilation_state()->CancelInitialCompilation(); 1000 } 1001 } 1002 1003 // After cancelling, wait for all current wrapper compilation to actually 1004 // finish. 1005 wrapper_compilation_barrier->CancelAndWait(); 1006} 1007 1008OperationsBarrier::Token WasmEngine::StartWrapperCompilation(Isolate* isolate) { 1009 base::MutexGuard guard(&mutex_); 1010 auto isolate_info_it = isolates_.find(isolate); 1011 if (isolate_info_it == isolates_.end()) return {}; 1012 return isolate_info_it->second->wrapper_compilation_barrier_->TryLock(); 1013} 1014 1015void WasmEngine::AddIsolate(Isolate* isolate) { 1016 base::MutexGuard guard(&mutex_); 1017 DCHECK_EQ(0, isolates_.count(isolate)); 1018 isolates_.emplace(isolate, std::make_unique<IsolateInfo>(isolate)); 1019 1020 // The isolate might access existing (cached) code without ever compiling any. 1021 // In that case, the current thread might still have the default permissions 1022 // for the memory protection key (== no access). Thus initialize the 1023 // permissions now. 1024 GetWasmCodeManager()->InitializeMemoryProtectionKeyPermissionsIfSupported(); 1025 1026 // Install sampling GC callback. 1027 // TODO(v8:7424): For now we sample module sizes in a GC callback. This will 1028 // bias samples towards apps with high memory pressure. We should switch to 1029 // using sampling based on regular intervals independent of the GC. 1030 auto callback = [](v8::Isolate* v8_isolate, v8::GCType type, 1031 v8::GCCallbackFlags flags, void* data) { 1032 Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate); 1033 Counters* counters = isolate->counters(); 1034 WasmEngine* engine = GetWasmEngine(); 1035 base::MutexGuard lock(&engine->mutex_); 1036 DCHECK_EQ(1, engine->isolates_.count(isolate)); 1037 for (auto* native_module : engine->isolates_[isolate]->native_modules) { 1038 native_module->SampleCodeSize(counters, NativeModule::kSampling); 1039 } 1040 }; 1041 isolate->heap()->AddGCEpilogueCallback(callback, v8::kGCTypeMarkSweepCompact, 1042 nullptr); 1043#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1044 if (gdb_server_) { 1045 gdb_server_->AddIsolate(isolate); 1046 } 1047#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1048} 1049 1050void WasmEngine::RemoveIsolate(Isolate* isolate) { 1051#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1052 if (gdb_server_) { 1053 gdb_server_->RemoveIsolate(isolate); 1054 } 1055#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1056 1057 base::MutexGuard guard(&mutex_); 1058 auto it = isolates_.find(isolate); 1059 DCHECK_NE(isolates_.end(), it); 1060 std::unique_ptr<IsolateInfo> info = std::move(it->second); 1061 isolates_.erase(it); 1062 for (auto* native_module : info->native_modules) { 1063 DCHECK_EQ(1, native_modules_.count(native_module)); 1064 DCHECK_EQ(1, native_modules_[native_module]->isolates.count(isolate)); 1065 auto* module = native_modules_[native_module].get(); 1066 module->isolates.erase(isolate); 1067 if (current_gc_info_) { 1068 for (WasmCode* code : module->potentially_dead_code) { 1069 current_gc_info_->dead_code.erase(code); 1070 } 1071 } 1072 if (native_module->HasDebugInfo()) { 1073 native_module->GetDebugInfo()->RemoveIsolate(isolate); 1074 } 1075 } 1076 if (current_gc_info_) { 1077 if (RemoveIsolateFromCurrentGC(isolate)) PotentiallyFinishCurrentGC(); 1078 } 1079 if (auto* task = info->log_codes_task) { 1080 task->Cancel(); 1081 for (auto& log_entry : info->code_to_log) { 1082 WasmCode::DecrementRefCount(base::VectorOf(log_entry.second.code)); 1083 } 1084 info->code_to_log.clear(); 1085 } 1086 DCHECK(info->code_to_log.empty()); 1087} 1088 1089void WasmEngine::LogCode(base::Vector<WasmCode*> code_vec) { 1090 if (code_vec.empty()) return; 1091 base::MutexGuard guard(&mutex_); 1092 NativeModule* native_module = code_vec[0]->native_module(); 1093 DCHECK_EQ(1, native_modules_.count(native_module)); 1094 for (Isolate* isolate : native_modules_[native_module]->isolates) { 1095 DCHECK_EQ(1, isolates_.count(isolate)); 1096 IsolateInfo* info = isolates_[isolate].get(); 1097 if (info->log_codes == false) continue; 1098 if (info->log_codes_task == nullptr) { 1099 auto new_task = std::make_unique<LogCodesTask>( 1100 &mutex_, &info->log_codes_task, isolate, this); 1101 info->log_codes_task = new_task.get(); 1102 info->foreground_task_runner->PostTask(std::move(new_task)); 1103 } 1104 if (info->code_to_log.empty()) { 1105 isolate->stack_guard()->RequestLogWasmCode(); 1106 } 1107 for (WasmCode* code : code_vec) { 1108 DCHECK_EQ(native_module, code->native_module()); 1109 code->IncRef(); 1110 } 1111 1112 auto script_it = info->scripts.find(native_module); 1113 // If the script does not yet exist, logging will happen later. If the weak 1114 // handle is cleared already, we also don't need to log any more. 1115 if (script_it == info->scripts.end()) continue; 1116 auto& log_entry = info->code_to_log[script_it->second.script_id()]; 1117 if (!log_entry.source_url) { 1118 log_entry.source_url = script_it->second.source_url(); 1119 } 1120 log_entry.code.insert(log_entry.code.end(), code_vec.begin(), 1121 code_vec.end()); 1122 } 1123} 1124 1125void WasmEngine::EnableCodeLogging(Isolate* isolate) { 1126 base::MutexGuard guard(&mutex_); 1127 auto it = isolates_.find(isolate); 1128 DCHECK_NE(isolates_.end(), it); 1129 it->second->log_codes = true; 1130} 1131 1132void WasmEngine::LogOutstandingCodesForIsolate(Isolate* isolate) { 1133 // Under the mutex, get the vector of wasm code to log. Then log and decrement 1134 // the ref count without holding the mutex. 1135 std::unordered_map<int, IsolateInfo::CodeToLogPerScript> code_to_log; 1136 { 1137 base::MutexGuard guard(&mutex_); 1138 DCHECK_EQ(1, isolates_.count(isolate)); 1139 code_to_log.swap(isolates_[isolate]->code_to_log); 1140 } 1141 1142 // Check again whether we still need to log code. 1143 bool should_log = WasmCode::ShouldBeLogged(isolate); 1144 1145 TRACE_EVENT0("v8.wasm", "wasm.LogCode"); 1146 for (auto& pair : code_to_log) { 1147 for (WasmCode* code : pair.second.code) { 1148 if (should_log) { 1149 code->LogCode(isolate, pair.second.source_url.get(), pair.first); 1150 } 1151 } 1152 WasmCode::DecrementRefCount(base::VectorOf(pair.second.code)); 1153 } 1154} 1155 1156std::shared_ptr<NativeModule> WasmEngine::NewNativeModule( 1157 Isolate* isolate, const WasmFeatures& enabled, 1158 std::shared_ptr<const WasmModule> module, size_t code_size_estimate) { 1159#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1160 if (FLAG_wasm_gdb_remote && !gdb_server_) { 1161 gdb_server_ = gdb_server::GdbServer::Create(); 1162 gdb_server_->AddIsolate(isolate); 1163 } 1164#endif // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING 1165 1166 std::shared_ptr<NativeModule> native_module = 1167 GetWasmCodeManager()->NewNativeModule( 1168 isolate, enabled, code_size_estimate, std::move(module)); 1169 base::MutexGuard lock(&mutex_); 1170 auto pair = native_modules_.insert(std::make_pair( 1171 native_module.get(), std::make_unique<NativeModuleInfo>(native_module))); 1172 DCHECK(pair.second); // inserted new entry. 1173 pair.first->second.get()->isolates.insert(isolate); 1174 auto* isolate_info = isolates_[isolate].get(); 1175 isolate_info->native_modules.insert(native_module.get()); 1176 if (isolate_info->keep_tiered_down) { 1177 native_module->SetTieringState(kTieredDown); 1178 } 1179 1180 // Record memory protection key support. 1181 if (!isolate_info->pku_support_sampled) { 1182 isolate_info->pku_support_sampled = true; 1183 auto* histogram = 1184 isolate->counters()->wasm_memory_protection_keys_support(); 1185 bool has_mpk = GetWasmCodeManager()->HasMemoryProtectionKeySupport(); 1186 histogram->AddSample(has_mpk ? 1 : 0); 1187 } 1188 1189 isolate->counters()->wasm_modules_per_isolate()->AddSample( 1190 static_cast<int>(isolate_info->native_modules.size())); 1191 isolate->counters()->wasm_modules_per_engine()->AddSample( 1192 static_cast<int>(native_modules_.size())); 1193 return native_module; 1194} 1195 1196std::shared_ptr<NativeModule> WasmEngine::MaybeGetNativeModule( 1197 ModuleOrigin origin, base::Vector<const uint8_t> wire_bytes, 1198 Isolate* isolate) { 1199 TRACE_EVENT1("v8.wasm", "wasm.GetNativeModuleFromCache", "wire_bytes", 1200 wire_bytes.size()); 1201 std::shared_ptr<NativeModule> native_module = 1202 native_module_cache_.MaybeGetNativeModule(origin, wire_bytes); 1203 bool recompile_module = false; 1204 if (native_module) { 1205 TRACE_EVENT0("v8.wasm", "CacheHit"); 1206 base::MutexGuard guard(&mutex_); 1207 auto& native_module_info = native_modules_[native_module.get()]; 1208 if (!native_module_info) { 1209 native_module_info = std::make_unique<NativeModuleInfo>(native_module); 1210 } 1211 native_module_info->isolates.insert(isolate); 1212 isolates_[isolate]->native_modules.insert(native_module.get()); 1213 if (isolates_[isolate]->keep_tiered_down) { 1214 native_module->SetTieringState(kTieredDown); 1215 recompile_module = true; 1216 } 1217 } 1218 // Potentially recompile the module for tier down, after releasing the mutex. 1219 if (recompile_module) native_module->RecompileForTiering(); 1220 return native_module; 1221} 1222 1223bool WasmEngine::UpdateNativeModuleCache( 1224 bool error, std::shared_ptr<NativeModule>* native_module, 1225 Isolate* isolate) { 1226 // Pass {native_module} by value here to keep it alive until at least after 1227 // we returned from {Update}. Otherwise, we might {Erase} it inside {Update} 1228 // which would lock the mutex twice. 1229 auto prev = native_module->get(); 1230 *native_module = native_module_cache_.Update(*native_module, error); 1231 1232 if (prev == native_module->get()) return true; 1233 1234 bool recompile_module = false; 1235 { 1236 base::MutexGuard guard(&mutex_); 1237 DCHECK_EQ(1, native_modules_.count(native_module->get())); 1238 native_modules_[native_module->get()]->isolates.insert(isolate); 1239 DCHECK_EQ(1, isolates_.count(isolate)); 1240 isolates_[isolate]->native_modules.insert(native_module->get()); 1241 if (isolates_[isolate]->keep_tiered_down) { 1242 native_module->get()->SetTieringState(kTieredDown); 1243 recompile_module = true; 1244 } 1245 } 1246 // Potentially recompile the module for tier down, after releasing the mutex. 1247 if (recompile_module) native_module->get()->RecompileForTiering(); 1248 return false; 1249} 1250 1251bool WasmEngine::GetStreamingCompilationOwnership(size_t prefix_hash) { 1252 TRACE_EVENT0("v8.wasm", "wasm.GetStreamingCompilationOwnership"); 1253 if (native_module_cache_.GetStreamingCompilationOwnership(prefix_hash)) { 1254 return true; 1255 } 1256 // This is only a marker, not for tracing execution time. There should be a 1257 // later "wasm.GetNativeModuleFromCache" event for trying to get the module 1258 // from the cache. 1259 TRACE_EVENT0("v8.wasm", "CacheHit"); 1260 return false; 1261} 1262 1263void WasmEngine::StreamingCompilationFailed(size_t prefix_hash) { 1264 native_module_cache_.StreamingCompilationFailed(prefix_hash); 1265} 1266 1267void WasmEngine::FreeNativeModule(NativeModule* native_module) { 1268 base::MutexGuard guard(&mutex_); 1269 auto module = native_modules_.find(native_module); 1270 DCHECK_NE(native_modules_.end(), module); 1271 for (Isolate* isolate : module->second->isolates) { 1272 DCHECK_EQ(1, isolates_.count(isolate)); 1273 IsolateInfo* info = isolates_[isolate].get(); 1274 DCHECK_EQ(1, info->native_modules.count(native_module)); 1275 info->native_modules.erase(native_module); 1276 info->scripts.erase(native_module); 1277 // If there are {WasmCode} objects of the deleted {NativeModule} 1278 // outstanding to be logged in this isolate, remove them. Decrementing the 1279 // ref count is not needed, since the {NativeModule} dies anyway. 1280 for (auto& log_entry : info->code_to_log) { 1281 auto part_of_native_module = [native_module](WasmCode* code) { 1282 return code->native_module() == native_module; 1283 }; 1284 std::vector<WasmCode*>& code = log_entry.second.code; 1285 auto new_end = 1286 std::remove_if(code.begin(), code.end(), part_of_native_module); 1287 code.erase(new_end, code.end()); 1288 } 1289 // Now remove empty entries in {code_to_log}. 1290 for (auto it = info->code_to_log.begin(), end = info->code_to_log.end(); 1291 it != end;) { 1292 if (it->second.code.empty()) { 1293 it = info->code_to_log.erase(it); 1294 } else { 1295 ++it; 1296 } 1297 } 1298 } 1299 // If there is a GC running which has references to code contained in the 1300 // deleted {NativeModule}, remove those references. 1301 if (current_gc_info_) { 1302 for (auto it = current_gc_info_->dead_code.begin(), 1303 end = current_gc_info_->dead_code.end(); 1304 it != end;) { 1305 if ((*it)->native_module() == native_module) { 1306 it = current_gc_info_->dead_code.erase(it); 1307 } else { 1308 ++it; 1309 } 1310 } 1311 TRACE_CODE_GC("Native module %p died, reducing dead code objects to %zu.\n", 1312 native_module, current_gc_info_->dead_code.size()); 1313 } 1314 native_module_cache_.Erase(native_module); 1315 native_modules_.erase(module); 1316} 1317 1318namespace { 1319class SampleTopTierCodeSizeTask : public CancelableTask { 1320 public: 1321 SampleTopTierCodeSizeTask(Isolate* isolate, 1322 std::weak_ptr<NativeModule> native_module) 1323 : CancelableTask(isolate), 1324 isolate_(isolate), 1325 native_module_(std::move(native_module)) {} 1326 1327 void RunInternal() override { 1328 if (std::shared_ptr<NativeModule> native_module = native_module_.lock()) { 1329 native_module->SampleCodeSize(isolate_->counters(), 1330 NativeModule::kAfterTopTier); 1331 } 1332 } 1333 1334 private: 1335 Isolate* const isolate_; 1336 const std::weak_ptr<NativeModule> native_module_; 1337}; 1338} // namespace 1339 1340void WasmEngine::SampleTopTierCodeSizeInAllIsolates( 1341 const std::shared_ptr<NativeModule>& native_module) { 1342 base::MutexGuard lock(&mutex_); 1343 DCHECK_EQ(1, native_modules_.count(native_module.get())); 1344 for (Isolate* isolate : native_modules_[native_module.get()]->isolates) { 1345 DCHECK_EQ(1, isolates_.count(isolate)); 1346 IsolateInfo* info = isolates_[isolate].get(); 1347 info->foreground_task_runner->PostTask( 1348 std::make_unique<SampleTopTierCodeSizeTask>(isolate, native_module)); 1349 } 1350} 1351 1352void WasmEngine::ReportLiveCodeForGC(Isolate* isolate, 1353 base::Vector<WasmCode*> live_code) { 1354 TRACE_EVENT0("v8.wasm", "wasm.ReportLiveCodeForGC"); 1355 TRACE_CODE_GC("Isolate %d reporting %zu live code objects.\n", isolate->id(), 1356 live_code.size()); 1357 base::MutexGuard guard(&mutex_); 1358 // This report might come in late (note that we trigger both a stack guard and 1359 // a foreground task). In that case, ignore it. 1360 if (current_gc_info_ == nullptr) return; 1361 if (!RemoveIsolateFromCurrentGC(isolate)) return; 1362 isolate->counters()->wasm_module_num_triggered_code_gcs()->AddSample( 1363 current_gc_info_->gc_sequence_index); 1364 for (WasmCode* code : live_code) current_gc_info_->dead_code.erase(code); 1365 PotentiallyFinishCurrentGC(); 1366} 1367 1368namespace { 1369void ReportLiveCodeFromFrameForGC( 1370 StackFrame* frame, std::unordered_set<wasm::WasmCode*>& live_wasm_code) { 1371 if (frame->type() != StackFrame::WASM) return; 1372 live_wasm_code.insert(WasmFrame::cast(frame)->wasm_code()); 1373#if V8_TARGET_ARCH_X64 1374 if (WasmFrame::cast(frame)->wasm_code()->for_debugging()) { 1375 Address osr_target = base::Memory<Address>(WasmFrame::cast(frame)->fp() - 1376 kOSRTargetOffset); 1377 if (osr_target) { 1378 WasmCode* osr_code = GetWasmCodeManager()->LookupCode(osr_target); 1379 DCHECK_NOT_NULL(osr_code); 1380 live_wasm_code.insert(osr_code); 1381 } 1382 } 1383#endif 1384} 1385} // namespace 1386 1387void WasmEngine::ReportLiveCodeFromStackForGC(Isolate* isolate) { 1388 wasm::WasmCodeRefScope code_ref_scope; 1389 std::unordered_set<wasm::WasmCode*> live_wasm_code; 1390 if (FLAG_experimental_wasm_stack_switching) { 1391 wasm::StackMemory* current = isolate->wasm_stacks(); 1392 DCHECK_NOT_NULL(current); 1393 do { 1394 if (current->IsActive()) { 1395 // The active stack's jump buffer does not match the current state, use 1396 // the thread info below instead. 1397 current = current->next(); 1398 continue; 1399 } 1400 for (StackFrameIterator it(isolate, current); !it.done(); it.Advance()) { 1401 StackFrame* const frame = it.frame(); 1402 ReportLiveCodeFromFrameForGC(frame, live_wasm_code); 1403 } 1404 current = current->next(); 1405 } while (current != isolate->wasm_stacks()); 1406 } 1407 for (StackFrameIterator it(isolate); !it.done(); it.Advance()) { 1408 StackFrame* const frame = it.frame(); 1409 ReportLiveCodeFromFrameForGC(frame, live_wasm_code); 1410 } 1411 1412 CheckNoArchivedThreads(isolate); 1413 1414 ReportLiveCodeForGC( 1415 isolate, base::OwnedVector<WasmCode*>::Of(live_wasm_code).as_vector()); 1416} 1417 1418bool WasmEngine::AddPotentiallyDeadCode(WasmCode* code) { 1419 base::MutexGuard guard(&mutex_); 1420 auto it = native_modules_.find(code->native_module()); 1421 DCHECK_NE(native_modules_.end(), it); 1422 NativeModuleInfo* info = it->second.get(); 1423 if (info->dead_code.count(code)) return false; // Code is already dead. 1424 auto added = info->potentially_dead_code.insert(code); 1425 if (!added.second) return false; // An entry already existed. 1426 new_potentially_dead_code_size_ += code->instructions().size(); 1427 if (FLAG_wasm_code_gc) { 1428 // Trigger a GC if 64kB plus 10% of committed code are potentially dead. 1429 size_t dead_code_limit = 1430 FLAG_stress_wasm_code_gc 1431 ? 0 1432 : 64 * KB + GetWasmCodeManager()->committed_code_space() / 10; 1433 if (new_potentially_dead_code_size_ > dead_code_limit) { 1434 bool inc_gc_count = 1435 info->num_code_gcs_triggered < std::numeric_limits<int8_t>::max(); 1436 if (current_gc_info_ == nullptr) { 1437 if (inc_gc_count) ++info->num_code_gcs_triggered; 1438 TRACE_CODE_GC( 1439 "Triggering GC (potentially dead: %zu bytes; limit: %zu bytes).\n", 1440 new_potentially_dead_code_size_, dead_code_limit); 1441 TriggerGC(info->num_code_gcs_triggered); 1442 } else if (current_gc_info_->next_gc_sequence_index == 0) { 1443 if (inc_gc_count) ++info->num_code_gcs_triggered; 1444 TRACE_CODE_GC( 1445 "Scheduling another GC after the current one (potentially dead: " 1446 "%zu bytes; limit: %zu bytes).\n", 1447 new_potentially_dead_code_size_, dead_code_limit); 1448 current_gc_info_->next_gc_sequence_index = info->num_code_gcs_triggered; 1449 DCHECK_NE(0, current_gc_info_->next_gc_sequence_index); 1450 } 1451 } 1452 } 1453 return true; 1454} 1455 1456void WasmEngine::FreeDeadCode(const DeadCodeMap& dead_code) { 1457 base::MutexGuard guard(&mutex_); 1458 FreeDeadCodeLocked(dead_code); 1459} 1460 1461void WasmEngine::FreeDeadCodeLocked(const DeadCodeMap& dead_code) { 1462 TRACE_EVENT0("v8.wasm", "wasm.FreeDeadCode"); 1463 DCHECK(!mutex_.TryLock()); 1464 for (auto& dead_code_entry : dead_code) { 1465 NativeModule* native_module = dead_code_entry.first; 1466 const std::vector<WasmCode*>& code_vec = dead_code_entry.second; 1467 DCHECK_EQ(1, native_modules_.count(native_module)); 1468 auto* info = native_modules_[native_module].get(); 1469 TRACE_CODE_GC("Freeing %zu code object%s of module %p.\n", code_vec.size(), 1470 code_vec.size() == 1 ? "" : "s", native_module); 1471 for (WasmCode* code : code_vec) { 1472 DCHECK_EQ(1, info->dead_code.count(code)); 1473 info->dead_code.erase(code); 1474 } 1475 native_module->FreeCode(base::VectorOf(code_vec)); 1476 } 1477} 1478 1479Handle<Script> WasmEngine::GetOrCreateScript( 1480 Isolate* isolate, const std::shared_ptr<NativeModule>& native_module, 1481 base::Vector<const char> source_url) { 1482 { 1483 base::MutexGuard guard(&mutex_); 1484 DCHECK_EQ(1, isolates_.count(isolate)); 1485 auto& scripts = isolates_[isolate]->scripts; 1486 auto it = scripts.find(native_module.get()); 1487 if (it != scripts.end()) { 1488 Handle<Script> weak_global_handle = it->second.handle(); 1489 if (weak_global_handle.is_null()) { 1490 scripts.erase(it); 1491 } else { 1492 return Handle<Script>::New(*weak_global_handle, isolate); 1493 } 1494 } 1495 } 1496 // Temporarily release the mutex to let the GC collect native modules. 1497 auto script = CreateWasmScript(isolate, native_module, source_url); 1498 { 1499 base::MutexGuard guard(&mutex_); 1500 DCHECK_EQ(1, isolates_.count(isolate)); 1501 auto& scripts = isolates_[isolate]->scripts; 1502 DCHECK_EQ(0, scripts.count(native_module.get())); 1503 scripts.emplace(native_module.get(), WeakScriptHandle(script)); 1504 return script; 1505 } 1506} 1507 1508std::shared_ptr<OperationsBarrier> 1509WasmEngine::GetBarrierForBackgroundCompile() { 1510 return operations_barrier_; 1511} 1512 1513namespace { 1514void SampleExceptionEvent(base::ElapsedTimer* timer, TimedHistogram* counter) { 1515 if (!timer->IsStarted()) { 1516 timer->Start(); 1517 return; 1518 } 1519 counter->AddSample(static_cast<int>(timer->Elapsed().InMilliseconds())); 1520 timer->Restart(); 1521} 1522} // namespace 1523 1524void WasmEngine::SampleThrowEvent(Isolate* isolate) { 1525 base::MutexGuard guard(&mutex_); 1526 IsolateInfo* isolate_info = isolates_[isolate].get(); 1527 int& throw_count = isolate_info->throw_count; 1528 // To avoid an int overflow, clip the count to the histogram's max value. 1529 throw_count = 1530 std::min(throw_count + 1, isolate->counters()->wasm_throw_count()->max()); 1531 isolate->counters()->wasm_throw_count()->AddSample(throw_count); 1532 SampleExceptionEvent(&isolate_info->throw_timer, 1533 isolate->counters()->wasm_time_between_throws()); 1534} 1535 1536void WasmEngine::SampleRethrowEvent(Isolate* isolate) { 1537 base::MutexGuard guard(&mutex_); 1538 IsolateInfo* isolate_info = isolates_[isolate].get(); 1539 int& rethrow_count = isolate_info->rethrow_count; 1540 // To avoid an int overflow, clip the count to the histogram's max value. 1541 rethrow_count = std::min(rethrow_count + 1, 1542 isolate->counters()->wasm_rethrow_count()->max()); 1543 isolate->counters()->wasm_rethrow_count()->AddSample(rethrow_count); 1544 SampleExceptionEvent(&isolate_info->rethrow_timer, 1545 isolate->counters()->wasm_time_between_rethrows()); 1546} 1547 1548void WasmEngine::SampleCatchEvent(Isolate* isolate) { 1549 base::MutexGuard guard(&mutex_); 1550 IsolateInfo* isolate_info = isolates_[isolate].get(); 1551 int& catch_count = isolate_info->catch_count; 1552 // To avoid an int overflow, clip the count to the histogram's max value. 1553 catch_count = 1554 std::min(catch_count + 1, isolate->counters()->wasm_catch_count()->max()); 1555 isolate->counters()->wasm_catch_count()->AddSample(catch_count); 1556 SampleExceptionEvent(&isolate_info->catch_timer, 1557 isolate->counters()->wasm_time_between_catch()); 1558} 1559 1560void WasmEngine::TriggerGC(int8_t gc_sequence_index) { 1561 DCHECK(!mutex_.TryLock()); 1562 DCHECK_NULL(current_gc_info_); 1563 DCHECK(FLAG_wasm_code_gc); 1564 new_potentially_dead_code_size_ = 0; 1565 current_gc_info_.reset(new CurrentGCInfo(gc_sequence_index)); 1566 // Add all potentially dead code to this GC, and trigger a GC task in each 1567 // isolate. 1568 for (auto& entry : native_modules_) { 1569 NativeModuleInfo* info = entry.second.get(); 1570 if (info->potentially_dead_code.empty()) continue; 1571 for (auto* isolate : native_modules_[entry.first]->isolates) { 1572 auto& gc_task = current_gc_info_->outstanding_isolates[isolate]; 1573 if (!gc_task) { 1574 auto new_task = std::make_unique<WasmGCForegroundTask>(isolate); 1575 gc_task = new_task.get(); 1576 DCHECK_EQ(1, isolates_.count(isolate)); 1577 isolates_[isolate]->foreground_task_runner->PostTask( 1578 std::move(new_task)); 1579 } 1580 isolate->stack_guard()->RequestWasmCodeGC(); 1581 } 1582 for (WasmCode* code : info->potentially_dead_code) { 1583 current_gc_info_->dead_code.insert(code); 1584 } 1585 } 1586 TRACE_CODE_GC( 1587 "Starting GC (nr %d). Number of potentially dead code objects: %zu\n", 1588 current_gc_info_->gc_sequence_index, current_gc_info_->dead_code.size()); 1589 // Ensure that there are outstanding isolates that will eventually finish this 1590 // GC. If there are no outstanding isolates, we finish the GC immediately. 1591 PotentiallyFinishCurrentGC(); 1592 DCHECK(current_gc_info_ == nullptr || 1593 !current_gc_info_->outstanding_isolates.empty()); 1594} 1595 1596bool WasmEngine::RemoveIsolateFromCurrentGC(Isolate* isolate) { 1597 DCHECK(!mutex_.TryLock()); 1598 DCHECK_NOT_NULL(current_gc_info_); 1599 return current_gc_info_->outstanding_isolates.erase(isolate) != 0; 1600} 1601 1602void WasmEngine::PotentiallyFinishCurrentGC() { 1603 DCHECK(!mutex_.TryLock()); 1604 TRACE_CODE_GC( 1605 "Remaining dead code objects: %zu; outstanding isolates: %zu.\n", 1606 current_gc_info_->dead_code.size(), 1607 current_gc_info_->outstanding_isolates.size()); 1608 1609 // If there are more outstanding isolates, return immediately. 1610 if (!current_gc_info_->outstanding_isolates.empty()) return; 1611 1612 // All remaining code in {current_gc_info->dead_code} is really dead. 1613 // Move it from the set of potentially dead code to the set of dead code, 1614 // and decrement its ref count. 1615 size_t num_freed = 0; 1616 DeadCodeMap dead_code; 1617 for (WasmCode* code : current_gc_info_->dead_code) { 1618 DCHECK_EQ(1, native_modules_.count(code->native_module())); 1619 auto* native_module_info = native_modules_[code->native_module()].get(); 1620 DCHECK_EQ(1, native_module_info->potentially_dead_code.count(code)); 1621 native_module_info->potentially_dead_code.erase(code); 1622 DCHECK_EQ(0, native_module_info->dead_code.count(code)); 1623 native_module_info->dead_code.insert(code); 1624 if (code->DecRefOnDeadCode()) { 1625 dead_code[code->native_module()].push_back(code); 1626 ++num_freed; 1627 } 1628 } 1629 1630 FreeDeadCodeLocked(dead_code); 1631 1632 TRACE_CODE_GC("Found %zu dead code objects, freed %zu.\n", 1633 current_gc_info_->dead_code.size(), num_freed); 1634 USE(num_freed); 1635 1636 int8_t next_gc_sequence_index = current_gc_info_->next_gc_sequence_index; 1637 current_gc_info_.reset(); 1638 if (next_gc_sequence_index != 0) TriggerGC(next_gc_sequence_index); 1639} 1640 1641namespace { 1642 1643struct GlobalWasmState { 1644 // Note: The order of fields is important here, as the WasmEngine's destructor 1645 // must run first. It contains a barrier which ensures that background threads 1646 // finished, and that has to happen before the WasmCodeManager gets destroyed. 1647 WasmCodeManager code_manager; 1648 WasmEngine engine; 1649}; 1650 1651GlobalWasmState* global_wasm_state = nullptr; 1652 1653} // namespace 1654 1655// static 1656void WasmEngine::InitializeOncePerProcess() { 1657 InitializeMemoryProtectionKeySupport(); 1658 DCHECK_NULL(global_wasm_state); 1659 global_wasm_state = new GlobalWasmState(); 1660} 1661 1662// static 1663void WasmEngine::GlobalTearDown() { 1664 // Note: This can be called multiple times in a row (see 1665 // test-api/InitializeAndDisposeMultiple). This is fine, as 1666 // {global_wasm_engine} will be nullptr then. 1667 delete global_wasm_state; 1668 global_wasm_state = nullptr; 1669} 1670 1671WasmEngine* GetWasmEngine() { 1672 DCHECK_NOT_NULL(global_wasm_state); 1673 return &global_wasm_state->engine; 1674} 1675 1676WasmCodeManager* GetWasmCodeManager() { 1677 DCHECK_NOT_NULL(global_wasm_state); 1678 return &global_wasm_state->code_manager; 1679} 1680 1681// {max_mem_pages} is declared in wasm-limits.h. 1682uint32_t max_mem_pages() { 1683 static_assert( 1684 kV8MaxWasmMemoryPages * kWasmPageSize <= JSArrayBuffer::kMaxByteLength, 1685 "Wasm memories must not be bigger than JSArrayBuffers"); 1686 STATIC_ASSERT(kV8MaxWasmMemoryPages <= kMaxUInt32); 1687 return std::min(uint32_t{kV8MaxWasmMemoryPages}, FLAG_wasm_max_mem_pages); 1688} 1689 1690// {max_table_init_entries} is declared in wasm-limits.h. 1691uint32_t max_table_init_entries() { 1692 return std::min(uint32_t{kV8MaxWasmTableInitEntries}, 1693 FLAG_wasm_max_table_size); 1694} 1695 1696// {max_module_size} is declared in wasm-limits.h. 1697size_t max_module_size() { 1698 return FLAG_experimental_wasm_allow_huge_modules 1699 ? RoundDown<kSystemPointerSize>(size_t{kMaxInt}) 1700 : kV8MaxWasmModuleSize; 1701} 1702 1703#undef TRACE_CODE_GC 1704 1705} // namespace wasm 1706} // namespace internal 1707} // namespace v8 1708