v8/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc
Ross McIlroy 72575d3c67 [Compile] Move stepping logic from compilation job to compiler dispatcher.
Now that UnoptimizedCompileJob only has three stages, move the logic for
stepping between these stages out of UnoptimizedCompileJob and back into
CompilerDispatcher.

BUG=v8:5203

Change-Id: I3bb776e14ef9da801dc9792e9e643b8026135060
Reviewed-on: https://chromium-review.googlesource.com/774743
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49422}
2017-11-16 17:20:07 +00:00

1021 lines
33 KiB
C++

// 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 <sstream>
#include "include/v8-platform.h"
#include "src/api.h"
#include "src/ast/ast-value-factory.h"
#include "src/base/platform/semaphore.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/compiler.h"
#include "src/flags.h"
#include "src/handles.h"
#include "src/objects-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/v8.h"
#include "test/unittests/test-helpers.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
// V8 is smart enough to know something was already compiled and return compiled
// code straight away. We need a unique name for each test function so that V8
// returns an empty SharedFunctionInfo.
#define _STR(x) #x
#define STR(x) _STR(x)
#define _SCRIPT(fn, a, b, c) a fn b fn c
#define SCRIPT(a, b, c) _SCRIPT("f" STR(__LINE__), a, b, c)
#define TEST_SCRIPT() \
"function f" STR(__LINE__) "(x, y) { return x * y }; f" STR(__LINE__) ";"
namespace v8 {
namespace internal {
class CompilerDispatcherTestFlags {
public:
static void SetFlagsForTest() {
CHECK_NULL(save_flags_);
save_flags_ = new SaveFlags();
FLAG_single_threaded = true;
FlagList::EnforceFlagImplications();
FLAG_compiler_dispatcher = true;
}
static void RestoreFlags() {
CHECK_NOT_NULL(save_flags_);
delete save_flags_;
save_flags_ = nullptr;
}
private:
static SaveFlags* save_flags_;
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilerDispatcherTestFlags);
};
SaveFlags* CompilerDispatcherTestFlags::save_flags_ = nullptr;
class CompilerDispatcherTest : public TestWithNativeContext {
public:
CompilerDispatcherTest() = default;
~CompilerDispatcherTest() override = default;
static void SetUpTestCase() {
CompilerDispatcherTestFlags::SetFlagsForTest();
TestWithNativeContext ::SetUpTestCase();
}
static void TearDownTestCase() {
TestWithNativeContext ::TearDownTestCase();
CompilerDispatcherTestFlags::RestoreFlags();
}
private:
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherTest);
};
namespace {
class MockPlatform : public v8::Platform {
public:
MockPlatform()
: time_(0.0),
time_step_(0.0),
idle_task_(nullptr),
sem_(0),
tracing_controller_(V8::GetCurrentPlatform()->GetTracingController()) {}
~MockPlatform() override {
base::LockGuard<base::Mutex> lock(&mutex_);
EXPECT_TRUE(foreground_tasks_.empty());
EXPECT_TRUE(background_tasks_.empty());
EXPECT_TRUE(idle_task_ == nullptr);
}
size_t NumberOfAvailableBackgroundThreads() override { return 1; }
void CallOnBackgroundThread(Task* task,
ExpectedRuntime expected_runtime) override {
base::LockGuard<base::Mutex> lock(&mutex_);
background_tasks_.push_back(task);
}
void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
base::LockGuard<base::Mutex> lock(&mutex_);
foreground_tasks_.push_back(task);
}
void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
double delay_in_seconds) override {
UNREACHABLE();
}
void CallIdleOnForegroundThread(v8::Isolate* isolate,
IdleTask* task) override {
base::LockGuard<base::Mutex> lock(&mutex_);
ASSERT_TRUE(idle_task_ == nullptr);
idle_task_ = task;
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return true; }
double MonotonicallyIncreasingTime() override {
time_ += time_step_;
return time_;
}
double CurrentClockTimeMillis() override {
return time_ * base::Time::kMillisecondsPerSecond;
}
v8::TracingController* GetTracingController() override {
return tracing_controller_;
}
void RunIdleTask(double deadline_in_seconds, double time_step) {
time_step_ = time_step;
IdleTask* task;
{
base::LockGuard<base::Mutex> lock(&mutex_);
task = idle_task_;
ASSERT_TRUE(idle_task_ != nullptr);
idle_task_ = nullptr;
}
task->Run(deadline_in_seconds);
delete task;
}
bool IdleTaskPending() {
base::LockGuard<base::Mutex> lock(&mutex_);
return idle_task_;
}
bool BackgroundTasksPending() {
base::LockGuard<base::Mutex> lock(&mutex_);
return !background_tasks_.empty();
}
bool ForegroundTasksPending() {
base::LockGuard<base::Mutex> lock(&mutex_);
return !foreground_tasks_.empty();
}
void RunBackgroundTasksAndBlock(Platform* platform) {
std::vector<Task*> tasks;
{
base::LockGuard<base::Mutex> lock(&mutex_);
tasks.swap(background_tasks_);
}
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true),
kShortRunningTask);
sem_.Wait();
}
void RunBackgroundTasks(Platform* platform) {
std::vector<Task*> tasks;
{
base::LockGuard<base::Mutex> lock(&mutex_);
tasks.swap(background_tasks_);
}
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false),
kShortRunningTask);
}
void RunForegroundTasks() {
std::vector<Task*> tasks;
{
base::LockGuard<base::Mutex> lock(&mutex_);
tasks.swap(foreground_tasks_);
}
for (auto& task : tasks) {
task->Run();
delete task;
}
}
void ClearBackgroundTasks() {
std::vector<Task*> tasks;
{
base::LockGuard<base::Mutex> lock(&mutex_);
tasks.swap(background_tasks_);
}
for (auto& task : tasks) {
delete task;
}
}
void ClearForegroundTasks() {
std::vector<Task*> tasks;
{
base::LockGuard<base::Mutex> lock(&mutex_);
tasks.swap(foreground_tasks_);
}
for (auto& task : tasks) {
delete task;
}
}
void ClearIdleTask() {
base::LockGuard<base::Mutex> lock(&mutex_);
ASSERT_TRUE(idle_task_ != nullptr);
delete idle_task_;
idle_task_ = nullptr;
}
private:
class TaskWrapper : public Task {
public:
TaskWrapper(MockPlatform* platform, const std::vector<Task*>& tasks,
bool signal)
: platform_(platform), tasks_(tasks), signal_(signal) {}
~TaskWrapper() = default;
void Run() override {
for (auto& task : tasks_) {
task->Run();
delete task;
}
if (signal_) platform_->sem_.Signal();
}
private:
MockPlatform* platform_;
std::vector<Task*> tasks_;
bool signal_;
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
};
double time_;
double time_step_;
// Protects all *_tasks_.
base::Mutex mutex_;
IdleTask* idle_task_;
std::vector<Task*> background_tasks_;
std::vector<Task*> foreground_tasks_;
base::Semaphore sem_;
v8::TracingController* tracing_controller_;
DISALLOW_COPY_AND_ASSIGN(MockPlatform);
};
} // namespace
TEST_F(CompilerDispatcherTest, Construct) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
}
TEST_F(CompilerDispatcherTest, IsEnqueued) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kBlock);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishNow) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(shared->is_compiled());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishAllNow) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
constexpr int num_funcs = 2;
Handle<JSFunction> f[num_funcs];
Handle<SharedFunctionInfo> shared[num_funcs];
for (int i = 0; i < num_funcs; ++i) {
std::stringstream ss;
ss << 'f' << STR(__LINE__) << '_' << i;
std::string func_name = ss.str();
std::string script("function f" + func_name + "(x, y) { return x * y }; f" +
func_name + ";");
f[i] = RunJS<JSFunction>(script.c_str());
shared[i] = Handle<SharedFunctionInfo>(f[i]->shared(), i_isolate());
ASSERT_FALSE(shared[i]->is_compiled());
ASSERT_TRUE(dispatcher.Enqueue(shared[i]));
}
dispatcher.FinishAllNow();
for (int i = 0; i < num_funcs; ++i) {
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared[i]));
ASSERT_TRUE(shared[i]->is_compiled());
}
platform.ClearIdleTask();
platform.ClearBackgroundTasks();
}
TEST_F(CompilerDispatcherTest, IdleTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
// Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
}
TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
// The job should be scheduled for the main thread.
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Only grant a little idle time and have time advance beyond it in one step.
platform.RunIdleTask(2.0, 1.0);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
// The job should be still scheduled for the main thread, but ready for
// parsing.
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
}
TEST_F(CompilerDispatcherTest, IdleTaskException) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string func_name("f" STR(__LINE__));
std::string script("function " + func_name + "(x) { var a = ");
for (int i = 0; i < 500; i++) {
// Alternate + and - to avoid n-ary operation nodes.
script += "'x' + 'x' - ";
}
script += " 'x'; }; " + func_name + ";";
Handle<JSFunction> f = RunJS<JSFunction>(script.c_str());
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
// Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(i_isolate()->has_pending_exception());
}
TEST_F(CompilerDispatcherTest, CompileOnBackgroundThread) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled,
dispatcher.jobs_.begin()->second->status());
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
}
TEST_F(CompilerDispatcherTest, FinishNowWithBackgroundTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
// This does not block, but races with the FinishNow() call below.
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
if (platform.IdleTaskPending()) platform.ClearIdleTask();
ASSERT_FALSE(platform.BackgroundTasksPending());
}
TEST_F(CompilerDispatcherTest, IdleTaskMultipleJobs) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT();
Handle<JSFunction> f1 = RunJS<JSFunction>(script1);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate());
const char script2[] = TEST_SCRIPT();
Handle<JSFunction> f2 = RunJS<JSFunction>(script2);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared1));
ASSERT_TRUE(dispatcher.Enqueue(shared2));
ASSERT_TRUE(platform.IdleTaskPending());
// Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared2));
ASSERT_TRUE(shared1->is_compiled());
ASSERT_TRUE(shared2->is_compiled());
}
TEST_F(CompilerDispatcherTest, FinishNowException) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string func_name("f" STR(__LINE__));
std::string script("function " + func_name + "(x) { var a = ");
for (int i = 0; i < 500; i++) {
// Alternate + and - to avoid n-ary operation nodes.
script += "'x' + 'x' - ";
}
script += " 'x'; }; " + func_name + ";";
Handle<JSFunction> f = RunJS<JSFunction>(script.c_str());
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(dispatcher.FinishNow(shared));
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_TRUE(i_isolate()->has_pending_exception());
i_isolate()->clear_pending_exception();
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, AsyncAbortAllPendingBackgroundTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
// The background task hasn't yet started, so we can just cancel it.
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
ASSERT_FALSE(platform.ForegroundTasksPending());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
if (platform.IdleTaskPending()) platform.ClearIdleTask();
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_FALSE(platform.ForegroundTasksPending());
}
TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningBackgroundTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT();
Handle<JSFunction> f1 = RunJS<JSFunction>(script1);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate());
const char script2[] = TEST_SCRIPT();
Handle<JSFunction> f2 = RunJS<JSFunction>(script2);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared1));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared1));
ASSERT_FALSE(shared1->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
// Kick off background tasks and freeze them.
dispatcher.block_for_testing_.SetValue(true);
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
// Busy loop until the background task started running.
while (dispatcher.block_for_testing_.Value()) {
}
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
ASSERT_TRUE(platform.ForegroundTasksPending());
// We can't schedule new tasks while we're aborting.
ASSERT_FALSE(dispatcher.Enqueue(shared2));
// Run the first AbortTask. Since the background job is still pending, it
// can't do anything.
platform.RunForegroundTasks();
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
ASSERT_TRUE(dispatcher.abort_);
}
// Release background task.
dispatcher.semaphore_for_testing_.Signal();
// Busy loop until the background task scheduled another AbortTask task.
while (!platform.ForegroundTasksPending()) {
}
platform.RunForegroundTasks();
ASSERT_TRUE(dispatcher.jobs_.empty());
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
ASSERT_FALSE(dispatcher.abort_);
}
ASSERT_TRUE(platform.IdleTaskPending());
platform.RunIdleTask(5.0, 1.0);
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_FALSE(platform.ForegroundTasksPending());
// Now it's possible to enqueue new functions again.
ASSERT_TRUE(dispatcher.Enqueue(shared2));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_FALSE(platform.ForegroundTasksPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
// Kick off background tasks and freeze them.
dispatcher.block_for_testing_.SetValue(true);
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
// Busy loop until the background task started running.
while (dispatcher.block_for_testing_.Value()) {
}
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
ASSERT_TRUE(platform.ForegroundTasksPending());
// Run the first AbortTask. Since the background job is still pending, it
// can't do anything.
platform.RunForegroundTasks();
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
ASSERT_TRUE(dispatcher.abort_);
}
// While the background thread holds on to a job, it is still enqueud.
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
// Release background task.
dispatcher.semaphore_for_testing_.Signal();
// Force the compilation to finish, even while aborting.
ASSERT_TRUE(dispatcher.FinishNow(shared));
ASSERT_TRUE(dispatcher.jobs_.empty());
// Busy wait for the background task to finish.
for (;;) {
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
if (dispatcher.num_background_tasks_ == 0) {
break;
}
}
ASSERT_TRUE(platform.ForegroundTasksPending());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
platform.RunForegroundTasks();
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
ASSERT_FALSE(dispatcher.abort_);
}
platform.ClearForegroundTasks();
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, MemoryPressure) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
// Can't enqueue tasks under memory pressure.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true);
ASSERT_FALSE(dispatcher.Enqueue(shared));
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kNone, true);
ASSERT_TRUE(dispatcher.Enqueue(shared));
// Memory pressure cancels current jobs.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
platform.ClearIdleTask();
}
namespace {
class PressureNotificationTask : public CancelableTask {
public:
PressureNotificationTask(Isolate* isolate, CompilerDispatcher* dispatcher,
base::Semaphore* sem)
: CancelableTask(isolate), dispatcher_(dispatcher), sem_(sem) {}
~PressureNotificationTask() override {}
void RunInternal() override {
dispatcher_->MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
false);
sem_->Signal();
}
private:
CompilerDispatcher* dispatcher_;
base::Semaphore* sem_;
DISALLOW_COPY_AND_ASSIGN(PressureNotificationTask);
};
} // namespace
TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Semaphore sem(0);
V8::GetCurrentPlatform()->CallOnBackgroundThread(
new PressureNotificationTask(i_isolate(), &dispatcher, &sem),
v8::Platform::kShortRunningTask);
sem.Wait();
// A memory pressure task is pending, and running it will cancel the job.
ASSERT_TRUE(platform.ForegroundTasksPending());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
platform.RunForegroundTasks();
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
// Since the AbortAll() call is made from a task, AbortAll thinks that there
// is at least one task running, and fires of an AbortTask to be safe.
ASSERT_TRUE(platform.ForegroundTasksPending());
platform.RunForegroundTasks();
ASSERT_FALSE(platform.ForegroundTasksPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, EnqueueJob) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
std::unique_ptr<CompilerDispatcherJob> job(
new UnoptimizedCompileJob(i_isolate(), dispatcher.tracer_.get(), shared,
dispatcher.max_stack_size_));
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
dispatcher.Enqueue(std::move(job));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
ASSERT_FALSE(platform.BackgroundTasksPending());
}
TEST_F(CompilerDispatcherTest, EnqueueAndStep) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
ASSERT_TRUE(platform.BackgroundTasksPending());
platform.ClearBackgroundTasks();
}
TEST_F(CompilerDispatcherTest, CompileLazyFinishesDispatcherJob) {
// Use the real dispatcher so that CompileLazy checks the same one for
// enqueued functions.
CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
const char script[] = "function lazy() { return 42; }; lazy;";
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared));
ASSERT_TRUE(dispatcher->Enqueue(shared));
ASSERT_TRUE(dispatcher->IsEnqueued(shared));
// Now force the function to run and ensure CompileLazy finished and dequeues
// it from the dispatcher.
RunJS("lazy();");
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared));
}
TEST_F(CompilerDispatcherTest, CompileLazy2FinishesDispatcherJob) {
// Use the real dispatcher so that CompileLazy checks the same one for
// enqueued functions.
CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
const char source2[] = "function lazy2() { return 42; }; lazy2;";
Handle<JSFunction> lazy2 = RunJS<JSFunction>(source2);
Handle<SharedFunctionInfo> shared2(lazy2->shared(), i_isolate());
ASSERT_FALSE(shared2->is_compiled());
const char source1[] = "function lazy1() { return lazy2(); }; lazy1;";
Handle<JSFunction> lazy1 = RunJS<JSFunction>(source1);
Handle<SharedFunctionInfo> shared1(lazy1->shared(), i_isolate());
ASSERT_FALSE(shared1->is_compiled());
ASSERT_TRUE(dispatcher->Enqueue(shared1));
ASSERT_TRUE(dispatcher->Enqueue(shared2));
RunJS("lazy1();");
ASSERT_TRUE(shared1->is_compiled());
ASSERT_TRUE(shared2->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared1));
ASSERT_FALSE(dispatcher->IsEnqueued(shared2));
}
TEST_F(CompilerDispatcherTest, EnqueueAndStepTwice) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
// EnqueueAndStep of the same function again (shouldn't step the job.
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
platform.ClearIdleTask();
platform.ClearBackgroundTasks();
}
TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT();
Handle<JSFunction> f1 = RunJS<JSFunction>(script1);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate());
const char script2[] = TEST_SCRIPT();
Handle<JSFunction> f2 = RunJS<JSFunction>(script2);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared1));
ASSERT_TRUE(dispatcher.Enqueue(shared2));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
(++dispatcher.jobs_.begin())->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
(++dispatcher.jobs_.begin())->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared1));
ASSERT_TRUE(dispatcher.IsEnqueued(shared2));
ASSERT_FALSE(shared1->is_compiled());
ASSERT_FALSE(shared2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled,
dispatcher.jobs_.begin()->second->status());
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled,
(++dispatcher.jobs_.begin())->second->status());
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared2));
ASSERT_TRUE(shared1->is_compiled());
ASSERT_TRUE(shared2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
}
#undef _STR
#undef STR
#undef _SCRIPT
#undef SCRIPT
#undef TEST_SCRIPT
} // namespace internal
} // namespace v8