blob: a22c79e0ad9f16638ab6e1bca4f4eb8aa7b434f7 [file] [log] [blame]
// 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