// The number of generations for each sub cache.
-// The number of ScriptGenerations is carefully chosen based on histograms.
-// See issue 458: http://code.google.com/p/v8/issues/detail?id=458
-static const int kScriptGenerations = 5;
-static const int kEvalGlobalGenerations = 2;
-static const int kEvalContextualGenerations = 2;
static const int kRegExpGenerations = 2;
// Initial size of each compilation cache table allocated.
CompilationCache::CompilationCache(Isolate* isolate)
: isolate_(isolate),
- script_(isolate, kScriptGenerations),
- eval_global_(isolate, kEvalGlobalGenerations),
- eval_contextual_(isolate, kEvalContextualGenerations),
+ script_(isolate, 1),
+ eval_global_(isolate, 1),
+ eval_contextual_(isolate, 1),
reg_exp_(isolate, kRegExpGenerations),
enabled_(true) {
CompilationSubCache* subcaches[kSubCacheCount] =
void CompilationSubCache::Age() {
+ // Don't directly age single-generation caches.
+ if (generations_ == 1) {
+ if (tables_[0] != isolate()->heap()->undefined_value()) {
+ CompilationCacheTable::cast(tables_[0])->Age();
+ }
+ return;
+ }
+
// Age the generations implicitly killing off the oldest.
for (int i = generations_ - 1; i > 0; i--) {
tables_[i] = tables_[i - 1];
CompilationCacheScript::CompilationCacheScript(Isolate* isolate,
int generations)
- : CompilationSubCache(isolate, generations),
- script_histogram_(NULL),
- script_histogram_initialized_(false) { }
+ : CompilationSubCache(isolate, generations) {}
// We only re-use a cached function for some script source code if the
}
}
- if (!script_histogram_initialized_) {
- script_histogram_ = isolate()->stats_table()->CreateHistogram(
- "V8.ScriptCache",
- 0,
- kScriptGenerations,
- kScriptGenerations + 1);
- script_histogram_initialized_ = true;
- }
-
- if (script_histogram_ != NULL) {
- // The level NUMBER_OF_SCRIPT_GENERATIONS is equivalent to a cache miss.
- isolate()->stats_table()->AddHistogramSample(script_histogram_, generation);
- }
-
// 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.
int column_offset,
bool is_shared_cross_origin);
- void* script_histogram_;
- bool script_histogram_initialized_;
-
DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript);
};
bool IsMatch(Object* other) OVERRIDE {
DisallowHeapAllocation no_allocation;
- if (!other->IsFixedArray()) return false;
+ if (!other->IsFixedArray()) {
+ if (!other->IsNumber()) return false;
+ uint32_t other_hash = static_cast<uint32_t>(other->Number());
+ return Hash() == other_hash;
+ }
FixedArray* other_array = FixedArray::cast(other);
SharedFunctionInfo* shared = SharedFunctionInfo::cast(other_array->get(0));
if (shared != *shared_) return false;
uint32_t HashForObject(Object* obj) OVERRIDE {
DisallowHeapAllocation no_allocation;
+ if (obj->IsNumber()) {
+ return static_cast<uint32_t>(obj->Number());
+ }
FixedArray* other_array = FixedArray::cast(obj);
SharedFunctionInfo* shared = SharedFunctionInfo::cast(other_array->get(0));
String* source = String::cast(other_array->get(1));
RelocInfo::kNoPosition);
int entry = FindEntry(&key);
if (entry == kNotFound) return isolate->factory()->undefined_value();
- return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
+ int index = EntryToIndex(entry);
+ if (!get(index)->IsFixedArray()) return isolate->factory()->undefined_value();
+ return Handle<Object>(get(index + 1), isolate);
}
StringSharedKey key(src, outer_info, strict_mode, scope_position);
int entry = FindEntry(&key);
if (entry == kNotFound) return isolate->factory()->undefined_value();
+ int index = EntryToIndex(entry);
+ if (!get(index)->IsFixedArray()) return isolate->factory()->undefined_value();
return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
}
Handle<SharedFunctionInfo> shared(context->closure()->shared());
StringSharedKey key(src, shared, FLAG_use_strict ? STRICT : SLOPPY,
RelocInfo::kNoPosition);
+ int entry = cache->FindEntry(&key);
+ if (entry != kNotFound) {
+ Handle<Object> k = key.AsHandle(isolate);
+ cache->set(EntryToIndex(entry), *k);
+ cache->set(EntryToIndex(entry) + 1, *value);
+ return cache;
+ }
+
cache = EnsureCapacity(cache, 1, &key);
- Handle<Object> k = key.AsHandle(isolate);
- int entry = cache->FindInsertionEntry(key.Hash());
+ entry = cache->FindInsertionEntry(key.Hash());
+ Handle<Object> k =
+ isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
cache->set(EntryToIndex(entry), *k);
- cache->set(EntryToIndex(entry) + 1, *value);
+ cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
cache->ElementAdded();
return cache;
}
int scope_position) {
Isolate* isolate = cache->GetIsolate();
StringSharedKey key(src, outer_info, value->strict_mode(), scope_position);
+ int entry = cache->FindEntry(&key);
+ if (entry != kNotFound) {
+ Handle<Object> k = key.AsHandle(isolate);
+ cache->set(EntryToIndex(entry), *k);
+ cache->set(EntryToIndex(entry) + 1, *value);
+ return cache;
+ }
+
cache = EnsureCapacity(cache, 1, &key);
- Handle<Object> k = key.AsHandle(isolate);
- int entry = cache->FindInsertionEntry(key.Hash());
+ entry = cache->FindInsertionEntry(key.Hash());
+ Handle<Object> k =
+ isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
cache->set(EntryToIndex(entry), *k);
- cache->set(EntryToIndex(entry) + 1, *value);
+ cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
cache->ElementAdded();
return cache;
}
}
+void CompilationCacheTable::Age() {
+ DisallowHeapAllocation no_allocation;
+ Object* the_hole_value = GetHeap()->the_hole_value();
+ for (int entry = 0, size = Capacity(); entry < size; entry++) {
+ int entry_index = EntryToIndex(entry);
+ int value_index = entry_index + 1;
+
+ if (get(entry_index)->IsNumber()) {
+ Smi* count = Smi::cast(get(value_index));
+ count = Smi::FromInt(count->value() - 1);
+ if (count->value() == 0) {
+ NoWriteBarrierSet(this, entry_index, the_hole_value);
+ NoWriteBarrierSet(this, value_index, the_hole_value);
+ ElementRemoved();
+ } else {
+ NoWriteBarrierSet(this, value_index, count);
+ }
+ } else if (get(entry_index)->IsFixedArray()) {
+ SharedFunctionInfo* info = SharedFunctionInfo::cast(get(value_index));
+ if (info->code()->kind() != Code::FUNCTION || info->code()->IsOld()) {
+ NoWriteBarrierSet(this, entry_index, the_hole_value);
+ NoWriteBarrierSet(this, value_index, the_hole_value);
+ ElementRemoved();
+ }
+ }
+ }
+}
+
+
void CompilationCacheTable::Remove(Object* value) {
DisallowHeapAllocation no_allocation;
Object* the_hole_value = GetHeap()->the_hole_value();
};
+// This cache is used in two different variants. For regexp caching, it simply
+// maps identifying info of the regexp to the cached regexp object. Scripts and
+// eval code only gets cached after a second probe for the code object. To do
+// so, on first "put" only a hash identifying the source is entered into the
+// cache, mapping it to a lifetime count of the hash. On each call to Age all
+// such lifetimes get reduced, and removed once they reach zero. If a second put
+// is called while such a hash is live in the cache, the hash gets replaced by
+// an actual cache entry. Age also removes stale live entries from the cache.
+// Such entries are identified by SharedFunctionInfos pointing to either the
+// recompilation stub, or to "old" code. This avoids memory leaks due to
+// premature caching of scripts and eval strings that are never needed later.
class CompilationCacheTable: public HashTable<CompilationCacheTable,
CompilationCacheShape,
HashTableKey*> {
Handle<CompilationCacheTable> cache, Handle<String> src,
JSRegExp::Flags flags, Handle<FixedArray> value);
void Remove(Object* value);
+ void Age();
+ static const int kHashGenerations = 10;
DECLARE_CAST(CompilationCacheTable)
}
+TEST(CompilationCacheCachingBehavior) {
+ // If we do not flush code, or have the compilation cache turned off, this
+ // test is invalid.
+ if (!FLAG_flush_code || !FLAG_flush_code_incrementally ||
+ !FLAG_compilation_cache) {
+ return;
+ }
+ CcTest::InitializeVM();
+ Isolate* isolate = CcTest::i_isolate();
+ Factory* factory = isolate->factory();
+ Heap* heap = isolate->heap();
+ CompilationCache* compilation_cache = isolate->compilation_cache();
+
+ v8::HandleScope scope(CcTest::isolate());
+ const char* raw_source =
+ "function foo() {"
+ " var x = 42;"
+ " var y = 42;"
+ " var z = x + y;"
+ "};"
+ "foo()";
+ Handle<String> source = factory->InternalizeUtf8String(raw_source);
+ Handle<Context> native_context = isolate->native_context();
+
+ {
+ v8::HandleScope scope(CcTest::isolate());
+ CompileRun(raw_source);
+ }
+
+ // On first compilation, only a hash is inserted in the code cache. We can't
+ // find that value.
+ MaybeHandle<SharedFunctionInfo> info = compilation_cache->LookupScript(
+ source, Handle<Object>(), 0, 0, true, native_context);
+ CHECK(info.is_null());
+
+ {
+ v8::HandleScope scope(CcTest::isolate());
+ CompileRun(raw_source);
+ }
+
+ // On second compilation, the hash is replaced by a real cache entry mapping
+ // the source to the shared function info containing the code.
+ info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+ native_context);
+ CHECK(!info.is_null());
+
+ heap->CollectAllGarbage(Heap::kNoGCFlags);
+
+ // On second compilation, the hash is replaced by a real cache entry mapping
+ // the source to the shared function info containing the code.
+ info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+ native_context);
+ CHECK(!info.is_null());
+
+ while (!info.ToHandleChecked()->code()->IsOld()) {
+ info.ToHandleChecked()->code()->MakeOlder(NO_MARKING_PARITY);
+ }
+
+ heap->CollectAllGarbage(Heap::kNoGCFlags);
+ // Ensure code aging cleared the entry from the cache.
+ info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+ native_context);
+ CHECK(info.is_null());
+
+ {
+ v8::HandleScope scope(CcTest::isolate());
+ CompileRun(raw_source);
+ }
+
+ // On first compilation, only a hash is inserted in the code cache. We can't
+ // find that value.
+ info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+ native_context);
+ CHECK(info.is_null());
+
+ for (int i = 0; i < CompilationCacheTable::kHashGenerations; i++) {
+ compilation_cache->MarkCompactPrologue();
+ }
+
+ {
+ v8::HandleScope scope(CcTest::isolate());
+ CompileRun(raw_source);
+ }
+
+ // If we aged the cache before caching the script, ensure that we didn't cache
+ // on next compilation.
+ info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+ native_context);
+ CHECK(info.is_null());
+}
+
+
// Count the number of native contexts in the weak list of native contexts.
int CountNativeContexts() {
int count = 0;