| // 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/base/template-utils.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) { |
| std::unique_ptr<ParseInfo> outer_parse_info = |
| test::OuterParseInfoForShared(isolate, shared); |
| 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, |
| FunctionLiteral::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(); |
| } |
| |
| void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override { |
| base::MutexGuard lock(&mutex_); |
| foreground_tasks_.push_back(std::unique_ptr<Task>(task)); |
| } |
| |
| void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task, |
| double delay_in_seconds) override { |
| UNREACHABLE(); |
| } |
| |
| void CallIdleOnForegroundThread(v8::Isolate* isolate, |
| IdleTask* task) override { |
| base::MutexGuard 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::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( |
| base::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( |
| base::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 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; } |
| |
| 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 |