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