JSC should dump object size inference statistics
https://bugs.webkit.org/show_bug.cgi?id=97618

Reviewed by Filip Pizlo.

Added an option to dump object size inference statistics.

To see statistics on live objects:

    jsc --showHeapStatistics=1

To see cumulative statistics on all objects ever allocated:

    jsc --showHeapStatistics=1 --objectsAreImmortal=1

    (This is useful for showing GC churn caused by over-allocation.)

To support this second mode, I refactored Zombies to separate out their
immortality feature so I could reuse it.

* heap/Heap.cpp:
(JSC::MarkObject): Helper for making things immortal. We have to checked
for being zapped because blocks start out in this state.

(JSC::StorageStatistics): Gather statistics by walking the heap. Ignore
arrays and hash tables for now because they're not our focus. (We'll
remove these exceptions in future.)

(JSC::Heap::collect): Moved zombify to the end so it wouldn't interfere
with statistics gathering.

(JSC::Heap::showStatistics):
(JSC::Heap::markAllObjects): Factored out helper, so statistics could
take advantage of immortal objects.

(Zombify): Don't mark immortal objects -- that's another class's job now.

(JSC::Zombify::operator()):
(JSC::Heap::zombifyDeadObjects): Take advantage of forEachDeadCell instead
of rolling our own.

* heap/Heap.h:
(Heap):
* heap/MarkedSpace.h:
(MarkedSpace):
(JSC::MarkedSpace::forEachDeadCell): Added, so clients don't have to do
the iteration logic themselves.

* runtime/Options.cpp:
(JSC::Options::initialize):
* runtime/Options.h: New options, listed above. Make sure to initialize
based on environment variable first, so we can override with specific settings.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@129586 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 32d5897..bf71bcd 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,58 @@
+2012-09-25  Geoffrey Garen  <ggaren@apple.com>
+
+        JSC should dump object size inference statistics
+        https://bugs.webkit.org/show_bug.cgi?id=97618
+
+        Reviewed by Filip Pizlo.
+
+        Added an option to dump object size inference statistics.
+
+        To see statistics on live objects:
+
+            jsc --showHeapStatistics=1
+
+        To see cumulative statistics on all objects ever allocated:
+
+            jsc --showHeapStatistics=1 --objectsAreImmortal=1
+
+            (This is useful for showing GC churn caused by over-allocation.)
+
+        To support this second mode, I refactored Zombies to separate out their
+        immortality feature so I could reuse it.
+
+        * heap/Heap.cpp:
+        (JSC::MarkObject): Helper for making things immortal. We have to checked
+        for being zapped because blocks start out in this state.
+
+        (JSC::StorageStatistics): Gather statistics by walking the heap. Ignore
+        arrays and hash tables for now because they're not our focus. (We'll
+        remove these exceptions in future.)
+
+        (JSC::Heap::collect): Moved zombify to the end so it wouldn't interfere
+        with statistics gathering.
+
+        (JSC::Heap::showStatistics):
+        (JSC::Heap::markAllObjects): Factored out helper, so statistics could
+        take advantage of immortal objects.
+
+        (Zombify): Don't mark immortal objects -- that's another class's job now.
+
+        (JSC::Zombify::operator()):
+        (JSC::Heap::zombifyDeadObjects): Take advantage of forEachDeadCell instead
+        of rolling our own.
+
+        * heap/Heap.h:
+        (Heap):
+        * heap/MarkedSpace.h:
+        (MarkedSpace):
+        (JSC::MarkedSpace::forEachDeadCell): Added, so clients don't have to do
+        the iteration logic themselves.
+
+        * runtime/Options.cpp:
+        (JSC::Options::initialize):
+        * runtime/Options.h: New options, listed above. Make sure to initialize
+        based on environment variable first, so we can override with specific settings.
+
 2012-09-25  Filip Pizlo  <fpizlo@apple.com>
 
         We shouldn't use the optimized versions of shift/unshift if the user is doing crazy things to the array
