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