f888f48e4c
To consume a code cache off-thread 1. The embedder creates a CachedData object wrapping the data blob. 2. The embedder calls ScriptCompiler::StartConsumingCodeCache with the CachedData, and receives a ScriptCompiler::CodeCacheConsumeTask which takes ownership of the CachedData. 3. The embedder calls ScriptCompiler::CodeCacheConsumeTask::Run on a different thread. 4. Once this completes, the embedded passes the completed task as an optional argument into Source constructor, and calls Compile as before. This is roughly similar to how streaming compilation works, with the QoL improvement that Source owns the CodeCacheConsumeTask and therefore we can reuse the same Compile method and do the off-thread finalization behind the scenes inside Compile. On the v8::internal side, ScriptCompiler::CodeCacheConsumeTask wraps a v8::internal::BackgroundDeserializeTask, which has a Run and a Finish method. The Run creates a LocalIsolate (again, similar to BackgroundCompileTask), calls some helpers on CodeSerializer, and stores the pre-finalization result in a OffThreadDeserializeData structure. This stores Persistent Handles to the off-thread initialized SFI and a vector of Scripts needing fixing up, and it owns the PersistentHandles object which owns those Handles. Finally, the Finish method consumes this OffThreadDeserializeData structure, fixes up Scripts, moves the SFI Handle into the caller HandleScope, and that's it. Since we don't yet have the source at off-thread deserialization time, the various code cache sanity checks are done without the source hash when deserializing, and the Finish method re-does them now that the source is available. Bug: chromium:1075999 Change-Id: If1faf35ba3ef840fa4e735581d0b29c96c1d5fc8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3067322 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Omer Katz <omerkatz@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#76155}
237 lines
7.8 KiB
C++
237 lines
7.8 KiB
C++
// Copyright 2021 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 "include/libplatform/libplatform.h"
|
|
#include "include/v8-platform.h"
|
|
#include "include/v8.h"
|
|
#include "test/unittests/test-utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace v8 {
|
|
|
|
class DeserializeTest : public testing::Test {
|
|
public:
|
|
class IsolateAndContextScope {
|
|
public:
|
|
explicit IsolateAndContextScope(DeserializeTest* test)
|
|
: test_(test),
|
|
isolate_wrapper_(kNoCounters),
|
|
isolate_scope_(isolate_wrapper_.isolate()),
|
|
handle_scope_(isolate_wrapper_.isolate()),
|
|
context_(Context::New(isolate_wrapper_.isolate())),
|
|
context_scope_(context_) {
|
|
CHECK_NULL(test->isolate_);
|
|
CHECK(test->context_.IsEmpty());
|
|
test->isolate_ = isolate_wrapper_.isolate();
|
|
test->context_ = context_;
|
|
}
|
|
~IsolateAndContextScope() {
|
|
test_->isolate_ = nullptr;
|
|
test_->context_ = {};
|
|
}
|
|
|
|
private:
|
|
DeserializeTest* test_;
|
|
v8::IsolateWrapper isolate_wrapper_;
|
|
v8::Isolate::Scope isolate_scope_;
|
|
v8::HandleScope handle_scope_;
|
|
v8::Local<v8::Context> context_;
|
|
v8::Context::Scope context_scope_;
|
|
};
|
|
|
|
Local<String> NewString(const char* val) {
|
|
return String::NewFromUtf8(isolate(), val).ToLocalChecked();
|
|
}
|
|
|
|
Local<Value> RunGlobalFunc(const char* name) {
|
|
Local<Value> func_val =
|
|
context()->Global()->Get(context(), NewString(name)).ToLocalChecked();
|
|
CHECK(func_val->IsFunction());
|
|
Local<Function> func = Local<Function>::Cast(func_val);
|
|
return func->Call(context(), Undefined(isolate()), 0, nullptr)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
Isolate* isolate() { return isolate_; }
|
|
v8::Local<v8::Context> context() { return context_.ToLocalChecked(); }
|
|
|
|
private:
|
|
Isolate* isolate_ = nullptr;
|
|
v8::MaybeLocal<v8::Context> context_;
|
|
};
|
|
|
|
// Check that deserialization works.
|
|
TEST_F(DeserializeTest, Deserialize) {
|
|
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
Local<Script> script =
|
|
Script::Compile(context(), source_code).ToLocalChecked();
|
|
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
|
|
|
|
cached_data.reset(
|
|
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
|
|
}
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
ScriptCompiler::Source source(source_code, cached_data.release());
|
|
Local<Script> script =
|
|
ScriptCompiler::Compile(context(), &source,
|
|
ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
|
|
CHECK(!source.GetCachedData()->rejected);
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
|
|
}
|
|
}
|
|
|
|
// Check that deserialization with a different script rejects the cache but
|
|
// still works via standard compilation.
|
|
TEST_F(DeserializeTest, DeserializeRejectsDifferentSource) {
|
|
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
Local<Script> script =
|
|
Script::Compile(context(), source_code).ToLocalChecked();
|
|
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
|
|
|
|
cached_data.reset(
|
|
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
|
|
}
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
// The source hash is based on the source length, so have to make sure that
|
|
// this is different here.
|
|
Local<String> source_code = NewString("function bar() { return 142; }");
|
|
ScriptCompiler::Source source(source_code, cached_data.release());
|
|
Local<Script> script =
|
|
ScriptCompiler::Compile(context(), &source,
|
|
ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
|
|
CHECK(source.GetCachedData()->rejected);
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
|
|
}
|
|
}
|
|
|
|
class DeserializeThread : public base::Thread {
|
|
public:
|
|
explicit DeserializeThread(ScriptCompiler::ConsumeCodeCacheTask* task)
|
|
: Thread(base::Thread::Options("DeserializeThread")), task_(task) {}
|
|
|
|
void Run() override { task_->Run(); }
|
|
|
|
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> TakeTask() {
|
|
return std::move(task_);
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task_;
|
|
};
|
|
|
|
// Check that off-thread deserialization works.
|
|
TEST_F(DeserializeTest, OffThreadDeserialize) {
|
|
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
Local<Script> script =
|
|
Script::Compile(context(), source_code).ToLocalChecked();
|
|
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
|
|
|
|
cached_data.reset(
|
|
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
|
|
}
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
DeserializeThread deserialize_thread(
|
|
ScriptCompiler::StartConsumingCodeCache(
|
|
isolate(), std::make_unique<ScriptCompiler::CachedData>(
|
|
cached_data->data, cached_data->length,
|
|
ScriptCompiler::CachedData::BufferNotOwned)));
|
|
CHECK(deserialize_thread.Start());
|
|
deserialize_thread.Join();
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
ScriptCompiler::Source source(source_code, cached_data.release(),
|
|
deserialize_thread.TakeTask().release());
|
|
Local<Script> script =
|
|
ScriptCompiler::Compile(context(), &source,
|
|
ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
|
|
CHECK(!source.GetCachedData()->rejected);
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
|
|
}
|
|
}
|
|
|
|
// Check that off-thread deserialization works.
|
|
TEST_F(DeserializeTest, OffThreadDeserializeRejectsDifferentSource) {
|
|
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
Local<String> source_code = NewString("function foo() { return 42; }");
|
|
Local<Script> script =
|
|
Script::Compile(context(), source_code).ToLocalChecked();
|
|
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
|
|
|
|
cached_data.reset(
|
|
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
|
|
}
|
|
|
|
{
|
|
IsolateAndContextScope scope(this);
|
|
|
|
DeserializeThread deserialize_thread(
|
|
ScriptCompiler::StartConsumingCodeCache(
|
|
isolate(), std::make_unique<ScriptCompiler::CachedData>(
|
|
cached_data->data, cached_data->length,
|
|
ScriptCompiler::CachedData::BufferNotOwned)));
|
|
CHECK(deserialize_thread.Start());
|
|
deserialize_thread.Join();
|
|
|
|
Local<String> source_code = NewString("function bar() { return 142; }");
|
|
ScriptCompiler::Source source(source_code, cached_data.release(),
|
|
deserialize_thread.TakeTask().release());
|
|
Local<Script> script =
|
|
ScriptCompiler::Compile(context(), &source,
|
|
ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
|
|
CHECK(source.GetCachedData()->rejected);
|
|
CHECK(!script->Run(context()).IsEmpty());
|
|
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
|
|
}
|
|
}
|
|
|
|
} // namespace v8
|