// Copyright 2011 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/codegen/compilation-cache.h" #include "src/common/globals.h" #include "src/heap/factory.h" #include "src/logging/counters.h" #include "src/logging/log.h" #include "src/objects/compilation-cache-table-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/slots.h" #include "src/objects/visitors.h" #include "src/utils/ostreams.h" namespace v8 { namespace internal { // Initial size of each compilation cache table allocated. static const int kInitialCacheSize = 64; CompilationCache::CompilationCache(Isolate* isolate) : isolate_(isolate), script_(isolate), eval_global_(isolate), eval_contextual_(isolate), reg_exp_(isolate), enabled_script_and_eval_(true) {} Handle CompilationCacheEvalOrScript::GetTable() { if (table_.IsUndefined(isolate())) { return CompilationCacheTable::New(isolate(), kInitialCacheSize); } return handle(CompilationCacheTable::cast(table_), isolate()); } Handle CompilationCacheRegExp::GetTable(int generation) { DCHECK_LT(generation, kGenerations); Handle result; if (tables_[generation].IsUndefined(isolate())) { result = CompilationCacheTable::New(isolate(), kInitialCacheSize); tables_[generation] = *result; } else { CompilationCacheTable table = CompilationCacheTable::cast(tables_[generation]); result = Handle(table, isolate()); } return result; } void CompilationCacheRegExp::Age() { static_assert(kGenerations > 1); // Age the generations implicitly killing off the oldest. for (int i = kGenerations - 1; i > 0; i--) { tables_[i] = tables_[i - 1]; } // Set the first generation as unborn. tables_[0] = ReadOnlyRoots(isolate()).undefined_value(); } void CompilationCacheScript::Age() { DisallowGarbageCollection no_gc; if (!v8_flags.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.HasBytecodeArray() || info.GetBytecodeArray(isolate()).IsOld()) { table.SetPrimaryValueAt(entry, ReadOnlyRoots(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); } } } } void CompilationCacheEvalOrScript::Iterate(RootVisitor* v) { v->VisitRootPointer(Root::kCompilationCache, nullptr, FullObjectSlot(&table_)); } void CompilationCacheRegExp::Iterate(RootVisitor* v) { v->VisitRootPointers(Root::kCompilationCache, nullptr, FullObjectSlot(&tables_[0]), FullObjectSlot(&tables_[kGenerations])); } void CompilationCacheEvalOrScript::Clear() { table_ = ReadOnlyRoots(isolate()).undefined_value(); } void CompilationCacheRegExp::Clear() { MemsetPointer(reinterpret_cast(tables_), ReadOnlyRoots(isolate()).undefined_value().ptr(), kGenerations); } void CompilationCacheEvalOrScript::Remove( Handle function_info) { if (table_.IsUndefined(isolate())) return; CompilationCacheTable::cast(table_).Remove(*function_info); } CompilationCacheScript::LookupResult CompilationCacheScript::Lookup( Handle source, const ScriptDetails& script_details) { LookupResult result; LookupResult::RawObjects raw_result_for_escaping_handle_scope; // Probe the script table. Make sure not to leak handles // into the caller's handle scope. { HandleScope scope(isolate()); Handle table = GetTable(); LookupResult probe = CompilationCacheTable::LookupScript( table, source, script_details, isolate()); raw_result_for_escaping_handle_scope = probe.GetRawObjects(); } result = LookupResult::FromRawObjects(raw_result_for_escaping_handle_scope, 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