diff --git a/Source/JavaScriptCore/heap/Heap.cpp b/Source/JavaScriptCore/heap/Heap.cpp
index c7fb8c0..ca936eb 100644
--- a/Source/JavaScriptCore/heap/Heap.cpp
+++ b/Source/JavaScriptCore/heap/Heap.cpp
@@ -176,6 +176,15 @@
     return true;
 }
 
+struct MarkObject : public MarkedBlock::VoidFunctor {
+    void operator()(JSCell* cell)
+    {
+        if (cell->isZapped())
+            return;
+        Heap::heap(cell)->setMarked(cell);
+    }
+};
+
 struct Count : public MarkedBlock::CountFunctor {
     void operator()(JSCell*) { count(1); }
 };
@@ -226,6 +235,74 @@
     return m_typeCountSet.release();
 }
 
+class StorageStatistics : public MarkedBlock::VoidFunctor {
+public:
+    StorageStatistics();
+
+    void operator()(JSCell*);
+
+    size_t objectWithOutOfLineStorageCount();
+    size_t objectCount();
+
+    size_t storageSize();
+    size_t storageCapacity();
+
+private:
+    size_t m_objectWithOutOfLineStorageCount;
+    size_t m_objectCount;
+    size_t m_storageSize;
+    size_t m_storageCapacity;
+};
+
+inline StorageStatistics::StorageStatistics()
+    : m_objectWithOutOfLineStorageCount(0)
+    , m_objectCount(0)
+    , m_storageSize(0)
+    , m_storageCapacity(0)
+{
+}
+
+inline void StorageStatistics::operator()(JSCell* cell)
+{
+    if (!cell->isObject())
+        return;
+
+    JSObject* object = jsCast<JSObject*>(cell);
+    if (hasIndexedProperties(object->structure()->indexingType()))
+        return;
+
+    if (object->structure()->isUncacheableDictionary())
+        return;
+
+    ++m_objectCount;
+    if (!object->hasInlineStorage())
+        ++m_objectWithOutOfLineStorageCount;
+    m_storageSize += object->structure()->totalStorageSize() * sizeof(WriteBarrierBase<Unknown>);
+    m_storageCapacity += object->structure()->totalStorageCapacity() * sizeof(WriteBarrierBase<Unknown>); 
+}
+
+inline size_t StorageStatistics::objectWithOutOfLineStorageCount()
+{
+    return m_objectWithOutOfLineStorageCount;
+}
+
+inline size_t StorageStatistics::objectCount()
+{
+    return m_objectCount;
+}
+
+
+inline size_t StorageStatistics::storageSize()
+{
+    return m_storageSize;
+}
+
+
+inline size_t StorageStatistics::storageCapacity()
+{
+    return m_storageCapacity;
+}
+
 } // anonymous namespace
 
 Heap::Heap(JSGlobalData* globalData, HeapType heapType)
@@ -753,9 +830,6 @@
         m_objectSpace.resetAllocators();
     }
     
