f92d7196b9
Calling FindIndexInScript performs a linear search on the script functions and can take considerable time. With Bytecode flushing we will lose the function_literal_id and have to call FindIndexInScript if we ever recompile the flushed function. This can take a significant proportion of the recompilation time and has caused regressions in rendering times for some web applications (e.g, 395ms in FindIndexInScript for 132ms spent lazily re-compiling code). To avoid this, add function_literal_id back into the SFI and remove it from UnoptimizedCompileInfo. This will slightly regress memory usage (particularly in cases where many of the SFIs are compiled), however it means we can remove the FindIndexInScript function and avoid these long-tail regressions when bytecode is flushed. BUG=chromium:965833 Change-Id: Ia31e82eb6c871a6d698a518326a8555822a7a1d8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1669700 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Commit-Queue: Ross McIlroy <rmcilroy@chromium.org> Cr-Commit-Position: refs/heads/master@{#62319}
269 lines
9.0 KiB
C++
269 lines
9.0 KiB
C++
// 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/base/template-utils.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;
|
|
|
|
AccountingAllocator* allocator() { return allocator_; }
|
|
|
|
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;
|
|
}
|
|
|
|
BackgroundCompileTask* NewBackgroundCompileTask(
|
|
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());
|
|
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,
|
|
FunctionLiteral::kAnonymousExpression,
|
|
FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
|
|
shared->function_literal_id(), nullptr);
|
|
|
|
return new BackgroundCompileTask(
|
|
allocator(), 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_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(BackgroundCompileTaskTest);
|
|
};
|
|
|
|
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;
|
|
|
|
void Run() override {
|
|
task_->Run();
|
|
semaphore_->Signal();
|
|
}
|
|
|
|
private:
|
|
BackgroundCompileTask* task_;
|
|
base::Semaphore* semaphore_;
|
|
DISALLOW_COPY_AND_ASSIGN(CompileTask);
|
|
};
|
|
|
|
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 = base::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
|