#include "src/objects/compilation-cache-table.h"
#include "src/codegen/script-details.h"
#include "src/common/assert-scope.h"
#include "src/objects/compilation-cache-table-inl.h"
namespace v8 {
namespace internal {
namespace {
Tagged<JSFunction> SearchLiteralsMap(Tagged<CompilationCacheTable> cache,
InternalIndex cache_entry,
Tagged<Context> native_context) {
DisallowGarbageCollection no_gc;
DCHECK(IsNativeContext(native_context));
Tagged<UnionOf<TheHole, WeakFixedArray>> obj =
cache->EvalJSFunctionsValueAt(cache_entry);
DCHECK(IsTheHole(obj) || IsWeakFixedArray(obj));
if (IsWeakFixedArray(obj)) {
Tagged<WeakFixedArray> literals_map = Cast<WeakFixedArray>(obj);
int length = literals_map->length();
for (int i = 0; i < length; i++) {
DCHECK(literals_map->get(i).IsWeakOrCleared());
Tagged<JSFunction> js_function;
if (literals_map->get(i).GetHeapObjectIfWeak(&js_function) &&
js_function->GetCreationContext().value() == native_context) {
return js_function;
}
}
}
return {};
}
void AddToJSFunctionMap(DirectHandle<CompilationCacheTable> cache,
InternalIndex cache_entry,
DirectHandle<JSFunction> js_function) {
Isolate* isolate = Isolate::Current();
DirectHandle<WeakFixedArray> new_js_functions;
int entry = -1;
Tagged<Object> obj = cache->EvalJSFunctionsValueAt(cache_entry);
DCHECK(IsTheHole(obj) || IsWeakFixedArray(obj));
if (IsTheHole(obj) || Cast<WeakFixedArray>(obj)->length() == 0) {
new_js_functions =
isolate->factory()->NewWeakFixedArray(1, AllocationType::kOld);
entry = 0;
} else {
DirectHandle<WeakFixedArray> old_js_functions(Cast<WeakFixedArray>(obj),
isolate);
DCHECK_LT(entry, 0);
int length = old_js_functions->length();
for (int i = 0; i < length; i++) {
if (old_js_functions->get(i).IsCleared()) {
new_js_functions = old_js_functions;
entry = i;
break;
}
}
if (entry < 0) {
new_js_functions =
isolate->factory()->CopyWeakFixedArrayAndGrow(old_js_functions, 1);
entry = old_js_functions->length();
}
}
new_js_functions->set(entry, MakeWeak(*js_function));
#ifdef DEBUG
for (int i = 0; i < new_js_functions->length(); i++) {
Tagged<MaybeObject> object = new_js_functions->get(i);
DCHECK_IMPLIES(!object.IsCleared(),
IsJSFunction(object.GetHeapObjectAssumeWeak()));
}
#endif
Tagged<Object> old_js_functions = cache->EvalJSFunctionsValueAt(cache_entry);
if (old_js_functions != *new_js_functions) {
cache->SetEvalJSFunctionsValueAt(cache_entry, *new_js_functions);
}
}
class EvalCacheKey : public HashTableKey {
public:
EvalCacheKey(DirectHandle<String> source,
DirectHandle<SharedFunctionInfo> shared,
LanguageMode language_mode, int position)
: HashTableKey(CompilationCacheShape::EvalHash(*source, *shared,
language_mode, position)),
source_(source),
shared_(shared),
language_mode_(language_mode),
position_(position) {}
bool IsMatch(Tagged<Object> other) override {
DisallowGarbageCollection no_gc;
Tagged<FixedArray> other_array = Cast<FixedArray>(other);
DCHECK(IsSharedFunctionInfo(other_array->get(0)));
if (*shared_ != other_array->get(0)) return false;
int language_unchecked = Smi::ToInt(other_array->get(2));
DCHECK(is_valid_language_mode(language_unchecked));
LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
if (language_mode != language_mode_) return false;
int position = Smi::ToInt(other_array->get(3));
if (position != position_) return false;
Tagged<String> source = Cast<String>(other_array->get(1));
return source->Equals(*source_);
}
DirectHandle<Object> AsHandle(Isolate* isolate) {
DirectHandle<FixedArray> array = isolate->factory()->NewFixedArray(4);
array->set(0, *shared_);
array->set(1, *source_);
array->set(2, Smi::FromEnum(language_mode_));
array->set(3, Smi::FromInt(position_));
array->set_map(isolate, ReadOnlyRoots(isolate).fixed_cow_array_map());
return array;
}
private:
DirectHandle<String> source_;
DirectHandle<SharedFunctionInfo> shared_;
LanguageMode language_mode_;
int position_;
};
class RegExpKey : public HashTableKey {
public:
RegExpKey(Isolate* isolate, DirectHandle<String> string,
JSRegExp::Flags flags)
: HashTableKey(
CompilationCacheShape::RegExpHash(*string, Smi::FromInt(flags))),
isolate_(isolate),
string_(string),
flags_(flags) {}
bool IsMatch(Tagged<Object> obj) override {
Tagged<RegExpData> val = Cast<RegExpDataWrapper>(obj)->data(isolate_);
return string_->Equals(val->source()) && (flags_ == val->flags());
}
Isolate* isolate_;
DirectHandle<String> string_;
JSRegExp::Flags flags_;
};
class CodeKey : public HashTableKey {
public:
explicit CodeKey(Handle<SharedFunctionInfo> key)
: HashTableKey(key->Hash()), key_(key) {}
bool IsMatch(Tagged<Object> string) override { return *key_ == string; }
Handle<SharedFunctionInfo> key_;
};
Tagged<Smi> ScriptHash(Tagged<String> source,
MaybeDirectHandle<Object> maybe_name, int line_offset,
int column_offset,
v8::ScriptOriginOptions origin_options,
Isolate* isolate) {
DisallowGarbageCollection no_gc;
size_t hash = base::hash_combine(source->EnsureHash());
if (DirectHandle<Object> name;
maybe_name.ToHandle(&name) && IsString(*name, isolate)) {
hash =
base::hash_combine(hash, Cast<String>(*name)->EnsureHash(), line_offset,
column_offset, origin_options.Flags());
}
return Smi::From31BitPattern(static_cast<int>(hash & (~(1u << 31))));
}
}
bool ScriptCacheKey::MatchesScript(Tagged<Script> script) {
DisallowGarbageCollection no_gc;
Handle<Object> name;
if (!name_.ToHandle(&name)) {
return IsUndefined(script->name(), isolate_);
}
if (line_offset_ != script->line_offset()) return false;
if (column_offset_ != script->column_offset()) return false;
if (!IsString(*name, isolate_) || !IsString(script->name(), isolate_))
return false;
if (origin_options_.Flags() != script->origin_options().Flags()) {
return false;
}
if (!Cast<String>(*name)->Equals(Cast<String>(script->name()))) {
return false;
}
Handle<FixedArray> wrapped_arguments_handle;
if (wrapped_arguments_.ToHandle(&wrapped_arguments_handle)) {
if (!script->is_wrapped()) {
return false;
}
Tagged<FixedArray> wrapped_arguments = *wrapped_arguments_handle;
Tagged<FixedArray> other_wrapped_arguments = script->wrapped_arguments();
int length = wrapped_arguments->length();
if (length != other_wrapped_arguments->length()) {
return false;
}
for (int i = 0; i < length; i++) {
Tagged<Object> arg = wrapped_arguments->get(i);
Tagged<Object> other_arg = other_wrapped_arguments->get(i);
DCHECK(IsString(arg));
DCHECK(IsString(other_arg));
if (!Cast<String>(arg)->Equals(Cast<String>(other_arg))) {
return false;
}
}
} else if (script->is_wrapped()) {
return false;
}
if (script->deserialized() &&
script->host_defined_options() ==
ReadOnlyRoots(isolate_).empty_fixed_array()) {
return true;
}
Handle<Object> maybe_host_defined_options;
if (!host_defined_options_.ToHandle(&maybe_host_defined_options)) {
maybe_host_defined_options = isolate_->factory()->empty_fixed_array();
}
Tagged<FixedArray> host_defined_options =
Cast<FixedArray>(*maybe_host_defined_options);
Tagged<FixedArray> script_options =
Cast<FixedArray>(script->host_defined_options());
int length = host_defined_options->length();
if (length != script_options->length()) return false;
for (int i = 0; i < length; i++) {
DCHECK(IsPrimitive(host_defined_options->get(i)));
DCHECK(IsPrimitive(script_options->get(i)));
if (!Object::StrictEquals(host_defined_options->get(i),
script_options->get(i))) {
return false;
}
}
return true;
}
ScriptCacheKey::ScriptCacheKey(Handle<String> source,
const ScriptDetails* script_details,
Isolate* isolate)
: ScriptCacheKey(source, script_details->name_obj,
script_details->line_offset, script_details->column_offset,
script_details->origin_options,
script_details->host_defined_options,
script_details->wrapped_arguments, isolate) {}
ScriptCacheKey::ScriptCacheKey(Handle<String> source, MaybeHandle<Object> name,
int line_offset, int column_offset,
v8::ScriptOriginOptions origin_options,
MaybeHandle<Object> host_defined_options,
MaybeHandle<FixedArray> maybe_wrapped_arguments,
Isolate* isolate)
: HashTableKey(static_cast<uint32_t>(ScriptHash(*source, name, line_offset,
column_offset,
origin_options, isolate)
.value())),
source_(source),
name_(name),
line_offset_(line_offset),
column_offset_(column_offset),
origin_options_(origin_options),
host_defined_options_(host_defined_options),
wrapped_arguments_(maybe_wrapped_arguments),
isolate_(isolate) {
DCHECK(Smi::IsValid(static_cast<int>(Hash())));
#ifdef DEBUG
DirectHandle<FixedArray> wrapped_arguments;
if (maybe_wrapped_arguments.ToHandle(&wrapped_arguments)) {
int length = wrapped_arguments->length();
for (int i = 0; i < length; i++) {
Tagged<Object> arg = wrapped_arguments->get(i);
DCHECK(IsString(arg));
}
}
#endif
}
bool ScriptCacheKey::IsMatch(Tagged<Object> other) {
DisallowGarbageCollection no_gc;
DCHECK(IsWeakFixedArray(other));
Tagged<WeakFixedArray> other_array = Cast<WeakFixedArray>(other);
DCHECK_EQ(other_array->length(), kEnd);
uint32_t other_hash =
static_cast<uint32_t>(other_array->get(kHash).ToSmi().value());
if (other_hash != Hash()) return false;
Tagged<HeapObject> other_script_object;
if (!other_array->get(kWeakScript)
.GetHeapObjectIfWeak(&other_script_object)) {
return false;
}
Tagged<Script> other_script = Cast<Script>(other_script_object);
Tagged<String> other_source = Cast<String>(other_script->source());
return other_source->Equals(*source_) && MatchesScript(other_script);
}
DirectHandle<Object> ScriptCacheKey::AsHandle(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared) {
DirectHandle<WeakFixedArray> array =
isolate->factory()->NewWeakFixedArray(kEnd);
DCHECK(IsScript(shared->script()));
array->set(kHash, Smi::FromInt(static_cast<int>(Hash())));
array->set(kWeakScript, MakeWeak(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(
DirectHandle<CompilationCacheTable> table, Handle<String> src,
const ScriptDetails& script_details, Isolate* isolate) {
src = String::Flatten(isolate, src);
ScriptCacheKey key(src, &script_details, isolate);
InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return {};
DisallowGarbageCollection no_gc;
Tagged<Object> key_in_table = table->KeyAt(entry);
Tagged<Script> script = Cast<Script>(Cast<WeakFixedArray>(key_in_table)
->get(ScriptCacheKey::kWeakScript)
.GetHeapObjectAssumeWeak());
Tagged<Object> obj = table->PrimaryValueAt(entry);
Tagged<SharedFunctionInfo> toplevel_sfi;
if (!IsUndefined(obj, isolate)) {
toplevel_sfi = Cast<SharedFunctionInfo>(obj);
DCHECK_EQ(toplevel_sfi->script(), script);
}
return CompilationCacheScriptLookupResult::FromRawObjects(
std::make_pair(script, toplevel_sfi), isolate);
}
InfoCellPair CompilationCacheTable::LookupEval(
DirectHandle<CompilationCacheTable> table, DirectHandle<String> src,
DirectHandle<SharedFunctionInfo> outer_info,
DirectHandle<NativeContext> native_context, LanguageMode language_mode,
int position) {
InfoCellPair empty_result;
Isolate* isolate = Isolate::Current();
src = String::Flatten(isolate, src);
EvalCacheKey key(src, outer_info, language_mode, position);
InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return empty_result;
if (!IsFixedArray(table->KeyAt(entry))) return empty_result;
Tagged<Object> obj = table->PrimaryValueAt(entry);
if (!IsSharedFunctionInfo(obj)) return empty_result;
static_assert(CompilationCacheShape::kEntrySize == 3);
Tagged<JSFunction> js_function =
SearchLiteralsMap(*table, entry, *native_context);
return InfoCellPair(isolate, Cast<SharedFunctionInfo>(obj), js_function);
}
DirectHandle<Object> CompilationCacheTable::LookupRegExp(
DirectHandle<String> src, JSRegExp::Flags flags) {
Isolate* isolate = Isolate::Current();
DisallowGarbageCollection no_gc;
RegExpKey key(isolate, src, flags);
InternalIndex entry = FindEntry(isolate, &key);
if (entry.is_not_found()) return isolate->factory()->undefined_value();
return DirectHandle<Object>(PrimaryValueAt(entry), isolate);
}
Handle<CompilationCacheTable> CompilationCacheTable::EnsureScriptTableCapacity(
Isolate* isolate, Handle<CompilationCacheTable> cache) {
if (cache->HasSufficientCapacityToAdd(1)) return cache;
{
DisallowGarbageCollection no_gc;
for (InternalIndex entry : cache->IterateEntries()) {
Tagged<Object> key;
if (!cache->ToKey(isolate, entry, &key)) continue;
if (Cast<WeakFixedArray>(key)
->get(ScriptCacheKey::kWeakScript)
.IsCleared()) {
DCHECK(IsUndefined(cache->PrimaryValueAt(entry)));
cache->RemoveEntry(entry);
}
}
}
return EnsureCapacity(isolate, cache);
}
DirectHandle<CompilationCacheTable> CompilationCacheTable::PutScript(
Handle<CompilationCacheTable> cache, Handle<String> src,
MaybeHandle<FixedArray> maybe_wrapped_arguments,
DirectHandle<SharedFunctionInfo> value, Isolate* isolate) {
src = String::Flatten(isolate, src);
DirectHandle<Script> script(Cast<Script>(value->script()), isolate);
MaybeHandle<Object> script_name;
if (IsString(script->name(), isolate)) {
script_name = handle(script->name(), isolate);
}
Handle<FixedArray> host_defined_options(script->host_defined_options(),
isolate);
ScriptCacheKey key(src, script_name, script->line_offset(),
script->column_offset(), script->origin_options(),
host_defined_options, maybe_wrapped_arguments, isolate);
DirectHandle<Object> k = key.AsHandle(isolate, value);
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());
}
cache->SetKeyAt(entry, *k);
cache->SetPrimaryValueAt(entry, *value);
if (!found_existing) {
cache->ElementAdded();
}
return cache;
}
void CompilationCacheTable::UpdateEval(
DirectHandle<CompilationCacheTable> table, DirectHandle<String> src,
DirectHandle<SharedFunctionInfo> outer_info,
DirectHandle<JSFunction> js_function, LanguageMode language_mode,
int position) {
Isolate* isolate = Isolate::Current();
src = String::Flatten(isolate, src);
EvalCacheKey key(src, outer_info, language_mode, position);
InternalIndex entry = table->FindEntry(isolate, &key);
if (entry.is_not_found()) return;
if (!IsFixedArray(table->KeyAt(entry))) return;
Tagged<Object> obj = table->PrimaryValueAt(entry);
if (!IsSharedFunctionInfo(obj)) return;
AddToJSFunctionMap(table, entry, js_function);
}
DirectHandle<CompilationCacheTable> CompilationCacheTable::PutEval(
DirectHandle<CompilationCacheTable> cache, DirectHandle<String> src,
DirectHandle<SharedFunctionInfo> outer_info,
DirectHandle<JSFunction> js_function, int position) {
Isolate* isolate = Isolate::Current();
src = String::Flatten(isolate, src);
EvalCacheKey key(src, outer_info, js_function->shared()->language_mode(),
position);
cache = EnsureCapacity(isolate, cache);
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
DirectHandle<Object> k = key.AsHandle(isolate);
cache->SetKeyAt(entry, *k);
cache->SetPrimaryValueAt(entry, js_function->shared());
cache->SetEvalJSFunctionsValueAt(entry,
ReadOnlyRoots(isolate).the_hole_value());
AddToJSFunctionMap(cache, entry, js_function);
cache->ElementAdded();
return cache;
}
DirectHandle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
Isolate* isolate, DirectHandle<CompilationCacheTable> cache,
DirectHandle<String> src, JSRegExp::Flags flags,
DirectHandle<RegExpData> value) {
RegExpKey key(isolate, src, flags);
cache = EnsureCapacity(isolate, cache);
InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
cache->SetKeyAt(entry, value->wrapper());
cache->SetPrimaryValueAt(entry, value->wrapper());
cache->ElementAdded();
return cache;
}
void CompilationCacheTable::Remove(Tagged<Object> value) {
DisallowGarbageCollection no_gc;
for (InternalIndex entry : IterateEntries()) {
if (PrimaryValueAt(entry) == value) {
RemoveEntry(entry);
}
}
}
void CompilationCacheTable::RemoveEntry(InternalIndex entry) {
int entry_index = EntryToIndex(entry);
Tagged<Object> the_hole_value = GetReadOnlyRoots().the_hole_value();
for (int i = 0; i < kEntrySize; i++) {
this->set(entry_index + i, the_hole_value, SKIP_WRITE_BARRIER);
}
ElementRemoved();
}
}
}