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