From 6617fac3d4b74d7ed8cbcfade026c598a85e3a26 Mon Sep 17 00:00:00 2001 From: "antonm@chromium.org" Date: Tue, 4 May 2010 16:42:11 +0000 Subject: [PATCH] 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 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4582 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/bootstrapper.cc | 12 +++---- src/heap.cc | 23 +++++++++++++ src/heap.h | 2 ++ src/objects-debug.cc | 26 ++++++++++++++ src/objects-inl.h | 31 +++++++++++++++++ src/objects.h | 11 ++++++ test/cctest/test-threads.cc | 84 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 7 deletions(-) diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index d0e5e6b..657d0dc 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1335,14 +1335,12 @@ bool Genesis::InstallNatives() { 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 cache = - Factory::NewFixedArrayWithHoles(array_size, TENURED); + // Cannot use cast as object is not fully initialized yet. + JSFunctionResultCache* cache = reinterpret_cast( + *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; } diff --git a/src/heap.cc b/src/heap.cc index 55d6c97..193f082 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -306,6 +306,7 @@ void Heap::ReportStatisticsAfterGC() { void Heap::GarbageCollectionPrologue() { TranscendentalCache::Clear(); + ClearJSFunctionResultCaches(); gc_count_++; unflattened_strings_length_ = 0; #ifdef DEBUG @@ -541,6 +542,28 @@ void Heap::EnsureFromSpaceIsCommitted() { } +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, GarbageCollector collector, GCTracer* tracer) { diff --git a/src/heap.h b/src/heap.h index cdfa6cc..902fc77 100644 --- a/src/heap.h +++ b/src/heap.h @@ -1178,6 +1178,8 @@ class Heap : public AllStatic { 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); diff --git a/src/objects-debug.cc b/src/objects-debug.cc index c82d194..b0a3fd6 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -1328,6 +1328,32 @@ bool DescriptorArray::IsSortedNoDuplicates() { } +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 } } // namespace v8::internal diff --git a/src/objects-inl.h b/src/objects-inl.h index f1e8596..ae7d2c2 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -569,6 +569,22 @@ bool Object::IsSymbolTable() { } +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(this)->JSFunctionResultCacheVerify(); +#endif + return true; +} + + bool Object::IsCompilationCacheTable() { return IsHashTable(); } @@ -1594,6 +1610,7 @@ void NumberDictionary::set_requires_slow_elements() { CAST_ACCESSOR(FixedArray) CAST_ACCESSOR(DescriptorArray) CAST_ACCESSOR(SymbolTable) +CAST_ACCESSOR(JSFunctionResultCache) CAST_ACCESSOR(CompilationCacheTable) CAST_ACCESSOR(CodeCacheHashTable) CAST_ACCESSOR(MapCache) @@ -1836,6 +1853,20 @@ void ExternalTwoByteString::set_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) { ASSERT(index >= 0 && index < this->length()); return READ_BYTE_FIELD(this, kHeaderSize + index * kCharSize); diff --git a/src/objects.h b/src/objects.h index ad40175..dcfb2ee 100644 --- a/src/objects.h +++ b/src/objects.h @@ -606,6 +606,7 @@ class Object BASE_EMBEDDED { 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 @@ class JSFunctionResultCache: public FixedArray { 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 }; diff --git a/test/cctest/test-threads.cc b/test/cctest/test-threads.cc index f70c4e8..0f48e24 100644 --- a/test/cctest/test-threads.cc +++ b/test/cctest/test-threads.cc @@ -50,3 +50,87 @@ TEST(Preemption) { 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 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); +} -- 2.7.4