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