[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:
parent
39e7d8f90c
commit
70b5fd3b6e
@ -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
|
||||
|
11
src/api.cc
11
src/api.cc
@ -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>();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user