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/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