-    if (Options::useZombieMode())
-        zombifyDeadObjects();
-
     size_t currentHeapSize = size();
     if (fullGC) {
         m_sizeAfterLastCollect = currentHeapSize;
@@ -769,10 +843,48 @@
     m_bytesAllocated = 0;
     double lastGCEndTime = WTF::currentTime();
     m_lastGCLength = lastGCEndTime - lastGCStartTime;
+
     if (m_operationInProgress != Collection)
         CRASH();
     m_operationInProgress = NoOperation;
     JAVASCRIPTCORE_GC_END();
+
+    if (Options::useZombieMode())
+        zombifyDeadObjects();
+
+    if (Options::objectsAreImmortal())
+        markDeadObjects();
+
+    if (Options::showHeapStatistics())
+        showStatistics();
+}
+
+void Heap::showStatistics()
+{
+    dataLog("\n=== Heap Statistics: ===\n");
+    dataLog("size: %ldkB\n", static_cast<long>(m_sizeAfterLastCollect / KB));
+    dataLog("capacity: %ldkB\n", static_cast<long>(capacity() / KB));
+    dataLog("pause time: %lfms\n\n", m_lastGCLength);
+
+    StorageStatistics storageStatistics;
+    m_objectSpace.forEachLiveCell(storageStatistics);
+    dataLog("wasted .property storage: %ldkB (%ld%%)\n",
+        static_cast<long>(
+            (storageStatistics.storageCapacity() - storageStatistics.storageSize()) / KB),
+        static_cast<long>(
+            (storageStatistics.storageCapacity() - storageStatistics.storageSize()) * 100
+                / storageStatistics.storageCapacity()));
+    dataLog("objects with out-of-line .property storage: %ld (%ld%%)\n",
+        static_cast<long>(
+            storageStatistics.objectWithOutOfLineStorageCount()),
+        static_cast<long>(
+            storageStatistics.objectWithOutOfLineStorageCount() * 100
+                / storageStatistics.objectCount()));
+}
+
+void Heap::markDeadObjects()
+{
+    m_objectSpace.forEachDeadCell<MarkObject>();
 }
 
 void Heap::setActivityCallback(GCActivityCallback* activityCallback)
@@ -844,18 +956,10 @@
     lastChanceToFinalize();
 }
 
-class ZombifyCellFunctor : public MarkedBlock::VoidFunctor {
+class Zombify : public MarkedBlock::VoidFunctor {
 public:
-    ZombifyCellFunctor(size_t cellSize)
-        : m_cellSize(cellSize)
-    {
-    }
-
     void operator()(JSCell* cell)
     {
-        if (Options::zombiesAreImmortal())
-            MarkedBlock::blockFor(cell)->setMarked(cell);
-
         void** current = reinterpret_cast<void**>(cell);
 
         // We want to maintain zapped-ness because that's how we know if we've called 
@@ -863,30 +967,17 @@
         if (cell->isZapped())
             current++;
 
-        void* limit = static_cast<void*>(reinterpret_cast<char*>(cell) + m_cellSize);
+        void* limit = static_cast<void*>(reinterpret_cast<char*>(cell) + MarkedBlock::blockFor(cell)->cellSize());
         for (; current < limit; current++)
             *current = reinterpret_cast<void*>(0xbbadbeef);
     }
-
-private:
-    size_t m_cellSize;
-};
-
-class ZombifyBlockFunctor : public MarkedBlock::VoidFunctor {
-public:
-    void operator()(MarkedBlock* block)
-    {
-        ZombifyCellFunctor functor(block->cellSize());
-        block->forEachDeadCell(functor);
-    }
 };
 
 void Heap::zombifyDeadObjects()
 {
+    // Sweep now because destructors will crash once we're zombified.
     m_objectSpace.sweep();
-
-    ZombifyBlockFunctor functor;
-    m_objectSpace.forEachBlock(functor);
+    m_objectSpace.forEachDeadCell<Zombify>();
 }
 
 } // namespace JSC
diff --git a/Source/JavaScriptCore/heap/Heap.h b/Source/JavaScriptCore/heap/Heap.h
index c9bbec1..92efff7 100644
--- a/Source/JavaScriptCore/heap/Heap.h
+++ b/Source/JavaScriptCore/heap/Heap.h
@@ -145,6 +145,7 @@
         JS_EXPORT_PRIVATE size_t protectedGlobalObjectCount();
         JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> protectedObjectTypeCounts();
         JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> objectTypeCounts();
+        void showStatistics();
 
         void pushTempSortVector(Vector<ValueStringPair>*);
         void popTempSortVector(Vector<ValueStringPair>*);
@@ -205,7 +206,8 @@
         void finalizeUnconditionalFinalizers();
         void deleteUnmarkedCompiledCode();
         void zombifyDeadObjects();
