Commit d21b37d3 authored by Seth Brenith's avatar Seth Brenith Committed by V8 LUCI CQ

Revert several changes that caused performance regressions

This change reverts the following:

400b2cc2 Don't rescue old top-level SharedFunctionInfos
Reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/3657472

16a7150b Reland "Disable recompilation of existing Scripts from
           Isolate compilation cache"
Reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/3655011

2df4d58a Fix rehashing of script compilation cache
Reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/3654413

c8848cf4 Refactor CompilationSubCache
Reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/3629603

25072178 Improve Script reuse in isolate compilation cache, part 1
Reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/3597106

Bug: v8:12808, chromium:1325566, chromium:1325567, chromium:1325601, chromium:1328671, chromium:1328672, chromium:1328678, chromium:1328811, chromium:1328810
Change-Id: I1d318dc172e5214166d3b15f19903186f4fe6024
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3664023Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80744}
parent a6cdc3a3
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
// The number of generations for each sub cache.
static const int kRegExpGenerations = 2;
// Initial size of each compilation cache table allocated. // Initial size of each compilation cache table allocated.
static const int kInitialCacheSize = 64; static const int kInitialCacheSize = 64;
...@@ -26,18 +29,17 @@ CompilationCache::CompilationCache(Isolate* isolate) ...@@ -26,18 +29,17 @@ CompilationCache::CompilationCache(Isolate* isolate)
script_(isolate), script_(isolate),
eval_global_(isolate), eval_global_(isolate),
eval_contextual_(isolate), eval_contextual_(isolate),
reg_exp_(isolate), reg_exp_(isolate, kRegExpGenerations),
enabled_script_and_eval_(true) {} enabled_script_and_eval_(true) {
CompilationSubCache* subcaches[kSubCacheCount] = {
Handle<CompilationCacheTable> CompilationCacheEvalOrScript::GetTable() { &script_, &eval_global_, &eval_contextual_, &reg_exp_};
if (table_.IsUndefined(isolate())) { for (int i = 0; i < kSubCacheCount; ++i) {
return CompilationCacheTable::New(isolate(), kInitialCacheSize); subcaches_[i] = subcaches[i];
} }
return handle(CompilationCacheTable::cast(table_), isolate());
} }
Handle<CompilationCacheTable> CompilationCacheRegExp::GetTable(int generation) { Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
DCHECK_LT(generation, kGenerations); DCHECK_LT(generation, generations());
Handle<CompilationCacheTable> result; Handle<CompilationCacheTable> result;
if (tables_[generation].IsUndefined(isolate())) { if (tables_[generation].IsUndefined(isolate())) {
result = CompilationCacheTable::New(isolate(), kInitialCacheSize); result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
...@@ -50,112 +52,68 @@ Handle<CompilationCacheTable> CompilationCacheRegExp::GetTable(int generation) { ...@@ -50,112 +52,68 @@ Handle<CompilationCacheTable> CompilationCacheRegExp::GetTable(int generation) {
return result; return result;
} }
void CompilationCacheRegExp::Age() { // static
static_assert(kGenerations > 1); void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
DCHECK_GT(c->generations(), 1);
// Age the generations implicitly killing off the oldest. // Age the generations implicitly killing off the oldest.
for (int i = kGenerations - 1; i > 0; i--) { for (int i = c->generations() - 1; i > 0; i--) {
tables_[i] = tables_[i - 1]; c->tables_[i] = c->tables_[i - 1];
} }
// Set the first generation as unborn. // Set the first generation as unborn.
tables_[0] = ReadOnlyRoots(isolate()).undefined_value(); c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value();
}
void CompilationCacheScript::Age() {
DisallowGarbageCollection no_gc;
if (!FLAG_isolate_script_cache_ageing) return;
if (table_.IsUndefined(isolate())) return;
CompilationCacheTable table = CompilationCacheTable::cast(table_);
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() { // static
DisallowGarbageCollection no_gc; void CompilationSubCache::AgeCustom(CompilationSubCache* c) {
if (table_.IsUndefined(isolate())) return; DCHECK_EQ(c->generations(), 1);
CompilationCacheTable table = CompilationCacheTable::cast(table_); if (c->tables_[0].IsUndefined(c->isolate())) return;
CompilationCacheTable::cast(c->tables_[0]).Age(c->isolate());
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 CompilationCacheEvalOrScript::Iterate(RootVisitor* v) { void CompilationCacheScript::Age() {
v->VisitRootPointer(Root::kCompilationCache, nullptr, if (FLAG_isolate_script_cache_ageing) AgeCustom(this);
FullObjectSlot(&table_));
} }
void CompilationCacheEval::Age() { AgeCustom(this); }
void CompilationCacheRegExp::Age() { AgeByGeneration(this); }
void CompilationCacheRegExp::Iterate(RootVisitor* v) { void CompilationSubCache::Iterate(RootVisitor* v) {
v->VisitRootPointers(Root::kCompilationCache, nullptr, v->VisitRootPointers(Root::kCompilationCache, nullptr,
FullObjectSlot(&tables_[0]), FullObjectSlot(&tables_[0]),
FullObjectSlot(&tables_[kGenerations])); FullObjectSlot(&tables_[generations()]));
}
void CompilationCacheEvalOrScript::Clear() {
table_ = ReadOnlyRoots(isolate()).undefined_value();
} }
void CompilationCacheRegExp::Clear() { void CompilationSubCache::Clear() {
MemsetPointer(reinterpret_cast<Address*>(tables_), MemsetPointer(reinterpret_cast<Address*>(tables_),
ReadOnlyRoots(isolate()).undefined_value().ptr(), kGenerations); ReadOnlyRoots(isolate()).undefined_value().ptr(),
generations());
} }
void CompilationCacheEvalOrScript::Remove( void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) {
Handle<SharedFunctionInfo> function_info) { // Probe the script generation tables. Make sure not to leak handles
if (table_.IsUndefined(isolate())) return; // into the caller's handle scope.
CompilationCacheTable::cast(table_).Remove(*function_info); {
HandleScope scope(isolate());
for (int generation = 0; generation < generations(); generation++) {
Handle<CompilationCacheTable> table = GetTable(generation);
table->Remove(*function_info);
}
}
} }
CompilationCacheScript::CompilationCacheScript(Isolate* isolate)
: CompilationSubCache(isolate, 1) {}
namespace { namespace {
// We only re-use a cached function for some script source code if the // 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 // script originates from the same place. This is to avoid issues
// when reporting errors, etc. // when reporting errors, etc.
bool HasOrigin(Isolate* isolate, Handle<Script> script, bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info,
const ScriptDetails& script_details) { 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 // If the script name isn't set, the boilerplate script should have
// an undefined name to have the same origin. // an undefined name to have the same origin.
Handle<Object> name; Handle<Object> name;
...@@ -207,43 +165,40 @@ bool HasOrigin(Isolate* isolate, Handle<Script> script, ...@@ -207,43 +165,40 @@ bool HasOrigin(Isolate* isolate, Handle<Script> script,
// be cached in the same script generation. Currently the first use // be cached in the same script generation. Currently the first use
// will be cached, but subsequent code from different source / line // will be cached, but subsequent code from different source / line
// won't. // won't.
CompilationCacheScript::LookupResult CompilationCacheScript::Lookup( MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
Handle<String> source, const ScriptDetails& script_details) { Handle<String> source, const ScriptDetails& script_details,
LookupResult result; LanguageMode language_mode) {
LookupResult::RawObjects raw_result; MaybeHandle<SharedFunctionInfo> result;
// Probe the script generation tables. Make sure not to leak handles // Probe the script generation tables. Make sure not to leak handles
// into the caller's handle scope. // into the caller's handle scope.
{ {
HandleScope scope(isolate()); HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable(); const int generation = 0;
LookupResult probe = DCHECK_EQ(generations(), 1);
CompilationCacheTable::LookupScript(table, source, isolate()); Handle<CompilationCacheTable> table = GetTable(generation);
Handle<Script> script; MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
if (probe.script().ToHandle(&script)) { table, source, language_mode, isolate());
// Break when we've found a suitable script that matches the origin. Handle<SharedFunctionInfo> function_info;
if (HasOrigin(isolate(), script, script_details)) { if (probe.ToHandle(&function_info)) {
raw_result = probe.GetRawObjects(); // 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);
} }
} }
} }
result = LookupResult::FromRawObjects(raw_result, isolate());
// Once outside the manacles of the handle scope, we need to recheck // 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 // to see if we actually found a cached script. If so, we return a
// handle created in the caller's handle scope. // handle created in the caller's handle scope.
Handle<Script> script; Handle<SharedFunctionInfo> function_info;
if (result.script().ToHandle(&script)) { if (result.ToHandle(&function_info)) {
// Since HasOrigin can allocate, we need to protect the lookup result with // Since HasOrigin can allocate, we need to protect the SharedFunctionInfo
// handles during the call. // with handles during the call.
DCHECK(HasOrigin(isolate(), script, script_details)); DCHECK(HasOrigin(isolate(), function_info, script_details));
Handle<SharedFunctionInfo> sfi; isolate()->counters()->compilation_cache_hits()->Increment();
if (result.toplevel_sfi().ToHandle(&sfi)) { LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info));
isolate()->counters()->compilation_cache_hits()->Increment();
LOG(isolate(), CompilationCacheEvent("hit", "script", *sfi));
} else {
isolate()->counters()->compilation_cache_partial_hits()->Increment();
}
} else { } else {
isolate()->counters()->compilation_cache_misses()->Increment(); isolate()->counters()->compilation_cache_misses()->Increment();
} }
...@@ -251,11 +206,12 @@ CompilationCacheScript::LookupResult CompilationCacheScript::Lookup( ...@@ -251,11 +206,12 @@ CompilationCacheScript::LookupResult CompilationCacheScript::Lookup(
} }
void CompilationCacheScript::Put(Handle<String> source, void CompilationCacheScript::Put(Handle<String> source,
LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info) { Handle<SharedFunctionInfo> function_info) {
HandleScope scope(isolate()); HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable(); Handle<CompilationCacheTable> table = GetFirstTable();
table_ = *CompilationCacheTable::PutScript(table, source, function_info, SetFirstTable(CompilationCacheTable::PutScript(table, source, language_mode,
isolate()); function_info, isolate()));
} }
InfoCellPair CompilationCacheEval::Lookup(Handle<String> source, InfoCellPair CompilationCacheEval::Lookup(Handle<String> source,
...@@ -268,7 +224,9 @@ InfoCellPair CompilationCacheEval::Lookup(Handle<String> source, ...@@ -268,7 +224,9 @@ InfoCellPair CompilationCacheEval::Lookup(Handle<String> source,
// scope. Otherwise, we risk keeping old tables around even after // scope. Otherwise, we risk keeping old tables around even after
// having cleared the cache. // having cleared the cache.
InfoCellPair result; InfoCellPair result;
Handle<CompilationCacheTable> table = GetTable(); const int generation = 0;
DCHECK_EQ(generations(), 1);
Handle<CompilationCacheTable> table = GetTable(generation);
result = CompilationCacheTable::LookupEval( result = CompilationCacheTable::LookupEval(
table, source, outer_info, native_context, language_mode, position); table, source, outer_info, native_context, language_mode, position);
if (result.has_shared()) { if (result.has_shared()) {
...@@ -286,10 +244,11 @@ void CompilationCacheEval::Put(Handle<String> source, ...@@ -286,10 +244,11 @@ void CompilationCacheEval::Put(Handle<String> source,
Handle<FeedbackCell> feedback_cell, Handle<FeedbackCell> feedback_cell,
int position) { int position) {
HandleScope scope(isolate()); HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable(); Handle<CompilationCacheTable> table = GetFirstTable();
table_ = table =
*CompilationCacheTable::PutEval(table, source, outer_info, function_info, CompilationCacheTable::PutEval(table, source, outer_info, function_info,
native_context, feedback_cell, position); native_context, feedback_cell, position);
SetFirstTable(table);
} }
MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source, MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
...@@ -300,7 +259,7 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source, ...@@ -300,7 +259,7 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
// having cleared the cache. // having cleared the cache.
Handle<Object> result = isolate()->factory()->undefined_value(); Handle<Object> result = isolate()->factory()->undefined_value();
int generation; int generation;
for (generation = 0; generation < kGenerations; generation++) { for (generation = 0; generation < generations(); generation++) {
Handle<CompilationCacheTable> table = GetTable(generation); Handle<CompilationCacheTable> table = GetTable(generation);
result = table->LookupRegExp(source, flags); result = table->LookupRegExp(source, flags);
if (result->IsFixedArray()) break; if (result->IsFixedArray()) break;
...@@ -321,9 +280,9 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source, ...@@ -321,9 +280,9 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags, void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags,
Handle<FixedArray> data) { Handle<FixedArray> data) {
HandleScope scope(isolate()); HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable(0); Handle<CompilationCacheTable> table = GetFirstTable();
tables_[0] = SetFirstTable(
*CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data); CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data));
} }
void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) { void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
...@@ -334,11 +293,11 @@ void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) { ...@@ -334,11 +293,11 @@ void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
script_.Remove(function_info); script_.Remove(function_info);
} }
CompilationCacheScript::LookupResult CompilationCache::LookupScript( MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
Handle<String> source, const ScriptDetails& script_details, Handle<String> source, const ScriptDetails& script_details,
LanguageMode language_mode) { LanguageMode language_mode) {
if (!IsEnabledScript(language_mode)) return {}; if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>();
return script_.Lookup(source, script_details); return script_.Lookup(source, script_details, language_mode);
} }
InfoCellPair CompilationCache::LookupEval(Handle<String> source, InfoCellPair CompilationCache::LookupEval(Handle<String> source,
...@@ -379,10 +338,10 @@ MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source, ...@@ -379,10 +338,10 @@ MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
void CompilationCache::PutScript(Handle<String> source, void CompilationCache::PutScript(Handle<String> source,
LanguageMode language_mode, LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info) { Handle<SharedFunctionInfo> function_info) {
if (!IsEnabledScript(language_mode)) return; if (!IsEnabledScriptAndEval()) return;
LOG(isolate(), CompilationCacheEvent("put", "script", *function_info)); LOG(isolate(), CompilationCacheEvent("put", "script", *function_info));
script_.Put(source, function_info); script_.Put(source, language_mode, function_info);
} }
void CompilationCache::PutEval(Handle<String> source, void CompilationCache::PutEval(Handle<String> source,
...@@ -415,24 +374,21 @@ void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags, ...@@ -415,24 +374,21 @@ void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
} }
void CompilationCache::Clear() { void CompilationCache::Clear() {
script_.Clear(); for (int i = 0; i < kSubCacheCount; i++) {
eval_global_.Clear(); subcaches_[i]->Clear();
eval_contextual_.Clear(); }
reg_exp_.Clear();
} }
void CompilationCache::Iterate(RootVisitor* v) { void CompilationCache::Iterate(RootVisitor* v) {
script_.Iterate(v); for (int i = 0; i < kSubCacheCount; i++) {
eval_global_.Iterate(v); subcaches_[i]->Iterate(v);
eval_contextual_.Iterate(v); }
reg_exp_.Iterate(v);
} }
void CompilationCache::MarkCompactPrologue() { void CompilationCache::MarkCompactPrologue() {
script_.Age(); for (int i = 0; i < kSubCacheCount; i++) {
eval_global_.Age(); subcaches_[i]->Age();
eval_contextual_.Age(); }
reg_exp_.Age();
} }
void CompilationCache::EnableScriptAndEval() { void CompilationCache::EnableScriptAndEval() {
......
...@@ -18,49 +18,78 @@ class Handle; ...@@ -18,49 +18,78 @@ class Handle;
class RootVisitor; class RootVisitor;
struct ScriptDetails; struct ScriptDetails;
// The compilation cache consists of several sub-caches: one each for evals and // The compilation cache consists of several generational sub-caches which uses
// scripts, which use this class as a base class, and a separate generational // this class as a base class. A sub-cache contains a compilation cache tables
// sub-cache for RegExps. Since the same source code string has different // for each generation of the sub-cache. Since the same source code string has
// compiled code for scripts and evals, we use separate sub-caches for different // different compiled code for scripts and evals, we use separate sub-caches
// compilation modes, to avoid retrieving the wrong result. // for different compilation modes, to avoid retrieving the wrong result.
class CompilationCacheEvalOrScript { class CompilationSubCache {
public: public:
explicit CompilationCacheEvalOrScript(Isolate* isolate) : isolate_(isolate) {} CompilationSubCache(Isolate* isolate, int generations)
: isolate_(isolate), generations_(generations) {
DCHECK_LE(generations, kMaxGenerations);
}
static constexpr int kFirstGeneration = 0;
static constexpr int kMaxGenerations = 2;
// Allocates the table if it didn't yet exist. // Get the compilation cache tables for a specific generation.
Handle<CompilationCacheTable> GetTable(); Handle<CompilationCacheTable> GetTable(int generation);
// Accessors for first generation.
Handle<CompilationCacheTable> GetFirstTable() {
return GetTable(kFirstGeneration);
}
void SetFirstTable(Handle<CompilationCacheTable> value) {
DCHECK_LT(kFirstGeneration, generations_);
tables_[kFirstGeneration] = *value;
}
// Age the sub-cache by evicting the oldest generation and creating a new
// young generation.
virtual void Age() = 0;
// GC support. // GC support.
void Iterate(RootVisitor* v); void Iterate(RootVisitor* v);
// Clears this sub-cache evicting all its content. // Clear this sub-cache evicting all its content.
void Clear(); void Clear();
// Removes given shared function info from sub-cache. // Remove given shared function info from sub-cache.
void Remove(Handle<SharedFunctionInfo> function_info); void Remove(Handle<SharedFunctionInfo> function_info);
// Number of generations in this sub-cache.
int generations() const { return generations_; }
protected: protected:
Isolate* isolate() const { return isolate_; } Isolate* isolate() const { return isolate_; }
// Ageing occurs either by removing the oldest generation, or with
// custom logic implemented in CompilationCacheTable::Age.
static void AgeByGeneration(CompilationSubCache* c);
static void AgeCustom(CompilationSubCache* c);
private:
Isolate* const isolate_; Isolate* const isolate_;
Object table_; const int generations_;
Object tables_[kMaxGenerations]; // One for each generation.
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEvalOrScript); DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationSubCache);
}; };
// Sub-cache for scripts. // Sub-cache for scripts.
class CompilationCacheScript : public CompilationCacheEvalOrScript { class CompilationCacheScript : public CompilationSubCache {
public: public:
explicit CompilationCacheScript(Isolate* isolate) explicit CompilationCacheScript(Isolate* isolate);
: CompilationCacheEvalOrScript(isolate) {}
using LookupResult = CompilationCacheScriptLookupResult; MaybeHandle<SharedFunctionInfo> Lookup(Handle<String> source,
LookupResult Lookup(Handle<String> source, const ScriptDetails& script_details,
const ScriptDetails& script_details); LanguageMode language_mode);
void Put(Handle<String> source, Handle<SharedFunctionInfo> function_info); void Put(Handle<String> source, LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info);
void Age(); void Age() override;
private: private:
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript); DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript);
...@@ -78,10 +107,10 @@ class CompilationCacheScript : public CompilationCacheEvalOrScript { ...@@ -78,10 +107,10 @@ class CompilationCacheScript : public CompilationCacheEvalOrScript {
// More specifically these are the CompileString, DebugEvaluate and // More specifically these are the CompileString, DebugEvaluate and
// DebugEvaluateGlobal runtime functions. // DebugEvaluateGlobal runtime functions.
// 4. The start position of the calling scope. // 4. The start position of the calling scope.
class CompilationCacheEval : public CompilationCacheEvalOrScript { class CompilationCacheEval : public CompilationSubCache {
public: public:
explicit CompilationCacheEval(Isolate* isolate) explicit CompilationCacheEval(Isolate* isolate)
: CompilationCacheEvalOrScript(isolate) {} : CompilationSubCache(isolate, 1) {}
InfoCellPair Lookup(Handle<String> source, InfoCellPair Lookup(Handle<String> source,
Handle<SharedFunctionInfo> outer_info, Handle<SharedFunctionInfo> outer_info,
...@@ -93,45 +122,26 @@ class CompilationCacheEval : public CompilationCacheEvalOrScript { ...@@ -93,45 +122,26 @@ class CompilationCacheEval : public CompilationCacheEvalOrScript {
Handle<Context> native_context, Handle<FeedbackCell> feedback_cell, Handle<Context> native_context, Handle<FeedbackCell> feedback_cell,
int position); int position);
void Age(); void Age() override;
private: private:
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEval); DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEval);
}; };
// Sub-cache for regular expressions. // Sub-cache for regular expressions.
class CompilationCacheRegExp { class CompilationCacheRegExp : public CompilationSubCache {
public: public:
CompilationCacheRegExp(Isolate* isolate) : isolate_(isolate) {} CompilationCacheRegExp(Isolate* isolate, int generations)
: CompilationSubCache(isolate, generations) {}
MaybeHandle<FixedArray> Lookup(Handle<String> source, JSRegExp::Flags flags); MaybeHandle<FixedArray> Lookup(Handle<String> source, JSRegExp::Flags flags);
void Put(Handle<String> source, JSRegExp::Flags flags, void Put(Handle<String> source, JSRegExp::Flags flags,
Handle<FixedArray> data); Handle<FixedArray> data);
// The number of generations for the RegExp sub cache. void Age() override;
static const int kGenerations = 2;
// Gets the compilation cache tables for a specific generation. Allocates the
// table if it does not yet exist.
Handle<CompilationCacheTable> GetTable(int generation);
// Ages the sub-cache by evicting the oldest generation and creating a new
// young generation.
void Age();
// GC support.
void Iterate(RootVisitor* v);
// Clears this sub-cache evicting all its content.
void Clear();
private: private:
Isolate* isolate() const { return isolate_; }
Isolate* const isolate_;
Object tables_[kGenerations]; // One for each generation.
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheRegExp); DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheRegExp);
}; };
...@@ -144,10 +154,10 @@ class V8_EXPORT_PRIVATE CompilationCache { ...@@ -144,10 +154,10 @@ class V8_EXPORT_PRIVATE CompilationCache {
CompilationCache(const CompilationCache&) = delete; CompilationCache(const CompilationCache&) = delete;
CompilationCache& operator=(const CompilationCache&) = delete; CompilationCache& operator=(const CompilationCache&) = delete;
// Finds the Script and root SharedFunctionInfo for a script source string. // Finds the script shared function info for a source
// Returns empty handles if the cache doesn't contain a script for the given // string. Returns an empty handle if the cache doesn't contain a
// source string with the right origin. // script for the given source string with the right origin.
CompilationCacheScript::LookupResult LookupScript( MaybeHandle<SharedFunctionInfo> LookupScript(
Handle<String> source, const ScriptDetails& script_details, Handle<String> source, const ScriptDetails& script_details,
LanguageMode language_mode); LanguageMode language_mode);
...@@ -164,7 +174,7 @@ class V8_EXPORT_PRIVATE CompilationCache { ...@@ -164,7 +174,7 @@ class V8_EXPORT_PRIVATE CompilationCache {
MaybeHandle<FixedArray> LookupRegExp(Handle<String> source, MaybeHandle<FixedArray> LookupRegExp(Handle<String> source,
JSRegExp::Flags flags); JSRegExp::Flags flags);
// Associate the source string to the shared function // Associate the (source, kind) pair to the shared function
// info. This may overwrite an existing mapping. // info. This may overwrite an existing mapping.
void PutScript(Handle<String> source, LanguageMode language_mode, void PutScript(Handle<String> source, LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info); Handle<SharedFunctionInfo> function_info);
...@@ -214,11 +224,6 @@ class V8_EXPORT_PRIVATE CompilationCache { ...@@ -214,11 +224,6 @@ class V8_EXPORT_PRIVATE CompilationCache {
bool IsEnabledScriptAndEval() const { bool IsEnabledScriptAndEval() const {
return FLAG_compilation_cache && enabled_script_and_eval_; 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_; } Isolate* isolate() const { return isolate_; }
...@@ -229,6 +234,9 @@ class V8_EXPORT_PRIVATE CompilationCache { ...@@ -229,6 +234,9 @@ class V8_EXPORT_PRIVATE CompilationCache {
CompilationCacheEval eval_contextual_; CompilationCacheEval eval_contextual_;
CompilationCacheRegExp reg_exp_; CompilationCacheRegExp reg_exp_;
static constexpr int kSubCacheCount = 4;
CompilationSubCache* subcaches_[kSubCacheCount];
// Current enable state of the compilation cache for scripts and eval. // Current enable state of the compilation cache for scripts and eval.
bool enabled_script_and_eval_; bool enabled_script_and_eval_;
......
...@@ -1430,19 +1430,6 @@ Handle<SharedFunctionInfo> CreateTopLevelSharedFunctionInfo( ...@@ -1430,19 +1430,6 @@ Handle<SharedFunctionInfo> CreateTopLevelSharedFunctionInfo(
parse_info->literal(), script, true); 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( MaybeHandle<SharedFunctionInfo> CompileToplevel(
ParseInfo* parse_info, Handle<Script> script, ParseInfo* parse_info, Handle<Script> script,
MaybeHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate, MaybeHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate,
...@@ -1476,7 +1463,7 @@ MaybeHandle<SharedFunctionInfo> CompileToplevel( ...@@ -1476,7 +1463,7 @@ MaybeHandle<SharedFunctionInfo> CompileToplevel(
// Create the SharedFunctionInfo and add it to the script's list. // Create the SharedFunctionInfo and add it to the script's list.
Handle<SharedFunctionInfo> shared_info = Handle<SharedFunctionInfo> shared_info =
GetOrCreateTopLevelSharedFunctionInfo(parse_info, script, isolate); CreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
FinalizeUnoptimizedCompilationDataList FinalizeUnoptimizedCompilationDataList
finalize_unoptimized_compilation_data_list; finalize_unoptimized_compilation_data_list;
...@@ -2819,22 +2806,17 @@ Handle<Script> NewScript( ...@@ -2819,22 +2806,17 @@ Handle<Script> NewScript(
} }
MaybeHandle<SharedFunctionInfo> CompileScriptOnMainThread( MaybeHandle<SharedFunctionInfo> CompileScriptOnMainThread(
UnoptimizedCompileFlags flags, Handle<String> source, const UnoptimizedCompileFlags flags, Handle<String> source,
const ScriptDetails& script_details, NativesFlag natives, const ScriptDetails& script_details, NativesFlag natives,
v8::Extension* extension, Isolate* isolate, v8::Extension* extension, Isolate* isolate,
MaybeHandle<Script> maybe_script, IsCompiledScope* is_compiled_scope) { IsCompiledScope* is_compiled_scope) {
Handle<Script> script;
if (maybe_script.ToHandle(&script)) {
flags.set_script_id(script->id());
}
UnoptimizedCompileState compile_state; UnoptimizedCompileState compile_state;
ReusableUnoptimizedCompileState reusable_state(isolate); ReusableUnoptimizedCompileState reusable_state(isolate);
ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state);
parse_info.set_extension(extension); parse_info.set_extension(extension);
if (script.is_null()) { Handle<Script> script =
script = NewScript(isolate, &parse_info, source, script_details, natives); NewScript(isolate, &parse_info, source, script_details, natives);
}
DCHECK_IMPLIES(parse_info.flags().collect_type_profile(), DCHECK_IMPLIES(parse_info.flags().collect_type_profile(),
script->IsUserJavaScript()); script->IsUserJavaScript());
DCHECK_EQ(parse_info.flags().is_repl_mode(), script->is_repl_mode()); DCHECK_EQ(parse_info.flags().is_repl_mode(), script->is_repl_mode());
...@@ -2914,10 +2896,8 @@ bool CompilationExceptionIsRangeError(Isolate* isolate, Handle<Object> obj) { ...@@ -2914,10 +2896,8 @@ bool CompilationExceptionIsRangeError(Isolate* isolate, Handle<Object> obj) {
MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread( MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
Handle<String> source, const ScriptDetails& script_details, Handle<String> source, const ScriptDetails& script_details,
MaybeHandle<Script> maybe_script, Isolate* isolate, Isolate* isolate, IsCompiledScope* is_compiled_scope) {
IsCompiledScope* is_compiled_scope) {
// Start a background thread compiling the script. // Start a background thread compiling the script.
// TODO(v8:12808): Use maybe_script for the background compilation.
StressBackgroundCompileThread background_compile_thread( StressBackgroundCompileThread background_compile_thread(
isolate, source, isolate, source,
script_details.origin_options.IsModule() ? ScriptType::kModule script_details.origin_options.IsModule() ? ScriptType::kModule
...@@ -2940,7 +2920,7 @@ MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread( ...@@ -2940,7 +2920,7 @@ MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
flags_copy.set_script_id(Script::kTemporaryScriptId); flags_copy.set_script_id(Script::kTemporaryScriptId);
main_thread_maybe_result = CompileScriptOnMainThread( main_thread_maybe_result = CompileScriptOnMainThread(
flags_copy, source, script_details, NOT_NATIVES_CODE, nullptr, isolate, flags_copy, source, script_details, NOT_NATIVES_CODE, nullptr, isolate,
MaybeHandle<Script>(), &inner_is_compiled_scope); &inner_is_compiled_scope);
if (main_thread_maybe_result.is_null()) { if (main_thread_maybe_result.is_null()) {
// Assume all range errors are stack overflows. // Assume all range errors are stack overflows.
main_thread_had_stack_overflow = CompilationExceptionIsRangeError( main_thread_had_stack_overflow = CompilationExceptionIsRangeError(
...@@ -3031,7 +3011,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( ...@@ -3031,7 +3011,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
const bool use_compilation_cache = const bool use_compilation_cache =
extension == nullptr && script_details.repl_mode == REPLMode::kNo; extension == nullptr && script_details.repl_mode == REPLMode::kNo;
MaybeHandle<SharedFunctionInfo> maybe_result; MaybeHandle<SharedFunctionInfo> maybe_result;
MaybeHandle<Script> maybe_script;
IsCompiledScope is_compiled_scope; IsCompiledScope is_compiled_scope;
if (use_compilation_cache) { if (use_compilation_cache) {
bool can_consume_code_cache = bool can_consume_code_cache =
...@@ -3041,13 +3020,8 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( ...@@ -3041,13 +3020,8 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
} }
// First check per-isolate compilation cache. // First check per-isolate compilation cache.
CompilationCacheScript::LookupResult lookup_result = maybe_result =
compilation_cache->LookupScript(source, script_details, language_mode); compilation_cache->LookupScript(source, script_details, language_mode);
if (FLAG_isolate_script_cache_recompilation) {
maybe_script = lookup_result.script();
}
maybe_result = lookup_result.toplevel_sfi();
is_compiled_scope = lookup_result.is_compiled_scope();
if (!maybe_result.is_null()) { if (!maybe_result.is_null()) {
compile_timer.set_hit_isolate_cache(); compile_timer.set_hit_isolate_cache();
} else if (can_consume_code_cache) { } else if (can_consume_code_cache) {
...@@ -3058,10 +3032,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( ...@@ -3058,10 +3032,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize); RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompileDeserialize"); "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 (deserialize_task) {
// If there's a cache consume task, finish it. // If there's a cache consume task, finish it.
maybe_result = deserialize_task->Finish(isolate, source, maybe_result = deserialize_task->Finish(isolate, source,
...@@ -3096,7 +3066,7 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( ...@@ -3096,7 +3066,7 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
// If the --stress-background-compile flag is set, do the actual // If the --stress-background-compile flag is set, do the actual
// compilation on a background thread, and wait for its result. // compilation on a background thread, and wait for its result.
maybe_result = CompileScriptOnBothBackgroundAndMainThread( maybe_result = CompileScriptOnBothBackgroundAndMainThread(
source, script_details, maybe_script, isolate, &is_compiled_scope); source, script_details, isolate, &is_compiled_scope);
} else { } else {
UnoptimizedCompileFlags flags = UnoptimizedCompileFlags flags =
UnoptimizedCompileFlags::ForToplevelCompile( UnoptimizedCompileFlags::ForToplevelCompile(
...@@ -3108,9 +3078,9 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl( ...@@ -3108,9 +3078,9 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
flags.set_is_eager(compile_options == ScriptCompiler::kEagerCompile); flags.set_is_eager(compile_options == ScriptCompiler::kEagerCompile);
maybe_result = CompileScriptOnMainThread( maybe_result =
flags, source, script_details, natives, extension, isolate, CompileScriptOnMainThread(flags, source, script_details, natives,
maybe_script, &is_compiled_scope); extension, isolate, &is_compiled_scope);
} }
// Add the result to the isolate cache. // Add the result to the isolate cache.
...@@ -3283,18 +3253,8 @@ Compiler::GetSharedFunctionInfoForStreamedScript( ...@@ -3283,18 +3253,8 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
{ {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.StreamingFinalization.CheckCache"); "V8.StreamingFinalization.CheckCache");
CompilationCacheScript::LookupResult lookup_result = maybe_result = compilation_cache->LookupScript(
compilation_cache->LookupScript(source, script_details, source, script_details, task->flags().outer_language_mode());
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()) { if (!maybe_result.is_null()) {
compile_timer.set_hit_isolate_cache(); compile_timer.set_hit_isolate_cache();
} }
...@@ -3315,8 +3275,6 @@ Compiler::GetSharedFunctionInfoForStreamedScript( ...@@ -3315,8 +3275,6 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
// Add compiled code to the isolate cache. // Add compiled code to the isolate cache.
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.StreamingFinalization.AddToCache"); "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(), compilation_cache->PutScript(source, task->flags().outer_language_mode(),
result); result);
} }
......
...@@ -3887,12 +3887,6 @@ class BigIntPlatform : public bigint::Platform { ...@@ -3887,12 +3887,6 @@ class BigIntPlatform : public bigint::Platform {
private: private:
Isolate* isolate_; 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 } // namespace
VirtualMemoryCage* Isolate::GetPtrComprCodeCageForTesting() { VirtualMemoryCage* Isolate::GetPtrComprCodeCageForTesting() {
...@@ -4250,9 +4244,6 @@ bool Isolate::Init(SnapshotData* startup_snapshot_data, ...@@ -4250,9 +4244,6 @@ bool Isolate::Init(SnapshotData* startup_snapshot_data,
} }
#endif #endif
heap()->AddGCPrologueCallback(MarkCompactPrologue, kGCTypeMarkSweepCompact,
nullptr);
initialized_ = true; initialized_ = true;
return true; return true;
......
...@@ -2055,6 +2055,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -2055,6 +2055,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
static void SetIsolateThreadLocals(Isolate* isolate, static void SetIsolateThreadLocals(Isolate* isolate,
PerIsolateThreadData* data); PerIsolateThreadData* data);
void MarkCompactPrologue(bool is_compacting,
ThreadLocalTop* archived_thread_data);
void MarkCompactEpilogue(bool is_compacting,
ThreadLocalTop* archived_thread_data);
void FillCache(); void FillCache();
// Propagate pending exception message to the v8::TryCatch. // Propagate pending exception message to the v8::TryCatch.
......
...@@ -965,9 +965,6 @@ DEFINE_BOOL(turbo_collect_feedback_in_generic_lowering, true, ...@@ -965,9 +965,6 @@ DEFINE_BOOL(turbo_collect_feedback_in_generic_lowering, true,
"enable experimental feedback collection in generic lowering.") "enable experimental feedback collection in generic lowering.")
DEFINE_BOOL(isolate_script_cache_ageing, true, DEFINE_BOOL(isolate_script_cache_ageing, true,
"enable ageing of the isolate script cache.") "enable ageing of the isolate script cache.")
DEFINE_BOOL(isolate_script_cache_recompilation, false,
"enable recompiling an existing Script from the Isolate cache when "
"there is not a compiled top-level SharedFunctionInfo")
DEFINE_FLOAT(script_delay, 0, "busy wait [ms] on every Script::Run") DEFINE_FLOAT(script_delay, 0, "busy wait [ms] on every Script::Run")
DEFINE_FLOAT(script_delay_once, 0, "busy wait [ms] on the first Script::Run") DEFINE_FLOAT(script_delay_once, 0, "busy wait [ms] on the first Script::Run")
......
...@@ -2673,6 +2673,8 @@ void Heap::MarkCompactPrologue() { ...@@ -2673,6 +2673,8 @@ void Heap::MarkCompactPrologue() {
RegExpResultsCache::Clear(string_split_cache()); RegExpResultsCache::Clear(string_split_cache());
RegExpResultsCache::Clear(regexp_multiple_cache()); RegExpResultsCache::Clear(regexp_multiple_cache());
isolate_->compilation_cache()->MarkCompactPrologue();
FlushNumberStringCache(); FlushNumberStringCache();
} }
......
...@@ -281,16 +281,13 @@ namespace internal { ...@@ -281,16 +281,13 @@ namespace internal {
// lines) rather than one macro (of length about 80 lines) to work around // lines) rather than one macro (of length about 80 lines) to work around
// this problem. Please avoid using recursive macros of this length when // this problem. Please avoid using recursive macros of this length when
// possible. // possible.
#define STATS_COUNTER_LIST_1(SC) \ #define STATS_COUNTER_LIST_1(SC) \
/* Global Handle Count*/ \ /* Global Handle Count*/ \
SC(global_handles, V8.GlobalHandles) \ SC(global_handles, V8.GlobalHandles) \
SC(alive_after_last_gc, V8.AliveAfterLastGC) \ SC(alive_after_last_gc, V8.AliveAfterLastGC) \
SC(compilation_cache_hits, V8.CompilationCacheHits) \ SC(compilation_cache_hits, V8.CompilationCacheHits) \
SC(compilation_cache_misses, V8.CompilationCacheMisses) \ SC(compilation_cache_misses, V8.CompilationCacheMisses) \
/* Number of times the cache contained a reusable Script but not \ SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
the root SharedFunctionInfo */ \
SC(compilation_cache_partial_hits, V8.CompilationCachePartialHits) \
SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
SC(objs_since_last_full, V8.ObjsSinceLastFull) SC(objs_since_last_full, V8.ObjsSinceLastFull)
#define STATS_COUNTER_LIST_2(SC) \ #define STATS_COUNTER_LIST_2(SC) \
......
...@@ -26,77 +26,14 @@ CompilationCacheTable::CompilationCacheTable(Address ptr) ...@@ -26,77 +26,14 @@ CompilationCacheTable::CompilationCacheTable(Address ptr)
NEVER_READ_ONLY_SPACE_IMPL(CompilationCacheTable) NEVER_READ_ONLY_SPACE_IMPL(CompilationCacheTable)
CAST_ACCESSOR(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 {
kHash,
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) { uint32_t CompilationCacheShape::RegExpHash(String string, Smi flags) {
return string.EnsureHash() + flags.value(); return string.EnsureHash() + flags.value();
} }
uint32_t CompilationCacheShape::EvalHash(String source, uint32_t CompilationCacheShape::StringSharedHash(String source,
SharedFunctionInfo shared, SharedFunctionInfo shared,
LanguageMode language_mode, LanguageMode language_mode,
int position) { int position) {
uint32_t hash = source.EnsureHash(); uint32_t hash = source.EnsureHash();
if (shared.HasSourceCode()) { if (shared.HasSourceCode()) {
// Instead of using the SharedFunctionInfo pointer in the hash // Instead of using the SharedFunctionInfo pointer in the hash
...@@ -113,6 +50,14 @@ uint32_t CompilationCacheShape::EvalHash(String source, ...@@ -113,6 +50,14 @@ uint32_t CompilationCacheShape::EvalHash(String source,
return hash; return hash;
} }
uint32_t CompilationCacheShape::StringSharedHash(String source,
LanguageMode language_mode) {
uint32_t hash = source.EnsureHash();
static_assert(LanguageModeSize == 2);
if (is_strict(language_mode)) hash ^= 0x8000;
return hash;
}
uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots, uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
Object object) { Object object) {
// Eval: The key field contains the hash as a Number. // Eval: The key field contains the hash as a Number.
...@@ -123,24 +68,7 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots, ...@@ -123,24 +68,7 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
return SharedFunctionInfo::cast(object).Hash(); return SharedFunctionInfo::cast(object).Hash();
} }
// Script. // Script: See StringSharedKey::ToHandle for the encoding.
if (object.IsWeakFixedArray()) {
uint32_t result = static_cast<uint32_t>(Smi::ToInt(
WeakFixedArray::cast(object).Get(ScriptCacheKey::kHash).ToSmi()));
#ifdef DEBUG
base::Optional<String> script_key =
ScriptCacheKey::SourceFromObject(object);
if (script_key) {
uint32_t source_hash;
if (script_key->TryGetHash(&source_hash)) {
DCHECK_EQ(result, source_hash);
}
}
#endif
return result;
}
// Eval: See EvalCacheKey::ToHandle for the encoding.
FixedArray val = FixedArray::cast(object); FixedArray val = FixedArray::cast(object);
if (val.map() == roots.fixed_cow_array_map()) { if (val.map() == roots.fixed_cow_array_map()) {
DCHECK_EQ(4, val.length()); DCHECK_EQ(4, val.length());
...@@ -149,10 +77,14 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots, ...@@ -149,10 +77,14 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
DCHECK(is_valid_language_mode(language_unchecked)); DCHECK(is_valid_language_mode(language_unchecked));
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked); LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
int position = Smi::ToInt(val.get(3)); int position = Smi::ToInt(val.get(3));
Object shared = val.get(0); Object shared_or_smi = val.get(0);
DCHECK(shared.IsSharedFunctionInfo()); if (shared_or_smi.IsSmi()) {
return EvalHash(source, SharedFunctionInfo::cast(shared), language_mode, DCHECK_EQ(position, kNoSourcePosition);
position); return StringSharedHash(source, language_mode);
} else {
return StringSharedHash(source, SharedFunctionInfo::cast(shared_or_smi),
language_mode, position);
}
} }
// RegExp: The key field (and the value field) contains the // RegExp: The key field (and the value field) contains the
......
...@@ -17,11 +17,14 @@ const int kLiteralInitialLength = 2; ...@@ -17,11 +17,14 @@ const int kLiteralInitialLength = 2;
const int kLiteralContextOffset = 0; const int kLiteralContextOffset = 0;
const int kLiteralLiteralsOffset = 1; const int kLiteralLiteralsOffset = 1;
int SearchLiteralsMapEntry(CompilationCacheTable cache, // The initial placeholder insertion of the eval cache survives this many GCs.
InternalIndex cache_entry, Context native_context) { const int kHashGenerations = 10;
int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry,
Context native_context) {
DisallowGarbageCollection no_gc; DisallowGarbageCollection no_gc;
DCHECK(native_context.IsNativeContext()); DCHECK(native_context.IsNativeContext());
Object obj = cache.EvalFeedbackValueAt(cache_entry); Object obj = cache.get(cache_entry);
// Check that there's no confusion between FixedArray and WeakFixedArray (the // Check that there's no confusion between FixedArray and WeakFixedArray (the
// object used to be a FixedArray here). // object used to be a FixedArray here).
...@@ -40,8 +43,7 @@ int SearchLiteralsMapEntry(CompilationCacheTable cache, ...@@ -40,8 +43,7 @@ int SearchLiteralsMapEntry(CompilationCacheTable cache,
return -1; return -1;
} }
void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
InternalIndex cache_entry,
Handle<Context> native_context, Handle<Context> native_context,
Handle<FeedbackCell> feedback_cell) { Handle<FeedbackCell> feedback_cell) {
Isolate* isolate = native_context->GetIsolate(); Isolate* isolate = native_context->GetIsolate();
...@@ -50,7 +52,7 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, ...@@ -50,7 +52,7 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
Handle<WeakFixedArray> new_literals_map; Handle<WeakFixedArray> new_literals_map;
int entry; int entry;
Object obj = cache->EvalFeedbackValueAt(cache_entry); Object obj = cache->get(cache_entry);
// Check that there's no confusion between FixedArray and WeakFixedArray (the // Check that there's no confusion between FixedArray and WeakFixedArray (the
// object used to be a FixedArray here). // object used to be a FixedArray here).
...@@ -104,20 +106,18 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, ...@@ -104,20 +106,18 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
} }
#endif #endif
Object old_literals_map = cache->EvalFeedbackValueAt(cache_entry); Object old_literals_map = cache->get(cache_entry);
if (old_literals_map != *new_literals_map) { if (old_literals_map != *new_literals_map) {
cache->SetEvalFeedbackValueAt(cache_entry, *new_literals_map); cache->set(cache_entry, *new_literals_map);
} }
} }
FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry,
InternalIndex cache_entry,
Context native_context) { Context native_context) {
FeedbackCell result; FeedbackCell result;
int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context); int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context);
if (entry >= 0) { if (entry >= 0) {
WeakFixedArray literals_map = WeakFixedArray literals_map = WeakFixedArray::cast(cache.get(cache_entry));
WeakFixedArray::cast(cache.EvalFeedbackValueAt(cache_entry));
DCHECK_LE(entry + kLiteralEntryLength, literals_map.length()); DCHECK_LE(entry + kLiteralEntryLength, literals_map.length());
MaybeObject object = literals_map.Get(entry + kLiteralLiteralsOffset); MaybeObject object = literals_map.Get(entry + kLiteralLiteralsOffset);
...@@ -129,8 +129,8 @@ FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, ...@@ -129,8 +129,8 @@ FeedbackCell SearchLiteralsMap(CompilationCacheTable cache,
return result; return result;
} }
// EvalCacheKeys are used as keys in the eval cache. // StringSharedKeys are used as keys in the eval cache.
class EvalCacheKey : public HashTableKey { class StringSharedKey : public HashTableKey {
public: public:
// This tuple unambiguously identifies calls to eval() or // This tuple unambiguously identifies calls to eval() or
// CreateDynamicFunction() (such as through the Function() constructor). // CreateDynamicFunction() (such as through the Function() constructor).
...@@ -142,15 +142,23 @@ class EvalCacheKey : public HashTableKey { ...@@ -142,15 +142,23 @@ class EvalCacheKey : public HashTableKey {
// * When positive, position is the position in the source where eval is // * When positive, position is the position in the source where eval is
// called. When negative, position is the negation of the position in the // called. When negative, position is the negation of the position in the
// dynamic function's effective source where the ')' ends the parameters. // dynamic function's effective source where the ')' ends the parameters.
EvalCacheKey(Handle<String> source, Handle<SharedFunctionInfo> shared, StringSharedKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
LanguageMode language_mode, int position) LanguageMode language_mode, int position)
: HashTableKey(CompilationCacheShape::EvalHash(*source, *shared, : HashTableKey(CompilationCacheShape::StringSharedHash(
language_mode, position)), *source, *shared, language_mode, position)),
source_(source), source_(source),
shared_(shared), shared_(shared),
language_mode_(language_mode), language_mode_(language_mode),
position_(position) {} 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 { bool IsMatch(Object other) override {
DisallowGarbageCollection no_gc; DisallowGarbageCollection no_gc;
if (!other.IsFixedArray()) { if (!other.IsFixedArray()) {
...@@ -159,8 +167,14 @@ class EvalCacheKey : public HashTableKey { ...@@ -159,8 +167,14 @@ class EvalCacheKey : public HashTableKey {
return Hash() == other_hash; return Hash() == other_hash;
} }
FixedArray other_array = FixedArray::cast(other); FixedArray other_array = FixedArray::cast(other);
DCHECK(other_array.get(0).IsSharedFunctionInfo()); DCHECK(other_array.get(0).IsSharedFunctionInfo() ||
if (*shared_ != other_array.get(0)) return false; 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;
}
int language_unchecked = Smi::ToInt(other_array.get(2)); int language_unchecked = Smi::ToInt(other_array.get(2));
DCHECK(is_valid_language_mode(language_unchecked)); DCHECK(is_valid_language_mode(language_unchecked));
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked); LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
...@@ -173,7 +187,12 @@ class EvalCacheKey : public HashTableKey { ...@@ -173,7 +187,12 @@ class EvalCacheKey : public HashTableKey {
Handle<Object> AsHandle(Isolate* isolate) { Handle<Object> AsHandle(Isolate* isolate) {
Handle<FixedArray> array = isolate->factory()->NewFixedArray(4); Handle<FixedArray> array = isolate->factory()->NewFixedArray(4);
array->set(0, *shared_); Handle<SharedFunctionInfo> shared;
if (shared_.ToHandle(&shared)) {
array->set(0, *shared);
} else {
array->set(0, Smi::zero());
}
array->set(1, *source_); array->set(1, *source_);
array->set(2, Smi::FromEnum(language_mode_)); array->set(2, Smi::FromEnum(language_mode_));
array->set(3, Smi::FromInt(position_)); array->set(3, Smi::FromInt(position_));
...@@ -183,7 +202,7 @@ class EvalCacheKey : public HashTableKey { ...@@ -183,7 +202,7 @@ class EvalCacheKey : public HashTableKey {
private: private:
Handle<String> source_; Handle<String> source_;
Handle<SharedFunctionInfo> shared_; MaybeHandle<SharedFunctionInfo> shared_;
LanguageMode language_mode_; LanguageMode language_mode_;
int position_; int position_;
}; };
...@@ -225,103 +244,22 @@ class CodeKey : public HashTableKey { ...@@ -225,103 +244,22 @@ class CodeKey : public HashTableKey {
} // namespace } // namespace
ScriptCacheKey::ScriptCacheKey(Handle<String> source) MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript(
: HashTableKey(source->EnsureHash()), source_(source) { Handle<CompilationCacheTable> table, Handle<String> src,
// Hash values must fit within a Smi. LanguageMode language_mode, Isolate* isolate) {
static_assert(Name::HashBits::kSize <= kSmiValueSize);
DCHECK_EQ(
static_cast<uint32_t>(Smi::ToInt(Smi::FromInt(static_cast<int>(Hash())))),
Hash());
}
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(kHash,
MaybeObject::FromObject(Smi::FromInt(static_cast<int>(Hash()))));
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); src = String::Flatten(isolate, src);
ScriptCacheKey key(src); StringSharedKey key(src, language_mode);
InternalIndex entry = table->FindEntry(isolate, &key); InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return MaybeHandle<SharedFunctionInfo>();
if (entry.is_not_found()) return {}; int index = EntryToIndex(entry);
if (!table->get(index).IsFixedArray()) {
DisallowGarbageCollection no_gc; return MaybeHandle<SharedFunctionInfo>();
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 if (FLAG_isolate_script_cache_recompilation) {
// 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);
return CompilationCacheScriptLookupResult::FromRawObjects( if (obj.IsSharedFunctionInfo()) {
std::make_pair(script, toplevel_sfi), isolate); return handle(SharedFunctionInfo::cast(obj), isolate);
}
return MaybeHandle<SharedFunctionInfo>();
} }
InfoCellPair CompilationCacheTable::LookupEval( InfoCellPair CompilationCacheTable::LookupEval(
...@@ -332,17 +270,18 @@ InfoCellPair CompilationCacheTable::LookupEval( ...@@ -332,17 +270,18 @@ InfoCellPair CompilationCacheTable::LookupEval(
Isolate* isolate = native_context->GetIsolate(); Isolate* isolate = native_context->GetIsolate();
src = String::Flatten(isolate, src); src = String::Flatten(isolate, src);
EvalCacheKey key(src, outer_info, language_mode, position); StringSharedKey key(src, outer_info, language_mode, position);
InternalIndex entry = table->FindEntry(isolate, &key); InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return empty_result; if (entry.is_not_found()) return empty_result;
if (!table->KeyAt(entry).IsFixedArray()) return empty_result; int index = EntryToIndex(entry);
Object obj = table->PrimaryValueAt(entry); if (!table->get(index).IsFixedArray()) return empty_result;
Object obj = table->get(index + 1);
if (!obj.IsSharedFunctionInfo()) return empty_result; if (!obj.IsSharedFunctionInfo()) return empty_result;
static_assert(CompilationCacheShape::kEntrySize == 3); static_assert(CompilationCacheShape::kEntrySize == 3);
FeedbackCell feedback_cell = FeedbackCell feedback_cell =
SearchLiteralsMap(*table, entry, *native_context); SearchLiteralsMap(*table, index + 2, *native_context);
return InfoCellPair(isolate, SharedFunctionInfo::cast(obj), feedback_cell); return InfoCellPair(isolate, SharedFunctionInfo::cast(obj), feedback_cell);
} }
...@@ -353,57 +292,21 @@ Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src, ...@@ -353,57 +292,21 @@ Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src,
RegExpKey key(src, flags); RegExpKey key(src, flags);
InternalIndex entry = FindEntry(isolate, &key); InternalIndex entry = FindEntry(isolate, &key);
if (entry.is_not_found()) return isolate->factory()->undefined_value(); if (entry.is_not_found()) return isolate->factory()->undefined_value();
return Handle<Object>(PrimaryValueAt(entry), isolate); return Handle<Object>(get(EntryToIndex(entry) + 1), 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> CompilationCacheTable::PutScript(
Handle<CompilationCacheTable> cache, Handle<String> src, Handle<CompilationCacheTable> cache, Handle<String> src,
Handle<SharedFunctionInfo> value, Isolate* isolate) { LanguageMode language_mode, Handle<SharedFunctionInfo> value,
Isolate* isolate) {
src = String::Flatten(isolate, src); src = String::Flatten(isolate, src);
ScriptCacheKey key(src); StringSharedKey key(src, language_mode);
Handle<Object> k = key.AsHandle(isolate, value); Handle<Object> k = key.AsHandle(isolate);
cache = EnsureCapacity(isolate, cache);
// Check whether there is already a matching entry. If so, we must overwrite InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
// it. This allows an entry whose value is undefined to upgrade to contain a cache->set(EntryToIndex(entry), *k);
// SharedFunctionInfo. cache->set(EntryToIndex(entry) + 1, *value);
InternalIndex entry = cache->FindEntry(isolate, &key); cache->ElementAdded();
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; return cache;
} }
...@@ -414,7 +317,7 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval( ...@@ -414,7 +317,7 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
int position) { int position) {
Isolate* isolate = native_context->GetIsolate(); Isolate* isolate = native_context->GetIsolate();
src = String::Flatten(isolate, src); src = String::Flatten(isolate, src);
EvalCacheKey key(src, outer_info, value->language_mode(), position); StringSharedKey key(src, outer_info, value->language_mode(), position);
// This block handles 'real' insertions, i.e. the initial dummy insert // This block handles 'real' insertions, i.e. the initial dummy insert
// (below) has already happened earlier. // (below) has already happened earlier.
...@@ -422,12 +325,14 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval( ...@@ -422,12 +325,14 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
Handle<Object> k = key.AsHandle(isolate); Handle<Object> k = key.AsHandle(isolate);
InternalIndex entry = cache->FindEntry(isolate, &key); InternalIndex entry = cache->FindEntry(isolate, &key);
if (entry.is_found()) { if (entry.is_found()) {
cache->SetKeyAt(entry, *k); cache->set(EntryToIndex(entry), *k);
cache->SetPrimaryValueAt(entry, *value); cache->set(EntryToIndex(entry) + 1, *value);
// AddToFeedbackCellsMap may allocate a new sub-array to live in the // AddToFeedbackCellsMap may allocate a new sub-array to live in the
// entry, but it won't change the cache array. Therefore EntryToIndex // entry, but it won't change the cache array. Therefore EntryToIndex
// and entry remains correct. // and entry remains correct.
AddToFeedbackCellsMap(cache, entry, native_context, feedback_cell); static_assert(CompilationCacheShape::kEntrySize == 3);
AddToFeedbackCellsMap(cache, EntryToIndex(entry) + 2, native_context,
feedback_cell);
// Add hash again even on cache hit to avoid unnecessary cache delay in // Add hash again even on cache hit to avoid unnecessary cache delay in
// case of hash collisions. // case of hash collisions.
} }
...@@ -438,8 +343,8 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval( ...@@ -438,8 +343,8 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash()); InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
Handle<Object> k = Handle<Object> k =
isolate->factory()->NewNumber(static_cast<double>(key.Hash())); isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
cache->SetKeyAt(entry, *k); cache->set(EntryToIndex(entry), *k);
cache->SetPrimaryValueAt(entry, Smi::FromInt(kHashGenerations)); cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
cache->ElementAdded(); cache->ElementAdded();
return cache; return cache;
} }
...@@ -452,32 +357,62 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp( ...@@ -452,32 +357,62 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash()); InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
// We store the value in the key slot, and compare the search key // We store the value in the key slot, and compare the search key
// to the stored value with a custom IsMatch function during lookups. // to the stored value with a custom IsMatch function during lookups.
cache->SetKeyAt(entry, *value); cache->set(EntryToIndex(entry), *value);
cache->SetPrimaryValueAt(entry, *value); cache->set(EntryToIndex(entry) + 1, *value);
cache->ElementAdded(); cache->ElementAdded();
return cache; 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) { void CompilationCacheTable::Remove(Object value) {
DisallowGarbageCollection no_gc; DisallowGarbageCollection no_gc;
for (InternalIndex entry : IterateEntries()) { for (InternalIndex entry : IterateEntries()) {
if (PrimaryValueAt(entry) == value) { int entry_index = EntryToIndex(entry);
RemoveEntry(entry); int value_index = entry_index + 1;
if (get(value_index) == value) {
RemoveEntry(entry_index);
} }
} }
} }
void CompilationCacheTable::RemoveEntry(InternalIndex entry) { void CompilationCacheTable::RemoveEntry(int entry_index) {
int entry_index = EntryToIndex(entry);
Object the_hole_value = GetReadOnlyRoots().the_hole_value(); Object the_hole_value = GetReadOnlyRoots().the_hole_value();
for (int i = 0; i < kEntrySize; i++) { for (int i = 0; i < kEntrySize; i++) {
NoWriteBarrierSet(*this, entry_index + i, the_hole_value); NoWriteBarrierSet(*this, entry_index + i, the_hole_value);
} }
ElementRemoved(); ElementRemoved();
// This table does not shrink upon deletion. The script cache depends on that
// fact, because EnsureScriptTableCapacity calls RemoveEntry at a time when
// shrinking the table would be counterproductive.
} }
} // namespace internal } // namespace internal
......
...@@ -29,8 +29,13 @@ class CompilationCacheShape : public BaseShape<HashTableKey*> { ...@@ -29,8 +29,13 @@ class CompilationCacheShape : public BaseShape<HashTableKey*> {
static inline uint32_t RegExpHash(String string, Smi flags); static inline uint32_t RegExpHash(String string, Smi flags);
static inline uint32_t EvalHash(String source, SharedFunctionInfo shared, static inline uint32_t StringSharedHash(String source,
LanguageMode language_mode, int position); SharedFunctionInfo shared,
LanguageMode language_mode,
int position);
static inline uint32_t StringSharedHash(String source,
LanguageMode language_mode);
static inline uint32_t HashForObject(ReadOnlyRoots roots, Object object); static inline uint32_t HashForObject(ReadOnlyRoots roots, Object object);
...@@ -74,34 +79,6 @@ class InfoCellPair { ...@@ -74,34 +79,6 @@ class InfoCellPair {
FeedbackCell feedback_cell_; 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) EXTERN_DECLARE_HASH_TABLE(CompilationCacheTable, CompilationCacheShape)
class CompilationCacheTable class CompilationCacheTable
...@@ -109,18 +86,14 @@ class CompilationCacheTable ...@@ -109,18 +86,14 @@ class CompilationCacheTable
public: public:
NEVER_READ_ONLY_SPACE NEVER_READ_ONLY_SPACE
// The 'script' cache contains SharedFunctionInfos. Once a root // The 'script' cache contains SharedFunctionInfos.
// SharedFunctionInfo has become old enough that its bytecode is flushed, the static MaybeHandle<SharedFunctionInfo> LookupScript(
// 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, Handle<CompilationCacheTable> table, Handle<String> src,
Isolate* isolate); LanguageMode language_mode, Isolate* isolate);
static Handle<CompilationCacheTable> PutScript( static Handle<CompilationCacheTable> PutScript(
Handle<CompilationCacheTable> cache, Handle<String> src, Handle<CompilationCacheTable> cache, Handle<String> src,
Handle<SharedFunctionInfo> value, Isolate* isolate); LanguageMode language_mode, Handle<SharedFunctionInfo> value,
Isolate* isolate);
// Eval code only gets cached after a second probe for the // Eval code only gets cached after a second probe for the
// code object. To do so, on first "put" only a hash identifying the // code object. To do so, on first "put" only a hash identifying the
...@@ -151,24 +124,12 @@ class CompilationCacheTable ...@@ -151,24 +124,12 @@ class CompilationCacheTable
JSRegExp::Flags flags, Handle<FixedArray> value); JSRegExp::Flags flags, Handle<FixedArray> value);
void Remove(Object value); void Remove(Object value);
void RemoveEntry(InternalIndex entry); void Age(Isolate* isolate);
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) DECL_CAST(CompilationCacheTable)
private: private:
static Handle<CompilationCacheTable> EnsureScriptTableCapacity( void RemoveEntry(int entry_index);
Isolate* isolate, Handle<CompilationCacheTable> cache);
OBJECT_CONSTRUCTORS(CompilationCacheTable, OBJECT_CONSTRUCTORS(CompilationCacheTable,
HashTable<CompilationCacheTable, CompilationCacheShape>); HashTable<CompilationCacheTable, CompilationCacheShape>);
......
...@@ -238,12 +238,6 @@ Object HashTable<Derived, Shape>::KeyAt(PtrComprCageBase cage_base, ...@@ -238,12 +238,6 @@ Object HashTable<Derived, Shape>::KeyAt(PtrComprCageBase cage_base,
return get(cage_base, EntryToIndex(entry) + kEntryKeyIndex, tag); 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> template <typename Derived, typename Shape>
void HashTable<Derived, Shape>::set_key(int index, Object value) { void HashTable<Derived, Shape>::set_key(int index, Object value) {
DCHECK(!IsEphemeronHashTable()); DCHECK(!IsEphemeronHashTable());
......
...@@ -165,9 +165,6 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) HashTable ...@@ -165,9 +165,6 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) HashTable
inline Object KeyAt(PtrComprCageBase cage_base, InternalIndex entry, inline Object KeyAt(PtrComprCageBase cage_base, InternalIndex entry,
RelaxedLoadTag tag); RelaxedLoadTag tag);
inline void SetKeyAt(InternalIndex entry, Object value,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
static const int kElementsStartIndex = kPrefixStartIndex + Shape::kPrefixSize; static const int kElementsStartIndex = kPrefixStartIndex + Shape::kPrefixSize;
static const int kEntrySize = Shape::kEntrySize; static const int kEntrySize = Shape::kEntrySize;
static_assert(kEntrySize > 0); static_assert(kEntrySize > 0);
......
...@@ -1469,7 +1469,7 @@ TEST(TestUseOfIncrementalBarrierOnCompileLazy) { ...@@ -1469,7 +1469,7 @@ TEST(TestUseOfIncrementalBarrierOnCompileLazy) {
CHECK(g_function->is_compiled()); CHECK(g_function->is_compiled());
} }
void CompilationCacheCachingBehavior(bool retain_script) { TEST(CompilationCacheCachingBehavior) {
// If we do not have the compilation cache turned off, this test is invalid. // If we do not have the compilation cache turned off, this test is invalid.
if (!FLAG_compilation_cache) { if (!FLAG_compilation_cache) {
return; return;
...@@ -1478,20 +1478,16 @@ void CompilationCacheCachingBehavior(bool retain_script) { ...@@ -1478,20 +1478,16 @@ void CompilationCacheCachingBehavior(bool retain_script) {
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
CompilationCache* compilation_cache = isolate->compilation_cache(); CompilationCache* compilation_cache = isolate->compilation_cache();
LanguageMode language_mode = LanguageMode::kSloppy; LanguageMode language_mode = construct_language_mode(FLAG_use_strict);
v8::HandleScope outer_scope(CcTest::isolate()); v8::HandleScope outer_scope(CcTest::isolate());
const char* raw_source = retain_script ? "function foo() {" const char* raw_source =
" var x = 42;" "function foo() {"
" var y = 42;" " var x = 42;"
" var z = x + y;" " var y = 42;"
"};" " var z = x + y;"
"foo();" "};"
: "(function foo() {" "foo();";
" var x = 42;"
" var y = 42;"
" var z = x + y;"
"})();";
Handle<String> source = factory->InternalizeUtf8String(raw_source); Handle<String> source = factory->InternalizeUtf8String(raw_source);
{ {
...@@ -1504,9 +1500,9 @@ void CompilationCacheCachingBehavior(bool retain_script) { ...@@ -1504,9 +1500,9 @@ void CompilationCacheCachingBehavior(bool retain_script) {
v8::HandleScope scope(CcTest::isolate()); v8::HandleScope scope(CcTest::isolate());
ScriptDetails script_details(Handle<Object>(), ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false)); v8::ScriptOriginOptions(true, false));
auto lookup_result = MaybeHandle<SharedFunctionInfo> cached_script =
compilation_cache->LookupScript(source, script_details, language_mode); compilation_cache->LookupScript(source, script_details, language_mode);
CHECK(!lookup_result.toplevel_sfi().is_null()); CHECK(!cached_script.is_null());
} }
// Check that the code cache entry survives at least one GC. // Check that the code cache entry survives at least one GC.
...@@ -1515,13 +1511,12 @@ void CompilationCacheCachingBehavior(bool retain_script) { ...@@ -1515,13 +1511,12 @@ void CompilationCacheCachingBehavior(bool retain_script) {
v8::HandleScope scope(CcTest::isolate()); v8::HandleScope scope(CcTest::isolate());
ScriptDetails script_details(Handle<Object>(), ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false)); v8::ScriptOriginOptions(true, false));
auto lookup_result = MaybeHandle<SharedFunctionInfo> cached_script =
compilation_cache->LookupScript(source, script_details, language_mode); compilation_cache->LookupScript(source, script_details, language_mode);
CHECK(!lookup_result.toplevel_sfi().is_null()); CHECK(!cached_script.is_null());
// Progress code age until it's old and ready for GC. // Progress code age until it's old and ready for GC.
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo> shared = cached_script.ToHandleChecked();
lookup_result.toplevel_sfi().ToHandleChecked();
CHECK(shared->HasBytecodeArray()); CHECK(shared->HasBytecodeArray());
const int kAgingThreshold = 6; const int kAgingThreshold = 6;
for (int i = 0; i < kAgingThreshold; i++) { for (int i = 0; i < kAgingThreshold; i++) {
...@@ -1536,254 +1531,10 @@ void CompilationCacheCachingBehavior(bool retain_script) { ...@@ -1536,254 +1531,10 @@ void CompilationCacheCachingBehavior(bool retain_script) {
// Ensure code aging cleared the entry from the cache. // Ensure code aging cleared the entry from the cache.
ScriptDetails script_details(Handle<Object>(), ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false)); v8::ScriptOriginOptions(true, false));
auto lookup_result = MaybeHandle<SharedFunctionInfo> cached_script =
compilation_cache->LookupScript(source, script_details, language_mode); compilation_cache->LookupScript(source, script_details, language_mode);
CHECK(lookup_result.toplevel_sfi().is_null()); CHECK(cached_script.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;
}
// If the compiler is configured to not recompile a flushed root SFI, then
// this test is invalid.
if (flush_root_sfi && !FLAG_isolate_script_cache_recompilation) {
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) { static void OptimizeEmptyFunction(const char* name) {
......
...@@ -23855,25 +23855,23 @@ TEST(StreamingWithHarmonyScopes) { ...@@ -23855,25 +23855,23 @@ TEST(StreamingWithHarmonyScopes) {
delete[] full_source; delete[] full_source;
} }
namespace { // Regression test for crbug.com/v8/12668. Verifies that after a streamed script
void StreamingWithIsolateScriptCache(bool run_gc) { // is inserted into the isolate script cache, a non-streamed script with
i::FLAG_expose_gc = true; // identical origin can reuse that data.
TEST(StreamingWithIsolateScriptCache) {
const char* chunks[] = {"'use strict'; (function test() { return 13; })", const char* chunks[] = {"'use strict'; (function test() { return 13; })",
nullptr}; nullptr};
const char* full_source = chunks[0]; const char* full_source = chunks[0];
v8::Isolate* isolate = CcTest::isolate(); v8::Isolate* isolate = CcTest::isolate();
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"), 0, 0, false, -1, v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"), 0, 0, false, -1,
v8::Local<v8::Value>(), false, false, false); v8::Local<v8::Value>(), false, false, false);
v8::Local<Value> first_function_untyped;
i::Handle<i::JSFunction> first_function; i::Handle<i::JSFunction> first_function;
i::Handle<i::JSFunction> second_function; i::Handle<i::JSFunction> second_function;
// Run the script using streaming. // Run the script using streaming.
{ {
LocalContext env; LocalContext env;
v8::EscapableHandleScope inner_scope(isolate);
v8::ScriptCompiler::StreamedSource source( v8::ScriptCompiler::StreamedSource source(
std::make_unique<TestSourceStream>(chunks), std::make_unique<TestSourceStream>(chunks),
...@@ -23888,38 +23886,13 @@ void StreamingWithIsolateScriptCache(bool run_gc) { ...@@ -23888,38 +23886,13 @@ void StreamingWithIsolateScriptCache(bool run_gc) {
origin) origin)
.ToLocalChecked(); .ToLocalChecked();
v8::Local<Value> result(script->Run(env.local()).ToLocalChecked()); v8::Local<Value> result(script->Run(env.local()).ToLocalChecked());
first_function_untyped = inner_scope.Escape(result); first_function =
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*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. // Run the same script in another Context without streaming.
{ {
LocalContext env; 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); v8::ScriptCompiler::Source script_source(v8_str(full_source), origin);
Local<Script> script = Local<Script> script =
v8::ScriptCompiler::Compile(env.local(), &script_source) v8::ScriptCompiler::Compile(env.local(), &script_source)
...@@ -23933,32 +23906,6 @@ void StreamingWithIsolateScriptCache(bool run_gc) { ...@@ -23933,32 +23906,6 @@ void StreamingWithIsolateScriptCache(bool run_gc) {
// SharedFunctionInfo instance due to the isolate script cache. // SharedFunctionInfo instance due to the isolate script cache.
CHECK_EQ(first_function->shared(), second_function->shared()); 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;
}
// If the compiler is configured to not recompile a flushed root SFI, then
// this test is invalid.
if (!v8::internal::FLAG_isolate_script_cache_recompilation) {
return;
}
StreamingWithIsolateScriptCache(true);
}
TEST(CodeCache) { TEST(CodeCache) {
v8::Isolate::CreateParams create_params; v8::Isolate::CreateParams create_params;
...@@ -1820,9 +1820,10 @@ TEST(CodeSerializerPromotedToCompilationCache) { ...@@ -1820,9 +1820,10 @@ TEST(CodeSerializerPromotedToCompilationCache) {
ScriptDetails script_details(src); ScriptDetails script_details(src);
script_details.host_defined_options = script_details.host_defined_options =
default_script_details.host_defined_options; default_script_details.host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy); LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
} }
{ {
...@@ -1836,9 +1837,10 @@ TEST(CodeSerializerPromotedToCompilationCache) { ...@@ -1836,9 +1837,10 @@ TEST(CodeSerializerPromotedToCompilationCache) {
default_host_defined_option_1_string); default_host_defined_option_1_string);
host_defined_options->set(1, *host_defined_option_1); host_defined_options->set(1, *host_defined_option_1);
script_details.host_defined_options = host_defined_options; script_details.host_defined_options = host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy); LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
} }
{ {
...@@ -1847,48 +1849,49 @@ TEST(CodeSerializerPromotedToCompilationCache) { ...@@ -1847,48 +1849,49 @@ TEST(CodeSerializerPromotedToCompilationCache) {
isolate->factory()->NewStringFromAsciiChecked(source)); isolate->factory()->NewStringFromAsciiChecked(source));
script_details.host_defined_options = script_details.host_defined_options =
default_script_details.host_defined_options; default_script_details.host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy); LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
} }
{ {
// Lookup with different name string should fail: // Lookup with different name string should fail:
ScriptDetails script_details( ScriptDetails script_details(
isolate->factory()->NewStringFromAsciiChecked("other")); isolate->factory()->NewStringFromAsciiChecked("other"));
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kSloppy);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
{ {
// Lookup with different position should fail: // Lookup with different position should fail:
ScriptDetails script_details(src); ScriptDetails script_details(src);
script_details.line_offset = 0xFF; script_details.line_offset = 0xFF;
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kSloppy);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
{ {
// Lookup with different position should fail: // Lookup with different position should fail:
ScriptDetails script_details(src); ScriptDetails script_details(src);
script_details.column_offset = 0xFF; script_details.column_offset = 0xFF;
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kSloppy);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
{ {
// Lookup with different language mode should fail: // Lookup with different language mode should fail:
ScriptDetails script_details(src); ScriptDetails script_details(src);
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kStrict); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kStrict);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
{ {
...@@ -1896,20 +1899,20 @@ TEST(CodeSerializerPromotedToCompilationCache) { ...@@ -1896,20 +1899,20 @@ TEST(CodeSerializerPromotedToCompilationCache) {
ScriptOriginOptions origin_options(false, true); ScriptOriginOptions origin_options(false, true);
CHECK_NE(ScriptOriginOptions().Flags(), origin_options.Flags()); CHECK_NE(ScriptOriginOptions().Flags(), origin_options.Flags());
ScriptDetails script_details(src, origin_options); ScriptDetails script_details(src, origin_options);
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kSloppy);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
{ {
// Lookup with different host_defined_options should fail: // Lookup with different host_defined_options should fail:
ScriptDetails script_details(src); ScriptDetails script_details(src);
script_details.host_defined_options = isolate->factory()->NewFixedArray(5); script_details.host_defined_options = isolate->factory()->NewFixedArray(5);
auto lookup_result = isolate->compilation_cache()->LookupScript( MaybeHandle<SharedFunctionInfo> shared =
src, script_details, LanguageMode::kSloppy); isolate->compilation_cache()->LookupScript(src, script_details,
CHECK(lookup_result.script().is_null() && LanguageMode::kSloppy);
lookup_result.toplevel_sfi().is_null()); CHECK(shared.is_null());
} }
delete cache; delete cache;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment