blob: cb1a70b1fa9f0da800d3ed0027775d17c2f5fef3 [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 "include/v8-platform.h"
#include "include/v8.h"
#include "src/base/platform/time.h"
#include "src/cancelable-task.h"
#include "src/compilation-info.h"
#include "src/compiler-dispatcher/compiler-dispatcher-job.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/compiler-dispatcher/unoptimized-compile-job.h"
#include "src/flags.h"
#include "src/objects-inl.h"
namespace v8 {
namespace internal {
namespace {
enum class ExceptionHandling { kSwallow, kThrow };
bool DoNextStepOnMainThread(Isolate* isolate, CompilerDispatcherJob* job,
ExceptionHandling exception_handling) {
DCHECK(ThreadId::Current().Equals(isolate->thread_id()));
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherForgroundStep");
job->StepNextOnMainThread(isolate);
DCHECK_EQ(job->IsFailed(), isolate->has_pending_exception());
if (job->IsFailed() && exception_handling == ExceptionHandling::kSwallow) {
isolate->clear_pending_exception();
}
return job->IsFailed();
}
void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
DCHECK(job->CanStepNextOnAnyThread());
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherBackgroundStep");
job->StepNextOnBackgroundThread();
}
// Theoretically we get 50ms of idle time max, however it's unlikely that
// we'll get all of it so try to be a conservative.
const double kMaxIdleTimeToExpectInMs = 40;
class MemoryPressureTask : public CancelableTask {
public:
MemoryPressureTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~MemoryPressureTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(MemoryPressureTask);
};
MemoryPressureTask::MemoryPressureTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(task_manager), dispatcher_(dispatcher) {}
MemoryPressureTask::~MemoryPressureTask() {}
void MemoryPressureTask::RunInternal() {
dispatcher_->AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
}
} // namespace
class CompilerDispatcher::AbortTask : public CancelableTask {
public:
AbortTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~AbortTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(AbortTask);
};
CompilerDispatcher::AbortTask::AbortTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::AbortTask::~AbortTask() {}
void CompilerDispatcher::AbortTask::RunInternal() {
dispatcher_->AbortInactiveJobs();
}
class CompilerDispatcher::BackgroundTask : public CancelableTask {
public:
BackgroundTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~BackgroundTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(BackgroundTask);
};
CompilerDispatcher::BackgroundTask::BackgroundTask(
Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::BackgroundTask::~BackgroundTask() {}
void CompilerDispatcher::BackgroundTask::RunInternal() {
dispatcher_->DoBackgroundWork();
}
class CompilerDispatcher::IdleTask : public CancelableIdleTask {
public:
IdleTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~IdleTask() override;
// CancelableIdleTask implementation.
void RunInternal(double deadline_in_seconds) override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(IdleTask);
};
CompilerDispatcher::IdleTask::IdleTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableIdleTask(task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::IdleTask::~IdleTask() {}
void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) {
dispatcher_->DoIdleWork(deadline_in_seconds);
}
CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
size_t max_stack_size)
: isolate_(isolate),
platform_(platform),
max_stack_size_(max_stack_size),
trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher),
tracer_(new CompilerDispatcherTracer(isolate_)),
task_manager_(new CancelableTaskManager()),
next_job_id_(0),
shared_to_unoptimized_job_id_(isolate->heap()),
memory_pressure_level_(MemoryPressureLevel::kNone),
abort_(false),
idle_task_scheduled_(false),
num_background_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() {
// To avoid crashing in unit tests due to unfished jobs.
AbortAll(BlockingBehavior::kBlock);
task_manager_->CancelAndWait();
}
bool CompilerDispatcher::CanEnqueue() {
if (!IsEnabled()) return false;
if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) {
return false;
}
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (abort_) return false;
}
return true;
}
bool CompilerDispatcher::CanEnqueue(Handle<SharedFunctionInfo> function) {
if (!CanEnqueue()) return false;
// We only handle functions (no eval / top-level code / native) that are
// attached to a script.
if (!function->script()->IsScript() || function->is_toplevel() ||
function->native()) {
return false;
}
return true;
}
CompilerDispatcher::JobId CompilerDispatcher::Enqueue(
std::unique_ptr<CompilerDispatcherJob> job) {
DCHECK(!job->IsFinished());
JobMap::const_iterator it = InsertJob(std::move(job));
ConsiderJobForBackgroundProcessing(it->second.get());
ScheduleIdleTaskIfNeeded();
return it->first;
}
CompilerDispatcher::JobId CompilerDispatcher::EnqueueAndStep(
std::unique_ptr<CompilerDispatcherJob> job) {
DCHECK(!job->IsFinished());
JobMap::const_iterator it = InsertJob(std::move(job));
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: stepping ");
it->second->ShortPrintOnMainThread();
PrintF("\n");
}
DoNextStepOnMainThread(isolate_, it->second.get(),
ExceptionHandling::kSwallow);
ConsiderJobForBackgroundProcessing(it->second.get());
RemoveIfFinished(it);
ScheduleIdleTaskIfNeeded();
return it->first;
}
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherEnqueue");
if (!CanEnqueue(function)) return false;
if (IsEnqueued(function)) return true;
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: enqueuing ");
function->ShortPrint();
PrintF(" for parse and compile\n");
}
std::unique_ptr<CompilerDispatcherJob> job(new UnoptimizedCompileJob(
isolate_, tracer_.get(), function, max_stack_size_));
Enqueue(std::move(job));
return true;
}
bool CompilerDispatcher::EnqueueAndStep(Handle<SharedFunctionInfo> function) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherEnqueueAndStep");
if (!CanEnqueue(function)) return false;
if (IsEnqueued(function)) return true;
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: enqueuing ");
function->ShortPrint();
PrintF(" for parse and compile\n");
}
std::unique_ptr<CompilerDispatcherJob> job(new UnoptimizedCompileJob(
isolate_, tracer_.get(), function, max_stack_size_));
EnqueueAndStep(std::move(job));
return true;
}
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();
}
void CompilerDispatcher::WaitForJobIfRunningOnBackground(
CompilerDispatcherJob* job) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherWaitForBackgroundJob");
RuntimeCallTimerScope runtimeTimer(
isolate_, &RuntimeCallStats::CompileWaitForDispatcher);
base::LockGuard<base::Mutex> 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(CompilerDispatcherJob* job) {
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: finishing ");
job->ShortPrintOnMainThread();
PrintF(" now\n");
}
WaitForJobIfRunningOnBackground(job);
while (!job->IsFinished()) {
DoNextStepOnMainThread(isolate_, job, ExceptionHandling::kThrow);
}
return !job->IsFailed();
}
bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherFinishNow");
JobMap::const_iterator job = GetJobFor(function);
CHECK(job != jobs_.end());
bool result = FinishNow(job->second.get());
RemoveIfFinished(job);
return result;
}
void CompilerDispatcher::FinishAllNow() {
// First finish all jobs not running in background
for (auto it = jobs_.cbegin(); it != jobs_.cend();) {
CompilerDispatcherJob* job = it->second.get();
bool is_running_in_background;
{
base::LockGuard<base::Mutex> lock(&mutex_);
is_running_in_background =
running_background_jobs_.find(job) != running_background_jobs_.end();
pending_background_jobs_.erase(job);
}
if (!is_running_in_background) {
while (!job->IsFinished()) {
DoNextStepOnMainThread(isolate_, job, ExceptionHandling::kThrow);
}
it = RemoveIfFinished(it);
} else {
++it;
}
}
// Potentially wait for jobs that were running in background
for (auto it = jobs_.cbegin(); it != jobs_.cend();
it = RemoveIfFinished(it)) {
FinishNow(it->second.get());
}
}
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
bool background_tasks_running =
task_manager_->TryAbortAll() == CancelableTaskManager::kTaskRunning;
if (!background_tasks_running || blocking == BlockingBehavior::kBlock) {
for (auto& it : jobs_) {
WaitForJobIfRunningOnBackground(it.second.get());
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: aborted ");
it.second->ShortPrintOnMainThread();
PrintF("\n");
}
it.second->ResetOnMainThread(isolate_);
}
jobs_.clear();
shared_to_unoptimized_job_id_.Clear();
{
base::LockGuard<base::Mutex> lock(&mutex_);
DCHECK(pending_background_jobs_.empty());
DCHECK(running_background_jobs_.empty());
abort_ = false;
}
return;
}
{
base::LockGuard<base::Mutex> lock(&mutex_);
abort_ = true;
pending_background_jobs_.clear();
}
AbortInactiveJobs();
// All running background jobs might already have scheduled idle tasks instead
// of abort tasks. Schedule a single abort task here to make sure they get
// processed as soon as possible (and not first when we have idle time).
ScheduleAbortTask();
}
void CompilerDispatcher::AbortInactiveJobs() {
{
base::LockGuard<base::Mutex> lock(&mutex_);
// Since we schedule two abort tasks per async abort, we might end up
// here with nothing left to do.
if (!abort_) return;
}
for (auto it = jobs_.cbegin(); it != jobs_.cend();) {
auto job = it;
++it;
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (running_background_jobs_.find(job->second.get()) !=
running_background_jobs_.end()) {
continue;
}
}
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: aborted ");
job->second->ShortPrintOnMainThread();
PrintF("\n");
}
it = RemoveJob(job);
}
if (jobs_.empty()) {
base::LockGuard<base::Mutex> lock(&mutex_);
if (num_background_tasks_ == 0) abort_ = false;
}
}
void CompilerDispatcher::MemoryPressureNotification(
v8::MemoryPressureLevel level, bool is_isolate_locked) {
MemoryPressureLevel previous = memory_pressure_level_.Value();
memory_pressure_level_.SetValue(level);
// If we're already under pressure, we haven't accepted new tasks meanwhile
// and can just return. If we're no longer under pressure, we're also done.
if (previous != MemoryPressureLevel::kNone ||
level == MemoryPressureLevel::kNone) {
return;
}
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: received memory pressure notification\n");
}
if (is_isolate_locked) {
AbortAll(BlockingBehavior::kDontBlock);
} else {
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (abort_) return;
// By going into abort mode here, and clearing the
// pending_background_jobs_, we at keep existing background jobs from
// picking up more work before the MemoryPressureTask gets executed.
abort_ = true;
pending_background_jobs_.clear();
}
platform_->CallOnForegroundThread(
reinterpret_cast<v8::Isolate*>(isolate_),
new MemoryPressureTask(isolate_, task_manager_.get(), this));
}
}
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);
DCHECK(job == jobs_.end() ||
job->second->AsUnoptimizedCompileJob()->IsAssociatedWith(shared));
}
return job;
}
void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
if (!platform_->IdleTasksEnabled(v8_isolate)) return;
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (idle_task_scheduled_) return;
idle_task_scheduled_ = true;
}
platform_->CallIdleOnForegroundThread(
v8_isolate, new IdleTask(isolate_, task_manager_.get(), this));
}
void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
if (jobs_.empty()) return;
ScheduleIdleTaskFromAnyThread();
}
void CompilerDispatcher::ScheduleAbortTask() {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
platform_->CallOnForegroundThread(
v8_isolate, new AbortTask(isolate_, task_manager_.get(), this));
}
void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
CompilerDispatcherJob* job) {
if (!job->CanStepNextOnAnyThread()) return;
{
base::LockGuard<base::Mutex> lock(&mutex_);
pending_background_jobs_.insert(job);
}
ScheduleMoreBackgroundTasksIfNeeded();
}
void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherScheduleMoreBackgroundTasksIfNeeded");
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (pending_background_jobs_.empty()) return;
if (platform_->NumberOfAvailableBackgroundThreads() <=
num_background_tasks_) {
return;
}
++num_background_tasks_;
}
platform_->CallOnBackgroundThread(
new BackgroundTask(isolate_, task_manager_.get(), this),
v8::Platform::kShortRunningTask);
}
void CompilerDispatcher::DoBackgroundWork() {
for (;;) {
CompilerDispatcherJob* job = nullptr;
{
base::LockGuard<base::Mutex> 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");
}
DoNextStepOnBackgroundThread(job);
// Unconditionally schedule an idle task, as all background steps have to be
// followed by a main thread step.
ScheduleIdleTaskFromAnyThread();
{
base::LockGuard<base::Mutex> lock(&mutex_);
running_background_jobs_.erase(job);
if (main_thread_blocking_on_job_ == job) {
main_thread_blocking_on_job_ = nullptr;
main_thread_blocking_signal_.NotifyOne();
}
}
}
{
base::LockGuard<base::Mutex> lock(&mutex_);
--num_background_tasks_;
if (running_background_jobs_.empty() && abort_) {
// This is the last background job that finished. The abort task
// scheduled by AbortAll might already have ran, so schedule another
// one to be on the safe side.
ScheduleAbortTask();
}
}
// Don't touch |this| anymore after this point, as it might have been
// deleted.
}
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
bool aborted = false;
{
base::LockGuard<base::Mutex> lock(&mutex_);
idle_task_scheduled_ = false;
aborted = abort_;
}
if (aborted) {
AbortInactiveJobs();
return;
}
// Number of jobs that are unlikely to make progress during any idle callback
// due to their estimated duration.
size_t too_long_jobs = 0;
// Iterate over all available jobs & remaining time. For each job, decide
// whether to 1) skip it (if it would take too long), 2) erase it (if it's
// finished), or 3) make progress on it.
double idle_time_in_seconds =
deadline_in_seconds - platform_->MonotonicallyIncreasingTime();
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: received %0.1lfms of idle time\n",
idle_time_in_seconds *
static_cast<double>(base::Time::kMillisecondsPerSecond));
}
for (auto job = jobs_.cbegin();
job != jobs_.cend() && idle_time_in_seconds > 0.0;
idle_time_in_seconds =
deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) {
// Don't work on jobs that are being worked on by background tasks.
// Similarly, remove jobs we work on from the set of available background
// jobs.
std::unique_ptr<base::LockGuard<base::Mutex>> lock(
new base::LockGuard<base::Mutex>(&mutex_));
if (running_background_jobs_.find(job->second.get()) !=
running_background_jobs_.end()) {
++job;
continue;
}
auto it = pending_background_jobs_.find(job->second.get());
double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs();
if (idle_time_in_seconds <
(estimate_in_ms /
static_cast<double>(base::Time::kMillisecondsPerSecond))) {
// If there's not enough time left, try to estimate whether we would
// have managed to finish the job in a large idle task to assess
// whether we should ask for another idle callback.
if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs;
if (it == pending_background_jobs_.end()) {
lock.reset();
ConsiderJobForBackgroundProcessing(job->second.get());
}
++job;
} else if (job->second->IsFinished()) {
DCHECK(it == pending_background_jobs_.end());
lock.reset();
job = RemoveJob(job);
continue;
} else {
// Do one step, and keep processing the job (as we don't advance the
// iterator).
if (it != pending_background_jobs_.end()) {
pending_background_jobs_.erase(it);
}
lock.reset();
DoNextStepOnMainThread(isolate_, job->second.get(),
ExceptionHandling::kSwallow);
}
}
if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded();
}
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveIfFinished(
JobMap::const_iterator job) {
if (!job->second->IsFinished()) {
return job;
}
if (trace_compiler_dispatcher_) {
bool result = !job->second->IsFailed();
PrintF("CompilerDispatcher: finished working on ");
job->second->ShortPrintOnMainThread();
PrintF(": %s\n", result ? "success" : "failure");
tracer_->DumpStatistics();
}
return RemoveJob(job);
}
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::InsertJob(
std::unique_ptr<CompilerDispatcherJob> job) {
bool added;
JobMap::const_iterator it;
std::tie(it, added) =
jobs_.insert(std::make_pair(next_job_id_++, std::move(job)));
DCHECK(added);
JobId id = it->first;
CompilerDispatcherJob* inserted_job = it->second.get();
// Maps unoptimized jobs' SFIs to their job id.
if (inserted_job->type() == CompilerDispatcherJob::kUnoptimizedCompile) {
Handle<SharedFunctionInfo> shared =
inserted_job->AsUnoptimizedCompileJob()->shared();
if (!shared.is_null()) {
shared_to_unoptimized_job_id_.Set(shared, id);
}
}
return it;
}
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveJob(
CompilerDispatcher::JobMap::const_iterator it) {
CompilerDispatcherJob* job = it->second.get();
job->ResetOnMainThread(isolate_);
// Unmaps unoptimized jobs' SFIs to their job id.
if (job->type() == CompilerDispatcherJob::kUnoptimizedCompile) {
Handle<SharedFunctionInfo> shared =
job->AsUnoptimizedCompileJob()->shared();
if (!shared.is_null()) {
JobId deleted_id = shared_to_unoptimized_job_id_.Delete(shared);
USE(deleted_id);
DCHECK_EQ(it->first, deleted_id);
}
}
it = jobs_.erase(it);
if (jobs_.empty()) {
base::LockGuard<base::Mutex> lock(&mutex_);
if (num_background_tasks_ == 0) abort_ = false;
}
return it;
}
} // namespace internal
} // namespace v8