Commit 6617fac3 authored by antonm@chromium.org's avatar antonm@chromium.org

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
parent 16d3811d
...@@ -1335,14 +1335,12 @@ bool Genesis::InstallNatives() { ...@@ -1335,14 +1335,12 @@ bool Genesis::InstallNatives() {
static FixedArray* CreateCache(int size, JSFunction* factory) { static FixedArray* CreateCache(int size, JSFunction* factory) {
// Caches are supposed to live for a long time, allocate in old space. // Caches are supposed to live for a long time, allocate in old space.
int array_size = JSFunctionResultCache::kEntriesIndex + 2 * size; int array_size = JSFunctionResultCache::kEntriesIndex + 2 * size;
Handle<FixedArray> cache = // Cannot use cast as object is not fully initialized yet.
Factory::NewFixedArrayWithHoles(array_size, TENURED); JSFunctionResultCache* cache = reinterpret_cast<JSFunctionResultCache*>(
*Factory::NewFixedArrayWithHoles(array_size, TENURED));
cache->set(JSFunctionResultCache::kFactoryIndex, factory); cache->set(JSFunctionResultCache::kFactoryIndex, factory);
cache->set(JSFunctionResultCache::kFingerIndex, cache->MakeZeroSize();
Smi::FromInt(JSFunctionResultCache::kEntriesIndex)); return cache;
cache->set(JSFunctionResultCache::kCacheSizeIndex,
Smi::FromInt(JSFunctionResultCache::kEntriesIndex));
return *cache;
} }
......
...@@ -306,6 +306,7 @@ void Heap::ReportStatisticsAfterGC() { ...@@ -306,6 +306,7 @@ void Heap::ReportStatisticsAfterGC() {
void Heap::GarbageCollectionPrologue() { void Heap::GarbageCollectionPrologue() {
TranscendentalCache::Clear(); TranscendentalCache::Clear();
ClearJSFunctionResultCaches();
gc_count_++; gc_count_++;
unflattened_strings_length_ = 0; unflattened_strings_length_ = 0;
#ifdef DEBUG #ifdef DEBUG
...@@ -541,6 +542,28 @@ void Heap::EnsureFromSpaceIsCommitted() { ...@@ -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, void Heap::PerformGarbageCollection(AllocationSpace space,
GarbageCollector collector, GarbageCollector collector,
GCTracer* tracer) { GCTracer* tracer) {
......
...@@ -1178,6 +1178,8 @@ class Heap : public AllStatic { ...@@ -1178,6 +1178,8 @@ class Heap : public AllStatic {
HeapObject* target, HeapObject* target,
int size); int size);
static void ClearJSFunctionResultCaches();
#if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING) #if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING)
// Record the copy of an object in the NewSpace's statistics. // Record the copy of an object in the NewSpace's statistics.
static void RecordCopiedObject(HeapObject* obj); static void RecordCopiedObject(HeapObject* obj);
......
...@@ -1328,6 +1328,32 @@ bool DescriptorArray::IsSortedNoDuplicates() { ...@@ -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 #endif // DEBUG
} } // namespace v8::internal } } // namespace v8::internal
...@@ -569,6 +569,22 @@ bool Object::IsSymbolTable() { ...@@ -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<JSFunctionResultCache*>(this)->JSFunctionResultCacheVerify();
#endif
return true;
}
bool Object::IsCompilationCacheTable() { bool Object::IsCompilationCacheTable() {
return IsHashTable(); return IsHashTable();
} }
...@@ -1594,6 +1610,7 @@ void NumberDictionary::set_requires_slow_elements() { ...@@ -1594,6 +1610,7 @@ void NumberDictionary::set_requires_slow_elements() {
CAST_ACCESSOR(FixedArray) CAST_ACCESSOR(FixedArray)
CAST_ACCESSOR(DescriptorArray) CAST_ACCESSOR(DescriptorArray)
CAST_ACCESSOR(SymbolTable) CAST_ACCESSOR(SymbolTable)
CAST_ACCESSOR(JSFunctionResultCache)
CAST_ACCESSOR(CompilationCacheTable) CAST_ACCESSOR(CompilationCacheTable)
CAST_ACCESSOR(CodeCacheHashTable) CAST_ACCESSOR(CodeCacheHashTable)
CAST_ACCESSOR(MapCache) CAST_ACCESSOR(MapCache)
...@@ -1836,6 +1853,20 @@ void ExternalTwoByteString::set_resource( ...@@ -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) { byte ByteArray::get(int index) {
ASSERT(index >= 0 && index < this->length()); ASSERT(index >= 0 && index < this->length());
return READ_BYTE_FIELD(this, kHeaderSize + index * kCharSize); return READ_BYTE_FIELD(this, kHeaderSize + index * kCharSize);
......
...@@ -606,6 +606,7 @@ class Object BASE_EMBEDDED { ...@@ -606,6 +606,7 @@ class Object BASE_EMBEDDED {
inline bool IsHashTable(); inline bool IsHashTable();
inline bool IsDictionary(); inline bool IsDictionary();
inline bool IsSymbolTable(); inline bool IsSymbolTable();
inline bool IsJSFunctionResultCache();
inline bool IsCompilationCacheTable(); inline bool IsCompilationCacheTable();
inline bool IsCodeCacheHashTable(); inline bool IsCodeCacheHashTable();
inline bool IsMapCache(); inline bool IsMapCache();
...@@ -2326,6 +2327,16 @@ class JSFunctionResultCache: public FixedArray { ...@@ -2326,6 +2327,16 @@ class JSFunctionResultCache: public FixedArray {
static const int kEntriesIndex = kDummyIndex + 1; static const int kEntriesIndex = kDummyIndex + 1;
static const int kEntrySize = 2; // key + value 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
}; };
......
...@@ -50,3 +50,87 @@ TEST(Preemption) { ...@@ -50,3 +50,87 @@ TEST(Preemption) {
script->Run(); 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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment