Improve Script reuse in isolate compilation cache, part 1
Once the root SharedFunctionInfo for any Script gets its bytecode flushed, the Isolate's compilation cache currently evicts that entry, to reduce memory usage. However, the associated Script is likely still alive, since scripts often declare functions which outlive the initial evaluation of the script. If an identical script is loaded later, a duplicate Script is created for it, which can waste memory. In this change, I propose that the compilation cache keys can refer weakly to the Script. When the root SharedFunctionInfo gets old, instead of deleting the cache entry entirely, we can just drop the strong reference to the SharedFunctionInfo. A subsequent lookup in the cache will retrieve the Script instead of the root SharedFunctionInfo, indicating an opportunity to save some memory by reusing the existing Script. Eventually, all callers to CompilationCache::LookupScript should reuse the Script if possible. This change implements only the easy case of reusing the Script for synchronous parsing. Follow-up changes will be required for the TODO comments left by this change. Bug: v8:12808 Change-Id: Ia8b0389441a682de9a43e73329049fd2e7835d3d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3597106 Reviewed-by: Toon Verwaest <verwaest@chromium.org> Commit-Queue: Seth Brenith <seth.brenith@microsoft.com> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#80472}
This commit is contained in:
parent
8fbefa4797
commit
2507217839
@ -65,17 +65,75 @@ void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
|
||||
c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value();
|
||||
}
|
||||
|
||||
// static
|
||||
void CompilationSubCache::AgeCustom(CompilationSubCache* c) {
|
||||
DCHECK_EQ(c->generations(), 1);
|
||||
if (c->tables_[0].IsUndefined(c->isolate())) return;
|
||||
CompilationCacheTable::cast(c->tables_[0]).Age(c->isolate());
|
||||
CompilationCacheTable CompilationSubCache::MaybeGetFirstTable() {
|
||||
DCHECK_GE(generations(), 1);
|
||||
if (tables_[0].IsUndefined(isolate())) return {};
|
||||
return CompilationCacheTable::cast(tables_[0]);
|
||||
}
|
||||
|
||||
void CompilationCacheScript::Age() {
|
||||
if (FLAG_isolate_script_cache_ageing) AgeCustom(this);
|
||||
DisallowGarbageCollection no_gc;
|
||||
if (!FLAG_isolate_script_cache_ageing) return;
|
||||
DCHECK_EQ(generations(), 1);
|
||||
CompilationCacheTable table = MaybeGetFirstTable();
|
||||
if (table.is_null()) return;
|
||||
|
||||
for (InternalIndex entry : table.IterateEntries()) {
|
||||
Object key;
|
||||
if (!table.ToKey(isolate(), entry, &key)) continue;
|
||||
DCHECK(key.IsWeakFixedArray());
|
||||
|
||||
Object value = table.PrimaryValueAt(entry);
|
||||
if (!value.IsUndefined(isolate())) {
|
||||
SharedFunctionInfo info = SharedFunctionInfo::cast(value);
|
||||
if (!info.is_compiled() || (info.HasBytecodeArray() &&
|
||||
info.GetBytecodeArray(isolate()).IsOld())) {
|
||||
table.SetPrimaryValueAt(entry,
|
||||
ReadOnlyRoots(isolate()).undefined_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void CompilationCacheEval::Age() { AgeCustom(this); }
|
||||
|
||||
void CompilationCacheEval::Age() {
|
||||
DisallowGarbageCollection no_gc;
|
||||
DCHECK_EQ(generations(), 1);
|
||||
CompilationCacheTable table = MaybeGetFirstTable();
|
||||
if (table.is_null()) return;
|
||||
|
||||
for (InternalIndex entry : table.IterateEntries()) {
|
||||
Object key;
|
||||
if (!table.ToKey(isolate(), entry, &key)) continue;
|
||||
|
||||
if (key.IsNumber(isolate())) {
|
||||
// The ageing mechanism for the initial dummy entry in the eval cache.
|
||||
// The 'key' is the hash represented as a Number. The 'value' is a smi
|
||||
// counting down from kHashGenerations. On reaching zero, the entry is
|
||||
// cleared.
|
||||
// Note: The following static assert only establishes an explicit
|
||||
// connection between initialization- and use-sites of the smi value
|
||||
// field.
|
||||
STATIC_ASSERT(CompilationCacheTable::kHashGenerations);
|
||||
const int new_count = Smi::ToInt(table.PrimaryValueAt(entry)) - 1;
|
||||
if (new_count == 0) {
|
||||
table.RemoveEntry(entry);
|
||||
} else {
|
||||
DCHECK_GT(new_count, 0);
|
||||
table.SetPrimaryValueAt(entry, Smi::FromInt(new_count),
|
||||
SKIP_WRITE_BARRIER);
|
||||
}
|
||||
} else {
|
||||
DCHECK(key.IsFixedArray());
|
||||
// The ageing mechanism for eval caches.
|
||||
SharedFunctionInfo info =
|
||||
SharedFunctionInfo::cast(table.PrimaryValueAt(entry));
|
||||
if (info.HasBytecodeArray() && info.GetBytecodeArray(isolate()).IsOld()) {
|
||||
table.RemoveEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationCacheRegExp::Age() { AgeByGeneration(this); }
|
||||
|
||||
void CompilationSubCache::Iterate(RootVisitor* v) {
|
||||
@ -110,10 +168,8 @@ namespace {
|
||||
// We only re-use a cached function for some script source code if the
|
||||
// script originates from the same place. This is to avoid issues
|
||||
// when reporting errors, etc.
|
||||
bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info,
|
||||
bool HasOrigin(Isolate* isolate, Handle<Script> script,
|
||||
const ScriptDetails& script_details) {
|
||||
Handle<Script> script =
|
||||
Handle<Script>(Script::cast(function_info->script()), isolate);
|
||||
// If the script name isn't set, the boilerplate script should have
|
||||
// an undefined name to have the same origin.
|
||||
Handle<Object> name;
|
||||
@ -165,10 +221,10 @@ bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info,
|
||||
// be cached in the same script generation. Currently the first use
|
||||
// will be cached, but subsequent code from different source / line
|
||||
// won't.
|
||||
MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
|
||||
Handle<String> source, const ScriptDetails& script_details,
|
||||
LanguageMode language_mode) {
|
||||
MaybeHandle<SharedFunctionInfo> result;
|
||||
CompilationCacheScript::LookupResult CompilationCacheScript::Lookup(
|
||||
Handle<String> source, const ScriptDetails& script_details) {
|
||||
LookupResult result;
|
||||
LookupResult::RawObjects raw_result;
|
||||
|
||||
// Probe the script generation tables. Make sure not to leak handles
|
||||
// into the caller's handle scope.
|
||||
@ -177,28 +233,33 @@ MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
|
||||
const int generation = 0;
|
||||
DCHECK_EQ(generations(), 1);
|
||||
Handle<CompilationCacheTable> table = GetTable(generation);
|
||||
MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
|
||||
table, source, language_mode, isolate());
|
||||
Handle<SharedFunctionInfo> function_info;
|
||||
if (probe.ToHandle(&function_info)) {
|
||||
// Break when we've found a suitable shared function info that
|
||||
// matches the origin.
|
||||
if (HasOrigin(isolate(), function_info, script_details)) {
|
||||
result = scope.CloseAndEscape(function_info);
|
||||
LookupResult probe =
|
||||
CompilationCacheTable::LookupScript(table, source, isolate());
|
||||
Handle<Script> script;
|
||||
if (probe.script().ToHandle(&script)) {
|
||||
// Break when we've found a suitable script that matches the origin.
|
||||
if (HasOrigin(isolate(), script, script_details)) {
|
||||
raw_result = probe.GetRawObjects();
|
||||
}
|
||||
}
|
||||
}
|
||||
result = LookupResult::FromRawObjects(raw_result, isolate());
|
||||
|
||||
// Once outside the manacles of the handle scope, we need to recheck
|
||||
// to see if we actually found a cached script. If so, we return a
|
||||
// handle created in the caller's handle scope.
|
||||
Handle<SharedFunctionInfo> function_info;
|
||||
if (result.ToHandle(&function_info)) {
|
||||
// Since HasOrigin can allocate, we need to protect the SharedFunctionInfo
|
||||
// with handles during the call.
|
||||
DCHECK(HasOrigin(isolate(), function_info, script_details));
|
||||
isolate()->counters()->compilation_cache_hits()->Increment();
|
||||
LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info));
|
||||
Handle<Script> script;
|
||||
if (result.script().ToHandle(&script)) {
|
||||
// Since HasOrigin can allocate, we need to protect the lookup result with
|
||||
// handles during the call.
|
||||
DCHECK(HasOrigin(isolate(), script, script_details));
|
||||
Handle<SharedFunctionInfo> sfi;
|
||||
if (result.toplevel_sfi().ToHandle(&sfi)) {
|
||||
isolate()->counters()->compilation_cache_hits()->Increment();
|
||||
LOG(isolate(), CompilationCacheEvent("hit", "script", *sfi));
|
||||
} else {
|
||||
isolate()->counters()->compilation_cache_partial_hits()->Increment();
|
||||
}
|
||||
} else {
|
||||
isolate()->counters()->compilation_cache_misses()->Increment();
|
||||
}
|
||||
@ -206,12 +267,11 @@ MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
|
||||
}
|
||||
|
||||
void CompilationCacheScript::Put(Handle<String> source,
|
||||
LanguageMode language_mode,
|
||||
Handle<SharedFunctionInfo> function_info) {
|
||||
HandleScope scope(isolate());
|
||||
Handle<CompilationCacheTable> table = GetFirstTable();
|
||||
SetFirstTable(CompilationCacheTable::PutScript(table, source, language_mode,
|
||||
function_info, isolate()));
|
||||
SetFirstTable(CompilationCacheTable::PutScript(table, source, function_info,
|
||||
isolate()));
|
||||
}
|
||||
|
||||
InfoCellPair CompilationCacheEval::Lookup(Handle<String> source,
|
||||
@ -293,11 +353,11 @@ void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
|
||||
script_.Remove(function_info);
|
||||
}
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
|
||||
CompilationCacheScript::LookupResult CompilationCache::LookupScript(
|
||||
Handle<String> source, const ScriptDetails& script_details,
|
||||
LanguageMode language_mode) {
|
||||
if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>();
|
||||
return script_.Lookup(source, script_details, language_mode);
|
||||
if (!IsEnabledScript(language_mode)) return {};
|
||||
return script_.Lookup(source, script_details);
|
||||
}
|
||||
|
||||
InfoCellPair CompilationCache::LookupEval(Handle<String> source,
|
||||
@ -338,10 +398,10 @@ MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
|
||||
void CompilationCache::PutScript(Handle<String> source,
|
||||
LanguageMode language_mode,
|
||||
Handle<SharedFunctionInfo> function_info) {
|
||||
if (!IsEnabledScriptAndEval()) return;
|
||||
if (!IsEnabledScript(language_mode)) return;
|
||||
LOG(isolate(), CompilationCacheEvent("put", "script", *function_info));
|
||||
|
||||
script_.Put(source, language_mode, function_info);
|
||||
script_.Put(source, function_info);
|
||||
}
|
||||
|
||||
void CompilationCache::PutEval(Handle<String> source,
|
||||
|
@ -65,9 +65,11 @@ class CompilationSubCache {
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
|
||||
// Ageing occurs either by removing the oldest generation, or with
|
||||
// custom logic implemented in CompilationCacheTable::Age.
|
||||
// custom subclass logic.
|
||||
static void AgeByGeneration(CompilationSubCache* c);
|
||||
static void AgeCustom(CompilationSubCache* c);
|
||||
|
||||
// Returns null if there is no first table.
|
||||
CompilationCacheTable MaybeGetFirstTable();
|
||||
|
||||
private:
|
||||
Isolate* const isolate_;
|
||||
@ -82,12 +84,11 @@ class CompilationCacheScript : public CompilationSubCache {
|
||||
public:
|
||||
explicit CompilationCacheScript(Isolate* isolate);
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> Lookup(Handle<String> source,
|
||||
const ScriptDetails& script_details,
|
||||
LanguageMode language_mode);
|
||||
using LookupResult = CompilationCacheScriptLookupResult;
|
||||
LookupResult Lookup(Handle<String> source,
|
||||
const ScriptDetails& script_details);
|
||||
|
||||
void Put(Handle<String> source, LanguageMode language_mode,
|
||||
Handle<SharedFunctionInfo> function_info);
|
||||
void Put(Handle<String> source, Handle<SharedFunctionInfo> function_info);
|
||||
|
||||
void Age() override;
|
||||
|
||||
@ -154,10 +155,10 @@ class V8_EXPORT_PRIVATE CompilationCache {
|
||||
CompilationCache(const CompilationCache&) = delete;
|
||||
CompilationCache& operator=(const CompilationCache&) = delete;
|
||||
|
||||
// Finds the script shared function info for a source
|
||||
// string. Returns an empty handle if the cache doesn't contain a
|
||||
// script for the given source string with the right origin.
|
||||
MaybeHandle<SharedFunctionInfo> LookupScript(
|
||||
// Finds the Script and root SharedFunctionInfo for a script source string.
|
||||
// Returns empty handles if the cache doesn't contain a script for the given
|
||||
// source string with the right origin.
|
||||
CompilationCacheScript::LookupResult LookupScript(
|
||||
Handle<String> source, const ScriptDetails& script_details,
|
||||
LanguageMode language_mode);
|
||||
|
||||
@ -174,7 +175,7 @@ class V8_EXPORT_PRIVATE CompilationCache {
|
||||
MaybeHandle<FixedArray> LookupRegExp(Handle<String> source,
|
||||
JSRegExp::Flags flags);
|
||||
|
||||
// Associate the (source, kind) pair to the shared function
|
||||
// Associate the source string to the shared function
|
||||
// info. This may overwrite an existing mapping.
|
||||
void PutScript(Handle<String> source, LanguageMode language_mode,
|
||||
Handle<SharedFunctionInfo> function_info);
|
||||
@ -224,6 +225,11 @@ class V8_EXPORT_PRIVATE CompilationCache {
|
||||
bool IsEnabledScriptAndEval() const {
|
||||
return FLAG_compilation_cache && enabled_script_and_eval_;
|
||||
}
|
||||
bool IsEnabledScript(LanguageMode language_mode) {
|
||||
// Tests can change FLAG_use_strict at runtime. The compilation cache only
|
||||
// contains scripts which were compiled with the default language mode.
|
||||
return IsEnabledScriptAndEval() && language_mode == LanguageMode::kSloppy;
|
||||
}
|
||||
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
|
||||
|
@ -1430,6 +1430,19 @@ Handle<SharedFunctionInfo> CreateTopLevelSharedFunctionInfo(
|
||||
parse_info->literal(), script, true);
|
||||
}
|
||||
|
||||
Handle<SharedFunctionInfo> GetOrCreateTopLevelSharedFunctionInfo(
|
||||
ParseInfo* parse_info, Handle<Script> script, Isolate* isolate) {
|
||||
EnsureSharedFunctionInfosArrayOnScript(script, parse_info, isolate);
|
||||
MaybeHandle<SharedFunctionInfo> maybe_shared =
|
||||
Script::FindSharedFunctionInfo(script, isolate, parse_info->literal());
|
||||
if (Handle<SharedFunctionInfo> shared; maybe_shared.ToHandle(&shared)) {
|
||||
DCHECK_EQ(shared->function_literal_id(),
|
||||
parse_info->literal()->function_literal_id());
|
||||
return shared;
|
||||
}
|
||||
return CreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
|
||||
}
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> CompileToplevel(
|
||||
ParseInfo* parse_info, Handle<Script> script,
|
||||
MaybeHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate,
|
||||
@ -1463,7 +1476,7 @@ MaybeHandle<SharedFunctionInfo> CompileToplevel(
|
||||
|
||||
// Create the SharedFunctionInfo and add it to the script's list.
|
||||
Handle<SharedFunctionInfo> shared_info =
|
||||
CreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
|
||||
GetOrCreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
|
||||
|
||||
FinalizeUnoptimizedCompilationDataList
|
||||
finalize_unoptimized_compilation_data_list;
|
||||
@ -2806,17 +2819,22 @@ Handle<Script> NewScript(
|
||||
}
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> CompileScriptOnMainThread(
|
||||
const UnoptimizedCompileFlags flags, Handle<String> source,
|
||||
UnoptimizedCompileFlags flags, Handle<String> source,
|
||||
const ScriptDetails& script_details, NativesFlag natives,
|
||||
v8::Extension* extension, Isolate* isolate,
|
||||
IsCompiledScope* is_compiled_scope) {
|
||||
MaybeHandle<Script> maybe_script, IsCompiledScope* is_compiled_scope) {
|
||||
Handle<Script> script;
|
||||
if (maybe_script.ToHandle(&script)) {
|
||||
flags.set_script_id(script->id());
|
||||
}
|
||||
UnoptimizedCompileState compile_state;
|
||||
ReusableUnoptimizedCompileState reusable_state(isolate);
|
||||
ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state);
|
||||
parse_info.set_extension(extension);
|
||||
|
||||
Handle<Script> script =
|
||||
NewScript(isolate, &parse_info, source, script_details, natives);
|
||||
if (script.is_null()) {
|
||||
script = NewScript(isolate, &parse_info, source, script_details, natives);
|
||||
}
|
||||
DCHECK_IMPLIES(parse_info.flags().collect_type_profile(),
|
||||
script->IsUserJavaScript());
|
||||
DCHECK_EQ(parse_info.flags().is_repl_mode(), script->is_repl_mode());
|
||||
@ -2896,8 +2914,10 @@ bool CompilationExceptionIsRangeError(Isolate* isolate, Handle<Object> obj) {
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
|
||||
Handle<String> source, const ScriptDetails& script_details,
|
||||
Isolate* isolate, IsCompiledScope* is_compiled_scope) {
|
||||
MaybeHandle<Script> maybe_script, Isolate* isolate,
|
||||
IsCompiledScope* is_compiled_scope) {
|
||||
// Start a background thread compiling the script.
|
||||
// TODO(v8:12808): Use maybe_script for the background compilation.
|
||||
StressBackgroundCompileThread background_compile_thread(
|
||||
isolate, source,
|
||||
script_details.origin_options.IsModule() ? ScriptType::kModule
|
||||
@ -2920,7 +2940,7 @@ MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
|
||||
flags_copy.set_script_id(Script::kTemporaryScriptId);
|
||||
main_thread_maybe_result = CompileScriptOnMainThread(
|
||||
flags_copy, source, script_details, NOT_NATIVES_CODE, nullptr, isolate,
|
||||
&inner_is_compiled_scope);
|
||||
MaybeHandle<Script>(), &inner_is_compiled_scope);
|
||||
if (main_thread_maybe_result.is_null()) {
|
||||
// Assume all range errors are stack overflows.
|
||||
main_thread_had_stack_overflow = CompilationExceptionIsRangeError(
|
||||
@ -3011,6 +3031,7 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
|
||||
const bool use_compilation_cache =
|
||||
extension == nullptr && script_details.repl_mode == REPLMode::kNo;
|
||||
MaybeHandle<SharedFunctionInfo> maybe_result;
|
||||
MaybeHandle<Script> maybe_script;
|
||||
IsCompiledScope is_compiled_scope;
|
||||
if (use_compilation_cache) {
|
||||
bool can_consume_code_cache =
|
||||
@ -3020,8 +3041,11 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
|
||||
}
|
||||
|
||||
// First check per-isolate compilation cache.
|
||||
maybe_result =
|
||||
CompilationCacheScript::LookupResult lookup_result =
|
||||
compilation_cache->LookupScript(source, script_details, language_mode);
|
||||
maybe_script = lookup_result.script();
|
||||
maybe_result = lookup_result.toplevel_sfi();
|
||||
is_compiled_scope = lookup_result.is_compiled_scope();
|
||||
if (!maybe_result.is_null()) {
|
||||
compile_timer.set_hit_isolate_cache();
|
||||
} else if (can_consume_code_cache) {
|
||||
@ -3032,6 +3056,10 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
|
||||
RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize);
|
||||
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
|
||||
"V8.CompileDeserialize");
|
||||
// TODO(v8:12808): If a Script was found in the compilation cache, then
|
||||
// both of the code paths below (Finish and Deserialize) should make use
|
||||
// of that Script to avoid duplicating the Script itself or any
|
||||
// preexisting SharedFunctionInfos.
|
||||
if (deserialize_task) {
|
||||
// If there's a cache consume task, finish it.
|
||||
maybe_result = deserialize_task->Finish(isolate, source,
|
||||
@ -3066,7 +3094,7 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
|
||||
// If the --stress-background-compile flag is set, do the actual
|
||||
// compilation on a background thread, and wait for its result.
|
||||
maybe_result = CompileScriptOnBothBackgroundAndMainThread(
|
||||
source, script_details, isolate, &is_compiled_scope);
|
||||
source, script_details, maybe_script, isolate, &is_compiled_scope);
|
||||
} else {
|
||||
UnoptimizedCompileFlags flags =
|
||||
UnoptimizedCompileFlags::ForToplevelCompile(
|
||||
@ -3078,9 +3106,9 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
|
||||
|
||||
flags.set_is_eager(compile_options == ScriptCompiler::kEagerCompile);
|
||||
|
||||
maybe_result =
|
||||
CompileScriptOnMainThread(flags, source, script_details, natives,
|
||||
extension, isolate, &is_compiled_scope);
|
||||
maybe_result = CompileScriptOnMainThread(
|
||||
flags, source, script_details, natives, extension, isolate,
|
||||
maybe_script, &is_compiled_scope);
|
||||
}
|
||||
|
||||
// Add the result to the isolate cache.
|
||||
@ -3253,8 +3281,18 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
|
||||
{
|
||||
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
|
||||
"V8.StreamingFinalization.CheckCache");
|
||||
maybe_result = compilation_cache->LookupScript(
|
||||
source, script_details, task->flags().outer_language_mode());
|
||||
CompilationCacheScript::LookupResult lookup_result =
|
||||
compilation_cache->LookupScript(source, script_details,
|
||||
task->flags().outer_language_mode());
|
||||
|
||||
// TODO(v8:12808): Determine what to do if we finish streaming and find that
|
||||
// another copy of the Script already exists but has no root
|
||||
// SharedFunctionInfo or has an uncompiled SharedFunctionInfo. For now, we
|
||||
// just ignore it and create a new Script.
|
||||
if (!lookup_result.toplevel_sfi().is_null()) {
|
||||
maybe_result = lookup_result.toplevel_sfi();
|
||||
}
|
||||
|
||||
if (!maybe_result.is_null()) {
|
||||
compile_timer.set_hit_isolate_cache();
|
||||
}
|
||||
@ -3275,6 +3313,8 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
|
||||
// Add compiled code to the isolate cache.
|
||||
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
|
||||
"V8.StreamingFinalization.AddToCache");
|
||||
DCHECK_EQ(task->flags().outer_language_mode(),
|
||||
construct_language_mode(FLAG_use_strict));
|
||||
compilation_cache->PutScript(source, task->flags().outer_language_mode(),
|
||||
result);
|
||||
}
|
||||
|
@ -3899,6 +3899,12 @@ class BigIntPlatform : public bigint::Platform {
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
};
|
||||
|
||||
void MarkCompactPrologue(v8::Isolate* v8_isolate, v8::GCType gc_type,
|
||||
v8::GCCallbackFlags flags, void* data) {
|
||||
Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
|
||||
isolate->compilation_cache()->MarkCompactPrologue();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
VirtualMemoryCage* Isolate::GetPtrComprCodeCageForTesting() {
|
||||
@ -4256,6 +4262,9 @@ bool Isolate::Init(SnapshotData* startup_snapshot_data,
|
||||
}
|
||||
#endif
|
||||
|
||||
heap()->AddGCPrologueCallback(MarkCompactPrologue, kGCTypeMarkSweepCompact,
|
||||
nullptr);
|
||||
|
||||
initialized_ = true;
|
||||
|
||||
return true;
|
||||
|
@ -2035,11 +2035,6 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
|
||||
static void SetIsolateThreadLocals(Isolate* isolate,
|
||||
PerIsolateThreadData* data);
|
||||
|
||||
void MarkCompactPrologue(bool is_compacting,
|
||||
ThreadLocalTop* archived_thread_data);
|
||||
void MarkCompactEpilogue(bool is_compacting,
|
||||
ThreadLocalTop* archived_thread_data);
|
||||
|
||||
void FillCache();
|
||||
|
||||
// Propagate pending exception message to the v8::TryCatch.
|
||||
|
@ -2669,8 +2669,6 @@ void Heap::MarkCompactPrologue() {
|
||||
RegExpResultsCache::Clear(string_split_cache());
|
||||
RegExpResultsCache::Clear(regexp_multiple_cache());
|
||||
|
||||
isolate_->compilation_cache()->MarkCompactPrologue();
|
||||
|
||||
FlushNumberStringCache();
|
||||
}
|
||||
|
||||
|
@ -281,13 +281,16 @@ namespace internal {
|
||||
// lines) rather than one macro (of length about 80 lines) to work around
|
||||
// this problem. Please avoid using recursive macros of this length when
|
||||
// possible.
|
||||
#define STATS_COUNTER_LIST_1(SC) \
|
||||
/* Global Handle Count*/ \
|
||||
SC(global_handles, V8.GlobalHandles) \
|
||||
SC(alive_after_last_gc, V8.AliveAfterLastGC) \
|
||||
SC(compilation_cache_hits, V8.CompilationCacheHits) \
|
||||
SC(compilation_cache_misses, V8.CompilationCacheMisses) \
|
||||
SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
|
||||
#define STATS_COUNTER_LIST_1(SC) \
|
||||
/* Global Handle Count*/ \
|
||||
SC(global_handles, V8.GlobalHandles) \
|
||||
SC(alive_after_last_gc, V8.AliveAfterLastGC) \
|
||||
SC(compilation_cache_hits, V8.CompilationCacheHits) \
|
||||
SC(compilation_cache_misses, V8.CompilationCacheMisses) \
|
||||
/* Number of times the cache contained a reusable Script but not \
|
||||
the root SharedFunctionInfo */ \
|
||||
SC(compilation_cache_partial_hits, V8.CompilationCachePartialHits) \
|
||||
SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
|
||||
SC(objs_since_last_full, V8.ObjsSinceLastFull)
|
||||
|
||||
#define STATS_COUNTER_LIST_2(SC) \
|
||||
|
@ -26,14 +26,76 @@ CompilationCacheTable::CompilationCacheTable(Address ptr)
|
||||
NEVER_READ_ONLY_SPACE_IMPL(CompilationCacheTable)
|
||||
CAST_ACCESSOR(CompilationCacheTable)
|
||||
|
||||
Object CompilationCacheTable::PrimaryValueAt(InternalIndex entry) {
|
||||
return get(EntryToIndex(entry) + 1);
|
||||
}
|
||||
|
||||
void CompilationCacheTable::SetPrimaryValueAt(InternalIndex entry, Object value,
|
||||
WriteBarrierMode mode) {
|
||||
set(EntryToIndex(entry) + 1, value, mode);
|
||||
}
|
||||
|
||||
Object CompilationCacheTable::EvalFeedbackValueAt(InternalIndex entry) {
|
||||
STATIC_ASSERT(CompilationCacheShape::kEntrySize == 3);
|
||||
return get(EntryToIndex(entry) + 2);
|
||||
}
|
||||
|
||||
void CompilationCacheTable::SetEvalFeedbackValueAt(InternalIndex entry,
|
||||
Object value,
|
||||
WriteBarrierMode mode) {
|
||||
set(EntryToIndex(entry) + 2, value, mode);
|
||||
}
|
||||
|
||||
// The key in a script cache is a WeakFixedArray containing a weak pointer to
|
||||
// the Script. The corresponding value can be either the root SharedFunctionInfo
|
||||
// or undefined. The purpose of storing the root SharedFunctionInfo as the value
|
||||
// is to keep it alive, not to save a lookup on the Script. A newly added entry
|
||||
// always contains the root SharedFunctionInfo. After the root
|
||||
// SharedFunctionInfo has aged sufficiently, it is replaced with undefined. In
|
||||
// this way, all strong references to large objects are dropped, but there is
|
||||
// still a way to get the Script if it happens to still be alive.
|
||||
class ScriptCacheKey : public HashTableKey {
|
||||
public:
|
||||
enum Index {
|
||||
kWeakScript,
|
||||
kEnd,
|
||||
};
|
||||
|
||||
explicit ScriptCacheKey(Handle<String> source);
|
||||
|
||||
bool IsMatch(Object other) override;
|
||||
|
||||
Handle<Object> AsHandle(Isolate* isolate, Handle<SharedFunctionInfo> shared);
|
||||
|
||||
static base::Optional<String> SourceFromObject(Object obj) {
|
||||
DisallowGarbageCollection no_gc;
|
||||
DCHECK(obj.IsWeakFixedArray());
|
||||
WeakFixedArray array = WeakFixedArray::cast(obj);
|
||||
DCHECK_EQ(array.length(), kEnd);
|
||||
|
||||
MaybeObject maybe_script = array.Get(kWeakScript);
|
||||
if (HeapObject script; maybe_script.GetHeapObjectIfWeak(&script)) {
|
||||
PrimitiveHeapObject source_or_undefined = Script::cast(script).source();
|
||||
// Scripts stored in the script cache should always have a source string.
|
||||
return String::cast(source_or_undefined);
|
||||
}
|
||||
|
||||
DCHECK(maybe_script.IsCleared());
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<String> source_;
|
||||
};
|
||||
|
||||
uint32_t CompilationCacheShape::RegExpHash(String string, Smi flags) {
|
||||
return string.EnsureHash() + flags.value();
|
||||
}
|
||||
|
||||
uint32_t CompilationCacheShape::StringSharedHash(String source,
|
||||
SharedFunctionInfo shared,
|
||||
LanguageMode language_mode,
|
||||
int position) {
|
||||
uint32_t CompilationCacheShape::EvalHash(String source,
|
||||
SharedFunctionInfo shared,
|
||||
LanguageMode language_mode,
|
||||
int position) {
|
||||
uint32_t hash = source.EnsureHash();
|
||||
if (shared.HasSourceCode()) {
|
||||
// Instead of using the SharedFunctionInfo pointer in the hash
|
||||
@ -50,11 +112,8 @@ uint32_t CompilationCacheShape::StringSharedHash(String source,
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t CompilationCacheShape::StringSharedHash(String source,
|
||||
LanguageMode language_mode) {
|
||||
uint32_t CompilationCacheShape::ScriptHash(String source) {
|
||||
uint32_t hash = source.EnsureHash();
|
||||
STATIC_ASSERT(LanguageModeSize == 2);
|
||||
if (is_strict(language_mode)) hash ^= 0x8000;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@ -68,7 +127,17 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
|
||||
return SharedFunctionInfo::cast(object).Hash();
|
||||
}
|
||||
|
||||
// Script: See StringSharedKey::ToHandle for the encoding.
|
||||
// Script.
|
||||
if (object.IsWeakFixedArray()) {
|
||||
base::Optional<String> script_key =
|
||||
ScriptCacheKey::SourceFromObject(object);
|
||||
// Rehashing should only happen after we've removed all of the keys
|
||||
// containing cleared weak refs.
|
||||
DCHECK(script_key);
|
||||
return ScriptHash(*script_key);
|
||||
}
|
||||
|
||||
// Eval: See EvalCacheKey::ToHandle for the encoding.
|
||||
FixedArray val = FixedArray::cast(object);
|
||||
if (val.map() == roots.fixed_cow_array_map()) {
|
||||
DCHECK_EQ(4, val.length());
|
||||
@ -77,14 +146,10 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
|
||||
DCHECK(is_valid_language_mode(language_unchecked));
|
||||
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
|
||||
int position = Smi::ToInt(val.get(3));
|
||||
Object shared_or_smi = val.get(0);
|
||||
if (shared_or_smi.IsSmi()) {
|
||||
DCHECK_EQ(position, kNoSourcePosition);
|
||||
return StringSharedHash(source, language_mode);
|
||||
} else {
|
||||
return StringSharedHash(source, SharedFunctionInfo::cast(shared_or_smi),
|
||||
language_mode, position);
|
||||
}
|
||||
Object shared = val.get(0);
|
||||
DCHECK(shared.IsSharedFunctionInfo());
|
||||
return EvalHash(source, SharedFunctionInfo::cast(shared), language_mode,
|
||||
position);
|
||||
}
|
||||
|
||||
// RegExp: The key field (and the value field) contains the
|
||||
|
@ -17,14 +17,11 @@ const int kLiteralInitialLength = 2;
|
||||
const int kLiteralContextOffset = 0;
|
||||
const int kLiteralLiteralsOffset = 1;
|
||||
|
||||
// The initial placeholder insertion of the eval cache survives this many GCs.
|
||||
const int kHashGenerations = 10;
|
||||
|
||||
int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry,
|
||||
Context native_context) {
|
||||
int SearchLiteralsMapEntry(CompilationCacheTable cache,
|
||||
InternalIndex cache_entry, Context native_context) {
|
||||
DisallowGarbageCollection no_gc;
|
||||
DCHECK(native_context.IsNativeContext());
|
||||
Object obj = cache.get(cache_entry);
|
||||
Object obj = cache.EvalFeedbackValueAt(cache_entry);
|
||||
|
||||
// Check that there's no confusion between FixedArray and WeakFixedArray (the
|
||||
// object used to be a FixedArray here).
|
||||
@ -43,7 +40,8 @@ int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry,
|
||||
return -1;
|
||||
}
|
||||
|
||||
void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
|
||||
void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
|
||||
InternalIndex cache_entry,
|
||||
Handle<Context> native_context,
|
||||
Handle<FeedbackCell> feedback_cell) {
|
||||
Isolate* isolate = native_context->GetIsolate();
|
||||
@ -52,7 +50,7 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
|
||||
Handle<WeakFixedArray> new_literals_map;
|
||||
int entry;
|
||||
|
||||
Object obj = cache->get(cache_entry);
|
||||
Object obj = cache->EvalFeedbackValueAt(cache_entry);
|
||||
|
||||
// Check that there's no confusion between FixedArray and WeakFixedArray (the
|
||||
// object used to be a FixedArray here).
|
||||
@ -106,18 +104,20 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
|
||||
}
|
||||
#endif
|
||||
|
||||
Object old_literals_map = cache->get(cache_entry);
|
||||
Object old_literals_map = cache->EvalFeedbackValueAt(cache_entry);
|
||||
if (old_literals_map != *new_literals_map) {
|
||||
cache->set(cache_entry, *new_literals_map);
|
||||
cache->SetEvalFeedbackValueAt(cache_entry, *new_literals_map);
|
||||
}
|
||||
}
|
||||
|
||||
FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry,
|
||||
FeedbackCell SearchLiteralsMap(CompilationCacheTable cache,
|
||||
InternalIndex cache_entry,
|
||||
Context native_context) {
|
||||
FeedbackCell result;
|
||||
int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context);
|
||||
if (entry >= 0) {
|
||||
WeakFixedArray literals_map = WeakFixedArray::cast(cache.get(cache_entry));
|
||||
WeakFixedArray literals_map =
|
||||
WeakFixedArray::cast(cache.EvalFeedbackValueAt(cache_entry));
|
||||
DCHECK_LE(entry + kLiteralEntryLength, literals_map.length());
|
||||
MaybeObject object = literals_map.Get(entry + kLiteralLiteralsOffset);
|
||||
|
||||
@ -129,8 +129,8 @@ FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry,
|
||||
return result;
|
||||
}
|
||||
|
||||
// StringSharedKeys are used as keys in the eval cache.
|
||||
class StringSharedKey : public HashTableKey {
|
||||
// EvalCacheKeys are used as keys in the eval cache.
|
||||
class EvalCacheKey : public HashTableKey {
|
||||
public:
|
||||
// This tuple unambiguously identifies calls to eval() or
|
||||
// CreateDynamicFunction() (such as through the Function() constructor).
|
||||
@ -142,23 +142,15 @@ class StringSharedKey : public HashTableKey {
|
||||
// * When positive, position is the position in the source where eval is
|
||||
// called. When negative, position is the negation of the position in the
|
||||
// dynamic function's effective source where the ')' ends the parameters.
|
||||
StringSharedKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
|
||||
LanguageMode language_mode, int position)
|
||||
: HashTableKey(CompilationCacheShape::StringSharedHash(
|
||||
*source, *shared, language_mode, position)),
|
||||
EvalCacheKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
|
||||
LanguageMode language_mode, int position)
|
||||
: HashTableKey(CompilationCacheShape::EvalHash(*source, *shared,
|
||||
language_mode, position)),
|
||||
source_(source),
|
||||
shared_(shared),
|
||||
language_mode_(language_mode),
|
||||
position_(position) {}
|
||||
|
||||
// This tuple unambiguously identifies script compilation.
|
||||
StringSharedKey(Handle<String> source, LanguageMode language_mode)
|
||||
: HashTableKey(
|
||||
CompilationCacheShape::StringSharedHash(*source, language_mode)),
|
||||
source_(source),
|
||||
language_mode_(language_mode),
|
||||
position_(kNoSourcePosition) {}
|
||||
|
||||
bool IsMatch(Object other) override {
|
||||
DisallowGarbageCollection no_gc;
|
||||
if (!other.IsFixedArray()) {
|
||||
@ -167,14 +159,8 @@ class StringSharedKey : public HashTableKey {
|
||||
return Hash() == other_hash;
|
||||
}
|
||||
FixedArray other_array = FixedArray::cast(other);
|
||||
DCHECK(other_array.get(0).IsSharedFunctionInfo() ||
|
||||
other_array.get(0) == Smi::zero());
|
||||
Handle<SharedFunctionInfo> shared;
|
||||
if (shared_.ToHandle(&shared)) {
|
||||
if (*shared != other_array.get(0)) return false;
|
||||
} else {
|
||||
if (Smi::zero() != other_array.get(0)) return false;
|
||||
}
|
||||
DCHECK(other_array.get(0).IsSharedFunctionInfo());
|
||||
if (*shared_ != other_array.get(0)) return false;
|
||||
int language_unchecked = Smi::ToInt(other_array.get(2));
|
||||
DCHECK(is_valid_language_mode(language_unchecked));
|
||||
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
|
||||
@ -187,12 +173,7 @@ class StringSharedKey : public HashTableKey {
|
||||
|
||||
Handle<Object> AsHandle(Isolate* isolate) {
|
||||
Handle<FixedArray> array = isolate->factory()->NewFixedArray(4);
|
||||
Handle<SharedFunctionInfo> shared;
|
||||
if (shared_.ToHandle(&shared)) {
|
||||
array->set(0, *shared);
|
||||
} else {
|
||||
array->set(0, Smi::zero());
|
||||
}
|
||||
array->set(0, *shared_);
|
||||
array->set(1, *source_);
|
||||
array->set(2, Smi::FromEnum(language_mode_));
|
||||
array->set(3, Smi::FromInt(position_));
|
||||
@ -202,7 +183,7 @@ class StringSharedKey : public HashTableKey {
|
||||
|
||||
private:
|
||||
Handle<String> source_;
|
||||
MaybeHandle<SharedFunctionInfo> shared_;
|
||||
Handle<SharedFunctionInfo> shared_;
|
||||
LanguageMode language_mode_;
|
||||
int position_;
|
||||
};
|
||||
@ -244,22 +225,96 @@ class CodeKey : public HashTableKey {
|
||||
|
||||
} // namespace
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript(
|
||||
Handle<CompilationCacheTable> table, Handle<String> src,
|
||||
LanguageMode language_mode, Isolate* isolate) {
|
||||
ScriptCacheKey::ScriptCacheKey(Handle<String> source)
|
||||
: HashTableKey(CompilationCacheShape::ScriptHash(*source)),
|
||||
source_(source) {}
|
||||
|
||||
bool ScriptCacheKey::IsMatch(Object other) {
|
||||
DisallowGarbageCollection no_gc;
|
||||
base::Optional<String> other_source = SourceFromObject(other);
|
||||
return other_source && other_source->Equals(*source_);
|
||||
}
|
||||
|
||||
Handle<Object> ScriptCacheKey::AsHandle(Isolate* isolate,
|
||||
Handle<SharedFunctionInfo> shared) {
|
||||
Handle<WeakFixedArray> array = isolate->factory()->NewWeakFixedArray(kEnd);
|
||||
// Any SharedFunctionInfo being stored in the script cache should have a
|
||||
// Script.
|
||||
DCHECK(shared->script().IsScript());
|
||||
array->Set(kWeakScript,
|
||||
MaybeObject::MakeWeak(MaybeObject::FromObject(shared->script())));
|
||||
return array;
|
||||
}
|
||||
|
||||
CompilationCacheScriptLookupResult::RawObjects
|
||||
CompilationCacheScriptLookupResult::GetRawObjects() const {
|
||||
RawObjects result;
|
||||
if (Handle<Script> script; script_.ToHandle(&script)) {
|
||||
result.first = *script;
|
||||
}
|
||||
if (Handle<SharedFunctionInfo> toplevel_sfi;
|
||||
toplevel_sfi_.ToHandle(&toplevel_sfi)) {
|
||||
result.second = *toplevel_sfi;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CompilationCacheScriptLookupResult
|
||||
CompilationCacheScriptLookupResult::FromRawObjects(
|
||||
CompilationCacheScriptLookupResult::RawObjects raw, Isolate* isolate) {
|
||||
CompilationCacheScriptLookupResult result;
|
||||
if (!raw.first.is_null()) {
|
||||
result.script_ = handle(raw.first, isolate);
|
||||
}
|
||||
if (!raw.second.is_null()) {
|
||||
result.is_compiled_scope_ = raw.second.is_compiled_scope(isolate);
|
||||
if (result.is_compiled_scope_.is_compiled()) {
|
||||
result.toplevel_sfi_ = handle(raw.second, isolate);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CompilationCacheScriptLookupResult CompilationCacheTable::LookupScript(
|
||||
Handle<CompilationCacheTable> table, Handle<String> src, Isolate* isolate) {
|
||||
src = String::Flatten(isolate, src);
|
||||
StringSharedKey key(src, language_mode);
|
||||
ScriptCacheKey key(src);
|
||||
InternalIndex entry = table->FindEntry(isolate, &key);
|
||||
if (entry.is_not_found()) return MaybeHandle<SharedFunctionInfo>();
|
||||
int index = EntryToIndex(entry);
|
||||
if (!table->get(index).IsFixedArray()) {
|
||||
return MaybeHandle<SharedFunctionInfo>();
|
||||
|
||||
if (entry.is_not_found()) return {};
|
||||
|
||||
DisallowGarbageCollection no_gc;
|
||||
Object key_in_table = table->KeyAt(entry);
|
||||
Script script = Script::cast(WeakFixedArray::cast(key_in_table)
|
||||
.Get(ScriptCacheKey::kWeakScript)
|
||||
.GetHeapObjectAssumeWeak());
|
||||
|
||||
Object obj = table->PrimaryValueAt(entry);
|
||||
SharedFunctionInfo toplevel_sfi;
|
||||
if (!obj.IsUndefined(isolate)) {
|
||||
toplevel_sfi = SharedFunctionInfo::cast(obj);
|
||||
DCHECK_EQ(toplevel_sfi.script(), script);
|
||||
} else {
|
||||
// Even though this cache no longer holds a strong reference to the root
|
||||
// SharedFunctionInfo for the Script, the root SharedFunctionInfo may still
|
||||
// exist. If it exists and is already compiled, then we should place it back
|
||||
// into the cache to keep it alive for now. Callers will treat this case as
|
||||
// a cache hit and assume that they needn't take any extra step to re-add
|
||||
// the SharedFunctionInfo to the cache.
|
||||
MaybeObject maybe_sfi =
|
||||
script.shared_function_infos().Get(kFunctionLiteralIdTopLevel);
|
||||
HeapObject maybe_sfi_obj;
|
||||
if (maybe_sfi.GetHeapObject(&maybe_sfi_obj) &&
|
||||
!maybe_sfi_obj.IsUndefined(isolate)) {
|
||||
toplevel_sfi = SharedFunctionInfo::cast(maybe_sfi_obj);
|
||||
if (toplevel_sfi.is_compiled()) {
|
||||
table->SetPrimaryValueAt(entry, toplevel_sfi);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object obj = table->get(index + 1);
|
||||
if (obj.IsSharedFunctionInfo()) {
|
||||
return handle(SharedFunctionInfo::cast(obj), isolate);
|
||||
}
|
||||
return MaybeHandle<SharedFunctionInfo>();
|
||||
|
||||
return CompilationCacheScriptLookupResult::FromRawObjects(
|
||||
std::make_pair(script, toplevel_sfi), isolate);
|
||||
}
|
||||
|
||||
InfoCellPair CompilationCacheTable::LookupEval(
|
||||
@ -270,18 +325,17 @@ InfoCellPair CompilationCacheTable::LookupEval(
|
||||
Isolate* isolate = native_context->GetIsolate();
|
||||
src = String::Flatten(isolate, src);
|
||||
|
||||
StringSharedKey key(src, outer_info, language_mode, position);
|
||||
EvalCacheKey key(src, outer_info, language_mode, position);
|
||||
InternalIndex entry = table->FindEntry(isolate, &key);
|
||||
if (entry.is_not_found()) return empty_result;
|
||||
|
||||
int index = EntryToIndex(entry);
|
||||
if (!table->get(index).IsFixedArray()) return empty_result;
|
||||
Object obj = table->get(index + 1);
|
||||
if (!table->KeyAt(entry).IsFixedArray()) return empty_result;
|
||||
Object obj = table->PrimaryValueAt(entry);
|
||||
if (!obj.IsSharedFunctionInfo()) return empty_result;
|
||||
|
||||
STATIC_ASSERT(CompilationCacheShape::kEntrySize == 3);
|
||||
FeedbackCell feedback_cell =
|
||||
SearchLiteralsMap(*table, index + 2, *native_context);
|
||||
SearchLiteralsMap(*table, entry, *native_context);
|
||||
return InfoCellPair(isolate, SharedFunctionInfo::cast(obj), feedback_cell);
|
||||
}
|
||||
|
||||
@ -292,21 +346,57 @@ Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src,
|
||||
RegExpKey key(src, flags);
|
||||
InternalIndex entry = FindEntry(isolate, &key);
|
||||
if (entry.is_not_found()) return isolate->factory()->undefined_value();
|
||||
return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
|
||||
return Handle<Object>(PrimaryValueAt(entry), isolate);
|
||||
}
|
||||
|
||||
Handle<CompilationCacheTable> CompilationCacheTable::EnsureScriptTableCapacity(
|
||||
Isolate* isolate, Handle<CompilationCacheTable> cache) {
|
||||
if (cache->HasSufficientCapacityToAdd(1)) return cache;
|
||||
|
||||
// Before resizing, delete are any entries whose keys contain cleared weak
|
||||
// pointers.
|
||||
{
|
||||
DisallowGarbageCollection no_gc;
|
||||
for (InternalIndex entry : cache->IterateEntries()) {
|
||||
Object key;
|
||||
if (!cache->ToKey(isolate, entry, &key)) continue;
|
||||
if (WeakFixedArray::cast(key)
|
||||
.Get(ScriptCacheKey::kWeakScript)
|
||||
.IsCleared()) {
|
||||
DCHECK(cache->PrimaryValueAt(entry).IsUndefined());
|
||||
cache->RemoveEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EnsureCapacity(isolate, cache);
|
||||
}
|
||||
|
||||
Handle<CompilationCacheTable> CompilationCacheTable::PutScript(
|
||||
Handle<CompilationCacheTable> cache, Handle<String> src,
|
||||
LanguageMode language_mode, Handle<SharedFunctionInfo> value,
|
||||
Isolate* isolate) {
|
||||
Handle<SharedFunctionInfo> value, Isolate* isolate) {
|
||||
src = String::Flatten(isolate, src);
|
||||
StringSharedKey key(src, language_mode);
|
||||
Handle<Object> k = key.AsHandle(isolate);
|
||||
cache = EnsureCapacity(isolate, cache);
|
||||
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
|
||||
cache->set(EntryToIndex(entry), *k);
|
||||
cache->set(EntryToIndex(entry) + 1, *value);
|
||||
cache->ElementAdded();
|
||||
ScriptCacheKey key(src);
|
||||
Handle<Object> k = key.AsHandle(isolate, value);
|
||||
|
||||
// Check whether there is already a matching entry. If so, we must overwrite
|
||||
// it. This allows an entry whose value is undefined to upgrade to contain a
|
||||
// SharedFunctionInfo.
|
||||
InternalIndex entry = cache->FindEntry(isolate, &key);
|
||||
bool found_existing = entry.is_found();
|
||||
if (!found_existing) {
|
||||
cache = EnsureScriptTableCapacity(isolate, cache);
|
||||
entry = cache->FindInsertionEntry(isolate, key.Hash());
|
||||
}
|
||||
// TODO(v8:12808): Once all code paths are updated to reuse a Script if
|
||||
// available, we could DCHECK here that the Script in the existing entry
|
||||
// matches the Script in the new key if their origins match. For now, there is
|
||||
// no such guarantee.
|
||||
cache->SetKeyAt(entry, *k);
|
||||
cache->SetPrimaryValueAt(entry, *value);
|
||||
if (!found_existing) {
|
||||
cache->ElementAdded();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@ -317,7 +407,7 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
|
||||
int position) {
|
||||
Isolate* isolate = native_context->GetIsolate();
|
||||
src = String::Flatten(isolate, src);
|
||||
StringSharedKey key(src, outer_info, value->language_mode(), position);
|
||||
EvalCacheKey key(src, outer_info, value->language_mode(), position);
|
||||
|
||||
// This block handles 'real' insertions, i.e. the initial dummy insert
|
||||
// (below) has already happened earlier.
|
||||
@ -325,14 +415,12 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
|
||||
Handle<Object> k = key.AsHandle(isolate);
|
||||
InternalIndex entry = cache->FindEntry(isolate, &key);
|
||||
if (entry.is_found()) {
|
||||
cache->set(EntryToIndex(entry), *k);
|
||||
cache->set(EntryToIndex(entry) + 1, *value);
|
||||
cache->SetKeyAt(entry, *k);
|
||||
cache->SetPrimaryValueAt(entry, *value);
|
||||
// AddToFeedbackCellsMap may allocate a new sub-array to live in the
|
||||
// entry, but it won't change the cache array. Therefore EntryToIndex
|
||||
// and entry remains correct.
|
||||
STATIC_ASSERT(CompilationCacheShape::kEntrySize == 3);
|
||||
AddToFeedbackCellsMap(cache, EntryToIndex(entry) + 2, native_context,
|
||||
feedback_cell);
|
||||
AddToFeedbackCellsMap(cache, entry, native_context, feedback_cell);
|
||||
// Add hash again even on cache hit to avoid unnecessary cache delay in
|
||||
// case of hash collisions.
|
||||
}
|
||||
@ -343,8 +431,8 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
|
||||
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
|
||||
Handle<Object> k =
|
||||
isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
|
||||
cache->set(EntryToIndex(entry), *k);
|
||||
cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
|
||||
cache->SetKeyAt(entry, *k);
|
||||
cache->SetPrimaryValueAt(entry, Smi::FromInt(kHashGenerations));
|
||||
cache->ElementAdded();
|
||||
return cache;
|
||||
}
|
||||
@ -357,62 +445,36 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
|
||||
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
|
||||
// We store the value in the key slot, and compare the search key
|
||||
// to the stored value with a custom IsMatch function during lookups.
|
||||
cache->set(EntryToIndex(entry), *value);
|
||||
cache->set(EntryToIndex(entry) + 1, *value);
|
||||
cache->SetKeyAt(entry, *value);
|
||||
cache->SetPrimaryValueAt(entry, *value);
|
||||
cache->ElementAdded();
|
||||
return cache;
|
||||
}
|
||||
|
||||
void CompilationCacheTable::Age(Isolate* isolate) {
|
||||
DisallowGarbageCollection no_gc;
|
||||
for (InternalIndex entry : IterateEntries()) {
|
||||
const int entry_index = EntryToIndex(entry);
|
||||
const int value_index = entry_index + 1;
|
||||
|
||||
Object key = get(entry_index);
|
||||
if (key.IsNumber()) {
|
||||
// The ageing mechanism for the initial dummy entry in the eval cache.
|
||||
// The 'key' is the hash represented as a Number. The 'value' is a smi
|
||||
// counting down from kHashGenerations. On reaching zero, the entry is
|
||||
// cleared.
|
||||
// Note: The following static assert only establishes an explicit
|
||||
// connection between initialization- and use-sites of the smi value
|
||||
// field.
|
||||
STATIC_ASSERT(kHashGenerations);
|
||||
const int new_count = Smi::ToInt(get(value_index)) - 1;
|
||||
if (new_count == 0) {
|
||||
RemoveEntry(entry_index);
|
||||
} else {
|
||||
DCHECK_GT(new_count, 0);
|
||||
NoWriteBarrierSet(*this, value_index, Smi::FromInt(new_count));
|
||||
}
|
||||
} else if (key.IsFixedArray()) {
|
||||
// The ageing mechanism for script and eval caches.
|
||||
SharedFunctionInfo info = SharedFunctionInfo::cast(get(value_index));
|
||||
if (info.HasBytecodeArray() && info.GetBytecodeArray(isolate).IsOld()) {
|
||||
RemoveEntry(entry_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationCacheTable::Remove(Object value) {
|
||||
DisallowGarbageCollection no_gc;
|
||||
for (InternalIndex entry : IterateEntries()) {
|
||||
int entry_index = EntryToIndex(entry);
|
||||
int value_index = entry_index + 1;
|
||||
if (get(value_index) == value) {
|
||||
RemoveEntry(entry_index);
|
||||
if (PrimaryValueAt(entry) == value) {
|
||||
RemoveEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationCacheTable::RemoveEntry(int entry_index) {
|
||||
void CompilationCacheTable::RemoveEntry(InternalIndex entry) {
|
||||
int entry_index = EntryToIndex(entry);
|
||||
Object the_hole_value = GetReadOnlyRoots().the_hole_value();
|
||||
for (int i = 0; i < kEntrySize; i++) {
|
||||
NoWriteBarrierSet(*this, entry_index + i, the_hole_value);
|
||||
}
|
||||
ElementRemoved();
|
||||
|
||||
// This table does not shrink upon deletion. The script cache depends on that
|
||||
// fact, in two ways:
|
||||
// 1. EnsureScriptTableCapacity calls RemoveEntry, at a time when shrinking
|
||||
// the table would be counterproductive, and
|
||||
// 2. CompilationCacheShape::HashForObject cannot produce a hash for keys that
|
||||
// contain cleared weak pointers, so rehashing must only occur right after
|
||||
// all such keys have been cleared.
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
@ -29,13 +29,10 @@ class CompilationCacheShape : public BaseShape<HashTableKey*> {
|
||||
|
||||
static inline uint32_t RegExpHash(String string, Smi flags);
|
||||
|
||||
static inline uint32_t StringSharedHash(String source,
|
||||
SharedFunctionInfo shared,
|
||||
LanguageMode language_mode,
|
||||
int position);
|
||||
static inline uint32_t EvalHash(String source, SharedFunctionInfo shared,
|
||||
LanguageMode language_mode, int position);
|
||||
|
||||
static inline uint32_t StringSharedHash(String source,
|
||||
LanguageMode language_mode);
|
||||
static inline uint32_t ScriptHash(String source);
|
||||
|
||||
static inline uint32_t HashForObject(ReadOnlyRoots roots, Object object);
|
||||
|
||||
@ -79,6 +76,34 @@ class InfoCellPair {
|
||||
FeedbackCell feedback_cell_;
|
||||
};
|
||||
|
||||
// A lookup result from the compilation cache for scripts. There are three
|
||||
// possible states:
|
||||
//
|
||||
// 1. Cache miss: script and toplevel_sfi are both null.
|
||||
// 2. Cache hit: script and toplevel_sfi are both non-null. toplevel_sfi is
|
||||
// guaranteed to be compiled, and to stay compiled while this lookup result
|
||||
// instance is alive.
|
||||
// 3. Partial cache hit: script is non-null, but toplevel_sfi is null. The
|
||||
// script may contain an uncompiled toplevel SharedFunctionInfo.
|
||||
class CompilationCacheScriptLookupResult {
|
||||
public:
|
||||
MaybeHandle<Script> script() const { return script_; }
|
||||
MaybeHandle<SharedFunctionInfo> toplevel_sfi() const { return toplevel_sfi_; }
|
||||
IsCompiledScope is_compiled_scope() const { return is_compiled_scope_; }
|
||||
|
||||
using RawObjects = std::pair<Script, SharedFunctionInfo>;
|
||||
|
||||
RawObjects GetRawObjects() const;
|
||||
|
||||
static CompilationCacheScriptLookupResult FromRawObjects(RawObjects raw,
|
||||
Isolate* isolate);
|
||||
|
||||
private:
|
||||
MaybeHandle<Script> script_;
|
||||
MaybeHandle<SharedFunctionInfo> toplevel_sfi_;
|
||||
IsCompiledScope is_compiled_scope_;
|
||||
};
|
||||
|
||||
EXTERN_DECLARE_HASH_TABLE(CompilationCacheTable, CompilationCacheShape)
|
||||
|
||||
class CompilationCacheTable
|
||||
@ -86,14 +111,18 @@ class CompilationCacheTable
|
||||
public:
|
||||
NEVER_READ_ONLY_SPACE
|
||||
|
||||
// The 'script' cache contains SharedFunctionInfos.
|
||||
static MaybeHandle<SharedFunctionInfo> LookupScript(
|
||||
// The 'script' cache contains SharedFunctionInfos. Once a root
|
||||
// SharedFunctionInfo has become old enough that its bytecode is flushed, the
|
||||
// entry is still present and can be used to get the Script. For consistency,
|
||||
// this function always returns the Script. Callers should check whether there
|
||||
// is a root SharedFunctionInfo in the script and whether it is already
|
||||
// compiled, and choose what to do accordingly.
|
||||
static CompilationCacheScriptLookupResult LookupScript(
|
||||
Handle<CompilationCacheTable> table, Handle<String> src,
|
||||
LanguageMode language_mode, Isolate* isolate);
|
||||
Isolate* isolate);
|
||||
static Handle<CompilationCacheTable> PutScript(
|
||||
Handle<CompilationCacheTable> cache, Handle<String> src,
|
||||
LanguageMode language_mode, Handle<SharedFunctionInfo> value,
|
||||
Isolate* isolate);
|
||||
Handle<SharedFunctionInfo> value, Isolate* isolate);
|
||||
|
||||
// Eval code only gets cached after a second probe for the
|
||||
// code object. To do so, on first "put" only a hash identifying the
|
||||
@ -124,12 +153,24 @@ class CompilationCacheTable
|
||||
JSRegExp::Flags flags, Handle<FixedArray> value);
|
||||
|
||||
void Remove(Object value);
|
||||
void Age(Isolate* isolate);
|
||||
void RemoveEntry(InternalIndex entry);
|
||||
|
||||
inline Object PrimaryValueAt(InternalIndex entry);
|
||||
inline void SetPrimaryValueAt(InternalIndex entry, Object value,
|
||||
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
|
||||
inline Object EvalFeedbackValueAt(InternalIndex entry);
|
||||
inline void SetEvalFeedbackValueAt(
|
||||
InternalIndex entry, Object value,
|
||||
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
|
||||
|
||||
// The initial placeholder insertion of the eval cache survives this many GCs.
|
||||
static constexpr int kHashGenerations = 10;
|
||||
|
||||
DECL_CAST(CompilationCacheTable)
|
||||
|
||||
private:
|
||||
void RemoveEntry(int entry_index);
|
||||
static Handle<CompilationCacheTable> EnsureScriptTableCapacity(
|
||||
Isolate* isolate, Handle<CompilationCacheTable> cache);
|
||||
|
||||
OBJECT_CONSTRUCTORS(CompilationCacheTable,
|
||||
HashTable<CompilationCacheTable, CompilationCacheShape>);
|
||||
|
@ -238,6 +238,12 @@ Object HashTable<Derived, Shape>::KeyAt(PtrComprCageBase cage_base,
|
||||
return get(cage_base, EntryToIndex(entry) + kEntryKeyIndex, tag);
|
||||
}
|
||||
|
||||
template <typename Derived, typename Shape>
|
||||
void HashTable<Derived, Shape>::SetKeyAt(InternalIndex entry, Object value,
|
||||
WriteBarrierMode mode) {
|
||||
set_key(EntryToIndex(entry), value, mode);
|
||||
}
|
||||
|
||||
template <typename Derived, typename Shape>
|
||||
void HashTable<Derived, Shape>::set_key(int index, Object value) {
|
||||
DCHECK(!IsEphemeronHashTable());
|
||||
|
@ -165,6 +165,9 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) HashTable
|
||||
inline Object KeyAt(PtrComprCageBase cage_base, InternalIndex entry,
|
||||
RelaxedLoadTag tag);
|
||||
|
||||
inline void SetKeyAt(InternalIndex entry, Object value,
|
||||
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
|
||||
|
||||
static const int kElementsStartIndex = kPrefixStartIndex + Shape::kPrefixSize;
|
||||
static const int kEntrySize = Shape::kEntrySize;
|
||||
STATIC_ASSERT(kEntrySize > 0);
|
||||
|
@ -1469,7 +1469,7 @@ TEST(TestUseOfIncrementalBarrierOnCompileLazy) {
|
||||
CHECK(g_function->is_compiled());
|
||||
}
|
||||
|
||||
TEST(CompilationCacheCachingBehavior) {
|
||||
void CompilationCacheCachingBehavior(bool retain_script) {
|
||||
// If we do not have the compilation cache turned off, this test is invalid.
|
||||
if (!FLAG_compilation_cache) {
|
||||
return;
|
||||
@ -1478,16 +1478,20 @@ TEST(CompilationCacheCachingBehavior) {
|
||||
Isolate* isolate = CcTest::i_isolate();
|
||||
Factory* factory = isolate->factory();
|
||||
CompilationCache* compilation_cache = isolate->compilation_cache();
|
||||
LanguageMode language_mode = construct_language_mode(FLAG_use_strict);
|
||||
LanguageMode language_mode = LanguageMode::kSloppy;
|
||||
|
||||
v8::HandleScope outer_scope(CcTest::isolate());
|
||||
const char* raw_source =
|
||||
"function foo() {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
"};"
|
||||
"foo();";
|
||||
const char* raw_source = retain_script ? "function foo() {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
"};"
|
||||
"foo();"
|
||||
: "(function foo() {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
"})();";
|
||||
Handle<String> source = factory->InternalizeUtf8String(raw_source);
|
||||
|
||||
{
|
||||
@ -1500,9 +1504,9 @@ TEST(CompilationCacheCachingBehavior) {
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
ScriptDetails script_details(Handle<Object>(),
|
||||
v8::ScriptOriginOptions(true, false));
|
||||
MaybeHandle<SharedFunctionInfo> cached_script =
|
||||
auto lookup_result =
|
||||
compilation_cache->LookupScript(source, script_details, language_mode);
|
||||
CHECK(!cached_script.is_null());
|
||||
CHECK(!lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
// Check that the code cache entry survives at least one GC.
|
||||
@ -1511,12 +1515,13 @@ TEST(CompilationCacheCachingBehavior) {
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
ScriptDetails script_details(Handle<Object>(),
|
||||
v8::ScriptOriginOptions(true, false));
|
||||
MaybeHandle<SharedFunctionInfo> cached_script =
|
||||
auto lookup_result =
|
||||
compilation_cache->LookupScript(source, script_details, language_mode);
|
||||
CHECK(!cached_script.is_null());
|
||||
CHECK(!lookup_result.toplevel_sfi().is_null());
|
||||
|
||||
// Progress code age until it's old and ready for GC.
|
||||
Handle<SharedFunctionInfo> shared = cached_script.ToHandleChecked();
|
||||
Handle<SharedFunctionInfo> shared =
|
||||
lookup_result.toplevel_sfi().ToHandleChecked();
|
||||
CHECK(shared->HasBytecodeArray());
|
||||
const int kAgingThreshold = 6;
|
||||
for (int i = 0; i < kAgingThreshold; i++) {
|
||||
@ -1531,12 +1536,249 @@ TEST(CompilationCacheCachingBehavior) {
|
||||
// Ensure code aging cleared the entry from the cache.
|
||||
ScriptDetails script_details(Handle<Object>(),
|
||||
v8::ScriptOriginOptions(true, false));
|
||||
MaybeHandle<SharedFunctionInfo> cached_script =
|
||||
auto lookup_result =
|
||||
compilation_cache->LookupScript(source, script_details, language_mode);
|
||||
CHECK(cached_script.is_null());
|
||||
CHECK(lookup_result.toplevel_sfi().is_null());
|
||||
CHECK_EQ(retain_script, !lookup_result.script().is_null());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CompilationCacheCachingBehaviorDiscardScript) {
|
||||
CompilationCacheCachingBehavior(false);
|
||||
}
|
||||
|
||||
TEST(CompilationCacheCachingBehaviorRetainScript) {
|
||||
CompilationCacheCachingBehavior(true);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
Handle<SharedFunctionInfo> GetSharedFunctionInfo(
|
||||
v8::Local<T> function_or_script) {
|
||||
Handle<JSFunction> i_function =
|
||||
Handle<JSFunction>::cast(v8::Utils::OpenHandle(*function_or_script));
|
||||
return handle(i_function->shared(), CcTest::i_isolate());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AgeBytecode(v8::Local<T> function_or_script) {
|
||||
Handle<SharedFunctionInfo> shared = GetSharedFunctionInfo(function_or_script);
|
||||
CHECK(shared->HasBytecodeArray());
|
||||
const int kAgingThreshold = 6;
|
||||
for (int i = 0; i < kAgingThreshold; i++) {
|
||||
shared->GetBytecodeArray(CcTest::i_isolate()).MakeOlder();
|
||||
}
|
||||
}
|
||||
|
||||
void CompilationCacheRegeneration(bool retain_root_sfi, bool flush_root_sfi,
|
||||
bool flush_eager_sfi) {
|
||||
// If the compilation cache is turned off, this test is invalid.
|
||||
if (!FLAG_compilation_cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(v8:12808): Remove this check once background compilation is capable of
|
||||
// reusing an existing Script.
|
||||
if (flush_root_sfi && FLAG_stress_background_compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some flags can prevent bytecode flushing, which affects this test.
|
||||
bool flushing_disabled = !FLAG_flush_bytecode ||
|
||||
(FLAG_always_sparkplug && !FLAG_flush_baseline_code);
|
||||
|
||||
CcTest::InitializeVM();
|
||||
Isolate* isolate = CcTest::i_isolate();
|
||||
|
||||
const char* source =
|
||||
"({"
|
||||
" lazyFunction: function () {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
" },"
|
||||
" eagerFunction: (function () {"
|
||||
" var x = 43;"
|
||||
" var y = 43;"
|
||||
" var z = x + y;"
|
||||
" })"
|
||||
"})";
|
||||
|
||||
v8::Global<v8::Script> outer_function;
|
||||
v8::Global<v8::Function> lazy_function;
|
||||
v8::Global<v8::Function> eager_function;
|
||||
|
||||
{
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
v8::Local<v8::Context> context =
|
||||
v8::Isolate::GetCurrent()->GetCurrentContext();
|
||||
v8::Local<v8::Script> script = v8_compile(v8_str(source));
|
||||
outer_function.Reset(CcTest::isolate(), script);
|
||||
|
||||
// Even though the script has not executed, it should already be parsed.
|
||||
Handle<SharedFunctionInfo> script_sfi = GetSharedFunctionInfo(script);
|
||||
CHECK(script_sfi->is_compiled());
|
||||
|
||||
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
|
||||
|
||||
// Now that the script has run, we can get references to the inner
|
||||
// functions, and verify that the eager parsing heuristics are behaving as
|
||||
// expected.
|
||||
v8::Local<v8::Object> result_obj =
|
||||
result->ToObject(context).ToLocalChecked();
|
||||
v8::Local<v8::Value> lazy_function_value =
|
||||
result_obj->GetRealNamedProperty(context, v8_str("lazyFunction"))
|
||||
.ToLocalChecked();
|
||||
CHECK(lazy_function_value->IsFunction());
|
||||
CHECK(!GetSharedFunctionInfo(lazy_function_value)->is_compiled());
|
||||
lazy_function.Reset(CcTest::isolate(),
|
||||
lazy_function_value.As<v8::Function>());
|
||||
v8::Local<v8::Value> eager_function_value =
|
||||
result_obj->GetRealNamedProperty(context, v8_str("eagerFunction"))
|
||||
.ToLocalChecked();
|
||||
CHECK(eager_function_value->IsFunction());
|
||||
eager_function.Reset(CcTest::isolate(),
|
||||
eager_function_value.As<v8::Function>());
|
||||
CHECK(GetSharedFunctionInfo(eager_function_value)->is_compiled());
|
||||
}
|
||||
|
||||
{
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
|
||||
// Progress code age until it's old and ready for GC.
|
||||
if (flush_root_sfi) {
|
||||
v8::Local<v8::Script> outer_function_value =
|
||||
outer_function.Get(CcTest::isolate());
|
||||
AgeBytecode(outer_function_value);
|
||||
}
|
||||
if (flush_eager_sfi) {
|
||||
v8::Local<v8::Function> eager_function_value =
|
||||
eager_function.Get(CcTest::isolate());
|
||||
AgeBytecode(eager_function_value);
|
||||
}
|
||||
if (!retain_root_sfi) {
|
||||
outer_function.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
CcTest::CollectAllGarbage();
|
||||
|
||||
if (FLAG_stress_incremental_marking) {
|
||||
// If incremental marking could have started before the bytecode was aged,
|
||||
// then we need a second collection to evict the cache entries.
|
||||
CcTest::CollectAllGarbage();
|
||||
}
|
||||
|
||||
// The root SharedFunctionInfo can be retained either by a Global in this
|
||||
// function or by the compilation cache.
|
||||
bool root_sfi_should_still_exist = retain_root_sfi || !flush_root_sfi;
|
||||
|
||||
{
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
|
||||
// The lazy function should still not be compiled.
|
||||
Handle<SharedFunctionInfo> lazy_sfi =
|
||||
GetSharedFunctionInfo(lazy_function.Get(CcTest::isolate()));
|
||||
CHECK(!lazy_sfi->is_compiled());
|
||||
|
||||
// The eager function may have had its bytecode flushed.
|
||||
Handle<SharedFunctionInfo> eager_sfi =
|
||||
GetSharedFunctionInfo(eager_function.Get(CcTest::isolate()));
|
||||
CHECK_EQ(!flush_eager_sfi || flushing_disabled, eager_sfi->is_compiled());
|
||||
|
||||
// Check whether the root SharedFunctionInfo is still reachable from the
|
||||
// Script.
|
||||
Handle<Script> script(Script::cast(lazy_sfi->script()), isolate);
|
||||
bool root_sfi_still_exists = false;
|
||||
MaybeObject maybe_root_sfi =
|
||||
script->shared_function_infos().Get(kFunctionLiteralIdTopLevel);
|
||||
if (HeapObject sfi_or_undefined;
|
||||
maybe_root_sfi.GetHeapObject(&sfi_or_undefined)) {
|
||||
root_sfi_still_exists = !sfi_or_undefined.IsUndefined();
|
||||
}
|
||||
CHECK_EQ(root_sfi_should_still_exist, root_sfi_still_exists);
|
||||
}
|
||||
|
||||
{
|
||||
// Run the script again and check that no SharedFunctionInfos were
|
||||
// duplicated, and that the expected ones were compiled.
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
v8::Local<v8::Context> context =
|
||||
v8::Isolate::GetCurrent()->GetCurrentContext();
|
||||
v8::Local<v8::Script> script = v8_compile(v8_str(source));
|
||||
|
||||
// The script should be compiled by now.
|
||||
Handle<SharedFunctionInfo> script_sfi = GetSharedFunctionInfo(script);
|
||||
CHECK(script_sfi->is_compiled());
|
||||
|
||||
// This compilation should not have created a new root SharedFunctionInfo if
|
||||
// one already existed.
|
||||
if (retain_root_sfi) {
|
||||
Handle<SharedFunctionInfo> old_script_sfi =
|
||||
GetSharedFunctionInfo(outer_function.Get(CcTest::isolate()));
|
||||
CHECK_EQ(*old_script_sfi, *script_sfi);
|
||||
}
|
||||
|
||||
Handle<SharedFunctionInfo> old_lazy_sfi =
|
||||
GetSharedFunctionInfo(lazy_function.Get(CcTest::isolate()));
|
||||
CHECK(!old_lazy_sfi->is_compiled());
|
||||
|
||||
// The only way for the eager function to be uncompiled at this point is if
|
||||
// it was flushed but the root function was not.
|
||||
Handle<SharedFunctionInfo> old_eager_sfi =
|
||||
GetSharedFunctionInfo(eager_function.Get(CcTest::isolate()));
|
||||
CHECK_EQ(!(flush_eager_sfi && !flush_root_sfi) || flushing_disabled,
|
||||
old_eager_sfi->is_compiled());
|
||||
|
||||
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
|
||||
|
||||
// Check that both functions reused the existing SharedFunctionInfos.
|
||||
v8::Local<v8::Object> result_obj =
|
||||
result->ToObject(context).ToLocalChecked();
|
||||
v8::Local<v8::Value> lazy_function_value =
|
||||
result_obj->GetRealNamedProperty(context, v8_str("lazyFunction"))
|
||||
.ToLocalChecked();
|
||||
CHECK(lazy_function_value->IsFunction());
|
||||
Handle<SharedFunctionInfo> lazy_sfi =
|
||||
GetSharedFunctionInfo(lazy_function_value);
|
||||
CHECK_EQ(*old_lazy_sfi, *lazy_sfi);
|
||||
v8::Local<v8::Value> eager_function_value =
|
||||
result_obj->GetRealNamedProperty(context, v8_str("eagerFunction"))
|
||||
.ToLocalChecked();
|
||||
CHECK(eager_function_value->IsFunction());
|
||||
Handle<SharedFunctionInfo> eager_sfi =
|
||||
GetSharedFunctionInfo(eager_function_value);
|
||||
CHECK_EQ(*old_eager_sfi, *eager_sfi);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CompilationCacheRegeneration0) {
|
||||
CompilationCacheRegeneration(false, false, false);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration1) {
|
||||
CompilationCacheRegeneration(false, false, true);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration2) {
|
||||
CompilationCacheRegeneration(false, true, false);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration3) {
|
||||
CompilationCacheRegeneration(false, true, true);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration4) {
|
||||
CompilationCacheRegeneration(true, false, false);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration5) {
|
||||
CompilationCacheRegeneration(true, false, true);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration6) {
|
||||
CompilationCacheRegeneration(true, true, false);
|
||||
}
|
||||
TEST(CompilationCacheRegeneration7) {
|
||||
CompilationCacheRegeneration(true, true, true);
|
||||
}
|
||||
|
||||
static void OptimizeEmptyFunction(const char* name) {
|
||||
HandleScope scope(CcTest::i_isolate());
|
||||
|
@ -23843,23 +23843,25 @@ TEST(StreamingWithHarmonyScopes) {
|
||||
delete[] full_source;
|
||||
}
|
||||
|
||||
// Regression test for crbug.com/v8/12668. Verifies that after a streamed script
|
||||
// is inserted into the isolate script cache, a non-streamed script with
|
||||
// identical origin can reuse that data.
|
||||
TEST(StreamingWithIsolateScriptCache) {
|
||||
namespace {
|
||||
void StreamingWithIsolateScriptCache(bool run_gc) {
|
||||
i::FLAG_expose_gc = true;
|
||||
const char* chunks[] = {"'use strict'; (function test() { return 13; })",
|
||||
nullptr};
|
||||
const char* full_source = chunks[0];
|
||||
v8::Isolate* isolate = CcTest::isolate();
|
||||
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"), 0, 0, false, -1,
|
||||
v8::Local<v8::Value>(), false, false, false);
|
||||
v8::Local<Value> first_function_untyped;
|
||||
i::Handle<i::JSFunction> first_function;
|
||||
i::Handle<i::JSFunction> second_function;
|
||||
|
||||
// Run the script using streaming.
|
||||
{
|
||||
LocalContext env;
|
||||
v8::EscapableHandleScope inner_scope(isolate);
|
||||
|
||||
v8::ScriptCompiler::StreamedSource source(
|
||||
std::make_unique<TestSourceStream>(chunks),
|
||||
@ -23874,13 +23876,38 @@ TEST(StreamingWithIsolateScriptCache) {
|
||||
origin)
|
||||
.ToLocalChecked();
|
||||
v8::Local<Value> result(script->Run(env.local()).ToLocalChecked());
|
||||
first_function =
|
||||
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*result));
|
||||
first_function_untyped = inner_scope.Escape(result);
|
||||
|
||||
if (run_gc) {
|
||||
// Age the top-level bytecode for the script to encourage the Isolate
|
||||
// script cache to evict it. However, there are still active Handles
|
||||
// referring to functions in that script, so the script itself should stay
|
||||
// alive and reachable via the Isolate script cache.
|
||||
i::Handle<i::JSFunction> script_function =
|
||||
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*script));
|
||||
i::Handle<i::BytecodeArray> script_bytecode(
|
||||
script_function->shared().GetBytecodeArray(i_isolate), i_isolate);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
script_bytecode->MakeOlder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
first_function = i::Handle<i::JSFunction>::cast(
|
||||
v8::Utils::OpenHandle(*first_function_untyped));
|
||||
|
||||
// Run the same script in another Context without streaming.
|
||||
{
|
||||
LocalContext env;
|
||||
|
||||
if (run_gc) {
|
||||
// Perform garbage collection, which should remove the top-level
|
||||
// SharedFunctionInfo from the Isolate script cache. However, the
|
||||
// corresponding Script is still reachable and therefore still present in
|
||||
// the Isolate script cache.
|
||||
CompileRun("gc();");
|
||||
}
|
||||
|
||||
v8::ScriptCompiler::Source script_source(v8_str(full_source), origin);
|
||||
Local<Script> script =
|
||||
v8::ScriptCompiler::Compile(env.local(), &script_source)
|
||||
@ -23894,6 +23921,26 @@ TEST(StreamingWithIsolateScriptCache) {
|
||||
// SharedFunctionInfo instance due to the isolate script cache.
|
||||
CHECK_EQ(first_function->shared(), second_function->shared());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Regression test for crbug.com/v8/12668. Verifies that after a streamed script
|
||||
// is inserted into the isolate script cache, a non-streamed script with
|
||||
// identical origin can reuse that data.
|
||||
TEST(StreamingWithIsolateScriptCache) {
|
||||
StreamingWithIsolateScriptCache(false);
|
||||
}
|
||||
|
||||
// Variant of the above test which evicts the root SharedFunctionInfo from the
|
||||
// Isolate script cache but still reuses the same Script.
|
||||
TEST(StreamingWithIsolateScriptCacheClearingRootSFI) {
|
||||
// TODO(v8:12808): Remove this check once background compilation is capable of
|
||||
// reusing an existing Script.
|
||||
if (v8::internal::FLAG_stress_background_compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamingWithIsolateScriptCache(true);
|
||||
}
|
||||
|
||||
TEST(CodeCache) {
|
||||
v8::Isolate::CreateParams create_params;
|
||||
|
@ -1820,10 +1820,9 @@ TEST(CodeSerializerPromotedToCompilationCache) {
|
||||
ScriptDetails script_details(src);
|
||||
script_details.host_defined_options =
|
||||
default_script_details.host_defined_options;
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK_EQ(*shared.ToHandleChecked(), *copy);
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
|
||||
}
|
||||
|
||||
{
|
||||
@ -1837,10 +1836,9 @@ TEST(CodeSerializerPromotedToCompilationCache) {
|
||||
default_host_defined_option_1_string);
|
||||
host_defined_options->set(1, *host_defined_option_1);
|
||||
script_details.host_defined_options = host_defined_options;
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK_EQ(*shared.ToHandleChecked(), *copy);
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
|
||||
}
|
||||
|
||||
{
|
||||
@ -1849,49 +1847,48 @@ TEST(CodeSerializerPromotedToCompilationCache) {
|
||||
isolate->factory()->NewStringFromAsciiChecked(source));
|
||||
script_details.host_defined_options =
|
||||
default_script_details.host_defined_options;
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK_EQ(*shared.ToHandleChecked(), *copy);
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
|
||||
}
|
||||
|
||||
{
|
||||
// Lookup with different name string should fail:
|
||||
ScriptDetails script_details(
|
||||
isolate->factory()->NewStringFromAsciiChecked("other"));
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
{
|
||||
// Lookup with different position should fail:
|
||||
ScriptDetails script_details(src);
|
||||
script_details.line_offset = 0xFF;
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
{
|
||||
// Lookup with different position should fail:
|
||||
ScriptDetails script_details(src);
|
||||
script_details.column_offset = 0xFF;
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
{
|
||||
// Lookup with different language mode should fail:
|
||||
ScriptDetails script_details(src);
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kStrict);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kStrict);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
{
|
||||
@ -1899,20 +1896,20 @@ TEST(CodeSerializerPromotedToCompilationCache) {
|
||||
ScriptOriginOptions origin_options(false, true);
|
||||
CHECK_NE(ScriptOriginOptions().Flags(), origin_options.Flags());
|
||||
ScriptDetails script_details(src, origin_options);
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
{
|
||||
// Lookup with different host_defined_options should fail:
|
||||
ScriptDetails script_details(src);
|
||||
script_details.host_defined_options = isolate->factory()->NewFixedArray(5);
|
||||
MaybeHandle<SharedFunctionInfo> shared =
|
||||
isolate->compilation_cache()->LookupScript(src, script_details,
|
||||
LanguageMode::kSloppy);
|
||||
CHECK(shared.is_null());
|
||||
auto lookup_result = isolate->compilation_cache()->LookupScript(
|
||||
src, script_details, LanguageMode::kSloppy);
|
||||
CHECK(lookup_result.script().is_null() &&
|
||||
lookup_result.toplevel_sfi().is_null());
|
||||
}
|
||||
|
||||
delete cache;
|
||||
|
Loading…
Reference in New Issue
Block a user