diff --git a/src/compilation-cache.cc b/src/compilation-cache.cc index 4c02d86ce2..1105945c5a 100644 --- a/src/compilation-cache.cc +++ b/src/compilation-cache.cc @@ -32,12 +32,20 @@ namespace v8 { namespace internal { enum { - NUMBER_OF_ENTRY_KINDS = CompilationCache::LAST_ENTRY + 1 + // The number of script generations tell how many GCs a script can + // survive in the compilation cache, before it will be flushed if it + // hasn't been used. + NUMBER_OF_SCRIPT_GENERATIONS = 5, + + // The compilation cache consists of tables - one for each entry + // kind plus extras for the script generations. + NUMBER_OF_TABLE_ENTRIES = + CompilationCache::LAST_ENTRY + NUMBER_OF_SCRIPT_GENERATIONS }; // Keep separate tables for the different entry kinds. -static Object* tables[NUMBER_OF_ENTRY_KINDS] = { 0, }; +static Object* tables[NUMBER_OF_TABLE_ENTRIES] = { 0, }; static Handle AllocateTable(int size) { @@ -121,41 +129,52 @@ static bool HasOrigin(Handle boilerplate, } -static Handle Lookup(Handle source, - CompilationCache::Entry entry) { - // Make sure not to leak the table into the surrounding handle - // scope. Otherwise, we risk keeping old tables around even after - // having cleared the cache. - Object* result; - { HandleScope scope; - Handle table = GetTable(entry); - result = table->Lookup(*source); - } - if (result->IsJSFunction()) { - return Handle(JSFunction::cast(result)); - } else { - return Handle::null(); - } -} - - -// TODO(245): Need to allow identical code from different contexts to be -// cached. Currently the first use will be cached, but subsequent code -// from different source / line won't. +// TODO(245): Need to allow identical code from different contexts to +// be cached in the same script generation. Currently the first use +// will be cached, but subsequent code from different source / line +// won't. Handle CompilationCache::LookupScript(Handle source, Handle name, int line_offset, int column_offset) { - Handle result = Lookup(source, SCRIPT); - if (result.is_null()) { - Counters::compilation_cache_misses.Increment(); - } else if (HasOrigin(result, name, line_offset, column_offset)) { - Counters::compilation_cache_hits.Increment(); - } else { - result = Handle::null(); - Counters::compilation_cache_misses.Increment(); + Object* result = NULL; + Entry generation = SCRIPT; // First generation. + + // Probe the script generation tables. Make sure not to leak handles + // into the caller's handle scope. + { HandleScope scope; + while (generation < SCRIPT + NUMBER_OF_SCRIPT_GENERATIONS) { + Handle table = GetTable(generation); + Handle probe(table->Lookup(*source)); + if (probe->IsJSFunction()) { + Handle boilerplate = Handle::cast(probe); + // Break when we've found a suitable boilerplate function that + // matches the origin. + if (HasOrigin(boilerplate, name, line_offset, column_offset)) { + result = *boilerplate; + break; + } + } + // Go to the next generation. + generation = static_cast(generation + 1); + } + } + + // Once outside the menacles 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. + if (result != NULL) { + Handle boilerplate(JSFunction::cast(result)); + ASSERT(HasOrigin(boilerplate, name, line_offset, column_offset)); + // If the script was found in a later generation, we promote it to + // the first generation to let it survive longer in the cache. + if (generation != SCRIPT) PutScript(source, boilerplate); + Counters::compilation_cache_hits.Increment(); + return boilerplate; + } else { + Counters::compilation_cache_misses.Increment(); + return Handle::null(); } - return result; } @@ -216,14 +235,25 @@ void CompilationCache::PutRegExp(Handle source, void CompilationCache::Clear() { - for (int i = 0; i < NUMBER_OF_ENTRY_KINDS; i++) { + for (int i = 0; i < NUMBER_OF_TABLE_ENTRIES; i++) { tables[i] = Heap::undefined_value(); } } void CompilationCache::Iterate(ObjectVisitor* v) { - v->VisitPointers(&tables[0], &tables[NUMBER_OF_ENTRY_KINDS]); + v->VisitPointers(&tables[0], &tables[NUMBER_OF_TABLE_ENTRIES]); +} + + +void CompilationCache::MarkCompactPrologue() { + ASSERT(LAST_ENTRY == SCRIPT); + for (int i = NUMBER_OF_TABLE_ENTRIES - 1; i > SCRIPT; i--) { + tables[i] = tables[i - 1]; + } + for (int j = 0; j <= LAST_ENTRY; j++) { + tables[j] = Heap::undefined_value(); + } } diff --git a/src/compilation-cache.h b/src/compilation-cache.h index 38a9e3a3a8..b10b5615bc 100644 --- a/src/compilation-cache.h +++ b/src/compilation-cache.h @@ -40,11 +40,11 @@ class CompilationCache { // scripts and evals. Internally, we use separate caches to avoid // getting the wrong kind of entry when looking up. enum Entry { - SCRIPT, EVAL_GLOBAL, EVAL_CONTEXTUAL, REGEXP, - LAST_ENTRY = REGEXP + SCRIPT, + LAST_ENTRY = SCRIPT }; // Finds the script function boilerplate for a source @@ -93,10 +93,8 @@ class CompilationCache { // Notify the cache that a mark-sweep garbage collection is about to // take place. This is used to retire entries from the cache to - // avoid keeping them alive too long without using them. For now, we - // just clear the cache but we should consider are more - // sophisticated LRU scheme. - static void MarkCompactPrologue() { Clear(); } + // avoid keeping them alive too long without using them. + static void MarkCompactPrologue(); }; diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 7c834c7a41..59e3e50d04 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -30,6 +30,7 @@ #include "v8.h" #include "api.h" +#include "compilation-cache.h" #include "snapshot.h" #include "platform.h" #include "top.h" @@ -464,6 +465,7 @@ THREADED_TEST(ScriptUsingStringResource) { v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(0, TestResource::dispose_count); } + v8::internal::CompilationCache::Clear(); v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(1, TestResource::dispose_count); } @@ -484,6 +486,7 @@ THREADED_TEST(ScriptUsingAsciiStringResource) { v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(0, TestAsciiResource::dispose_count); } + v8::internal::CompilationCache::Clear(); v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(1, TestAsciiResource::dispose_count); } @@ -505,6 +508,7 @@ THREADED_TEST(ScriptMakingExternalString) { v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(0, TestResource::dispose_count); } + v8::internal::CompilationCache::Clear(); v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(1, TestResource::dispose_count); } @@ -527,6 +531,7 @@ THREADED_TEST(ScriptMakingExternalAsciiString) { v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(0, TestAsciiResource::dispose_count); } + v8::internal::CompilationCache::Clear(); v8::internal::Heap::CollectAllGarbage(); CHECK_EQ(1, TestAsciiResource::dispose_count); } diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc index 288efbaed3..9e5cf6d3c6 100644 --- a/test/cctest/test-debug.cc +++ b/test/cctest/test-debug.cc @@ -30,6 +30,7 @@ #include "v8.h" #include "api.h" +#include "compilation-cache.h" #include "debug.h" #include "platform.h" #include "stub-cache.h" @@ -1678,6 +1679,11 @@ TEST(ScriptBreakPointIgnoreCount) { } CHECK_EQ(5, break_point_hit_count); + // BUG(343): It should not really be necessary to clear the + // compilation cache here, but right now the debugger relies on the + // script being recompiled, not just fetched from the cache. + i::CompilationCache::Clear(); + // Reload the script and get f again checking that the ignore survives. v8::Script::Compile(script, &origin)->Run(); f = v8::Local::Cast(env->Global()->Get(v8::String::New("f")));