Flushing of code from functions that we expect not to use again.
authorricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 7 Jun 2010 15:39:10 +0000 (15:39 +0000)
committerricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 7 Jun 2010 15:39:10 +0000 (15:39 +0000)
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
src/compilation-cache.h
src/compiler.cc
src/heap.cc
src/heap.h
src/objects-inl.h
src/objects.h
test/cctest/test-heap.cc

index cec10fd..14252a5 100644 (file)
@@ -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<CompilationCacheTable> 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);
index 6358a26..583f04c 100644 (file)
@@ -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);
 
index ca92ed9..ebb9743 100755 (executable)
@@ -601,6 +601,7 @@ void Compiler::SetFunctionInfo(Handle<SharedFunctionInfo> 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());
 }
 
 
index fd7291b..23371ee 100644 (file)
@@ -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,
index e99c538..9170ac3 100644 (file)
@@ -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;
 
index fceb76f..4112f93 100644 (file)
@@ -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)
index 94b2253..095dd98 100644 (file)
@@ -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);
 };
index 0a919a1..d30b5ab 100644 (file)
@@ -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<String> 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<JSFunction> 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());
+}