From 316092c364ff8ce27ea584765cd3f2f7c7481b8b Mon Sep 17 00:00:00 2001 From: "ricow@chromium.org" Date: Mon, 7 Jun 2010 15:39:10 +0000 Subject: [PATCH] Flushing of code from functions that we expect not to use again. This adds an additional step to full gc, removing code from functions that are no longer in the compilation cache. The code is replaced with a lazy compile version enabling us to recompile the function in case we do actually need it again. Review URL: http://codereview.chromium.org/2632003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4814 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/compilation-cache.cc | 28 +++++++++++++++++ src/compilation-cache.h | 3 ++ src/compiler.cc | 1 + src/heap.cc | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ src/heap.h | 4 +++ src/objects-inl.h | 4 +++ src/objects.h | 7 +++++ test/cctest/test-heap.cc | 39 +++++++++++++++++++++++ 8 files changed, 168 insertions(+) diff --git a/src/compilation-cache.cc b/src/compilation-cache.cc index cec10fd..14252a5 100644 --- a/src/compilation-cache.cc +++ b/src/compilation-cache.cc @@ -79,6 +79,8 @@ class CompilationSubCache { // young generation. void Age(); + bool HasFunction(SharedFunctionInfo* function_info); + // GC support. void Iterate(ObjectVisitor* v); @@ -204,6 +206,27 @@ Handle CompilationSubCache::GetTable(int generation) { } +bool CompilationSubCache::HasFunction(SharedFunctionInfo* function_info) { + if (function_info->script()->IsUndefined() || + Script::cast(function_info->script())->source()->IsUndefined()) { + return false; + } + + String* source = + String::cast(Script::cast(function_info->script())->source()); + // Check all generations. + for (int generation = 0; generation < generations(); generation++) { + if (tables_[generation]->IsUndefined()) continue; + + CompilationCacheTable* table = + CompilationCacheTable::cast(tables_[generation]); + Object* object = table->Lookup(source); + if (object->IsSharedFunctionInfo()) return true; + } + return false; +} + + void CompilationSubCache::Age() { // Age the generations implicitly killing off the oldest. for (int i = generations_ - 1; i > 0; i--) { @@ -506,6 +529,11 @@ void CompilationCache::Clear() { } +bool CompilationCache::HasFunction(SharedFunctionInfo* function_info) { + return script.HasFunction(function_info); +} + + void CompilationCache::Iterate(ObjectVisitor* v) { for (int i = 0; i < kSubCacheCount; i++) { subcaches[i]->Iterate(v); diff --git a/src/compilation-cache.h b/src/compilation-cache.h index 6358a26..583f04c 100644 --- a/src/compilation-cache.h +++ b/src/compilation-cache.h @@ -79,6 +79,9 @@ class CompilationCache { // Clear the cache - also used to initialize the cache at startup. static void Clear(); + + static bool HasFunction(SharedFunctionInfo* function_info); + // GC support. static void Iterate(ObjectVisitor* v); diff --git a/src/compiler.cc b/src/compiler.cc index ca92ed9..ebb9743 100755 --- a/src/compiler.cc +++ b/src/compiler.cc @@ -601,6 +601,7 @@ void Compiler::SetFunctionInfo(Handle function_info, lit->has_only_simple_this_property_assignments(), *lit->this_property_assignments()); function_info->set_try_full_codegen(lit->try_full_codegen()); + function_info->set_allows_lazy_compilation(lit->AllowsLazyCompilation()); } diff --git a/src/heap.cc b/src/heap.cc index fd7291b..23371ee 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -607,6 +607,9 @@ void Heap::PerformGarbageCollection(AllocationSpace space, EnsureFromSpaceIsCommitted(); if (collector == MARK_COMPACTOR) { + // Flush all potentially unused code. + FlushCode(); + // Perform mark-sweep with optional compaction. MarkCompact(tracer); @@ -2186,6 +2189,85 @@ Object* Heap::AllocateExternalArray(int length, } +// The StackVisitor is used to traverse all the archived threads to see if +// there are activations on any of the stacks corresponding to the code. +class FlushingStackVisitor : public ThreadVisitor { + public: + explicit FlushingStackVisitor(Code* code) : found_(false), code_(code) {} + + void VisitThread(ThreadLocalTop* top) { + // If we already found the code in a previous traversed thread we return. + if (found_) return; + + for (StackFrameIterator it(top); !it.done(); it.Advance()) { + if (code_->contains(it.frame()->pc())) { + found_ = true; + return; + } + } + } + bool FoundCode() {return found_;} + + private: + bool found_; + Code* code_; +}; + + +static void FlushCodeForFunction(SharedFunctionInfo* function_info) { + // The function must be compiled and have the source code available, + // to be able to recompile it in case we need the function again. + if (!(function_info->is_compiled() && function_info->HasSourceCode())) return; + + // We never flush code for Api functions. + if (function_info->IsApiFunction()) return; + + // Only flush code for functions. + if (!function_info->code()->kind() == Code::FUNCTION) return; + + // Function must be lazy compilable. + if (!function_info->allows_lazy_compilation()) return; + + // If this is a full script wrapped in a function we do no flush the code. + if (function_info->is_toplevel()) return; + + // If this function is in the compilation cache we do not flush the code. + if (CompilationCache::HasFunction(function_info)) return; + + // Make sure we are not referencing the code from the stack. + for (StackFrameIterator it; !it.done(); it.Advance()) { + if (function_info->code()->contains(it.frame()->pc())) return; + } + // Iterate the archived stacks in all threads to check if + // the code is referenced. + FlushingStackVisitor threadvisitor(function_info->code()); + ThreadManager::IterateArchivedThreads(&threadvisitor); + if (threadvisitor.FoundCode()) return; + + HandleScope scope; + // Compute the lazy compilable version of the code. + function_info->set_code(*ComputeLazyCompile(function_info->length())); +} + + +void Heap::FlushCode() { + // Do not flush code if the debugger is loaded or there are breakpoints. + if (Debug::IsLoaded() || Debug::has_break_points()) return; + HeapObjectIterator it(old_pointer_space()); + for (HeapObject* obj = it.next(); obj != NULL; obj = it.next()) { + if (obj->IsJSFunction()) { + JSFunction* jsfunction = JSFunction::cast(obj); + + // The function must have a valid context and not be a builtin. + if (jsfunction->unchecked_context()->IsContext() && + !jsfunction->IsBuiltin()) { + FlushCodeForFunction(jsfunction->shared()); + } + } + } +} + + Object* Heap::CreateCode(const CodeDesc& desc, ZoneScopeInfo* sinfo, Code::Flags flags, diff --git a/src/heap.h b/src/heap.h index e99c538..9170ac3 100644 --- a/src/heap.h +++ b/src/heap.h @@ -1274,6 +1274,10 @@ class Heap : public AllStatic { // Flush the number to string cache. static void FlushNumberStringCache(); + // Flush code from functions we do not expect to use again. The code will + // be replaced with a lazy compilable version. + static void FlushCode(); + static const int kInitialSymbolTableSize = 2048; static const int kInitialEvalCacheSize = 64; diff --git a/src/objects-inl.h b/src/objects-inl.h index fceb76f..4112f93 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2468,6 +2468,10 @@ BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, try_full_codegen, kTryFullCodegen) +BOOL_ACCESSORS(SharedFunctionInfo, + compiler_hints, + allows_lazy_compilation, + kAllowLazyCompilation) #if V8_HOST_ARCH_32_BIT SMI_ACCESSORS(SharedFunctionInfo, length, kLengthOffset) diff --git a/src/objects.h b/src/objects.h index 94b2253..095dd98 100644 --- a/src/objects.h +++ b/src/objects.h @@ -3308,6 +3308,12 @@ class SharedFunctionInfo: public HeapObject { inline bool try_full_codegen(); inline void set_try_full_codegen(bool flag); + // Indicates if this function can be lazy compiled. + // This is used to determine if we can safely flush code from a function + // when doing GC if we expect that the function will no longer be used. + inline bool allows_lazy_compilation(); + inline void set_allows_lazy_compilation(bool flag); + // Check whether a inlined constructor can be generated with the given // prototype. bool CanGenerateInlineConstructor(Object* prototype); @@ -3433,6 +3439,7 @@ class SharedFunctionInfo: public HeapObject { // Bit positions in compiler_hints. static const int kHasOnlySimpleThisPropertyAssignments = 0; static const int kTryFullCodegen = 1; + static const int kAllowLazyCompilation = 2; DISALLOW_IMPLICIT_CONSTRUCTORS(SharedFunctionInfo); }; diff --git a/test/cctest/test-heap.cc b/test/cctest/test-heap.cc index 0a919a1..d30b5ab 100644 --- a/test/cctest/test-heap.cc +++ b/test/cctest/test-heap.cc @@ -957,3 +957,42 @@ TEST(Regression39128) { // Check that region covering inobject property 1 is marked dirty. CHECK(page->IsRegionDirty(clone_addr + (object_size - kPointerSize))); } + +TEST(TestCodeFlushing) { + i::FLAG_allow_natives_syntax = true; + InitializeVM(); + v8::HandleScope scope; + const char* source = "function foo() {" + " var x = 42;" + " var y = 42;" + " var z = x + y;" + "};" + "foo()"; + Handle foo_name = Factory::LookupAsciiSymbol("foo"); + + // This compile will add the code to the compilation cache. + CompileRun(source); + + // Check function is compiled. + Object* func_value = Top::context()->global()->GetProperty(*foo_name); + CHECK(func_value->IsJSFunction()); + Handle function(JSFunction::cast(func_value)); + CHECK(function->shared()->is_compiled()); + + Heap::CollectAllGarbage(true); + Heap::CollectAllGarbage(true); + + // foo should still be in the compilation cache and therefore not + // have been removed. + CHECK(function->shared()->is_compiled()); + Heap::CollectAllGarbage(true); + Heap::CollectAllGarbage(true); + Heap::CollectAllGarbage(true); + Heap::CollectAllGarbage(true); + + // foo should no longer be in the compilation cache + CHECK(!function->shared()->is_compiled()); + // Call foo to get it recompiled. + CompileRun("foo()"); + CHECK(function->shared()->is_compiled()); +} -- 2.7.4