Revision: 4582
Author: [email protected]
Date: Tue May  4 09:42:11 2010
Log: Clean JS function results cache on each major GC.

We don't want to retain cached objects for too long.

Review URL: http://codereview.chromium.org/1780001
http://code.google.com/p/v8/source/detail?r=4582

Modified:
 /branches/bleeding_edge/src/bootstrapper.cc
 /branches/bleeding_edge/src/heap.cc
 /branches/bleeding_edge/src/heap.h
 /branches/bleeding_edge/src/objects-debug.cc
 /branches/bleeding_edge/src/objects-inl.h
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/test/cctest/test-threads.cc

=======================================
--- /branches/bleeding_edge/src/bootstrapper.cc Thu Apr 29 03:39:36 2010
+++ /branches/bleeding_edge/src/bootstrapper.cc Tue May  4 09:42:11 2010
@@ -1335,14 +1335,12 @@
 static FixedArray* CreateCache(int size, JSFunction* factory) {
   // Caches are supposed to live for a long time, allocate in old space.
   int array_size = JSFunctionResultCache::kEntriesIndex + 2 * size;
-  Handle<FixedArray> cache =
-      Factory::NewFixedArrayWithHoles(array_size, TENURED);
+  // Cannot use cast as object is not fully initialized yet.
+  JSFunctionResultCache* cache = reinterpret_cast<JSFunctionResultCache*>(
+      *Factory::NewFixedArrayWithHoles(array_size, TENURED));
   cache->set(JSFunctionResultCache::kFactoryIndex, factory);
-  cache->set(JSFunctionResultCache::kFingerIndex,
-      Smi::FromInt(JSFunctionResultCache::kEntriesIndex));
-  cache->set(JSFunctionResultCache::kCacheSizeIndex,
-      Smi::FromInt(JSFunctionResultCache::kEntriesIndex));
-  return *cache;
+  cache->MakeZeroSize();
+  return cache;
 }


=======================================
--- /branches/bleeding_edge/src/heap.cc Thu Apr 29 07:01:37 2010
+++ /branches/bleeding_edge/src/heap.cc Tue May  4 09:42:11 2010
@@ -306,6 +306,7 @@

 void Heap::GarbageCollectionPrologue() {
   TranscendentalCache::Clear();
+  ClearJSFunctionResultCaches();
   gc_count_++;
   unflattened_strings_length_ = 0;
 #ifdef DEBUG
@@ -539,6 +540,28 @@
   // Memory is exhausted and we will die.
   V8::FatalProcessOutOfMemory("Committing semi space failed.");
 }
+
+
+class ClearThreadJSFunctionResultCachesVisitor: public ThreadVisitor  {
+  virtual void VisitThread(ThreadLocalTop* top) {
+    Context* context = top->context_;
+    if (context == NULL) return;
+
+    FixedArray* caches =
+      context->global()->global_context()->jsfunction_result_caches();
+    int length = caches->length();
+    for (int i = 0; i < length; i++) {
+      JSFunctionResultCache::cast(caches->get(i))->Clear();
+    }
+  }
+};
+
+
+void Heap::ClearJSFunctionResultCaches() {
+  if (Bootstrapper::IsActive()) return;
+  ClearThreadJSFunctionResultCachesVisitor visitor;
+  ThreadManager::IterateThreads(&visitor);
+}


 void Heap::PerformGarbageCollection(AllocationSpace space,
=======================================
--- /branches/bleeding_edge/src/heap.h  Thu Apr 29 07:01:37 2010
+++ /branches/bleeding_edge/src/heap.h  Tue May  4 09:42:11 2010
@@ -1178,6 +1178,8 @@
                                           HeapObject* target,
                                           int size);

+  static void ClearJSFunctionResultCaches();
+
 #if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING)
   // Record the copy of an object in the NewSpace's statistics.
   static void RecordCopiedObject(HeapObject* obj);
=======================================
--- /branches/bleeding_edge/src/objects-debug.cc        Mon Apr 19 05:39:07 2010
+++ /branches/bleeding_edge/src/objects-debug.cc        Tue May  4 09:42:11 2010
@@ -1326,6 +1326,32 @@
   }
   return true;
 }
+
+
+void JSFunctionResultCache::JSFunctionResultCacheVerify() {
+  JSFunction::cast(get(kFactoryIndex))->Verify();
+
+  int size = Smi::cast(get(kCacheSizeIndex))->value();
+  ASSERT(kEntriesIndex <= size);
+  ASSERT(size <= length());
+  ASSERT_EQ(0, size % kEntrySize);
+
+  int finger = Smi::cast(get(kFingerIndex))->value();
+  ASSERT(kEntriesIndex <= finger);
+  ASSERT(finger < size || finger == kEntriesIndex);
+  ASSERT_EQ(0, finger % kEntrySize);
+
+  if (FLAG_enable_slow_asserts) {
+    for (int i = kEntriesIndex; i < size; i++) {
+      ASSERT(!get(i)->IsTheHole());
+      get(i)->Verify();
+    }
+    for (int i = size; i < length(); i++) {
+      ASSERT(get(i)->IsTheHole());
+      get(i)->Verify();
+    }
+  }
+}


 #endif  // DEBUG
=======================================
--- /branches/bleeding_edge/src/objects-inl.h   Tue May  4 07:49:50 2010
+++ /branches/bleeding_edge/src/objects-inl.h   Tue May  4 09:42:11 2010
@@ -567,6 +567,22 @@
 bool Object::IsSymbolTable() {
   return IsHashTable() && this == Heap::raw_unchecked_symbol_table();
 }
