v8/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc
Leszek Swirski 6458a5296b [compile] Add an UnoptimizedCompileState class
Move the persistent compilation state and Isolate inputs (such as the
allocator, shared AST constants, hash seed, logger, etc.) which survives
across both parse and compile, out of ParseInfo and into a new
UnoptimizedCompileState class. Also add UnoptimizedCompilePerThreadState
for per-thread state such as stack limit and RCS.

In particular, this new state survives the ParseInfo being destructed,
which means it is available after off-thread finalization. This allows a
followup to access the PendingCompilationErrorHandler after finalization
and report errors on merge.

Bug: v8:10314
Change-Id: Ia186bc0f267c704efd771aa1895f50a4525a8364
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2105636
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67329}
2020-04-23 07:08:28 +00:00

843 lines
28 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/api-inl.h"
#include "src/ast/ast-value-factory.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/base/platform/semaphore.h"
#include "src/codegen/compiler.h"
#include "src/flags/flags.h"
#include "src/handles/handles.h"
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/zone/zone-list-inl.h"
#include "test/unittests/test-helpers.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
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();
}
static base::Optional<CompilerDispatcher::JobId> EnqueueUnoptimizedCompileJob(
CompilerDispatcher* dispatcher, Isolate* isolate,
Handle<SharedFunctionInfo> shared) {
UnoptimizedCompileState state(isolate);
std::unique_ptr<ParseInfo> outer_parse_info =
test::OuterParseInfoForShared(isolate, shared, &state);
AstValueFactory* ast_value_factory =
outer_parse_info->GetOrCreateAstValueFactory();
AstNodeFactory ast_node_factory(ast_value_factory,
outer_parse_info->zone());
const AstRawString* function_name =
ast_value_factory->GetOneByteString("f");
DeclarationScope* script_scope = new (outer_parse_info->zone())
DeclarationScope(outer_parse_info->zone(), ast_value_factory);
DeclarationScope* function_scope =
new (outer_parse_info->zone()) DeclarationScope(
outer_parse_info->zone(), script_scope, FUNCTION_SCOPE);
function_scope->set_start_position(shared->StartPosition());
function_scope->set_end_position(shared->EndPosition());
std::vector<void*> pointer_buffer;
ScopedPtrList<Statement> statements(&pointer_buffer);
const FunctionLiteral* function_literal =
ast_node_factory.NewFunctionLiteral(
function_name, function_scope, statements, -1, -1, -1,
FunctionLiteral::kNoDuplicateParameters,
FunctionSyntaxKind::kAnonymousExpression,
FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
shared->function_literal_id(), nullptr);
return dispatcher->Enqueue(outer_parse_info.get(), function_name,
function_literal);
}
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::MutexGuard lock(&mutex_);
EXPECT_TRUE(foreground_tasks_.empty());
EXPECT_TRUE(worker_tasks_.empty());
EXPECT_TRUE(idle_task_ == nullptr);
}
int NumberOfWorkerThreads() override { return 1; }
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate) override {
return std::make_shared<MockForegroundTaskRunner>(this);
}
void CallOnWorkerThread(std::unique_ptr<Task> task) override {
base::MutexGuard lock(&mutex_);
worker_tasks_.push_back(std::move(task));
}
void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
double delay_in_seconds) override {
UNREACHABLE();
}
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::MutexGuard lock(&mutex_);
task = idle_task_;
ASSERT_TRUE(idle_task_ != nullptr);
idle_task_ = nullptr;
}
task->Run(deadline_in_seconds);
delete task;
}
bool IdleTaskPending() {
base::MutexGuard lock(&mutex_);
return idle_task_;
}
bool WorkerTasksPending() {
base::MutexGuard lock(&mutex_);
return !worker_tasks_.empty();
}
bool ForegroundTasksPending() {
base::MutexGuard lock(&mutex_);
return !foreground_tasks_.empty();
}
void RunWorkerTasksAndBlock(Platform* platform) {
std::vector<std::unique_ptr<Task>> tasks;
{
base::MutexGuard lock(&mutex_);
tasks.swap(worker_tasks_);
}
platform->CallOnWorkerThread(
std::make_unique<TaskWrapper>(this, std::move(tasks), true));
sem_.Wait();
}
void RunWorkerTasks(Platform* platform) {
std::vector<std::unique_ptr<Task>> tasks;
{
base::MutexGuard lock(&mutex_);
tasks.swap(worker_tasks_);
}
platform->CallOnWorkerThread(
std::make_unique<TaskWrapper>(this, std::move(tasks), false));
}
void RunForegroundTasks() {
std::vector<std::unique_ptr<Task>> tasks;
{
base::MutexGuard lock(&mutex_);
tasks.swap(foreground_tasks_);
}
for (auto& task : tasks) {
task->Run();
// Reset |task| before running the next one.
task.reset();
}
}
void ClearWorkerTasks() {
std::vector<std::unique_ptr<Task>> tasks;
{
base::MutexGuard lock(&mutex_);
tasks.swap(worker_tasks_);
}
}
void ClearForegroundTasks() {
std::vector<std::unique_ptr<Task>> tasks;
{
base::MutexGuard lock(&mutex_);
tasks.swap(foreground_tasks_);
}
}
void ClearIdleTask() {
base::MutexGuard lock(&mutex_);
ASSERT_TRUE(idle_task_ != nullptr);
delete idle_task_;
idle_task_ = nullptr;
}
private:
class TaskWrapper : public Task {
public:
TaskWrapper(MockPlatform* platform,
std::vector<std::unique_ptr<Task>> tasks, bool signal)
: platform_(platform), tasks_(std::move(tasks)), signal_(signal) {}
~TaskWrapper() override = default;
void Run() override {
for (auto& task : tasks_) {
task->Run();
// Reset |task| before running the next one.
task.reset();
}
if (signal_) platform_->sem_.Signal();
}
private:
MockPlatform* platform_;
std::vector<std::unique_ptr<Task>> tasks_;
bool signal_;
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
};
class MockForegroundTaskRunner final : public TaskRunner {
public:
explicit MockForegroundTaskRunner(MockPlatform* platform)
: platform_(platform) {}
void PostTask(std::unique_ptr<v8::Task> task) override {
base::MutexGuard lock(&platform_->mutex_);
platform_->foreground_tasks_.push_back(std::move(task));
}
void PostNonNestableTask(std::unique_ptr<v8::Task> task) override {
// The mock platform does not nest tasks.
PostTask(std::move(task));
}
void PostDelayedTask(std::unique_ptr<Task> task,
double delay_in_seconds) override {
UNREACHABLE();
}
void PostIdleTask(std::unique_ptr<IdleTask> task) override {
DCHECK(IdleTasksEnabled());
base::MutexGuard lock(&platform_->mutex_);
ASSERT_TRUE(platform_->idle_task_ == nullptr);
platform_->idle_task_ = task.release();
}
bool IdleTasksEnabled() override { return true; }
bool NonNestableTasksEnabled() const override { return false; }
private:
MockPlatform* platform_;
};
double time_;
double time_step_;
// Protects all *_tasks_.
base::Mutex mutex_;
IdleTask* idle_task_;
std::vector<std::unique_ptr<Task>> worker_tasks_;
std::vector<std::unique_ptr<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);
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, IsEnqueued) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(job_id);
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); // SFI not yet registered.
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
dispatcher.AbortAll();
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending());
platform.ClearWorkerTasks();
}
TEST_F(CompilerDispatcherTest, FinishNow) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
platform.ClearWorkerTasks();
ASSERT_FALSE(platform.IdleTaskPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, CompileAndFinalize) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.WorkerTasksPending());
// Run compile steps.
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
// Since we haven't yet registered the SFI for the job, it should still be
// enqueued and waiting.
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
// Register SFI, which should schedule another idle task to finalize the
// compilation.
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(platform.IdleTaskPending());
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.WorkerTasksPending());
ASSERT_FALSE(platform.IdleTaskPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, IdleTaskNoIdleTime) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
// Run compile steps.
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
// Job should be ready to finalize.
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(platform.IdleTaskPending());
// Grant no idle time and have time advance beyond it in one step.
platform.RunIdleTask(0.0, 1.0);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
// Job should be ready to finalize.
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
// 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());
ASSERT_FALSE(platform.WorkerTasksPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared_1 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared_2 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_2->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id_1 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
// Run compile steps.
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
// Both jobs should be ready to finalize.
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE((++dispatcher.jobs_.begin())->second->has_run);
ASSERT_TRUE(platform.IdleTaskPending());
// Grant a small anount of idle time and have time advance beyond it in one
// step.
platform.RunIdleTask(2.0, 1.0);
// Only one of the jobs should be finalized.
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_NE(dispatcher.IsEnqueued(shared_1), dispatcher.IsEnqueued(shared_2));
ASSERT_NE(shared_1->is_compiled(), shared_2->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared_1) ||
dispatcher.IsEnqueued(shared_2));
ASSERT_TRUE(shared_1->is_compiled() && shared_2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, IdleTaskException) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string raw_script("(x) { var a = ");
for (int i = 0; i < 1000; i++) {
// Alternate + and - to avoid n-ary operation nodes.
raw_script += "'x' + 'x' - ";
}
raw_script += " 'x'; };";
test::ScriptResource* script =
new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), script);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
// Run compile steps and finalize.
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(i_isolate()->has_pending_exception());
platform.ClearWorkerTasks();
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, FinishNowWithWorkerTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(platform.WorkerTasksPending());
// This does not block, but races with the FinishNow() call below.
platform.RunWorkerTasks(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.WorkerTasksPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, IdleTaskMultipleJobs) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared_1 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared_2 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_2->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id_1 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
// Run compile steps and finalize.
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
ASSERT_TRUE(shared_1->is_compiled());
ASSERT_TRUE(shared_2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending());
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, FinishNowException) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string raw_script("(x) { var a = ");
for (int i = 0; i < 1000; i++) {
// Alternate + and - to avoid n-ary operation nodes.
raw_script += "'x' + 'x' - ";
}
raw_script += " 'x'; };";
test::ScriptResource* script =
new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), script);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
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();
ASSERT_FALSE(platform.IdleTaskPending());
platform.ClearWorkerTasks();
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, AbortJobNotStarted) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(platform.WorkerTasksPending());
dispatcher.AbortJob(*job_id);
// Aborting removes the job from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
platform.ClearWorkerTasks();
dispatcher.AbortAll();
}
TEST_F(CompilerDispatcherTest, AbortJobAlreadyStarted) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(platform.WorkerTasksPending());
// Have dispatcher block on the background thread when running the job.
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
dispatcher.block_for_testing_.SetValue(true);
}
// Start background thread and wait until it is about to run the job.
platform.RunWorkerTasks(V8::GetCurrentPlatform());
while (dispatcher.block_for_testing_.Value()) {
}
// Now abort while dispatcher is in the middle of running the job.
dispatcher.AbortJob(*job_id);
// Unblock background thread, and wait for job to complete.
{
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
dispatcher.main_thread_blocking_on_job_ =
dispatcher.jobs_.begin()->second.get();
dispatcher.semaphore_for_testing_.Signal();
while (dispatcher.main_thread_blocking_on_job_ != nullptr) {
dispatcher.main_thread_blocking_signal_.Wait(&dispatcher.mutex_);
}
}
// Job should have finished running and then been aborted.
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->aborted);
ASSERT_FALSE(platform.WorkerTasksPending());
ASSERT_TRUE(platform.IdleTaskPending());
// Runt the pending idle task
platform.RunIdleTask(1000.0, 0.0);
// Aborting removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending());
dispatcher.AbortAll();
}
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 raw_script[] = "function lazy() { return 42; }; lazy;";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared);
dispatcher->RegisterSharedFunctionInfo(*job_id, *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 raw_source_2[] = "function lazy2() { return 42; }; lazy2;";
test::ScriptResource* source_2 =
new test::ScriptResource(raw_source_2, strlen(raw_source_2));
Handle<JSFunction> lazy2 = RunJS<JSFunction>(source_2);
Handle<SharedFunctionInfo> shared_2(lazy2->shared(), i_isolate());
ASSERT_FALSE(shared_2->is_compiled());
const char raw_source_1[] = "function lazy1() { return lazy2(); }; lazy1;";
test::ScriptResource* source_1 =
new test::ScriptResource(raw_source_1, strlen(raw_source_1));
Handle<JSFunction> lazy1 = RunJS<JSFunction>(source_1);
Handle<SharedFunctionInfo> shared_1(lazy1->shared(), i_isolate());
ASSERT_FALSE(shared_1->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id_1 =
EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_1);
dispatcher->RegisterSharedFunctionInfo(*job_id_1, *shared_1);
base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_2);
dispatcher->RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_TRUE(dispatcher->IsEnqueued(shared_1));
ASSERT_TRUE(dispatcher->IsEnqueued(shared_2));
RunJS("lazy1();");
ASSERT_TRUE(shared_1->is_compiled());
ASSERT_TRUE(shared_2->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher->IsEnqueued(shared_2));
}
TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
Handle<SharedFunctionInfo> shared_1 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared_2 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_2->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id_1 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_FALSE((++dispatcher.jobs_.begin())->second->has_run);
ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
ASSERT_FALSE(shared_1->is_compiled());
ASSERT_FALSE(shared_2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending());
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending());
ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
ASSERT_TRUE((++dispatcher.jobs_.begin())->second->has_run);
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
ASSERT_TRUE(shared_1->is_compiled());
ASSERT_TRUE(shared_2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
dispatcher.AbortAll();
}
} // namespace internal
} // namespace v8