Add multiple generations (5) to the script compilation cache
authorkasperl@chromium.org <kasperl@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 15 May 2009 06:45:50 +0000 (06:45 +0000)
committerkasperl@chromium.org <kasperl@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 15 May 2009 06:45:50 +0000 (06:45 +0000)
to allow scripts that are used alot to survive a number of GCs
in the compilation cache.
Review URL: http://codereview.chromium.org/113445

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1955 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/compilation-cache.cc
src/compilation-cache.h
test/cctest/test-api.cc
test/cctest/test-debug.cc

index 4c02d86ce28cc90c4f1e9cd7113a7f5f0a68b046..1105945c5ad1d4d359fd8ecc695d09ca4e6160c1 100644 (file)
 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<CompilationCacheTable> AllocateTable(int size) {
@@ -121,41 +129,52 @@ static bool HasOrigin(Handle<JSFunction> boilerplate,
 }
 
 
-static Handle<JSFunction> Lookup(Handle<String> 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<CompilationCacheTable> table = GetTable(entry);
-    result = table->Lookup(*source);
-  }
-  if (result->IsJSFunction()) {
-    return Handle<JSFunction>(JSFunction::cast(result));
-  } else {
-    return Handle<JSFunction>::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<JSFunction> CompilationCache::LookupScript(Handle<String> source,
                                                   Handle<Object> name,
                                                   int line_offset,
                                                   int column_offset) {
-  Handle<JSFunction> result = Lookup(source, SCRIPT);
-  if (result.is_null()) {
-    Counters::compilation_cache_misses.Increment();
-  } else if (HasOrigin(result, name, line_offset, column_offset)) {
+  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<CompilationCacheTable> table = GetTable(generation);
+      Handle<Object> probe(table->Lookup(*source));
+      if (probe->IsJSFunction()) {
+        Handle<JSFunction> boilerplate = Handle<JSFunction>::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<Entry>(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<JSFunction> 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 {
-    result = Handle<JSFunction>::null();
     Counters::compilation_cache_misses.Increment();
+    return Handle<JSFunction>::null();
   }
-  return result;
 }
 
 
@@ -216,14 +235,25 @@ void CompilationCache::PutRegExp(Handle<String> 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();
+  }
 }
 
 
index 38a9e3a3a857e4fceeae7c99691ab47806cbd86b..b10b5615bc7f0f6e7dea921360798a0f754eb8f1 100644 (file)
@@ -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();
 };
 
 
index 7c834c7a410e6120b00f36db97f76664c3de4d6f..59e3e50d0451aa29129b2d4d9b90a9e10d3423cd 100644 (file)
@@ -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);
 }
index 288efbaed39d4c52adc0649dacb125251b38c35a..9e5cf6d3c6508ba9b7e3261f27c84e96a23bf56d 100644 (file)
@@ -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<v8::Function>::Cast(env->Global()->Get(v8::String::New("f")));