| // Copyright 2018 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/api-inl.h" |
| #include "src/ast/ast.h" |
| #include "src/ast/scopes.h" |
| #include "src/base/platform/semaphore.h" |
| #include "src/codegen/compiler.h" |
| #include "src/execution/isolate-inl.h" |
| #include "src/flags/flags.h" |
| #include "src/init/v8.h" |
| #include "src/objects/smi.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/parsing/parser.h" |
| #include "src/parsing/preparse-data.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 BackgroundCompileTaskTest : public TestWithNativeContext { |
| public: |
| BackgroundCompileTaskTest() : allocator_(isolate()->allocator()) {} |
| ~BackgroundCompileTaskTest() override = default; |
| BackgroundCompileTaskTest(const BackgroundCompileTaskTest&) = delete; |
| BackgroundCompileTaskTest& operator=(const BackgroundCompileTaskTest&) = |
| delete; |
| |
| AccountingAllocator* allocator() { return allocator_; } |
| |
| static void SetUpTestCase() { |
| CHECK_NULL(save_flags_); |
| save_flags_ = new SaveFlags(); |
| // TODO(leszeks): Support background finalization in compiler dispatcher. |
| FLAG_finalize_streaming_on_background = false; |
| TestWithNativeContext::SetUpTestCase(); |
| } |
| |
| static void TearDownTestCase() { |
| TestWithNativeContext::TearDownTestCase(); |
| CHECK_NOT_NULL(save_flags_); |
| delete save_flags_; |
| save_flags_ = nullptr; |
| } |
| |
| BackgroundCompileTask* NewBackgroundCompileTask( |
| Isolate* isolate, Handle<SharedFunctionInfo> shared, |
| size_t stack_size = FLAG_stack_size) { |
| 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 = |
| outer_parse_info->zone()->New<DeclarationScope>( |
| outer_parse_info->zone(), ast_value_factory); |
| DeclarationScope* function_scope = |
| outer_parse_info->zone()->New<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*> buffer; |
| ScopedPtrList<Statement> statements(&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 new BackgroundCompileTask( |
| outer_parse_info.get(), function_name, function_literal, |
| isolate->counters()->worker_thread_runtime_call_stats(), |
| isolate->counters()->compile_function_on_background(), FLAG_stack_size); |
| } |
| |
| private: |
| AccountingAllocator* allocator_; |
| static SaveFlags* save_flags_; |
| }; |
| |
| SaveFlags* BackgroundCompileTaskTest::save_flags_ = nullptr; |
| |
| TEST_F(BackgroundCompileTaskTest, Construct) { |
| Handle<SharedFunctionInfo> shared = |
| test::CreateSharedFunctionInfo(isolate(), nullptr); |
| ASSERT_FALSE(shared->is_compiled()); |
| std::unique_ptr<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| } |
| |
| TEST_F(BackgroundCompileTaskTest, SyntaxError) { |
| test::ScriptResource* script = new test::ScriptResource("^^^", strlen("^^^")); |
| Handle<SharedFunctionInfo> shared = |
| test::CreateSharedFunctionInfo(isolate(), script); |
| std::unique_ptr<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| |
| task->Run(); |
| ASSERT_FALSE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(isolate()->has_pending_exception()); |
| |
| isolate()->clear_pending_exception(); |
| } |
| |
| TEST_F(BackgroundCompileTaskTest, 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<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| |
| task->Run(); |
| ASSERT_TRUE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(shared->is_compiled()); |
| |
| Smi value = Smi::cast(*RunJS("f(100);")); |
| ASSERT_TRUE(value == Smi::FromInt(160)); |
| } |
| |
| TEST_F(BackgroundCompileTaskTest, CompileFailure) { |
| 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<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared, 100)); |
| |
| task->Run(); |
| ASSERT_FALSE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(isolate()->has_pending_exception()); |
| |
| isolate()->clear_pending_exception(); |
| } |
| |
| class CompileTask : public Task { |
| public: |
| CompileTask(BackgroundCompileTask* task, base::Semaphore* semaphore) |
| : task_(task), semaphore_(semaphore) {} |
| ~CompileTask() override = default; |
| CompileTask(const CompileTask&) = delete; |
| CompileTask& operator=(const CompileTask&) = delete; |
| |
| void Run() override { |
| task_->Run(); |
| semaphore_->Signal(); |
| } |
| |
| private: |
| BackgroundCompileTask* task_; |
| base::Semaphore* semaphore_; |
| }; |
| |
| TEST_F(BackgroundCompileTaskTest, 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<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| |
| base::Semaphore semaphore(0); |
| auto background_task = std::make_unique<CompileTask>(task.get(), &semaphore); |
| |
| V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(background_task)); |
| semaphore.Wait(); |
| ASSERT_TRUE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(shared->is_compiled()); |
| } |
| |
| TEST_F(BackgroundCompileTaskTest, EagerInnerFunctions) { |
| const char raw_script[] = |
| "function g() {\n" |
| " f = function() {\n" |
| " // Simulate an eager IIFE with brackets.\n " |
| " var 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<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| |
| task->Run(); |
| ASSERT_TRUE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(shared->is_compiled()); |
| |
| Handle<JSFunction> e = RunJS<JSFunction>("f();"); |
| |
| ASSERT_TRUE(e->shared().is_compiled()); |
| } |
| |
| TEST_F(BackgroundCompileTaskTest, LazyInnerFunctions) { |
| const char raw_script[] = |
| "function g() {\n" |
| " f = function() {\n" |
| " function e() { 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<BackgroundCompileTask> task( |
| NewBackgroundCompileTask(isolate(), shared)); |
| |
| task->Run(); |
| ASSERT_TRUE(Compiler::FinalizeBackgroundCompileTask( |
| task.get(), shared, isolate(), Compiler::KEEP_EXCEPTION)); |
| ASSERT_TRUE(shared->is_compiled()); |
| |
| Handle<JSFunction> e = RunJS<JSFunction>("f();"); |
| |
| ASSERT_FALSE(e->shared().is_compiled()); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |