v8/test/unittests/compiler-dispatcher/unoptimized-compile-job-unittest.cc
Ross McIlroy 80195fc58d [Compile] Refactor CompilerDispatcher for inner function compilation jobs
Refactors the CompilerDispatcher to be able to enqueue eager inner functions
for off-thread compilation during top-level compilation of a script.

Unoptimized compile jobs are simplified to only have two phases - compile
and finalization. Only finalization requires heap access (and therefore
needs to be run on the main thread). The change also introduces a requirement
to register a SFI with a given compile job after that job is posted, this
is due to the fact that an SFI won't necessarily exist at the point the job
is posted, but is created later when top-level compile is being finalized.
Logic in the compile dispatcher is update to deal with the fact that a job
may not be able to progress if it doesn't yet have an associated SFI
registered with it.

BUG=v8:8041

Change-Id: I66cccd626136738304a7cab0e501fc65cf342514
Reviewed-on: https://chromium-review.googlesource.com/1215782
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Marja Hölttä <marja@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56088}
2018-09-20 14:06:39 +00:00

323 lines
11 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 <memory>
#include "include/v8.h"
#include "src/api-inl.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/compiler-dispatcher/compiler-dispatcher-job.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/compiler-dispatcher/unoptimized-compile-job.h"
#include "src/flags.h"
#include "src/isolate-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/preparsed-scope-data.h"
#include "src/v8.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 UnoptimizedCompileJobTest : public TestWithNativeContext {
public:
UnoptimizedCompileJobTest()
: tracer_(isolate()), allocator_(isolate()->allocator()) {}
~UnoptimizedCompileJobTest() override = default;
AccountingAllocator* allocator() { return allocator_; }
CompilerDispatcherTracer* tracer() { return &tracer_; }
static void SetUpTestCase() {
CHECK_NULL(save_flags_);
save_flags_ = new SaveFlags();
TestWithNativeContext ::SetUpTestCase();
}
static void TearDownTestCase() {
TestWithNativeContext ::TearDownTestCase();
CHECK_NOT_NULL(save_flags_);
delete save_flags_;
save_flags_ = nullptr;
}
static Variable* LookupVariableByName(UnoptimizedCompileJob* job,
const char* name) {
const AstRawString* name_raw_string =
job->parse_info_->ast_value_factory()->GetOneByteString(name);
return job->parse_info_->literal()->scope()->Lookup(name_raw_string);
}
UnoptimizedCompileJob* NewUnoptimizedCompileJob(
Isolate* isolate, Handle<SharedFunctionInfo> shared,
size_t stack_size = FLAG_stack_size) {
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());
const FunctionLiteral* function_literal =
ast_node_factory.NewFunctionLiteral(
function_name, function_scope, nullptr, -1, -1, -1,
FunctionLiteral::kNoDuplicateParameters,
FunctionLiteral::kAnonymousExpression,
FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
shared->FunctionLiteralId(isolate), nullptr);
return new UnoptimizedCompileJob(
tracer(), allocator(), outer_parse_info.get(), function_name,
function_literal,
isolate->counters()->worker_thread_runtime_call_stats(),
FLAG_stack_size);
}
private:
CompilerDispatcherTracer tracer_;
AccountingAllocator* allocator_;
static SaveFlags* save_flags_;
DISALLOW_COPY_AND_ASSIGN(UnoptimizedCompileJobTest);
};
SaveFlags* UnoptimizedCompileJobTest::save_flags_ = nullptr;
#define ASSERT_JOB_STATUS(STATUS, JOB) ASSERT_EQ(STATUS, JOB->status())
TEST_F(UnoptimizedCompileJobTest, Construct) {
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
}
TEST_F(UnoptimizedCompileJobTest, StateTransitions) {
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
job->Compile(false);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
TEST_F(UnoptimizedCompileJobTest, SyntaxError) {
test::ScriptResource* script = new test::ScriptResource("^^^", strlen("^^^"));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(isolate(), script);
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->Compile(false);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception());
isolate()->clear_pending_exception();
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
TEST_F(UnoptimizedCompileJobTest, CompileAndRun) {
const char raw_script[] =
"function g() {\n"
" f = function(a) {\n"
" for (var i = 0; i < 3; i++) { a += 20; }\n"
" return a;\n"
" }\n"
" return f;\n"
"}\n"
"g();";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared = handle(f->shared(), isolate());
ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->Compile(false);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
ASSERT_TRUE(shared->is_compiled());
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
Smi* value = Smi::cast(*RunJS("f(100);"));
ASSERT_TRUE(value == Smi::FromInt(160));
}
TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) {
std::string raw_script("() { var a = ");
for (int i = 0; i < 10000; i++) {
// TODO(leszeks): Figure out a more "unit-test-y" way of forcing an analysis
// failure than a binop stack overflow.
// 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(isolate(), script);
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared, 100));
job->Compile(false);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception());
isolate()->clear_pending_exception();
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
TEST_F(UnoptimizedCompileJobTest, CompileFailureToFinalize) {
std::string raw_script("() { var a = ");
for (int i = 0; i < 5000; 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(isolate(), script);
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared, 50));
job->Compile(false);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception());
isolate()->clear_pending_exception();
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
class CompileTask : public Task {
public:
CompileTask(UnoptimizedCompileJob* job, base::Semaphore* semaphore)
: job_(job), semaphore_(semaphore) {}
~CompileTask() override = default;
void Run() override {
job_->Compile(true);
ASSERT_FALSE(job_->IsFailed());
semaphore_->Signal();
}
private:
UnoptimizedCompileJob* job_;
base::Semaphore* semaphore_;
DISALLOW_COPY_AND_ASSIGN(CompileTask);
};
TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) {
const char* raw_script =
"(a, b) {\n"
" var c = a + b;\n"
" function bar() { return b }\n"
" var d = { foo: 100, bar : bar() }\n"
" return bar;"
"}";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(isolate(), script);
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
base::Semaphore semaphore(0);
auto background_task = base::make_unique<CompileTask>(job.get(), &semaphore);
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(background_task));
semaphore.Wait();
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
TEST_F(UnoptimizedCompileJobTest, LazyInnerFunctions) {
const char raw_script[] =
"function g() {\n"
" f = function() {\n"
" e = (function() { return 42; });\n"
" return e;\n"
" }\n"
" return f;\n"
"}\n"
"g();";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared = handle(f->shared(), isolate());
ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->Compile(false);
ASSERT_FALSE(job->IsFailed());
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
ASSERT_TRUE(shared->is_compiled());
Handle<JSFunction> e = RunJS<JSFunction>("f();");
ASSERT_FALSE(e->shared()->is_compiled());
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
}
#undef ASSERT_JOB_STATUS
} // namespace internal
} // namespace v8