[api] Add option to consume code cache on module compilation

This extends the ScriptCompiler::CompileModule function with a
CompileOptions argument. Accepted values are kNoCompileOptions (in
which case, behavior remains unmodified) and kConsumeCodeCache. If the
latter is passed, we try to fetch the given module from the code
cache.

Since it is possible to compile the same source code as both a script
and a module (and different code is generated for the two cases), a
new is_module bit is added to the SerializedCodeData header to
disambiguate between the two cases.

Bug: v8:7685
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: I34b3642505577ed9ed0caedbee5876308c5a53ea
Reviewed-on: https://chromium-review.googlesource.com/1073327
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53432}
This commit is contained in:
jgruber 2018-05-30 08:41:11 +02:00 committed by Commit Bot
parent 39e7d8f90c
commit 70b5fd3b6e
6 changed files with 294 additions and 43 deletions

View File

@ -1656,7 +1656,9 @@ class V8_EXPORT ScriptCompiler {
* ECMAScript specification.
*/
static V8_WARN_UNUSED_RESULT MaybeLocal<Module> CompileModule(
Isolate* isolate, Source* source);
Isolate* isolate, Source* source,
CompileOptions options = kNoCompileOptions,
NoCacheReason no_cache_reason = kNoCacheNoReason);
/**
* Compile a function for a given context. This is equivalent to running

View File

@ -2475,15 +2475,18 @@ MaybeLocal<Script> ScriptCompiler::Compile(Local<Context> context,
return result->BindToCurrentContext();
}
MaybeLocal<Module> ScriptCompiler::CompileModule(Isolate* isolate,
Source* source) {
MaybeLocal<Module> ScriptCompiler::CompileModule(
Isolate* isolate, Source* source, CompileOptions options,
NoCacheReason no_cache_reason) {
CHECK(options == kNoCompileOptions || options == kConsumeCodeCache);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
Utils::ApiCheck(source->GetResourceOptions().IsModule(),
"v8::ScriptCompiler::CompileModule",
"Invalid ScriptOrigin: is_module must be true");
auto maybe = CompileUnboundInternal(isolate, source, kNoCompileOptions,
kNoCacheBecauseModule);
auto maybe =
CompileUnboundInternal(isolate, source, options, no_cache_reason);
Local<UnboundScript> unbound;
if (!maybe.ToLocal(&unbound)) return MaybeLocal<Module>();

View File

@ -1687,7 +1687,8 @@ MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompileDeserialize");
Handle<SharedFunctionInfo> inner_result;
if (CodeSerializer::Deserialize(isolate, cached_data, source)
if (CodeSerializer::Deserialize(isolate, cached_data, source,
origin_options)
.ToHandle(&inner_result)) {
// Promote to per-isolate compilation cache.
DCHECK(inner_result->is_compiled());
@ -1765,7 +1766,8 @@ MaybeHandle<JSFunction> Compiler::GetWrappedFunction(
isolate, RuntimeCallCounterId::kCompileDeserialize);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompileDeserialize");
maybe_result = CodeSerializer::Deserialize(isolate, cached_data, source);
maybe_result = CodeSerializer::Deserialize(isolate, cached_data, source,
origin_options);
if (maybe_result.is_null()) {
// Deserializer failed. Fall through to compile.
compile_timer.set_consuming_code_cache_failed();

View File

@ -58,7 +58,8 @@ ScriptCompiler::CachedData* CodeSerializer::Serialize(
// Serialize code object.
Handle<String> source(String::cast(script->source()), isolate);
CodeSerializer cs(isolate, SerializedCodeData::SourceHash(source));
CodeSerializer cs(isolate, SerializedCodeData::SourceHash(
source, script->origin_options()));
DisallowHeapAllocation no_gc;
cs.reference_map()->AddAttachedReference(*source);
ScriptData* script_data = cs.SerializeSharedFunctionInfo(info);
@ -251,7 +252,8 @@ void CodeSerializer::SerializeCodeStub(Code* code_stub, HowToCode how_to_code,
}
MaybeHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
Isolate* isolate, ScriptData* cached_data, Handle<String> source) {
Isolate* isolate, ScriptData* cached_data, Handle<String> source,
ScriptOriginOptions origin_options) {
base::ElapsedTimer timer;
if (FLAG_profile_deserialization) timer.Start();
@ -260,7 +262,8 @@ MaybeHandle<SharedFunctionInfo> CodeSerializer::Deserialize(
SerializedCodeData::SanityCheckResult sanity_check_result =
SerializedCodeData::CHECK_SUCCESS;
const SerializedCodeData scd = SerializedCodeData::FromCachedData(
isolate, cached_data, SerializedCodeData::SourceHash(source),
isolate, cached_data,
SerializedCodeData::SourceHash(source, origin_options),
&sanity_check_result);
if (sanity_check_result != SerializedCodeData::CHECK_SUCCESS) {
if (FLAG_profile_deserialization) PrintF("[Cached code failed check]\n");
@ -427,8 +430,15 @@ SerializedCodeData::SanityCheckResult SerializedCodeData::SanityCheck(
return CHECK_SUCCESS;
}
uint32_t SerializedCodeData::SourceHash(Handle<String> source) {
return source->length();
uint32_t SerializedCodeData::SourceHash(Handle<String> source,
ScriptOriginOptions origin_options) {
const uint32_t source_length = source->length();
static constexpr uint32_t kModuleFlagMask = (1 << 31);
const uint32_t is_module = origin_options.IsModule() ? kModuleFlagMask : 0;
DCHECK_EQ(0, source_length & kModuleFlagMask);
return source_length | is_module;
}
// Return ScriptData object and relinquish ownership over it to the caller.

View File

@ -50,7 +50,8 @@ class CodeSerializer : public Serializer<> {
ScriptData* SerializeSharedFunctionInfo(Handle<SharedFunctionInfo> info);
V8_WARN_UNUSED_RESULT static MaybeHandle<SharedFunctionInfo> Deserialize(
Isolate* isolate, ScriptData* cached_data, Handle<String> source);
Isolate* isolate, ScriptData* cached_data, Handle<String> source,
ScriptOriginOptions origin_options);
const std::vector<uint32_t>* stub_keys() const { return &stub_keys_; }
@ -148,7 +149,8 @@ class SerializedCodeData : public SerializedData {
Vector<const uint32_t> CodeStubKeys() const;
static uint32_t SourceHash(Handle<String> source);
static uint32_t SourceHash(Handle<String> source,
ScriptOriginOptions origin_options);
private:
explicit SerializedCodeData(ScriptData* data);

View File

@ -25659,48 +25659,280 @@ v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(Local<Context> context,
CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
}
namespace {
Local<Module> CompileAndInstantiateModule(v8::Isolate* isolate,
Local<Context> context,
const char* resource_name,
const char* source) {
Local<String> source_string = v8_str(source);
v8::ScriptOrigin script_origin(
v8_str(resource_name), Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(), Local<v8::Value>(),
Local<v8::Boolean>(), Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source script_compiler_source(source_string,
script_origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &script_compiler_source)
.ToLocalChecked();
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
.ToChecked();
return module;
}
Local<Module> CompileAndInstantiateModuleFromCache(
v8::Isolate* isolate, Local<Context> context, const char* resource_name,
const char* source, v8::ScriptCompiler::CachedData* cache) {
Local<String> source_string = v8_str(source);
v8::ScriptOrigin script_origin(
v8_str(resource_name), Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(), Local<v8::Value>(),
Local<v8::Boolean>(), Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source script_compiler_source(source_string,
script_origin, cache);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &script_compiler_source,
v8::ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
.ToChecked();
return module;
}
} // namespace
TEST(ModuleCodeCache) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
const char* origin = "code cache test";
const char* source =
"export default 5; export const a = 10; function f() { return 42; } "
"(function() { return f(); })();";
v8::ScriptCompiler::CachedData* cache;
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate1);
v8::HandleScope scope(isolate1);
v8::Local<v8::Context> context = v8::Context::New(isolate1);
v8::Context::Scope cscope(context);
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
Local<String> source_text = v8_str(
"export default 5; export const a = 10; function f() { return 42; } "
"(function() { return f(); })();");
v8::ScriptOrigin script_origin(
v8_str(origin), Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(), Local<v8::Value>(),
Local<v8::Boolean>(), Local<v8::Boolean>(), True(isolate1));
v8::ScriptCompiler::Source source(source_text, script_origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate1, &source).ToLocalChecked();
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
.ToChecked();
Local<Module> module =
CompileAndInstantiateModule(isolate, context, origin, source);
// Fetch the shared function info before evaluation.
Local<v8::UnboundModuleScript> unbound_module_script =
module->GetUnboundModuleScript();
// Fetch the shared function info before evaluation.
Local<v8::UnboundModuleScript> unbound_module_script =
module->GetUnboundModuleScript();
// Evaluate for possible lazy compilation.
Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
// Evaluate for possible lazy compilation.
Local<Value> completion_value =
module->Evaluate(context).ToLocalChecked();
CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
// Now create the cache.
cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
// Now create the cache. Note that it is freed, obscurely, when
// ScriptCompiler::Source goes out of scope below.
cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
}
isolate->Dispose();
}
isolate1->Dispose();
// TODO(jgruber,v8:7685): Test module code cache consumption once implemented.
delete cache;
// Test that the cache is consumed and execution still works.
{
// Disable --always_opt, otherwise we try to optimize during module
// instantiation, violating the DisallowCompilation scope.
i::FLAG_always_opt = false;
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
Local<Module> module;
{
i::DisallowCompilation no_compile(
reinterpret_cast<i::Isolate*>(isolate));
module = CompileAndInstantiateModuleFromCache(isolate, context, origin,
source, cache);
}
Local<Value> completion_value =
module->Evaluate(context).ToLocalChecked();
CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
}
isolate->Dispose();
}
}
// Tests that the code cache does not confuse the same source code compiled as a
// script and as a module.
TEST(CodeCacheModuleScriptMismatch) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
const char* origin = "code cache test";
const char* source = "42";
v8::ScriptCompiler::CachedData* cache;
{
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
Local<Module> module =
CompileAndInstantiateModule(isolate, context, origin, source);
// Fetch the shared function info before evaluation.
Local<v8::UnboundModuleScript> unbound_module_script =
module->GetUnboundModuleScript();
// Evaluate for possible lazy compilation.
Local<Value> completion_value =
module->Evaluate(context).ToLocalChecked();
CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
// Now create the cache. Note that it is freed, obscurely, when
// ScriptCompiler::Source goes out of scope below.
cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
}
isolate->Dispose();
}
// Test that the cache is not consumed when source is compiled as a script.
{
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
v8::ScriptOrigin script_origin(v8_str(origin));
v8::ScriptCompiler::Source script_compiler_source(v8_str(source),
script_origin, cache);
v8::Local<v8::Script> script =
v8::ScriptCompiler::Compile(context, &script_compiler_source,
v8::ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(cache->rejected);
CHECK_EQ(42, script->Run(context)
.ToLocalChecked()
->ToInt32(context)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
}
isolate->Dispose();
}
}
// Same as above but other way around.
TEST(CodeCacheScriptModuleMismatch) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
const char* origin = "code cache test";
const char* source = "42";
v8::ScriptCompiler::CachedData* cache;
{
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
v8::Local<v8::String> source_string = v8_str(source);
v8::ScriptOrigin script_origin(v8_str(origin));
v8::ScriptCompiler::Source source(source_string, script_origin);
v8::ScriptCompiler::CompileOptions option =
v8::ScriptCompiler::kNoCompileOptions;
v8::Local<v8::Script> script =
v8::ScriptCompiler::Compile(context, &source, option)
.ToLocalChecked();
cache = v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
}
isolate->Dispose();
}
// Test that the cache is not consumed when source is compiled as a module.
{
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
v8::ScriptOrigin script_origin(
v8_str(origin), Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(), Local<v8::Value>(),
Local<v8::Boolean>(), Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source script_compiler_source(v8_str(source),
script_origin, cache);
Local<Module> module = v8::ScriptCompiler::CompileModule(
isolate, &script_compiler_source,
v8::ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
.ToChecked();
CHECK(cache->rejected);
Local<Value> completion_value =
module->Evaluate(context).ToLocalChecked();
CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
}
isolate->Dispose();
}
}
// Tests that compilation can handle a garbled cache.
TEST(InvalidCodeCacheDataInCompileModule) {
v8::V8::Initialize();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext local_context;
const char* garbage = "garbage garbage garbage garbage garbage garbage";
const uint8_t* data = reinterpret_cast<const uint8_t*>(garbage);
Local<String> origin = v8_str("origin");
int length = 16;
v8::ScriptCompiler::CachedData* cached_data =
new v8::ScriptCompiler::CachedData(data, length);
CHECK(!cached_data->rejected);
v8::ScriptOrigin script_origin(
origin, Local<v8::Integer>(), Local<v8::Integer>(), Local<v8::Boolean>(),
Local<v8::Integer>(), Local<v8::Value>(), Local<v8::Boolean>(),
Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source source(v8_str("42"), script_origin, cached_data);
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &source,
v8::ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
.ToChecked();
CHECK(cached_data->rejected);
CHECK_EQ(42, module->Evaluate(context)
.ToLocalChecked()
->Int32Value(context)
.FromJust());
}
void TestInvalidCacheData(v8::ScriptCompiler::CompileOptions option) {