// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/wasm/wasm-engine.h" #include "src/base/platform/time.h" #include "src/diagnostics/code-tracer.h" #include "src/diagnostics/compilation-statistics.h" #include "src/execution/frames.h" #include "src/execution/v8threads.h" #include "src/logging/counters.h" #include "src/objects/heap-number.h" #include "src/objects/js-promise.h" #include "src/objects/objects-inl.h" #include "src/utils/ostreams.h" #include "src/wasm/function-compiler.h" #include "src/wasm/module-compiler.h" #include "src/wasm/module-decoder.h" #include "src/wasm/module-instantiate.h" #include "src/wasm/streaming-decoder.h" #include "src/wasm/wasm-objects-inl.h" namespace v8 { namespace internal { namespace wasm { #define TRACE_CODE_GC(...) \ do { \ if (FLAG_trace_wasm_code_gc) PrintF("[wasm-gc] " __VA_ARGS__); \ } while (false) namespace { // A task to log a set of {WasmCode} objects in an isolate. It does not own any // data itself, since it is owned by the platform, so lifetime is not really // bound to the wasm engine. class LogCodesTask : public Task { public: LogCodesTask(base::Mutex* mutex, LogCodesTask** task_slot, Isolate* isolate, WasmEngine* engine) : mutex_(mutex), task_slot_(task_slot), isolate_(isolate), engine_(engine) { DCHECK_NOT_NULL(task_slot); DCHECK_NOT_NULL(isolate); } ~LogCodesTask() { // If the platform deletes this task before executing it, we also deregister // it to avoid use-after-free from still-running background threads. if (!cancelled()) DeregisterTask(); } void Run() override { if (cancelled()) return; DeregisterTask(); engine_->LogOutstandingCodesForIsolate(isolate_); } void Cancel() { // Cancel will only be called on Isolate shutdown, which happens on the // Isolate's foreground thread. Thus no synchronization needed. isolate_ = nullptr; } bool cancelled() const { return isolate_ == nullptr; } void DeregisterTask() { // The task will only be deregistered from the foreground thread (executing // this task or calling its destructor), thus we do not need synchronization // on this field access. if (task_slot_ == nullptr) return; // already deregistered. // Remove this task from the {IsolateInfo} in the engine. The next // logging request will allocate and schedule a new task. base::MutexGuard guard(mutex_); DCHECK_EQ(this, *task_slot_); *task_slot_ = nullptr; task_slot_ = nullptr; } private: // The mutex of the WasmEngine. base::Mutex* const mutex_; // The slot in the WasmEngine where this LogCodesTask is stored. This is // cleared by this task before execution or on task destruction. LogCodesTask** task_slot_; Isolate* isolate_; WasmEngine* const engine_; }; void CheckNoArchivedThreads(Isolate* isolate) { class ArchivedThreadsVisitor : public ThreadVisitor { void VisitThread(Isolate* isolate, ThreadLocalTop* top) override { // Archived threads are rarely used, and not combined with Wasm at the // moment. Implement this and test it properly once we have a use case for // that. FATAL("archived threads in combination with wasm not supported"); } } archived_threads_visitor; isolate->thread_manager()->IterateArchivedThreads(&archived_threads_visitor); } class WasmGCForegroundTask : public CancelableTask { public: explicit WasmGCForegroundTask(Isolate* isolate) : CancelableTask(isolate->cancelable_task_manager()), isolate_(isolate) {} void RunInternal() final { WasmEngine* engine = isolate_->wasm_engine(); // If the foreground task is executing, there is no wasm code active. Just // report an empty set of live wasm code. #ifdef ENABLE_SLOW_DCHECKS for (StackFrameIterator it(isolate_); !it.done(); it.Advance()) { DCHECK_NE(StackFrame::WASM_COMPILED, it.frame()->type()); } #endif CheckNoArchivedThreads(isolate_); engine->ReportLiveCodeForGC(isolate_, Vector{}); } private: Isolate* isolate_; }; } // namespace struct WasmEngine::CurrentGCInfo { explicit CurrentGCInfo(int8_t gc_sequence_index) : gc_sequence_index(gc_sequence_index) { DCHECK_NE(0, gc_sequence_index); } // Set of isolates that did not scan their stack yet for used WasmCode, and // their scheduled foreground task. std::unordered_map outstanding_isolates; // Set of dead code. Filled with all potentially dead code on initialization. // Code that is still in-use is removed by the individual isolates. std::unordered_set dead_code; // The number of GCs triggered in the native module that triggered this GC. // This is stored in the histogram for each participating isolate during // execution of that isolate's foreground task. const int8_t gc_sequence_index; // If during this GC, another GC was requested, we skipped that other GC (we // only run one GC at a time). Remember though to trigger another one once // this one finishes. {next_gc_sequence_index} is 0 if no next GC is needed, // and >0 otherwise. It stores the {num_code_gcs_triggered} of the native // module which triggered the next GC. int8_t next_gc_sequence_index = 0; // The start time of this GC; used for tracing and sampled via {Counters}. // Can be null ({TimeTicks::IsNull()}) if timer is not high resolution. base::TimeTicks start_time; }; struct WasmEngine::IsolateInfo { explicit IsolateInfo(Isolate* isolate) : log_codes(WasmCode::ShouldBeLogged(isolate)), async_counters(isolate->async_counters()) { v8::Isolate* v8_isolate = reinterpret_cast(isolate); v8::Platform* platform = V8::GetCurrentPlatform(); foreground_task_runner = platform->GetForegroundTaskRunner(v8_isolate); } #ifdef DEBUG ~IsolateInfo() { // Before destructing, the {WasmEngine} must have cleared outstanding code // to log. DCHECK_EQ(0, code_to_log.size()); } #endif // All native modules that are being used by this Isolate (currently only // grows, never shrinks). std::set native_modules; // Caches whether code needs to be logged on this isolate. bool log_codes; // The currently scheduled LogCodesTask. LogCodesTask* log_codes_task = nullptr; // The vector of code objects that still need to be logged in this isolate. std::vector code_to_log; // The foreground task runner of the isolate (can be called from background). std::shared_ptr foreground_task_runner; const std::shared_ptr async_counters; }; struct WasmEngine::NativeModuleInfo { // Set of isolates using this NativeModule. std::unordered_set isolates; // Set of potentially dead code. This set holds one ref for each code object, // until code is detected to be really dead. At that point, the ref count is // decremented and code is move to the {dead_code} set. If the code is finally // deleted, it is also removed from {dead_code}. std::unordered_set potentially_dead_code; // Code that is not being executed in any isolate any more, but the ref count // did not drop to zero yet. std::unordered_set dead_code; // Number of code GCs triggered because code in this native module became // potentially dead. int8_t num_code_gcs_triggered = 0; }; WasmEngine::WasmEngine() : code_manager_(FLAG_wasm_max_code_space * MB) {} WasmEngine::~WasmEngine() { // Synchronize on all background compile tasks. background_compile_task_manager_.CancelAndWait(); // All AsyncCompileJobs have been canceled. DCHECK(async_compile_jobs_.empty()); // All Isolates have been deregistered. DCHECK(isolates_.empty()); // All NativeModules did die. DCHECK(native_modules_.empty()); } bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled, const ModuleWireBytes& bytes) { // TODO(titzer): remove dependency on the isolate. if (bytes.start() == nullptr || bytes.length() == 0) return false; ModuleResult result = DecodeWasmModule(enabled, bytes.start(), bytes.end(), true, kWasmOrigin, isolate->counters(), allocator()); return result.ok(); } MaybeHandle WasmEngine::SyncCompileTranslatedAsmJs( Isolate* isolate, ErrorThrower* thrower, const ModuleWireBytes& bytes, Vector asm_js_offset_table_bytes, Handle uses_bitset, LanguageMode language_mode) { ModuleOrigin origin = language_mode == LanguageMode::kSloppy ? kAsmJsSloppyOrigin : kAsmJsStrictOrigin; ModuleResult result = DecodeWasmModule(kAsmjsWasmFeatures, bytes.start(), bytes.end(), false, origin, isolate->counters(), allocator()); if (result.failed()) { // This happens once in a while when we have missed some limit check // in the asm parser. Output an error message to help diagnose, but crash. std::cout << result.error().message(); UNREACHABLE(); } // Transfer ownership of the WasmModule to the {Managed} generated // in {CompileToNativeModule}. Handle export_wrappers; std::shared_ptr native_module = CompileToNativeModule(isolate, kAsmjsWasmFeatures, thrower, std::move(result).value(), bytes, &export_wrappers); if (!native_module) return {}; // Create heap objects for asm.js offset table to be stored in the module // object. Handle asm_js_offset_table = isolate->factory()->NewByteArray(asm_js_offset_table_bytes.length()); asm_js_offset_table->copy_in(0, asm_js_offset_table_bytes.begin(), asm_js_offset_table_bytes.length()); return AsmWasmData::New(isolate, std::move(native_module), export_wrappers, asm_js_offset_table, uses_bitset); } Handle WasmEngine::FinalizeTranslatedAsmJs( Isolate* isolate, Handle asm_wasm_data, Handle