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