- 
+        void markDeadObjects();
+
         RegisterFile& registerFile();
         BlockAllocator& blockAllocator();
 
diff --git a/Source/JavaScriptCore/heap/MarkedSpace.h b/Source/JavaScriptCore/heap/MarkedSpace.h
index e68ba91..151099b 100644
--- a/Source/JavaScriptCore/heap/MarkedSpace.h
+++ b/Source/JavaScriptCore/heap/MarkedSpace.h
@@ -95,6 +95,8 @@
     
     template<typename Functor> typename Functor::ReturnType forEachLiveCell(Functor&);
     template<typename Functor> typename Functor::ReturnType forEachLiveCell();
+    template<typename Functor> typename Functor::ReturnType forEachDeadCell(Functor&);
+    template<typename Functor> typename Functor::ReturnType forEachDeadCell();
     template<typename Functor> typename Functor::ReturnType forEachBlock(Functor&);
     template<typename Functor> typename Functor::ReturnType forEachBlock();
     
@@ -156,6 +158,22 @@
     return forEachLiveCell(functor);
 }
 
+template<typename Functor> inline typename Functor::ReturnType MarkedSpace::forEachDeadCell(Functor& functor)
+{
+    canonicalizeCellLivenessData();
+
+    BlockIterator end = m_blocks.set().end();
+    for (BlockIterator it = m_blocks.set().begin(); it != end; ++it)
+        (*it)->forEachDeadCell(functor);
+    return functor.returnValue();
+}
+
+template<typename Functor> inline typename Functor::ReturnType MarkedSpace::forEachDeadCell()
+{
+    Functor functor;
+    return forEachDeadCell(functor);
+}
+
 inline MarkedAllocator& MarkedSpace::firstAllocator()
 {
     return m_normalSpace.preciseAllocators[0];
diff --git a/Source/JavaScriptCore/runtime/Options.cpp b/Source/JavaScriptCore/runtime/Options.cpp
index b164948..ed0720b 100644
--- a/Source/JavaScriptCore/runtime/Options.cpp
+++ b/Source/JavaScriptCore/runtime/Options.cpp
@@ -127,6 +127,11 @@
     JSC_OPTIONS(FOR_EACH_OPTION)
 #undef FOR_EACH_OPTION
         
+#if USE(CF) || OS(UNIX)
+    objectsAreImmortal() = !!getenv("JSImmortalZombieEnabled");
+    useZombieMode() = !!getenv("JSImmortalZombieEnabled") || !!getenv("JSZombieEnabled");
+#endif
+
     // Allow environment vars to override options if applicable.
     // The evn var should be the name of the option prefixed with
     // "JSC_".
@@ -149,11 +154,6 @@
     useRegExpJIT() = false;
 #endif
 
-#if USE(CF) || OS(UNIX)
-    zombiesAreImmortal() = !!getenv("JSImmortalZombieEnabled");
-    useZombieMode() = zombiesAreImmortal() || !!getenv("JSZombieEnabled");
-#endif
-
     // Do range checks where needed and make corrections to the options:
     ASSERT(thresholdForOptimizeAfterLongWarmUp() >= thresholdForOptimizeAfterWarmUp());
     ASSERT(thresholdForOptimizeAfterWarmUp() >= thresholdForOptimizeSoon());
diff --git a/Source/JavaScriptCore/runtime/Options.h b/Source/JavaScriptCore/runtime/Options.h
index 5e53d1c..7571f91 100644
--- a/Source/JavaScriptCore/runtime/Options.h
+++ b/Source/JavaScriptCore/runtime/Options.h
@@ -121,7 +121,8 @@
     v(unsigned, forcedWeakRandomSeed, 0) \
     \
     v(bool, useZombieMode, false) \
-    v(bool, zombiesAreImmortal, false) 
+    v(bool, objectsAreImmortal, false) \
+    v(bool, showHeapStatistics, false)
 
 
 class Options {