// Copyright 2016 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/compiler-dispatcher/compiler-dispatcher.h" #include "src/ast/ast.h" #include "src/base/platform/time.h" #include "src/codegen/compiler.h" #include "src/flags/flags.h" #include "src/handles/global-handles.h" #include "src/objects/objects-inl.h" #include "src/parsing/parse-info.h" #include "src/parsing/parser.h" #include "src/tasks/cancelable-task.h" #include "src/tasks/task-utils.h" #include "src/zone/zone-list-inl.h" // crbug.com/v8/8816 namespace v8 { namespace internal { CompilerDispatcher::Job::Job(BackgroundCompileTask* task_arg) : task(task_arg), has_run(false), aborted(false) {} CompilerDispatcher::Job::~Job() = default; CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, size_t max_stack_size) : isolate_(isolate), worker_thread_runtime_call_stats_( isolate->counters()->worker_thread_runtime_call_stats()), background_compile_timer_( isolate->counters()->compile_function_on_background()), taskrunner_(platform->GetForegroundTaskRunner( reinterpret_cast<v8::Isolate*>(isolate))), platform_(platform), max_stack_size_(max_stack_size), trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher), task_manager_(new CancelableTaskManager()), next_job_id_(0), shared_to_unoptimized_job_id_(isolate->heap()), idle_task_scheduled_(false), num_worker_tasks_(0), main_thread_blocking_on_job_(nullptr), block_for_testing_(false), semaphore_for_testing_(0) { if (trace_compiler_dispatcher_ && !IsEnabled()) { PrintF("CompilerDispatcher: dispatcher is disabled\n"); } } CompilerDispatcher::~CompilerDispatcher() { // AbortAll must be called before CompilerDispatcher is destroyed. CHECK(task_manager_->canceled()); } base::Optional<CompilerDispatcher::JobId> CompilerDispatcher::Enqueue( const ParseInfo* outer_parse_info, const AstRawString* function_name, const FunctionLiteral* function_literal) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherEnqueue"); RuntimeCallTimerScope runtimeTimer( isolate_, RuntimeCallCounterId::kCompileEnqueueOnDispatcher); if (!IsEnabled()) return base::nullopt; std::unique_ptr<Job> job = std::make_unique<Job>(new BackgroundCompileTask( outer_parse_info, function_name, function_literal, worker_thread_runtime_call_stats_, background_compile_timer_, static_cast<int>(max_stack_size_))); JobMap::const_iterator it = InsertJob(std::move(job)); JobId id = it->first; if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: enqueued job %zu for function literal id %d\n", id, function_literal->function_literal_id()); } // Post a a background worker task to perform the compilation on the worker // thread. { base::MutexGuard lock(&mutex_); pending_background_jobs_.insert(it->second.get()); } ScheduleMoreWorkerTasksIfNeeded(); return base::make_optional(id); } bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; } bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { if (jobs_.empty()) return false; return GetJobFor(function) != jobs_.end(); } bool CompilerDispatcher::IsEnqueued(JobId job_id) const { return jobs_.find(job_id) != jobs_.end(); } void CompilerDispatcher::RegisterSharedFunctionInfo( JobId job_id, SharedFunctionInfo function) { DCHECK_NE(jobs_.find(job_id), jobs_.end()); if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: registering "); function.ShortPrint(); PrintF(" with job id %zu\n", job_id); } // Make a global handle to the function. Handle<SharedFunctionInfo> function_handle = Handle<SharedFunctionInfo>::cast( isolate_->global_handles()->Create(function)); // Register mapping. auto job_it = jobs_.find(job_id); DCHECK_NE(job_it, jobs_.end()); Job* job = job_it->second.get(); shared_to_unoptimized_job_id_.Insert(function_handle, job_id); { base::MutexGuard lock(&mutex_); job->function = function_handle; if (job->IsReadyToFinalize(lock)) { // Schedule an idle task to finalize job if it is ready. ScheduleIdleTaskFromAnyThread(lock); } } } void CompilerDispatcher::WaitForJobIfRunningOnBackground(Job* job) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherWaitForBackgroundJob"); RuntimeCallTimerScope runtimeTimer( isolate_, RuntimeCallCounterId::kCompileWaitForDispatcher); base::MutexGuard lock(&mutex_); if (running_background_jobs_.find(job) == running_background_jobs_.end()) { pending_background_jobs_.erase(job); return; } DCHECK_NULL(main_thread_blocking_on_job_); main_thread_blocking_on_job_ = job; while (main_thread_blocking_on_job_ != nullptr) { main_thread_blocking_signal_.Wait(&mutex_); } DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end()); DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end()); } bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherFinishNow"); RuntimeCallTimerScope runtimeTimer( isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher); if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: finishing "); function->ShortPrint(); PrintF(" now\n"); } JobMap::const_iterator it = GetJobFor(function); CHECK(it != jobs_.end()); Job* job = it->second.get(); WaitForJobIfRunningOnBackground(job); if (!job->has_run) { job->task->Run(); job->has_run = true; } DCHECK(job->IsReadyToFinalize(&mutex_)); DCHECK(!job->aborted); bool success = Compiler::FinalizeBackgroundCompileTask( job->task.get(), function, isolate_, Compiler::KEEP_EXCEPTION); DCHECK_NE(success, isolate_->has_pending_exception()); RemoveJob(it); return success; } void CompilerDispatcher::AbortJob(JobId job_id) { if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: aborted job %zu\n", job_id); } JobMap::const_iterator job_it = jobs_.find(job_id); Job* job = job_it->second.get(); base::LockGuard<base::Mutex> lock(&mutex_); pending_background_jobs_.erase(job); if (running_background_jobs_.find(job) == running_background_jobs_.end()) { RemoveJob(job_it); } else { // Job is currently running on the background thread, wait until it's done // and remove job then. job->aborted = true; } } void CompilerDispatcher::AbortAll() { task_manager_->TryAbortAll(); for (auto& it : jobs_) { WaitForJobIfRunningOnBackground(it.second.get()); if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: aborted job %zu\n", it.first); } } jobs_.clear(); shared_to_unoptimized_job_id_.Clear(); { base::MutexGuard lock(&mutex_); DCHECK(pending_background_jobs_.empty()); DCHECK(running_background_jobs_.empty()); } task_manager_->CancelAndWait(); } CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( Handle<SharedFunctionInfo> shared) const { JobId* job_id_ptr = shared_to_unoptimized_job_id_.Find(shared); JobMap::const_iterator job = jobs_.end(); if (job_id_ptr) { job = jobs_.find(*job_id_ptr); } return job; } void CompilerDispatcher::ScheduleIdleTaskFromAnyThread( const base::MutexGuard&) { if (!taskrunner_->IdleTasksEnabled()) return; if (idle_task_scheduled_) return; idle_task_scheduled_ = true; taskrunner_->PostIdleTask(MakeCancelableIdleTask( task_manager_.get(), [this](double deadline_in_seconds) { DoIdleWork(deadline_in_seconds); })); } void CompilerDispatcher::ScheduleMoreWorkerTasksIfNeeded() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherScheduleMoreWorkerTasksIfNeeded"); { base::MutexGuard lock(&mutex_); if (pending_background_jobs_.empty()) return; if (platform_->NumberOfWorkerThreads() <= num_worker_tasks_) { return; } ++num_worker_tasks_; } platform_->CallOnWorkerThread( MakeCancelableTask(task_manager_.get(), [this] { DoBackgroundWork(); })); } void CompilerDispatcher::DoBackgroundWork() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherDoBackgroundWork"); for (;;) { Job* job = nullptr; { base::MutexGuard lock(&mutex_); if (!pending_background_jobs_.empty()) { auto it = pending_background_jobs_.begin(); job = *it; pending_background_jobs_.erase(it); running_background_jobs_.insert(job); } } if (job == nullptr) break; if (V8_UNLIKELY(block_for_testing_.Value())) { block_for_testing_.SetValue(false); semaphore_for_testing_.Wait(); } if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: doing background work\n"); } job->task->Run(); { base::MutexGuard lock(&mutex_); running_background_jobs_.erase(job); job->has_run = true; if (job->IsReadyToFinalize(lock)) { // Schedule an idle task to finalize the compilation on the main thread // if the job has a shared function info registered. ScheduleIdleTaskFromAnyThread(lock); } if (main_thread_blocking_on_job_ == job) { main_thread_blocking_on_job_ = nullptr; main_thread_blocking_signal_.NotifyOne(); } } } { base::MutexGuard lock(&mutex_); --num_worker_tasks_; } // Don't touch |this| anymore after this point, as it might have been // deleted. } void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompilerDispatcherDoIdleWork"); { base::MutexGuard lock(&mutex_); idle_task_scheduled_ = false; } if (trace_compiler_dispatcher_) { PrintF("CompilerDispatcher: received %0.1lfms of idle time\n", (deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) * static_cast<double>(base::Time::kMillisecondsPerSecond)); } while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) { // Find a job which is pending finalization and has a shared function info CompilerDispatcher::JobMap::const_iterator it; { base::MutexGuard lock(&mutex_); for (it = jobs_.cbegin(); it != jobs_.cend(); ++it) { if (it->second->IsReadyToFinalize(lock)) break; } // Since we hold the lock here, we can be sure no jobs have become ready // for finalization while we looped through the list. if (it == jobs_.cend()) return; DCHECK(it->second->IsReadyToFinalize(lock)); DCHECK_EQ(running_background_jobs_.find(it->second.get()), running_background_jobs_.end()); DCHECK_EQ(pending_background_jobs_.find(it->second.get()), pending_background_jobs_.end()); } Job* job = it->second.get(); if (!job->aborted) { Compiler::FinalizeBackgroundCompileTask( job->task.get(), job->function.ToHandleChecked(), isolate_, Compiler::CLEAR_EXCEPTION); } RemoveJob(it); } // We didn't return above so there still might be jobs to finalize. { base::MutexGuard lock(&mutex_); ScheduleIdleTaskFromAnyThread(lock); } } CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::InsertJob( std::unique_ptr<Job> job) { bool added; JobMap::const_iterator it; std::tie(it, added) = jobs_.insert(std::make_pair(next_job_id_++, std::move(job))); DCHECK(added); return it; } CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveJob( CompilerDispatcher::JobMap::const_iterator it) { Job* job = it->second.get(); DCHECK_EQ(running_background_jobs_.find(job), running_background_jobs_.end()); DCHECK_EQ(pending_background_jobs_.find(job), pending_background_jobs_.end()); // Delete SFI associated with job if its been registered. Handle<SharedFunctionInfo> function; if (job->function.ToHandle(&function)) { GlobalHandles::Destroy(function.location()); } // Delete job. return jobs_.erase(it); } } // namespace internal } // namespace v8