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