diff options
Diffstat (limited to 'deps/v8/test/unittests/compiler-dispatcher')
5 files changed, 921 insertions, 52 deletions
diff --git a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.cc b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.cc new file mode 100644 index 0000000000..16fa160aec --- /dev/null +++ b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.cc @@ -0,0 +1,28 @@ +// 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 "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h" + +#include <memory> + +#include "include/v8.h" +#include "src/api.h" +#include "src/objects-inl.h" + +namespace v8 { +namespace internal { + +Handle<Object> RunJS(v8::Isolate* isolate, const char* script) { + return Utils::OpenHandle( + *v8::Script::Compile( + isolate->GetCurrentContext(), + v8::String::NewFromUtf8(isolate, script, v8::NewStringType::kNormal) + .ToLocalChecked()) + .ToLocalChecked() + ->Run(isolate->GetCurrentContext()) + .ToLocalChecked()); +} + +} // namespace internal +} // namespace v8 diff --git a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h new file mode 100644 index 0000000000..97084bd31f --- /dev/null +++ b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef V8_UNITTESTS_COMPILER_DISPATCHER_COMPILER_DISPATCHER_HELPER_H_ +#define V8_UNITTESTS_COMPILER_DISPATCHER_COMPILER_DISPATCHER_HELPER_H_ + +namespace v8 { + +class Isolate; + +namespace internal { + +class Object; +template <typename T> +class Handle; + +Handle<Object> RunJS(v8::Isolate* isolate, const char* script); + +} // namespace internal +} // namespace v8 + +#endif // V8_UNITTESTS_COMPILER_DISPATCHER_COMPILER_DISPATCHER_HELPER_H_ diff --git a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-job-unittest.cc b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-job-unittest.cc index d4c54247e2..ca9f44725b 100644 --- a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-job-unittest.cc +++ b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-job-unittest.cc @@ -10,27 +10,28 @@ #include "src/ast/scopes.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/flags.h" #include "src/isolate-inl.h" #include "src/parsing/parse-info.h" #include "src/v8.h" +#include "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { -typedef TestWithContext CompilerDispatcherJobTest; - -class IgnitionCompilerDispatcherJobTest : public TestWithContext { +class CompilerDispatcherJobTest : public TestWithContext { public: - IgnitionCompilerDispatcherJobTest() {} - ~IgnitionCompilerDispatcherJobTest() override {} + CompilerDispatcherJobTest() : tracer_(i_isolate()) {} + ~CompilerDispatcherJobTest() override {} + + CompilerDispatcherTracer* tracer() { return &tracer_; } static void SetUpTestCase() { old_flag_ = i::FLAG_ignition; i::FLAG_ignition = true; - i::FLAG_never_compact = true; TestWithContext::SetUpTestCase(); } @@ -40,11 +41,13 @@ class IgnitionCompilerDispatcherJobTest : public TestWithContext { } private: + CompilerDispatcherTracer tracer_; static bool old_flag_; - DISALLOW_COPY_AND_ASSIGN(IgnitionCompilerDispatcherJobTest); + + DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherJobTest); }; -bool IgnitionCompilerDispatcherJobTest::old_flag_; +bool CompilerDispatcherJobTest::old_flag_; namespace { @@ -78,53 +81,29 @@ Handle<SharedFunctionInfo> CreateSharedFunctionInfo( source = isolate->factory()->NewStringFromAsciiChecked(test_script); } Handle<Script> script = isolate->factory()->NewScript(source); + Handle<FixedArray> infos = isolate->factory()->NewFixedArray(3); + script->set_shared_function_infos(*infos); Handle<SharedFunctionInfo> shared = isolate->factory()->NewSharedFunctionInfo( isolate->factory()->NewStringFromAsciiChecked("f"), isolate->builtins()->CompileLazy(), false); - SharedFunctionInfo::SetScript(shared, script); shared->set_end_position(source->length()); shared->set_outer_scope_info(ScopeInfo::Empty(isolate)); + shared->set_function_literal_id(1); + SharedFunctionInfo::SetScript(shared, script); return scope.CloseAndEscape(shared); } -Handle<Object> RunJS(v8::Isolate* isolate, const char* script) { - return Utils::OpenHandle( - *v8::Script::Compile( - isolate->GetCurrentContext(), - v8::String::NewFromUtf8(isolate, script, v8::NewStringType::kNormal) - .ToLocalChecked()) - .ToLocalChecked() - ->Run(isolate->GetCurrentContext()) - .ToLocalChecked()); -} - } // namespace TEST_F(CompilerDispatcherJobTest, Construct) { std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), nullptr), + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), nullptr), FLAG_stack_size)); } -TEST_F(CompilerDispatcherJobTest, CanParseOnBackgroundThread) { - { - std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), nullptr), - FLAG_stack_size)); - ASSERT_FALSE(job->can_parse_on_background_thread()); - } - { - ScriptResource script(test_script, strlen(test_script)); - std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), &script), - FLAG_stack_size)); - ASSERT_TRUE(job->can_parse_on_background_thread()); - } -} - TEST_F(CompilerDispatcherJobTest, StateTransitions) { std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), nullptr), + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), nullptr), FLAG_stack_size)); ASSERT_TRUE(job->status() == CompileJobStatus::kInitial); @@ -147,7 +126,7 @@ TEST_F(CompilerDispatcherJobTest, StateTransitions) { TEST_F(CompilerDispatcherJobTest, SyntaxError) { ScriptResource script("^^^", strlen("^^^")); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), &script), + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), &script), FLAG_stack_size)); job->PrepareToParseOnMainThread(); @@ -169,7 +148,7 @@ TEST_F(CompilerDispatcherJobTest, ScopeChain) { Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), handle(f->shared()), FLAG_stack_size)); + i_isolate(), tracer(), handle(f->shared()), FLAG_stack_size)); job->PrepareToParseOnMainThread(); job->Parse(); @@ -205,7 +184,7 @@ TEST_F(CompilerDispatcherJobTest, CompileAndRun) { "g();"; Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), handle(f->shared()), FLAG_stack_size)); + i_isolate(), tracer(), handle(f->shared()), FLAG_stack_size)); job->PrepareToParseOnMainThread(); job->Parse(); @@ -230,7 +209,8 @@ TEST_F(CompilerDispatcherJobTest, CompileFailureToPrepare) { raw_script += " 'x'; }"; ScriptResource script(raw_script.c_str(), strlen(raw_script.c_str())); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), &script), 100)); + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), &script), + 100)); job->PrepareToParseOnMainThread(); job->Parse(); @@ -252,7 +232,8 @@ TEST_F(CompilerDispatcherJobTest, CompileFailureToFinalize) { raw_script += " 'x'; }"; ScriptResource script(raw_script.c_str(), strlen(raw_script.c_str())); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), &script), 50)); + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), &script), + 50)); job->PrepareToParseOnMainThread(); job->Parse(); @@ -285,7 +266,7 @@ class CompileTask : public Task { DISALLOW_COPY_AND_ASSIGN(CompileTask); }; -TEST_F(IgnitionCompilerDispatcherJobTest, CompileOnBackgroundThread) { +TEST_F(CompilerDispatcherJobTest, CompileOnBackgroundThread) { const char* raw_script = "(a, b) {\n" " var c = a + b;\n" @@ -295,13 +276,13 @@ TEST_F(IgnitionCompilerDispatcherJobTest, CompileOnBackgroundThread) { "}"; ScriptResource script(raw_script, strlen(raw_script)); std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( - i_isolate(), CreateSharedFunctionInfo(i_isolate(), &script), 100)); + i_isolate(), tracer(), CreateSharedFunctionInfo(i_isolate(), &script), + 100)); job->PrepareToParseOnMainThread(); job->Parse(); job->FinalizeParsingOnMainThread(); job->PrepareToCompileOnMainThread(); - ASSERT_TRUE(job->can_compile_on_background_thread()); base::Semaphore semaphore(0); CompileTask* background_task = new CompileTask(job.get(), &semaphore); @@ -315,5 +296,36 @@ TEST_F(IgnitionCompilerDispatcherJobTest, CompileOnBackgroundThread) { ASSERT_TRUE(job->status() == CompileJobStatus::kInitial); } +TEST_F(CompilerDispatcherJobTest, LazyInnerFunctions) { + const char script[] = + "function g() {\n" + " f = function() {\n" + " e = (function() { return 42; });\n" + " return e;\n" + " };\n" + " return f;\n" + "}\n" + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); + + std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( + i_isolate(), tracer(), handle(f->shared()), FLAG_stack_size)); + + job->PrepareToParseOnMainThread(); + job->Parse(); + ASSERT_TRUE(job->FinalizeParsingOnMainThread()); + ASSERT_TRUE(job->PrepareToCompileOnMainThread()); + job->Compile(); + ASSERT_TRUE(job->FinalizeCompilingOnMainThread()); + ASSERT_TRUE(job->status() == CompileJobStatus::kDone); + + Handle<JSFunction> e = Handle<JSFunction>::cast(RunJS(isolate(), "f();")); + + ASSERT_FALSE(e->shared()->HasBaselineCode()); + + job->ResetOnMainThread(); + ASSERT_TRUE(job->status() == CompileJobStatus::kInitial); +} + } // namespace internal } // namespace v8 diff --git a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-tracer-unittest.cc b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-tracer-unittest.cc index 997765ff83..21ffe76210 100644 --- a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-tracer-unittest.cc +++ b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-tracer-unittest.cc @@ -8,16 +8,16 @@ namespace v8 { namespace internal { -TEST(CompilerDispatcherTracerTest, EstimateZeroWithoutSamples) { +TEST(CompilerDispatcherTracerTest, EstimateWithoutSamples) { CompilerDispatcherTracer tracer(nullptr); EXPECT_EQ(0.0, tracer.EstimatePrepareToParseInMs()); - EXPECT_EQ(0.0, tracer.EstimateParseInMs(0)); - EXPECT_EQ(0.0, tracer.EstimateParseInMs(42)); + EXPECT_EQ(1.0, tracer.EstimateParseInMs(0)); + EXPECT_EQ(1.0, tracer.EstimateParseInMs(42)); EXPECT_EQ(0.0, tracer.EstimateFinalizeParsingInMs()); EXPECT_EQ(0.0, tracer.EstimatePrepareToCompileInMs()); - EXPECT_EQ(0.0, tracer.EstimateCompileInMs(0)); - EXPECT_EQ(0.0, tracer.EstimateCompileInMs(42)); + EXPECT_EQ(1.0, tracer.EstimateCompileInMs(0)); + EXPECT_EQ(1.0, tracer.EstimateCompileInMs(42)); EXPECT_EQ(0.0, tracer.EstimateFinalizeCompilingInMs()); } @@ -36,7 +36,7 @@ TEST(CompilerDispatcherTracerTest, Average) { TEST(CompilerDispatcherTracerTest, SizeBasedAverage) { CompilerDispatcherTracer tracer(nullptr); - EXPECT_EQ(0.0, tracer.EstimateParseInMs(100)); + EXPECT_EQ(1.0, tracer.EstimateParseInMs(100)); // All three samples parse 100 units/ms. tracer.RecordParse(1.0, 100); diff --git a/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc new file mode 100644 index 0000000000..8e47c48866 --- /dev/null +++ b/deps/v8/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc @@ -0,0 +1,806 @@ +// 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 "src/base/platform/semaphore.h" +#include "src/compiler-dispatcher/compiler-dispatcher-job.h" +#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" +#include "src/flags.h" +#include "src/handles.h" +#include "src/objects-inl.h" +#include "src/v8.h" +#include "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h" +#include "test/unittests/test-utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace v8 { +namespace internal { + +class CompilerDispatcherTest : public TestWithContext { + public: + CompilerDispatcherTest() = default; + ~CompilerDispatcherTest() override = default; + + static void SetUpTestCase() { + old_flag_ = i::FLAG_ignition; + i::FLAG_compiler_dispatcher = true; + old_ignition_flag_ = i::FLAG_ignition; + i::FLAG_ignition = true; + TestWithContext::SetUpTestCase(); + } + + static void TearDownTestCase() { + TestWithContext::TearDownTestCase(); + i::FLAG_compiler_dispatcher = old_flag_; + i::FLAG_ignition = old_ignition_flag_; + } + + private: + static bool old_flag_; + static bool old_ignition_flag_; + + DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherTest); +}; + +bool CompilerDispatcherTest::old_flag_; +bool CompilerDispatcherTest::old_ignition_flag_; + +namespace { + +class MockPlatform : public v8::Platform { + public: + MockPlatform() : time_(0.0), time_step_(0.0), idle_task_(nullptr), sem_(0) {} + ~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_; + } + + 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_; + + 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[] = + "function g() { var y = 1; function f1(x) { return x * y }; return f1; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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[] = + "function g() { var y = 1; function f2(x) { return x * y }; return f2; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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, IdleTask) { + MockPlatform platform; + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); + + const char script[] = + "function g() { var y = 1; function f3(x) { return x * y }; return f3; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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[] = + "function g() { var y = 1; function f4(x) { return x * y }; return f4; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToParse); + + // 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 script("function g() { function f5(x) { var a = "); + for (int i = 0; i < 1000; i++) { + script += "'x' + "; + } + script += " 'x'; }; return f5; } g();"; + Handle<JSFunction> f = + Handle<JSFunction>::cast(RunJS(isolate(), 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[] = + "function g() { var y = 1; function f6(x) { return x * y }; return f6; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToCompile); + + 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kCompiled); + + // 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[] = + "function g() { var y = 1; function f7(x) { return x * y }; return f7; } " + "g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToCompile); + + 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[] = + "function g() { var y = 1; function f8(x) { return x * y }; return f8; } " + "g();"; + Handle<JSFunction> f1 = Handle<JSFunction>::cast(RunJS(isolate(), script1)); + Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); + + const char script2[] = + "function g() { var y = 1; function f9(x) { return x * y }; return f9; } " + "g();"; + Handle<JSFunction> f2 = Handle<JSFunction>::cast(RunJS(isolate(), 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 script("function g() { function f10(x) { var a = "); + for (int i = 0; i < 1000; i++) { + script += "'x' + "; + } + script += " 'x'; }; return f10; } g();"; + Handle<JSFunction> f = + Handle<JSFunction>::cast(RunJS(isolate(), 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[] = + "function g() { var y = 1; function f11(x) { return x * y }; return f11; " + "} g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToCompile); + + 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[] = + "function g() { var y = 1; function f11(x) { return x * y }; return f11; " + "} g();"; + Handle<JSFunction> f1 = Handle<JSFunction>::cast(RunJS(isolate(), script1)); + Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); + + const char script2[] = + "function g() { var y = 1; function f12(x) { return x * y }; return f12; " + "} g();"; + Handle<JSFunction> f2 = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToCompile); + + 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[] = + "function g() { var y = 1; function f13(x) { return x * y }; return f13; " + "} g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kInitial); + + // 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_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToCompile); + + 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()); + { + base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); + ASSERT_FALSE(dispatcher.abort_); + } + + ASSERT_TRUE(platform.ForegroundTasksPending()); + ASSERT_TRUE(platform.IdleTaskPending()); + ASSERT_FALSE(platform.BackgroundTasksPending()); + platform.ClearForegroundTasks(); + platform.ClearIdleTask(); +} + +TEST_F(CompilerDispatcherTest, MemoryPressure) { + MockPlatform platform; + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); + + const char script[] = + "function g() { var y = 1; function f14(x) { return x * y }; return f14; " + "} g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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[] = + "function g() { var y = 1; function f15(x) { return x * y }; return f15; " + "} g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), 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, EnqueueAndStep) { + MockPlatform platform; + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); + + const char script[] = + "function g() { var y = 1; function f16(x) { return x * y }; return f16; " + "} g();"; + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); + Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); + + ASSERT_FALSE(dispatcher.IsEnqueued(shared)); + ASSERT_TRUE(dispatcher.EnqueueAndStep(shared)); + ASSERT_TRUE(dispatcher.IsEnqueued(shared)); + + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == + CompileJobStatus::kReadyToParse); + + ASSERT_TRUE(platform.IdleTaskPending()); + platform.ClearIdleTask(); + ASSERT_TRUE(platform.BackgroundTasksPending()); + platform.ClearBackgroundTasks(); +} + +} // namespace internal +} // namespace v8 |