+
+
+bool Object::IsJSFunctionResultCache() {
+  if (!IsFixedArray()) return false;
+  FixedArray* self = FixedArray::cast(this);
+  int length = self->length();
+  if (length < JSFunctionResultCache::kEntriesIndex) return false;
+  if ((length - JSFunctionResultCache::kEntriesIndex)
+      % JSFunctionResultCache::kEntrySize != 0) {
+    return false;
+  }
+#ifdef DEBUG
+ reinterpret_cast<JSFunctionResultCache*>(this)->JSFunctionResultCacheVerify();
+#endif
+  return true;
+}


 bool Object::IsCompilationCacheTable() {
@@ -1594,6 +1610,7 @@
 CAST_ACCESSOR(FixedArray)
 CAST_ACCESSOR(DescriptorArray)
 CAST_ACCESSOR(SymbolTable)
+CAST_ACCESSOR(JSFunctionResultCache)
 CAST_ACCESSOR(CompilationCacheTable)
 CAST_ACCESSOR(CodeCacheHashTable)
 CAST_ACCESSOR(MapCache)
@@ -1834,6 +1851,20 @@
     ExternalTwoByteString::Resource* resource) {
*reinterpret_cast<Resource**>(FIELD_ADDR(this, kResourceOffset)) = resource;
 }
+
+
+void JSFunctionResultCache::MakeZeroSize() {
+  set(kFingerIndex, Smi::FromInt(kEntriesIndex));
+  set(kCacheSizeIndex, Smi::FromInt(kEntriesIndex));
+}
+
+
+void JSFunctionResultCache::Clear() {
+  int cache_size = Smi::cast(get(kCacheSizeIndex))->value();
+ Object** entries_start = RawField(this, OffsetOfElementAt(kEntriesIndex));
+  MemsetPointer(entries_start, Heap::the_hole_value(), cache_size);
+  MakeZeroSize();
+}


 byte ByteArray::get(int index) {
=======================================
--- /branches/bleeding_edge/src/objects.h       Tue May  4 07:49:50 2010
+++ /branches/bleeding_edge/src/objects.h       Tue May  4 09:42:11 2010
@@ -606,6 +606,7 @@
   inline bool IsHashTable();
   inline bool IsDictionary();
   inline bool IsSymbolTable();
+  inline bool IsJSFunctionResultCache();
   inline bool IsCompilationCacheTable();
   inline bool IsCodeCacheHashTable();
   inline bool IsMapCache();
@@ -2326,6 +2327,16 @@
   static const int kEntriesIndex = kDummyIndex + 1;

   static const int kEntrySize = 2;  // key + value
+
+  inline void MakeZeroSize();
+  inline void Clear();
+
+  // Casting
+  static inline JSFunctionResultCache* cast(Object* obj);
+
+#ifdef DEBUG
+  void JSFunctionResultCacheVerify();
+#endif
 };


=======================================
--- /branches/bleeding_edge/test/cctest/test-threads.cc Wed Aug 19 08:14:11 2009 +++ /branches/bleeding_edge/test/cctest/test-threads.cc Tue May 4 09:42:11 2010
@@ -50,3 +50,87 @@

   script->Run();
 }
+
+
+enum Turn {
+  FILL_CACHE,
+  CLEAN_CACHE,
+  SECOND_TIME_FILL_CACHE,
+  DONE
+};
+
+static Turn turn = FILL_CACHE;
+
+
+class ThreadA: public v8::internal::Thread {
+ public:
+  void Run() {
+    v8::Locker locker;
+    v8::HandleScope scope;
+    v8::Context::Scope context_scope(v8::Context::New());
+
+    CHECK_EQ(FILL_CACHE, turn);
+
+    // Fill String.search cache.
+    v8::Handle<v8::Script> script = v8::Script::Compile(
+        v8::String::New(
+          "for (var i = 0; i < 3; i++) {"
+          "  var result = \"a\".search(\"a\");"
+          "  if (result != 0) throw \"result: \" + result + \" @\" + i;"
+          "};"
+          "true"));
+    CHECK(script->Run()->IsTrue());
+
+    turn = CLEAN_CACHE;
+    do {
+      {
+        v8::Unlocker unlocker;
+        Thread::YieldCPU();
+      }
+    } while (turn != SECOND_TIME_FILL_CACHE);
+
+    // Rerun the script.
+    CHECK(script->Run()->IsTrue());
+
+    turn = DONE;
+  }
+};
+
+
+class ThreadB: public v8::internal::Thread {
+ public:
+  void Run() {
+    do {
+      {
+        v8::Locker locker;
+        if (turn == CLEAN_CACHE) {
+          v8::HandleScope scope;
+          v8::Context::Scope context_scope(v8::Context::New());
+
+          // Clear the caches by forcing major GC.
+          v8::internal::Heap::CollectAllGarbage(false);
+          turn = SECOND_TIME_FILL_CACHE;
+          break;
+        }
+      }
+
+      Thread::YieldCPU();
+    } while (true);
+  }
+};
+
+
+TEST(JSFunctionResultCachesInTwoThreads) {
+  v8::V8::Initialize();
+
+  ThreadA threadA;
+  ThreadB threadB;
+
+  threadA.Start();
+  threadB.Start();
+
+  threadA.Join();
+  threadB.Join();
+
+  CHECK_EQ(DONE, turn);
+}

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to