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 @@
namespace v8 {
namespace internal {
// The number of generations for each sub cache.
static const int kRegExpGenerations = 2;
// Initial size of each compilation cache table allocated.
static const int kInitialCacheSize = 64;
......@@ -26,18 +29,17 @@ CompilationCache::CompilationCache(Isolate* isolate)
script_(isolate),
eval_global_(isolate),
eval_contextual_(isolate),
reg_exp_(isolate),
enabled_script_and_eval_(true) {}
Handle<CompilationCacheTable> CompilationCacheEvalOrScript::GetTable() {
if (table_.IsUndefined(isolate())) {
return CompilationCacheTable::New(isolate(), kInitialCacheSize);
reg_exp_(isolate, kRegExpGenerations),
enabled_script_and_eval_(true) {
CompilationSubCache* subcaches[kSubCacheCount] = {
&script_, &eval_global_, &eval_contextual_, &reg_exp_};
for (int i = 0; i < kSubCacheCount; ++i) {
subcaches_[i] = subcaches[i];
}
return handle(CompilationCacheTable::cast(table_), isolate());
}
Handle<CompilationCacheTable> CompilationCacheRegExp::GetTable(int generation) {
DCHECK_LT(generation, kGenerations);
Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
DCHECK_LT(generation, generations());
Handle<CompilationCacheTable> result;
if (tables_[generation].IsUndefined(isolate())) {
result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
......@@ -50,112 +52,68 @@ Handle<CompilationCacheTable> CompilationCacheRegExp::GetTable(int generation) {
return result;
}
void CompilationCacheRegExp::Age() {
static_assert(kGenerations > 1);
// static
void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
DCHECK_GT(c->generations(), 1);
// Age the generations implicitly killing off the oldest.
for (int i = kGenerations - 1; i > 0; i--) {
tables_[i] = tables_[i - 1];
for (int i = c->generations() - 1; i > 0; i--) {
c->tables_[i] = c->tables_[i - 1];
}
// Set the first generation as unborn.
tables_[0] = ReadOnlyRoots(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());
}
}
}
c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value();
}
void CompilationCacheEval::Age() {
DisallowGarbageCollection no_gc;
if (table_.IsUndefined(isolate())) return;
CompilationCacheTable table = CompilationCacheTable::cast(table_);
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);
}
}
}
// static
void CompilationSubCache::AgeCustom(CompilationSubCache* c) {
DCHECK_EQ(c->generations(), 1);
if (c->tables_[0].IsUndefined(c->isolate())) return;
CompilationCacheTable::cast(c->tables_[0]).Age(c->isolate());
}
void CompilationCacheEvalOrScript::Iterate(RootVisitor* v) {
v->VisitRootPointer(Root::kCompilationCache, nullptr,
FullObjectSlot(&table_));
void CompilationCacheScript::Age() {
if (FLAG_isolate_script_cache_ageing) AgeCustom(this);
}
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,
FullObjectSlot(&tables_[0]),
FullObjectSlot(&tables_[kGenerations]));
}
void CompilationCacheEvalOrScript::Clear() {
table_ = ReadOnlyRoots(isolate()).undefined_value();
FullObjectSlot(&tables_[generations()]));
}
void CompilationCacheRegExp::Clear() {
void CompilationSubCache::Clear() {
MemsetPointer(reinterpret_cast<Address*>(tables_),
ReadOnlyRoots(isolate()).undefined_value().ptr(), kGenerations);
ReadOnlyRoots(isolate()).undefined_value().ptr(),
generations());
}
void CompilationCacheEvalOrScript::Remove(
Handle<SharedFunctionInfo> function_info) {
if (table_.IsUndefined(isolate())) return;
CompilationCacheTable::cast(table_).Remove(*function_info);
void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) {
// Probe the script generation tables. Make sure not to leak handles
// into the caller's handle scope.
{
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 {
// We only re-use a cached function for some script source code if the
// script originates from the same place. This is to avoid issues
// when reporting errors, etc.
bool HasOrigin(Isolate* isolate, Handle<Script> script,
bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info,
const ScriptDetails& script_details) {
Handle<Script> script =
Handle<Script>(Script::cast(function_info->script()), isolate);
// If the script name isn't set, the boilerplate script should have
// an undefined name to have the same origin.
Handle<Object> name;
......@@ -207,43 +165,40 @@ bool HasOrigin(Isolate* isolate, Handle<Script> script,
// be cached in the same script generation. Currently the first use
// will be cached, but subsequent code from different source / line
// won't.
CompilationCacheScript::LookupResult CompilationCacheScript::Lookup(
Handle<String> source, const ScriptDetails& script_details) {
LookupResult result;
LookupResult::RawObjects raw_result;
MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
Handle<String> source, const ScriptDetails& script_details,
LanguageMode language_mode) {
MaybeHandle<SharedFunctionInfo> result;
// Probe the script generation tables. Make sure not to leak handles
// into the caller's handle scope.
{
HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable();
LookupResult probe =
CompilationCacheTable::LookupScript(table, source, isolate());
Handle<Script> script;
if (probe.script().ToHandle(&script)) {
// Break when we've found a suitable script that matches the origin.
if (HasOrigin(isolate(), script, script_details)) {
raw_result = probe.GetRawObjects();
const int generation = 0;
DCHECK_EQ(generations(), 1);
Handle<CompilationCacheTable> table = GetTable(generation);
MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
table, source, language_mode, isolate());
Handle<SharedFunctionInfo> function_info;
if (probe.ToHandle(&function_info)) {
// Break when we've found a suitable shared function info that
// matches the origin.
if (HasOrigin(isolate(), function_info, script_details)) {
result = scope.CloseAndEscape(function_info);
}
}
}
result = LookupResult::FromRawObjects(raw_result, isolate());
// Once outside the manacles of the handle scope, we need to recheck
// to see if we actually found a cached script. If so, we return a
// handle created in the caller's handle scope.
Handle<Script> script;
if (result.script().ToHandle(&script)) {
// Since HasOrigin can allocate, we need to protect the lookup result with
// handles during the call.
DCHECK(HasOrigin(isolate(), script, script_details));
Handle<SharedFunctionInfo> sfi;
if (result.toplevel_sfi().ToHandle(&sfi)) {
isolate()->counters()->compilation_cache_hits()->Increment();
LOG(isolate(), CompilationCacheEvent("hit", "script", *sfi));
} else {
isolate()->counters()->compilation_cache_partial_hits()->Increment();
}
Handle<SharedFunctionInfo> function_info;
if (result.ToHandle(&function_info)) {
// Since HasOrigin can allocate, we need to protect the SharedFunctionInfo
// with handles during the call.
DCHECK(HasOrigin(isolate(), function_info, script_details));
isolate()->counters()->compilation_cache_hits()->Increment();
LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info));
} else {
isolate()->counters()->compilation_cache_misses()->Increment();
}
......@@ -251,11 +206,12 @@ CompilationCacheScript::LookupResult CompilationCacheScript::Lookup(
}
void CompilationCacheScript::Put(Handle<String> source,
LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info) {
HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable();
table_ = *CompilationCacheTable::PutScript(table, source, function_info,
isolate());
Handle<CompilationCacheTable> table = GetFirstTable();
SetFirstTable(CompilationCacheTable::PutScript(table, source, language_mode,
function_info, isolate()));
}
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
// having cleared the cache.
InfoCellPair result;
Handle<CompilationCacheTable> table = GetTable();
const int generation = 0;
DCHECK_EQ(generations(), 1);
Handle<CompilationCacheTable> table = GetTable(generation);
result = CompilationCacheTable::LookupEval(
table, source, outer_info, native_context, language_mode, position);
if (result.has_shared()) {
......@@ -286,10 +244,11 @@ void CompilationCacheEval::Put(Handle<String> source,
Handle<FeedbackCell> feedback_cell,
int position) {
HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable();
table_ =
*CompilationCacheTable::PutEval(table, source, outer_info, function_info,
native_context, feedback_cell, position);
Handle<CompilationCacheTable> table = GetFirstTable();
table =
CompilationCacheTable::PutEval(table, source, outer_info, function_info,
native_context, feedback_cell, position);
SetFirstTable(table);
}
MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
......@@ -300,7 +259,7 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
// having cleared the cache.
Handle<Object> result = isolate()->factory()->undefined_value();
int generation;
for (generation = 0; generation < kGenerations; generation++) {
for (generation = 0; generation < generations(); generation++) {
Handle<CompilationCacheTable> table = GetTable(generation);
result = table->LookupRegExp(source, flags);
if (result->IsFixedArray()) break;
......@@ -321,9 +280,9 @@ MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags,
Handle<FixedArray> data) {
HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetTable(0);
tables_[0] =
*CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data);
Handle<CompilationCacheTable> table = GetFirstTable();
SetFirstTable(
CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data));
}
void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
......@@ -334,11 +293,11 @@ void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
script_.Remove(function_info);
}
CompilationCacheScript::LookupResult CompilationCache::LookupScript(
MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
Handle<String> source, const ScriptDetails& script_details,
LanguageMode language_mode) {
if (!IsEnabledScript(language_mode)) return {};
return script_.Lookup(source, script_details);
if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>();
return script_.Lookup(source, script_details, language_mode);
}
InfoCellPair CompilationCache::LookupEval(Handle<String> source,
......@@ -379,10 +338,10 @@ MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
void CompilationCache::PutScript(Handle<String> source,
LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info) {
if (!IsEnabledScript(language_mode)) return;
if (!IsEnabledScriptAndEval()) return;
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,
......@@ -415,24 +374,21 @@ void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
}
void CompilationCache::Clear() {
script_.Clear();
eval_global_.Clear();
eval_contextual_.Clear();
reg_exp_.Clear();
for (int i = 0; i < kSubCacheCount; i++) {
subcaches_[i]->Clear();
}
}
void CompilationCache::Iterate(RootVisitor* v) {
script_.Iterate(v);
eval_global_.Iterate(v);
eval_contextual_.Iterate(v);
reg_exp_.Iterate(v);
for (int i = 0; i < kSubCacheCount; i++) {
subcaches_[i]->Iterate(v);
}
}
void CompilationCache::MarkCompactPrologue() {
script_.Age();
eval_global_.Age();
eval_contextual_.Age();
reg_exp_.Age();
for (int i = 0; i < kSubCacheCount; i++) {
subcaches_[i]->Age();
}
}
void CompilationCache::EnableScriptAndEval() {
......
......@@ -18,49 +18,78 @@ class Handle;
class RootVisitor;
struct ScriptDetails;
// The compilation cache consists of several sub-caches: one each for evals and
// scripts, which use this class as a base class, and a separate generational
// sub-cache for RegExps. Since the same source code string has different
// compiled code for scripts and evals, we use separate sub-caches for different
// compilation modes, to avoid retrieving the wrong result.
class CompilationCacheEvalOrScript {
// The compilation cache consists of several generational sub-caches which uses
// this class as a base class. A sub-cache contains a compilation cache tables
// for each generation of the sub-cache. Since the same source code string has
// different compiled code for scripts and evals, we use separate sub-caches
// for different compilation modes, to avoid retrieving the wrong result.
class CompilationSubCache {
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.
Handle<CompilationCacheTable> GetTable();
// Get the compilation cache tables for a specific generation.
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.
void Iterate(RootVisitor* v);
// Clears this sub-cache evicting all its content.
// Clear this sub-cache evicting all its content.
void Clear();
// Removes given shared function info from sub-cache.
// Remove given shared function info from sub-cache.
void Remove(Handle<SharedFunctionInfo> function_info);
// Number of generations in this sub-cache.
int generations() const { return generations_; }
protected:
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_;
Object table_;
const int generations_;
Object tables_[kMaxGenerations]; // One for each generation.
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEvalOrScript);
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationSubCache);
};
// Sub-cache for scripts.
class CompilationCacheScript : public CompilationCacheEvalOrScript {
class CompilationCacheScript : public CompilationSubCache {
public:
explicit CompilationCacheScript(Isolate* isolate)
: CompilationCacheEvalOrScript(isolate) {}
explicit CompilationCacheScript(Isolate* isolate);
using LookupResult = CompilationCacheScriptLookupResult;
LookupResult Lookup(Handle<String> source,
const ScriptDetails& script_details);
MaybeHandle<SharedFunctionInfo> Lookup(Handle<String> source,
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:
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript);
......@@ -78,10 +107,10 @@ class CompilationCacheScript : public CompilationCacheEvalOrScript {
// More specifically these are the CompileString, DebugEvaluate and
// DebugEvaluateGlobal runtime functions.
// 4. The start position of the calling scope.
class CompilationCacheEval : public CompilationCacheEvalOrScript {
class CompilationCacheEval : public CompilationSubCache {
public:
explicit CompilationCacheEval(Isolate* isolate)
: CompilationCacheEvalOrScript(isolate) {}
: CompilationSubCache(isolate, 1) {}
InfoCellPair Lookup(Handle<String> source,
Handle<SharedFunctionInfo> outer_info,
......@@ -93,45 +122,26 @@ class CompilationCacheEval : public CompilationCacheEvalOrScript {
Handle<Context> native_context, Handle<FeedbackCell> feedback_cell,
int position);
void Age();
void Age() override;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEval);
};
// Sub-cache for regular expressions.
class CompilationCacheRegExp {
class CompilationCacheRegExp : public CompilationSubCache {
public:
CompilationCacheRegExp(Isolate* isolate) : isolate_(isolate) {}
CompilationCacheRegExp(Isolate* isolate, int generations)
: CompilationSubCache(isolate, generations) {}
MaybeHandle<FixedArray> Lookup(Handle<String> source, JSRegExp::Flags flags);
void Put(Handle<String> source, JSRegExp::Flags flags,
Handle<FixedArray> data);
// The number of generations for the RegExp sub cache.
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();
void Age() override;
private:
Isolate* isolate() const { return isolate_; }
Isolate* const isolate_;
Object tables_[kGenerations]; // One for each generation.
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheRegExp);
};
......@@ -144,10 +154,10 @@ class V8_EXPORT_PRIVATE CompilationCache {
CompilationCache(const CompilationCache&) = delete;
CompilationCache& operator=(const CompilationCache&) = delete;
// Finds the Script and root SharedFunctionInfo for a script source string.
// Returns empty handles if the cache doesn't contain a script for the given
// source string with the right origin.
CompilationCacheScript::LookupResult LookupScript(
// Finds the script shared function info for a source
// string. Returns an empty handle if the cache doesn't contain a
// script for the given source string with the right origin.
MaybeHandle<SharedFunctionInfo> LookupScript(
Handle<String> source, const ScriptDetails& script_details,
LanguageMode language_mode);
......@@ -164,7 +174,7 @@ class V8_EXPORT_PRIVATE CompilationCache {
MaybeHandle<FixedArray> LookupRegExp(Handle<String> source,
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.
void PutScript(Handle<String> source, LanguageMode language_mode,
Handle<SharedFunctionInfo> function_info);
......@@ -214,11 +224,6 @@ class V8_EXPORT_PRIVATE CompilationCache {
bool IsEnabledScriptAndEval() const {
return FLAG_compilation_cache && enabled_script_and_eval_;
}
bool IsEnabledScript(LanguageMode language_mode) {
// Tests can change FLAG_use_strict at runtime. The compilation cache only
// contains scripts which were compiled with the default language mode.
return IsEnabledScriptAndEval() && language_mode == LanguageMode::kSloppy;
}
Isolate* isolate() const { return isolate_; }
......@@ -229,6 +234,9 @@ class V8_EXPORT_PRIVATE CompilationCache {
CompilationCacheEval eval_contextual_;
CompilationCacheRegExp reg_exp_;
static constexpr int kSubCacheCount = 4;
CompilationSubCache* subcaches_[kSubCacheCount];
// Current enable state of the compilation cache for scripts and eval.
bool enabled_script_and_eval_;
......
......@@ -1430,19 +1430,6 @@ Handle<SharedFunctionInfo> CreateTopLevelSharedFunctionInfo(
parse_info->literal(), script, true);
}
Handle<SharedFunctionInfo> GetOrCreateTopLevelSharedFunctionInfo(
ParseInfo* parse_info, Handle<Script> script, Isolate* isolate) {
EnsureSharedFunctionInfosArrayOnScript(script, parse_info, isolate);
MaybeHandle<SharedFunctionInfo> maybe_shared =
Script::FindSharedFunctionInfo(script, isolate, parse_info->literal());
if (Handle<SharedFunctionInfo> shared; maybe_shared.ToHandle(&shared)) {
DCHECK_EQ(shared->function_literal_id(),
parse_info->literal()->function_literal_id());
return shared;
}
return CreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
}
MaybeHandle<SharedFunctionInfo> CompileToplevel(
ParseInfo* parse_info, Handle<Script> script,
MaybeHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate,
......@@ -1476,7 +1463,7 @@ MaybeHandle<SharedFunctionInfo> CompileToplevel(
// Create the SharedFunctionInfo and add it to the script's list.
Handle<SharedFunctionInfo> shared_info =
GetOrCreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
CreateTopLevelSharedFunctionInfo(parse_info, script, isolate);
FinalizeUnoptimizedCompilationDataList
finalize_unoptimized_compilation_data_list;
......@@ -2819,22 +2806,17 @@ Handle<Script> NewScript(
}
MaybeHandle<SharedFunctionInfo> CompileScriptOnMainThread(
UnoptimizedCompileFlags flags, Handle<String> source,
const UnoptimizedCompileFlags flags, Handle<String> source,
const ScriptDetails& script_details, NativesFlag natives,
v8::Extension* extension, Isolate* isolate,
MaybeHandle<Script> maybe_script, IsCompiledScope* is_compiled_scope) {
Handle<Script> script;
if (maybe_script.ToHandle(&script)) {
flags.set_script_id(script->id());
}
IsCompiledScope* is_compiled_scope) {
UnoptimizedCompileState compile_state;
ReusableUnoptimizedCompileState reusable_state(isolate);
ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state);
parse_info.set_extension(extension);
if (script.is_null()) {
script = NewScript(isolate, &parse_info, source, script_details, natives);
}
Handle<Script> script =
NewScript(isolate, &parse_info, source, script_details, natives);
DCHECK_IMPLIES(parse_info.flags().collect_type_profile(),
script->IsUserJavaScript());
DCHECK_EQ(parse_info.flags().is_repl_mode(), script->is_repl_mode());
......@@ -2914,10 +2896,8 @@ bool CompilationExceptionIsRangeError(Isolate* isolate, Handle<Object> obj) {
MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
Handle<String> source, const ScriptDetails& script_details,
MaybeHandle<Script> maybe_script, Isolate* isolate,
IsCompiledScope* is_compiled_scope) {
Isolate* isolate, IsCompiledScope* is_compiled_scope) {
// Start a background thread compiling the script.
// TODO(v8:12808): Use maybe_script for the background compilation.
StressBackgroundCompileThread background_compile_thread(
isolate, source,
script_details.origin_options.IsModule() ? ScriptType::kModule
......@@ -2940,7 +2920,7 @@ MaybeHandle<SharedFunctionInfo> CompileScriptOnBothBackgroundAndMainThread(
flags_copy.set_script_id(Script::kTemporaryScriptId);
main_thread_maybe_result = CompileScriptOnMainThread(
flags_copy, source, script_details, NOT_NATIVES_CODE, nullptr, isolate,
MaybeHandle<Script>(), &inner_is_compiled_scope);
&inner_is_compiled_scope);
if (main_thread_maybe_result.is_null()) {
// Assume all range errors are stack overflows.
main_thread_had_stack_overflow = CompilationExceptionIsRangeError(
......@@ -3031,7 +3011,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
const bool use_compilation_cache =
extension == nullptr && script_details.repl_mode == REPLMode::kNo;
MaybeHandle<SharedFunctionInfo> maybe_result;
MaybeHandle<Script> maybe_script;
IsCompiledScope is_compiled_scope;
if (use_compilation_cache) {
bool can_consume_code_cache =
......@@ -3041,13 +3020,8 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
}
// First check per-isolate compilation cache.
CompilationCacheScript::LookupResult lookup_result =
maybe_result =
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()) {
compile_timer.set_hit_isolate_cache();
} else if (can_consume_code_cache) {
......@@ -3058,10 +3032,6 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileDeserialize);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompileDeserialize");
// TODO(v8:12808): If a Script was found in the compilation cache, then
// both of the code paths below (Finish and Deserialize) should make use
// of that Script to avoid duplicating the Script itself or any
// preexisting SharedFunctionInfos.
if (deserialize_task) {
// If there's a cache consume task, finish it.
maybe_result = deserialize_task->Finish(isolate, source,
......@@ -3096,7 +3066,7 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
// If the --stress-background-compile flag is set, do the actual
// compilation on a background thread, and wait for its result.
maybe_result = CompileScriptOnBothBackgroundAndMainThread(
source, script_details, maybe_script, isolate, &is_compiled_scope);
source, script_details, isolate, &is_compiled_scope);
} else {
UnoptimizedCompileFlags flags =
UnoptimizedCompileFlags::ForToplevelCompile(
......@@ -3108,9 +3078,9 @@ MaybeHandle<SharedFunctionInfo> GetSharedFunctionInfoForScriptImpl(
flags.set_is_eager(compile_options == ScriptCompiler::kEagerCompile);
maybe_result = CompileScriptOnMainThread(
flags, source, script_details, natives, extension, isolate,
maybe_script, &is_compiled_scope);
maybe_result =
CompileScriptOnMainThread(flags, source, script_details, natives,
extension, isolate, &is_compiled_scope);
}
// Add the result to the isolate cache.
......@@ -3283,18 +3253,8 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
{
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.StreamingFinalization.CheckCache");
CompilationCacheScript::LookupResult lookup_result =
compilation_cache->LookupScript(source, script_details,
task->flags().outer_language_mode());
// TODO(v8:12808): Determine what to do if we finish streaming and find that
// another copy of the Script already exists but has no root
// SharedFunctionInfo or has an uncompiled SharedFunctionInfo. For now, we
// just ignore it and create a new Script.
if (!lookup_result.toplevel_sfi().is_null()) {
maybe_result = lookup_result.toplevel_sfi();
}
maybe_result = compilation_cache->LookupScript(
source, script_details, task->flags().outer_language_mode());
if (!maybe_result.is_null()) {
compile_timer.set_hit_isolate_cache();
}
......@@ -3315,8 +3275,6 @@ Compiler::GetSharedFunctionInfoForStreamedScript(
// Add compiled code to the isolate cache.
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.StreamingFinalization.AddToCache");
DCHECK_EQ(task->flags().outer_language_mode(),
construct_language_mode(FLAG_use_strict));
compilation_cache->PutScript(source, task->flags().outer_language_mode(),
result);
}
......
......@@ -3887,12 +3887,6 @@ class BigIntPlatform : public bigint::Platform {
private:
Isolate* isolate_;
};
void MarkCompactPrologue(v8::Isolate* v8_isolate, v8::GCType gc_type,
v8::GCCallbackFlags flags, void* data) {
Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->compilation_cache()->MarkCompactPrologue();
}
} // namespace
VirtualMemoryCage* Isolate::GetPtrComprCodeCageForTesting() {
......@@ -4250,9 +4244,6 @@ bool Isolate::Init(SnapshotData* startup_snapshot_data,
}
#endif
heap()->AddGCPrologueCallback(MarkCompactPrologue, kGCTypeMarkSweepCompact,
nullptr);
initialized_ = true;
return true;
......
......@@ -2055,6 +2055,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
static void SetIsolateThreadLocals(Isolate* isolate,
PerIsolateThreadData* data);
void MarkCompactPrologue(bool is_compacting,
ThreadLocalTop* archived_thread_data);
void MarkCompactEpilogue(bool is_compacting,
ThreadLocalTop* archived_thread_data);
void FillCache();
// Propagate pending exception message to the v8::TryCatch.
......
......@@ -965,9 +965,6 @@ DEFINE_BOOL(turbo_collect_feedback_in_generic_lowering, true,
"enable experimental feedback collection in generic lowering.")
DEFINE_BOOL(isolate_script_cache_ageing, true,
"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_once, 0, "busy wait [ms] on the first Script::Run")
......
......@@ -2673,6 +2673,8 @@ void Heap::MarkCompactPrologue() {
RegExpResultsCache::Clear(string_split_cache());
RegExpResultsCache::Clear(regexp_multiple_cache());
isolate_->compilation_cache()->MarkCompactPrologue();
FlushNumberStringCache();
}
......
......@@ -281,16 +281,13 @@ namespace internal {
// lines) rather than one macro (of length about 80 lines) to work around
// this problem. Please avoid using recursive macros of this length when
// possible.
#define STATS_COUNTER_LIST_1(SC) \
/* Global Handle Count*/ \
SC(global_handles, V8.GlobalHandles) \
SC(alive_after_last_gc, V8.AliveAfterLastGC) \
SC(compilation_cache_hits, V8.CompilationCacheHits) \
SC(compilation_cache_misses, V8.CompilationCacheMisses) \
/* Number of times the cache contained a reusable Script but not \
the root SharedFunctionInfo */ \
SC(compilation_cache_partial_hits, V8.CompilationCachePartialHits) \
SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
#define STATS_COUNTER_LIST_1(SC) \
/* Global Handle Count*/ \
SC(global_handles, V8.GlobalHandles) \
SC(alive_after_last_gc, V8.AliveAfterLastGC) \
SC(compilation_cache_hits, V8.CompilationCacheHits) \
SC(compilation_cache_misses, V8.CompilationCacheMisses) \
SC(objs_since_last_young, V8.ObjsSinceLastYoung) \
SC(objs_since_last_full, V8.ObjsSinceLastFull)
#define STATS_COUNTER_LIST_2(SC) \
......
......@@ -26,77 +26,14 @@ CompilationCacheTable::CompilationCacheTable(Address ptr)
NEVER_READ_ONLY_SPACE_IMPL(CompilationCacheTable)
CAST_ACCESSOR(CompilationCacheTable)
Object CompilationCacheTable::PrimaryValueAt(InternalIndex entry) {
return get(EntryToIndex(entry) + 1);
}
void CompilationCacheTable::SetPrimaryValueAt(InternalIndex entry, Object value,
WriteBarrierMode mode) {
set(EntryToIndex(entry) + 1, value, mode);
}
Object CompilationCacheTable::EvalFeedbackValueAt(InternalIndex entry) {
static_assert(CompilationCacheShape::kEntrySize == 3);
return get(EntryToIndex(entry) + 2);
}
void CompilationCacheTable::SetEvalFeedbackValueAt(InternalIndex entry,
Object value,
WriteBarrierMode mode) {
set(EntryToIndex(entry) + 2, value, mode);
}
// The key in a script cache is a WeakFixedArray containing a weak pointer to
// the Script. The corresponding value can be either the root SharedFunctionInfo
// or undefined. The purpose of storing the root SharedFunctionInfo as the value
// is to keep it alive, not to save a lookup on the Script. A newly added entry
// always contains the root SharedFunctionInfo. After the root
// SharedFunctionInfo has aged sufficiently, it is replaced with undefined. In
// this way, all strong references to large objects are dropped, but there is
// still a way to get the Script if it happens to still be alive.
class ScriptCacheKey : public HashTableKey {
public:
enum Index {
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) {
return string.EnsureHash() + flags.value();
}
uint32_t CompilationCacheShape::EvalHash(String source,
SharedFunctionInfo shared,
LanguageMode language_mode,
int position) {
uint32_t CompilationCacheShape::StringSharedHash(String source,
SharedFunctionInfo shared,
LanguageMode language_mode,
int position) {
uint32_t hash = source.EnsureHash();
if (shared.HasSourceCode()) {
// Instead of using the SharedFunctionInfo pointer in the hash
......@@ -113,6 +50,14 @@ uint32_t CompilationCacheShape::EvalHash(String source,
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,
Object object) {
// Eval: The key field contains the hash as a Number.
......@@ -123,24 +68,7 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
return SharedFunctionInfo::cast(object).Hash();
}
// Script.
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.
// Script: See StringSharedKey::ToHandle for the encoding.
FixedArray val = FixedArray::cast(object);
if (val.map() == roots.fixed_cow_array_map()) {
DCHECK_EQ(4, val.length());
......@@ -149,10 +77,14 @@ uint32_t CompilationCacheShape::HashForObject(ReadOnlyRoots roots,
DCHECK(is_valid_language_mode(language_unchecked));
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
int position = Smi::ToInt(val.get(3));
Object shared = val.get(0);
DCHECK(shared.IsSharedFunctionInfo());
return EvalHash(source, SharedFunctionInfo::cast(shared), language_mode,
position);
Object shared_or_smi = val.get(0);
if (shared_or_smi.IsSmi()) {
DCHECK_EQ(position, kNoSourcePosition);
return StringSharedHash(source, language_mode);
} else {
return StringSharedHash(source, SharedFunctionInfo::cast(shared_or_smi),
language_mode, position);
}
}
// RegExp: The key field (and the value field) contains the
......
......@@ -17,11 +17,14 @@ const int kLiteralInitialLength = 2;
const int kLiteralContextOffset = 0;
const int kLiteralLiteralsOffset = 1;
int SearchLiteralsMapEntry(CompilationCacheTable cache,
InternalIndex cache_entry, Context native_context) {
// The initial placeholder insertion of the eval cache survives this many GCs.
const int kHashGenerations = 10;
int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry,
Context native_context) {
DisallowGarbageCollection no_gc;
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
// object used to be a FixedArray here).
......@@ -40,8 +43,7 @@ int SearchLiteralsMapEntry(CompilationCacheTable cache,
return -1;
}
void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
InternalIndex cache_entry,
void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
Handle<Context> native_context,
Handle<FeedbackCell> feedback_cell) {
Isolate* isolate = native_context->GetIsolate();
......@@ -50,7 +52,7 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
Handle<WeakFixedArray> new_literals_map;
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
// object used to be a FixedArray here).
......@@ -104,20 +106,18 @@ void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,
}
#endif
Object old_literals_map = cache->EvalFeedbackValueAt(cache_entry);
Object old_literals_map = cache->get(cache_entry);
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,
InternalIndex cache_entry,
FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry,
Context native_context) {
FeedbackCell result;
int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context);
if (entry >= 0) {
WeakFixedArray literals_map =
WeakFixedArray::cast(cache.EvalFeedbackValueAt(cache_entry));
WeakFixedArray literals_map = WeakFixedArray::cast(cache.get(cache_entry));
DCHECK_LE(entry + kLiteralEntryLength, literals_map.length());
MaybeObject object = literals_map.Get(entry + kLiteralLiteralsOffset);
......@@ -129,8 +129,8 @@ FeedbackCell SearchLiteralsMap(CompilationCacheTable cache,
return result;
}
// EvalCacheKeys are used as keys in the eval cache.
class EvalCacheKey : public HashTableKey {
// StringSharedKeys are used as keys in the eval cache.
class StringSharedKey : public HashTableKey {
public:
// This tuple unambiguously identifies calls to eval() or
// CreateDynamicFunction() (such as through the Function() constructor).
......@@ -142,15 +142,23 @@ class EvalCacheKey : public HashTableKey {
// * When positive, position is the position in the source where eval is
// called. When negative, position is the negation of the position in the
// dynamic function's effective source where the ')' ends the parameters.
EvalCacheKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
LanguageMode language_mode, int position)
: HashTableKey(CompilationCacheShape::EvalHash(*source, *shared,
language_mode, position)),
StringSharedKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
LanguageMode language_mode, int position)
: HashTableKey(CompilationCacheShape::StringSharedHash(
*source, *shared, language_mode, position)),
source_(source),
shared_(shared),
language_mode_(language_mode),
position_(position) {}
// This tuple unambiguously identifies script compilation.
StringSharedKey(Handle<String> source, LanguageMode language_mode)
: HashTableKey(
CompilationCacheShape::StringSharedHash(*source, language_mode)),
source_(source),
language_mode_(language_mode),
position_(kNoSourcePosition) {}
bool IsMatch(Object other) override {
DisallowGarbageCollection no_gc;
if (!other.IsFixedArray()) {
......@@ -159,8 +167,14 @@ class EvalCacheKey : public HashTableKey {
return Hash() == other_hash;
}
FixedArray other_array = FixedArray::cast(other);
DCHECK(other_array.get(0).IsSharedFunctionInfo());
if (*shared_ != other_array.get(0)) return false;
DCHECK(other_array.get(0).IsSharedFunctionInfo() ||
other_array.get(0) == Smi::zero());
Handle<SharedFunctionInfo> shared;
if (shared_.ToHandle(&shared)) {
if (*shared != other_array.get(0)) return false;
} else {
if (Smi::zero() != other_array.get(0)) return false;
}
int language_unchecked = Smi::ToInt(other_array.get(2));
DCHECK(is_valid_language_mode(language_unchecked));
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
......@@ -173,7 +187,12 @@ class EvalCacheKey : public HashTableKey {
Handle<Object> AsHandle(Isolate* isolate) {
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(2, Smi::FromEnum(language_mode_));
array->set(3, Smi::FromInt(position_));
......@@ -183,7 +202,7 @@ class EvalCacheKey : public HashTableKey {
private:
Handle<String> source_;
Handle<SharedFunctionInfo> shared_;
MaybeHandle<SharedFunctionInfo> shared_;
LanguageMode language_mode_;
int position_;
};
......@@ -225,103 +244,22 @@ class CodeKey : public HashTableKey {
} // namespace
ScriptCacheKey::ScriptCacheKey(Handle<String> source)
: HashTableKey(source->EnsureHash()), source_(source) {
// Hash values must fit within a Smi.
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) {
MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript(
Handle<CompilationCacheTable> table, Handle<String> src,
LanguageMode language_mode, Isolate* isolate) {
src = String::Flatten(isolate, src);
ScriptCacheKey key(src);
StringSharedKey key(src, language_mode);
InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return {};
DisallowGarbageCollection no_gc;
Object key_in_table = table->KeyAt(entry);
Script script = Script::cast(WeakFixedArray::cast(key_in_table)
.Get(ScriptCacheKey::kWeakScript)
.GetHeapObjectAssumeWeak());
Object obj = table->PrimaryValueAt(entry);
SharedFunctionInfo toplevel_sfi;
if (!obj.IsUndefined(isolate)) {
toplevel_sfi = SharedFunctionInfo::cast(obj);
DCHECK_EQ(toplevel_sfi.script(), script);
} else 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);
}
}
if (entry.is_not_found()) return MaybeHandle<SharedFunctionInfo>();
int index = EntryToIndex(entry);
if (!table->get(index).IsFixedArray()) {
return MaybeHandle<SharedFunctionInfo>();
}
return CompilationCacheScriptLookupResult::FromRawObjects(
std::make_pair(script, toplevel_sfi), isolate);
Object obj = table->get(index + 1);
if (obj.IsSharedFunctionInfo()) {
return handle(SharedFunctionInfo::cast(obj), isolate);
}
return MaybeHandle<SharedFunctionInfo>();
}
InfoCellPair CompilationCacheTable::LookupEval(
......@@ -332,17 +270,18 @@ InfoCellPair CompilationCacheTable::LookupEval(
Isolate* isolate = native_context->GetIsolate();
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);
if (entry.is_not_found()) return empty_result;
if (!table->KeyAt(entry).IsFixedArray()) return empty_result;
Object obj = table->PrimaryValueAt(entry);
int index = EntryToIndex(entry);
if (!table->get(index).IsFixedArray()) return empty_result;
Object obj = table->get(index + 1);
if (!obj.IsSharedFunctionInfo()) return empty_result;
static_assert(CompilationCacheShape::kEntrySize == 3);
FeedbackCell feedback_cell =
SearchLiteralsMap(*table, entry, *native_context);
SearchLiteralsMap(*table, index + 2, *native_context);
return InfoCellPair(isolate, SharedFunctionInfo::cast(obj), feedback_cell);
}
......@@ -353,57 +292,21 @@ Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src,
RegExpKey key(src, flags);
InternalIndex entry = FindEntry(isolate, &key);
if (entry.is_not_found()) return isolate->factory()->undefined_value();
return Handle<Object>(PrimaryValueAt(entry), isolate);
}
Handle<CompilationCacheTable> CompilationCacheTable::EnsureScriptTableCapacity(
Isolate* isolate, Handle<CompilationCacheTable> cache) {
if (cache->HasSufficientCapacityToAdd(1)) return cache;
// Before resizing, delete are any entries whose keys contain cleared weak
// pointers.
{
DisallowGarbageCollection no_gc;
for (InternalIndex entry : cache->IterateEntries()) {
Object key;
if (!cache->ToKey(isolate, entry, &key)) continue;
if (WeakFixedArray::cast(key)
.Get(ScriptCacheKey::kWeakScript)
.IsCleared()) {
DCHECK(cache->PrimaryValueAt(entry).IsUndefined());
cache->RemoveEntry(entry);
}
}
}
return EnsureCapacity(isolate, cache);
return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
}
Handle<CompilationCacheTable> CompilationCacheTable::PutScript(
Handle<CompilationCacheTable> cache, Handle<String> src,
Handle<SharedFunctionInfo> value, Isolate* isolate) {
LanguageMode language_mode, Handle<SharedFunctionInfo> value,
Isolate* isolate) {
src = String::Flatten(isolate, src);
ScriptCacheKey key(src);
Handle<Object> k = key.AsHandle(isolate, value);
// Check whether there is already a matching entry. If so, we must overwrite
// it. This allows an entry whose value is undefined to upgrade to contain a
// SharedFunctionInfo.
InternalIndex entry = cache->FindEntry(isolate, &key);
bool found_existing = entry.is_found();
if (!found_existing) {
cache = EnsureScriptTableCapacity(isolate, cache);
entry = cache->FindInsertionEntry(isolate, key.Hash());
}
// TODO(v8:12808): Once all code paths are updated to reuse a Script if
// available, we could DCHECK here that the Script in the existing entry
// matches the Script in the new key if their origins match. For now, there is
// no such guarantee.
cache->SetKeyAt(entry, *k);
cache->SetPrimaryValueAt(entry, *value);
if (!found_existing) {
cache->ElementAdded();
}
StringSharedKey key(src, language_mode);
Handle<Object> k = key.AsHandle(isolate);
cache = EnsureCapacity(isolate, cache);
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
cache->set(EntryToIndex(entry), *k);
cache->set(EntryToIndex(entry) + 1, *value);
cache->ElementAdded();
return cache;
}
......@@ -414,7 +317,7 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
int position) {
Isolate* isolate = native_context->GetIsolate();
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
// (below) has already happened earlier.
......@@ -422,12 +325,14 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
Handle<Object> k = key.AsHandle(isolate);
InternalIndex entry = cache->FindEntry(isolate, &key);
if (entry.is_found()) {
cache->SetKeyAt(entry, *k);
cache->SetPrimaryValueAt(entry, *value);
cache->set(EntryToIndex(entry), *k);
cache->set(EntryToIndex(entry) + 1, *value);
// AddToFeedbackCellsMap may allocate a new sub-array to live in the
// entry, but it won't change the cache array. Therefore EntryToIndex
// and entry remains correct.
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
// case of hash collisions.
}
......@@ -438,8 +343,8 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
Handle<Object> k =
isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
cache->SetKeyAt(entry, *k);
cache->SetPrimaryValueAt(entry, Smi::FromInt(kHashGenerations));
cache->set(EntryToIndex(entry), *k);
cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
cache->ElementAdded();
return cache;
}
......@@ -452,32 +357,62 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
// We store the value in the key slot, and compare the search key
// to the stored value with a custom IsMatch function during lookups.
cache->SetKeyAt(entry, *value);
cache->SetPrimaryValueAt(entry, *value);
cache->set(EntryToIndex(entry), *value);
cache->set(EntryToIndex(entry) + 1, *value);
cache->ElementAdded();
return cache;
}
void CompilationCacheTable::Age(Isolate* isolate) {
DisallowGarbageCollection no_gc;
for (InternalIndex entry : IterateEntries()) {
const int entry_index = EntryToIndex(entry);
const int value_index = entry_index + 1;
Object key = get(entry_index);
if (key.IsNumber()) {
// The ageing mechanism for the initial dummy entry in the eval cache.
// The 'key' is the hash represented as a Number. The 'value' is a smi
// counting down from kHashGenerations. On reaching zero, the entry is
// cleared.
// Note: The following static assert only establishes an explicit
// connection between initialization- and use-sites of the smi value
// field.
static_assert(kHashGenerations);
const int new_count = Smi::ToInt(get(value_index)) - 1;
if (new_count == 0) {
RemoveEntry(entry_index);
} else {
DCHECK_GT(new_count, 0);
NoWriteBarrierSet(*this, value_index, Smi::FromInt(new_count));
}
} else if (key.IsFixedArray()) {
// The ageing mechanism for script and eval caches.
SharedFunctionInfo info = SharedFunctionInfo::cast(get(value_index));
if (info.HasBytecodeArray() && info.GetBytecodeArray(isolate).IsOld()) {
RemoveEntry(entry_index);
}
}
}
}
void CompilationCacheTable::Remove(Object value) {
DisallowGarbageCollection no_gc;
for (InternalIndex entry : IterateEntries()) {
if (PrimaryValueAt(entry) == value) {
RemoveEntry(entry);
int entry_index = EntryToIndex(entry);
int value_index = entry_index + 1;
if (get(value_index) == value) {
RemoveEntry(entry_index);
}
}
}
void CompilationCacheTable::RemoveEntry(InternalIndex entry) {
int entry_index = EntryToIndex(entry);
void CompilationCacheTable::RemoveEntry(int entry_index) {
Object the_hole_value = GetReadOnlyRoots().the_hole_value();
for (int i = 0; i < kEntrySize; i++) {
NoWriteBarrierSet(*this, entry_index + i, the_hole_value);
}
ElementRemoved();
// This table does not shrink upon deletion. The script cache depends on that
// fact, because EnsureScriptTableCapacity calls RemoveEntry at a time when
// shrinking the table would be counterproductive.
}
} // namespace internal
......
......@@ -29,8 +29,13 @@ class CompilationCacheShape : public BaseShape<HashTableKey*> {
static inline uint32_t RegExpHash(String string, Smi flags);
static inline uint32_t EvalHash(String source, SharedFunctionInfo shared,
LanguageMode language_mode, int position);
static inline uint32_t StringSharedHash(String source,
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);
......@@ -74,34 +79,6 @@ class InfoCellPair {
FeedbackCell feedback_cell_;
};
// A lookup result from the compilation cache for scripts. There are three
// possible states:
//
// 1. Cache miss: script and toplevel_sfi are both null.
// 2. Cache hit: script and toplevel_sfi are both non-null. toplevel_sfi is
// guaranteed to be compiled, and to stay compiled while this lookup result
// instance is alive.
// 3. Partial cache hit: script is non-null, but toplevel_sfi is null. The
// script may contain an uncompiled toplevel SharedFunctionInfo.
class CompilationCacheScriptLookupResult {
public:
MaybeHandle<Script> script() const { return script_; }
MaybeHandle<SharedFunctionInfo> toplevel_sfi() const { return toplevel_sfi_; }
IsCompiledScope is_compiled_scope() const { return is_compiled_scope_; }
using RawObjects = std::pair<Script, SharedFunctionInfo>;
RawObjects GetRawObjects() const;
static CompilationCacheScriptLookupResult FromRawObjects(RawObjects raw,
Isolate* isolate);
private:
MaybeHandle<Script> script_;
MaybeHandle<SharedFunctionInfo> toplevel_sfi_;
IsCompiledScope is_compiled_scope_;
};
EXTERN_DECLARE_HASH_TABLE(CompilationCacheTable, CompilationCacheShape)
class CompilationCacheTable
......@@ -109,18 +86,14 @@ class CompilationCacheTable
public:
NEVER_READ_ONLY_SPACE
// The 'script' cache contains SharedFunctionInfos. Once a root
// SharedFunctionInfo has become old enough that its bytecode is flushed, the
// entry is still present and can be used to get the Script. For consistency,
// this function always returns the Script. Callers should check whether there
// is a root SharedFunctionInfo in the script and whether it is already
// compiled, and choose what to do accordingly.
static CompilationCacheScriptLookupResult LookupScript(
// The 'script' cache contains SharedFunctionInfos.
static MaybeHandle<SharedFunctionInfo> LookupScript(
Handle<CompilationCacheTable> table, Handle<String> src,
Isolate* isolate);
LanguageMode language_mode, Isolate* isolate);
static Handle<CompilationCacheTable> PutScript(
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
// code object. To do so, on first "put" only a hash identifying the
......@@ -151,24 +124,12 @@ class CompilationCacheTable
JSRegExp::Flags flags, Handle<FixedArray> value);
void Remove(Object value);
void RemoveEntry(InternalIndex entry);
inline Object PrimaryValueAt(InternalIndex entry);
inline void SetPrimaryValueAt(InternalIndex entry, Object value,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
inline Object EvalFeedbackValueAt(InternalIndex entry);
inline void SetEvalFeedbackValueAt(
InternalIndex entry, Object value,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// The initial placeholder insertion of the eval cache survives this many GCs.
static constexpr int kHashGenerations = 10;
void Age(Isolate* isolate);
DECL_CAST(CompilationCacheTable)
private:
static Handle<CompilationCacheTable> EnsureScriptTableCapacity(
Isolate* isolate, Handle<CompilationCacheTable> cache);
void RemoveEntry(int entry_index);
OBJECT_CONSTRUCTORS(CompilationCacheTable,
HashTable<CompilationCacheTable, CompilationCacheShape>);
......
......@@ -238,12 +238,6 @@ Object HashTable<Derived, Shape>::KeyAt(PtrComprCageBase cage_base,
return get(cage_base, EntryToIndex(entry) + kEntryKeyIndex, tag);
}
template <typename Derived, typename Shape>
void HashTable<Derived, Shape>::SetKeyAt(InternalIndex entry, Object value,
WriteBarrierMode mode) {
set_key(EntryToIndex(entry), value, mode);
}
template <typename Derived, typename Shape>
void HashTable<Derived, Shape>::set_key(int index, Object value) {
DCHECK(!IsEphemeronHashTable());
......
......@@ -165,9 +165,6 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) HashTable
inline Object KeyAt(PtrComprCageBase cage_base, InternalIndex entry,
RelaxedLoadTag tag);
inline void SetKeyAt(InternalIndex entry, Object value,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
static const int kElementsStartIndex = kPrefixStartIndex + Shape::kPrefixSize;
static const int kEntrySize = Shape::kEntrySize;
static_assert(kEntrySize > 0);
......
......@@ -1469,7 +1469,7 @@ TEST(TestUseOfIncrementalBarrierOnCompileLazy) {
CHECK(g_function->is_compiled());
}
void CompilationCacheCachingBehavior(bool retain_script) {
TEST(CompilationCacheCachingBehavior) {
// If we do not have the compilation cache turned off, this test is invalid.
if (!FLAG_compilation_cache) {
return;
......@@ -1478,20 +1478,16 @@ void CompilationCacheCachingBehavior(bool retain_script) {
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
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());
const char* raw_source = retain_script ? "function foo() {"
" var x = 42;"
" var y = 42;"
" var z = x + y;"
"};"
"foo();"
: "(function foo() {"
" var x = 42;"
" var y = 42;"
" var z = x + y;"
"})();";
const char* raw_source =
"function foo() {"
" var x = 42;"
" var y = 42;"
" var z = x + y;"
"};"
"foo();";
Handle<String> source = factory->InternalizeUtf8String(raw_source);
{
......@@ -1504,9 +1500,9 @@ void CompilationCacheCachingBehavior(bool retain_script) {
v8::HandleScope scope(CcTest::isolate());
ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false));
auto lookup_result =
MaybeHandle<SharedFunctionInfo> cached_script =
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.
......@@ -1515,13 +1511,12 @@ void CompilationCacheCachingBehavior(bool retain_script) {
v8::HandleScope scope(CcTest::isolate());
ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false));
auto lookup_result =
MaybeHandle<SharedFunctionInfo> cached_script =
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.
Handle<SharedFunctionInfo> shared =
lookup_result.toplevel_sfi().ToHandleChecked();
Handle<SharedFunctionInfo> shared = cached_script.ToHandleChecked();
CHECK(shared->HasBytecodeArray());
const int kAgingThreshold = 6;
for (int i = 0; i < kAgingThreshold; i++) {
......@@ -1536,254 +1531,10 @@ void CompilationCacheCachingBehavior(bool retain_script) {
// Ensure code aging cleared the entry from the cache.
ScriptDetails script_details(Handle<Object>(),
v8::ScriptOriginOptions(true, false));
auto lookup_result =
MaybeHandle<SharedFunctionInfo> cached_script =
compilation_cache->LookupScript(source, script_details, language_mode);
CHECK(lookup_result.toplevel_sfi().is_null());
CHECK_EQ(retain_script, !lookup_result.script().is_null());
}
}
TEST(CompilationCacheCachingBehaviorDiscardScript) {
CompilationCacheCachingBehavior(false);
}
TEST(CompilationCacheCachingBehaviorRetainScript) {
CompilationCacheCachingBehavior(true);
}
namespace {
template <typename T>
Handle<SharedFunctionInfo> GetSharedFunctionInfo(
v8::Local<T> function_or_script) {
Handle<JSFunction> i_function =
Handle<JSFunction>::cast(v8::Utils::OpenHandle(*function_or_script));
return handle(i_function->shared(), CcTest::i_isolate());
}
template <typename T>
void AgeBytecode(v8::Local<T> function_or_script) {
Handle<SharedFunctionInfo> shared = GetSharedFunctionInfo(function_or_script);
CHECK(shared->HasBytecodeArray());
const int kAgingThreshold = 6;
for (int i = 0; i < kAgingThreshold; i++) {
shared->GetBytecodeArray(CcTest::i_isolate()).MakeOlder();
}
}
void CompilationCacheRegeneration(bool retain_root_sfi, bool flush_root_sfi,
bool flush_eager_sfi) {
// If the compilation cache is turned off, this test is invalid.
if (!FLAG_compilation_cache) {
return;
}
// 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());
CHECK(cached_script.is_null());
}
{
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) {
......
......@@ -23855,25 +23855,23 @@ TEST(StreamingWithHarmonyScopes) {
delete[] full_source;
}
namespace {
void StreamingWithIsolateScriptCache(bool run_gc) {
i::FLAG_expose_gc = true;
// 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) {
const char* chunks[] = {"'use strict'; (function test() { return 13; })",
nullptr};
const char* full_source = chunks[0];
v8::Isolate* isolate = CcTest::isolate();
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::HandleScope scope(isolate);
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"), 0, 0, false, -1,
v8::Local<v8::Value>(), false, false, false);
v8::Local<Value> first_function_untyped;
i::Handle<i::JSFunction> first_function;
i::Handle<i::JSFunction> second_function;
// Run the script using streaming.
{
LocalContext env;
v8::EscapableHandleScope inner_scope(isolate);
v8::ScriptCompiler::StreamedSource source(
std::make_unique<TestSourceStream>(chunks),
......@@ -23888,38 +23886,13 @@ void StreamingWithIsolateScriptCache(bool run_gc) {
origin)
.ToLocalChecked();
v8::Local<Value> result(script->Run(env.local()).ToLocalChecked());
first_function_untyped = inner_scope.Escape(result);
if (run_gc) {
// Age the top-level bytecode for the script to encourage the Isolate
// script cache to evict it. However, there are still active Handles
// referring to functions in that script, so the script itself should stay
// alive and reachable via the Isolate script cache.
i::Handle<i::JSFunction> script_function =
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*script));
i::Handle<i::BytecodeArray> script_bytecode(
script_function->shared().GetBytecodeArray(i_isolate), i_isolate);
for (int i = 0; i < 5; ++i) {
script_bytecode->MakeOlder();
}
}
first_function =
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*result));
}
first_function = i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*first_function_untyped));
// Run the same script in another Context without streaming.
{
LocalContext env;
if (run_gc) {
// Perform garbage collection, which should remove the top-level
// SharedFunctionInfo from the Isolate script cache. However, the
// corresponding Script is still reachable and therefore still present in
// the Isolate script cache.
CompileRun("gc();");
}
v8::ScriptCompiler::Source script_source(v8_str(full_source), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(env.local(), &script_source)
......@@ -23933,32 +23906,6 @@ void StreamingWithIsolateScriptCache(bool run_gc) {
// SharedFunctionInfo instance due to the isolate script cache.
CHECK_EQ(first_function->shared(), second_function->shared());
}
} // namespace
// Regression test for crbug.com/v8/12668. Verifies that after a streamed script
// is inserted into the isolate script cache, a non-streamed script with
// identical origin can reuse that data.
TEST(StreamingWithIsolateScriptCache) {
StreamingWithIsolateScriptCache(false);
}
// Variant of the above test which evicts the root SharedFunctionInfo from the
// Isolate script cache but still reuses the same Script.
TEST(StreamingWithIsolateScriptCacheClearingRootSFI) {
// TODO(v8:12808): Remove this check once background compilation is capable of
// reusing an existing Script.
if (v8::internal::FLAG_stress_background_compile) {
return;
}
// 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) {
v8::Isolate::CreateParams create_params;
......@@ -1820,9 +1820,10 @@ TEST(CodeSerializerPromotedToCompilationCache) {
ScriptDetails script_details(src);
script_details.host_defined_options =
default_script_details.host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
}
{
......@@ -1836,9 +1837,10 @@ TEST(CodeSerializerPromotedToCompilationCache) {
default_host_defined_option_1_string);
host_defined_options->set(1, *host_defined_option_1);
script_details.host_defined_options = host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
}
{
......@@ -1847,48 +1849,49 @@ TEST(CodeSerializerPromotedToCompilationCache) {
isolate->factory()->NewStringFromAsciiChecked(source));
script_details.host_defined_options =
default_script_details.host_defined_options;
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK_EQ(*lookup_result.toplevel_sfi().ToHandleChecked(), *copy);
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK_EQ(*shared.ToHandleChecked(), *copy);
}
{
// Lookup with different name string should fail:
ScriptDetails script_details(
isolate->factory()->NewStringFromAsciiChecked("other"));
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK(shared.is_null());
}
{
// Lookup with different position should fail:
ScriptDetails script_details(src);
script_details.line_offset = 0xFF;
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK(shared.is_null());
}
{
// Lookup with different position should fail:
ScriptDetails script_details(src);
script_details.column_offset = 0xFF;
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK(shared.is_null());
}
{
// Lookup with different language mode should fail:
ScriptDetails script_details(src);
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kStrict);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kStrict);
CHECK(shared.is_null());
}
{
......@@ -1896,20 +1899,20 @@ TEST(CodeSerializerPromotedToCompilationCache) {
ScriptOriginOptions origin_options(false, true);
CHECK_NE(ScriptOriginOptions().Flags(), origin_options.Flags());
ScriptDetails script_details(src, origin_options);
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK(shared.is_null());
}
{
// Lookup with different host_defined_options should fail:
ScriptDetails script_details(src);
script_details.host_defined_options = isolate->factory()->NewFixedArray(5);
auto lookup_result = isolate->compilation_cache()->LookupScript(
src, script_details, LanguageMode::kSloppy);
CHECK(lookup_result.script().is_null() &&
lookup_result.toplevel_sfi().is_null());
MaybeHandle<SharedFunctionInfo> shared =
isolate->compilation_cache()->LookupScript(src, script_details,
LanguageMode::kSloppy);
CHECK(shared.is_null());
}
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