Heap Snapshot should include different Edge types and data (Property, Index, Variable)
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Mar 2016 23:45:38 +0000 (23:45 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Mar 2016 23:45:38 +0000 (23:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154937

Patch by Joseph Pecoraro <pecoraro@apple.com> on 2016-03-07
Reviewed by Geoffrey Garen.

* heap/SlotVisitor.cpp:
(JSC::SlotVisitor::appendHidden):
* heap/SlotVisitor.h:
* heap/SlotVisitorInlines.h:
(JSC::SlotVisitor::appendHidden):
(JSC::SlotVisitor::appendValuesHidden):
Add new visit methods to visit a reference without snapshotting the edge.

* heap/Heap.cpp:
(JSC::AddExtraHeapSnapshotEdges::AddExtraHeapSnapshotEdges):
(JSC::AddExtraHeapSnapshotEdges::operator()):
(JSC::Heap::addHeapSnapshotEdges):
(JSC::Heap::removeDeadHeapSnapshotNodes):
(JSC::Heap::collectImpl):
* heap/Heap.h:
After marking, visit the live cells for a chance to record extra
heap snapshotting information about the cell.

* heap/HeapSnapshotBuilder.cpp:
(JSC::HeapSnapshotBuilder::appendNode):
(JSC::HeapSnapshotBuilder::appendEdge):
(JSC::HeapSnapshotBuilder::appendPropertyNameEdge):
(JSC::HeapSnapshotBuilder::appendVariableNameEdge):
(JSC::HeapSnapshotBuilder::appendIndexEdge):
(JSC::HeapSnapshotBuilder::json):
* heap/HeapSnapshotBuilder.h:
(JSC::HeapSnapshotEdge::HeapSnapshotEdge):
Construct edges with extra data.

* runtime/ClassInfo.h:
* runtime/JSCell.cpp:
(JSC::JSCell::heapSnapshot):
* runtime/JSCell.h:
Add a new method to provide cells with an opportunity to provide
extra heap snapshotting information.

* runtime/JSObject.cpp:
(JSC::JSObject::visitButterfly):
(JSC::JSObject::visitChildren):
(JSC::JSObject::heapSnapshot):
(JSC::JSFinalObject::visitChildren):
* runtime/JSObject.h:
Capture object property names and index names when heap snapshotting.
Do not include them as internal edges in normal visitChildren.

* runtime/JSEnvironmentRecord.cpp:
(JSC::JSEnvironmentRecord::visitChildren):
(JSC::JSEnvironmentRecord::heapSnapshot):
* runtime/JSEnvironmentRecord.h:
* runtime/JSSegmentedVariableObject.cpp:
(JSC::JSSegmentedVariableObject::visitChildren):
(JSC::JSSegmentedVariableObject::heapSnapshot):
* runtime/JSSegmentedVariableObject.h:
Capture scope variable names when heap snapshotting.

* runtime/Structure.cpp:
(JSC::Structure::visitChildren):
* runtime/Structure.h:
* runtime/StructureInlines.h:
(JSC::Structure::propertyTable):
When performing a heap snapshotting collection, don't clear the
property table so that accessing the table during this GC is okay.

* tests/heapProfiler/driver/driver.js:
* tests/heapProfiler/property-edge-types.js: Added.
* tests/heapProfiler/variable-edge-types.js: Added.
Tests covering the different edge types and data we capture.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@197712 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/heap/Heap.cpp
Source/JavaScriptCore/heap/Heap.h
Source/JavaScriptCore/heap/HeapSnapshotBuilder.cpp
Source/JavaScriptCore/heap/HeapSnapshotBuilder.h
Source/JavaScriptCore/heap/SlotVisitor.cpp
Source/JavaScriptCore/heap/SlotVisitor.h
Source/JavaScriptCore/heap/SlotVisitorInlines.h
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/ClassInfo.h
Source/JavaScriptCore/runtime/JSCell.cpp
Source/JavaScriptCore/runtime/JSCell.h
Source/JavaScriptCore/runtime/JSEnvironmentRecord.cpp
Source/JavaScriptCore/runtime/JSEnvironmentRecord.h
Source/JavaScriptCore/runtime/JSObject.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/JSSegmentedVariableObject.cpp
Source/JavaScriptCore/runtime/JSSegmentedVariableObject.h
Source/JavaScriptCore/runtime/Structure.cpp
Source/JavaScriptCore/runtime/Structure.h
Source/JavaScriptCore/runtime/StructureInlines.h
Source/JavaScriptCore/tests/heapProfiler/driver/driver.js
Source/JavaScriptCore/tests/heapProfiler/property-edge-types.js [new file with mode: 0644]
Source/JavaScriptCore/tests/heapProfiler/variable-edge-types.js [new file with mode: 0644]

index 534b492..a51742c 100644 (file)
@@ -1,3 +1,78 @@
+2016-03-07  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Heap Snapshot should include different Edge types and data (Property, Index, Variable)
+        https://bugs.webkit.org/show_bug.cgi?id=154937
+
+        Reviewed by Geoffrey Garen.
+
+        * heap/SlotVisitor.cpp:
+        (JSC::SlotVisitor::appendHidden):
+        * heap/SlotVisitor.h:
+        * heap/SlotVisitorInlines.h:
+        (JSC::SlotVisitor::appendHidden):
+        (JSC::SlotVisitor::appendValuesHidden):
+        Add new visit methods to visit a reference without snapshotting the edge.
+
+        * heap/Heap.cpp:
+        (JSC::AddExtraHeapSnapshotEdges::AddExtraHeapSnapshotEdges):
+        (JSC::AddExtraHeapSnapshotEdges::operator()):
+        (JSC::Heap::addHeapSnapshotEdges):
+        (JSC::Heap::removeDeadHeapSnapshotNodes):
+        (JSC::Heap::collectImpl):
+        * heap/Heap.h:
+        After marking, visit the live cells for a chance to record extra
+        heap snapshotting information about the cell.
+
+        * heap/HeapSnapshotBuilder.cpp:
+        (JSC::HeapSnapshotBuilder::appendNode):
+        (JSC::HeapSnapshotBuilder::appendEdge):
+        (JSC::HeapSnapshotBuilder::appendPropertyNameEdge):
+        (JSC::HeapSnapshotBuilder::appendVariableNameEdge):
+        (JSC::HeapSnapshotBuilder::appendIndexEdge):
+        (JSC::HeapSnapshotBuilder::json):
+        * heap/HeapSnapshotBuilder.h:
+        (JSC::HeapSnapshotEdge::HeapSnapshotEdge):
+        Construct edges with extra data.
+
+        * runtime/ClassInfo.h:
+        * runtime/JSCell.cpp:
+        (JSC::JSCell::heapSnapshot):
+        * runtime/JSCell.h:
+        Add a new method to provide cells with an opportunity to provide
+        extra heap snapshotting information.
+
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::visitButterfly):
+        (JSC::JSObject::visitChildren):
+        (JSC::JSObject::heapSnapshot):
+        (JSC::JSFinalObject::visitChildren):
+        * runtime/JSObject.h:
+        Capture object property names and index names when heap snapshotting.
+        Do not include them as internal edges in normal visitChildren.
+
+        * runtime/JSEnvironmentRecord.cpp:
+        (JSC::JSEnvironmentRecord::visitChildren):
+        (JSC::JSEnvironmentRecord::heapSnapshot):
+        * runtime/JSEnvironmentRecord.h:
+        * runtime/JSSegmentedVariableObject.cpp:
+        (JSC::JSSegmentedVariableObject::visitChildren):
+        (JSC::JSSegmentedVariableObject::heapSnapshot):
+        * runtime/JSSegmentedVariableObject.h:
+        Capture scope variable names when heap snapshotting.
+
+        * runtime/Structure.cpp:
+        (JSC::Structure::visitChildren):
+        * runtime/Structure.h:
+        * runtime/StructureInlines.h:
+        (JSC::Structure::propertyTable):
+        When performing a heap snapshotting collection, don't clear the
+        property table so that accessing the table during this GC is okay.
+
+        * tests/heapProfiler/driver/driver.js:
+        * tests/heapProfiler/property-edge-types.js: Added.
+        * tests/heapProfiler/variable-edge-types.js: Added.
+        Tests covering the different edge types and data we capture.
+
 2016-03-07  Saam barati  <sbarati@apple.com>
 
         [ES6] Implement Proxy.[[GetPrototypeOf]]
index d388d57..ee17edd 100644 (file)
@@ -768,6 +768,31 @@ bool Heap::isHeapSnapshotting() const
     return false;
 }
 
+struct GatherHeapSnapshotData : MarkedBlock::CountFunctor {
+    GatherHeapSnapshotData(HeapSnapshotBuilder& builder)
+        : m_builder(builder)
+    {
+    }
+
+    IterationStatus operator()(JSCell* cell)
+    {
+        cell->methodTable()->heapSnapshot(cell, m_builder);
+        return IterationStatus::Continue;
+    }
+
+    HeapSnapshotBuilder& m_builder;
+};
+
+void Heap::gatherExtraHeapSnapshotData(HeapProfiler& heapProfiler)
+{
+    GCPHASE(GatherExtraHeapSnapshotData);
+    if (HeapSnapshotBuilder* builder = heapProfiler.activeSnapshotBuilder()) {
+        HeapIterationScope heapIterationScope(*this);
+        GatherHeapSnapshotData functor(*builder);
+        m_objectSpace.forEachLiveCell(heapIterationScope, functor);
+    }
+}
+
 struct RemoveDeadHeapSnapshotNodes : MarkedBlock::CountFunctor {
     RemoveDeadHeapSnapshotNodes(HeapSnapshot& snapshot)
         : m_snapshot(snapshot)
@@ -783,17 +808,14 @@ struct RemoveDeadHeapSnapshotNodes : MarkedBlock::CountFunctor {
     HeapSnapshot& m_snapshot;
 };
 
-void Heap::removeDeadHeapSnapshotNodes()
+void Heap::removeDeadHeapSnapshotNodes(HeapProfiler& heapProfiler)
 {
     GCPHASE(RemoveDeadHeapSnapshotNodes);
-    HeapProfiler* heapProfiler = m_vm->heapProfiler();
-    if (UNLIKELY(heapProfiler)) {
-        if (HeapSnapshot* snapshot = heapProfiler->mostRecentSnapshot()) {
-            HeapIterationScope heapIterationScope(*this);
-            RemoveDeadHeapSnapshotNodes functor(*snapshot);
-            m_objectSpace.forEachDeadCell(heapIterationScope, functor);
-            snapshot->shrinkToFit();
-        }
+    if (HeapSnapshot* snapshot = heapProfiler.mostRecentSnapshot()) {
+        HeapIterationScope heapIterationScope(*this);
+        RemoveDeadHeapSnapshotNodes functor(*snapshot);
+        m_objectSpace.forEachDeadCell(heapIterationScope, functor);
+        snapshot->shrinkToFit();
     }
 }
 
@@ -1163,7 +1185,12 @@ NEVER_INLINE void Heap::collectImpl(HeapOperation collectionType, void* stackOri
     removeDeadCompilerWorklistEntries();
     deleteUnmarkedCompiledCode();
     deleteSourceProviderCaches();
-    removeDeadHeapSnapshotNodes();
+
+    if (HeapProfiler* heapProfiler = m_vm->heapProfiler()) {
+        gatherExtraHeapSnapshotData(*heapProfiler);
+        removeDeadHeapSnapshotNodes(*heapProfiler);
+    }
+
     notifyIncrementalSweeper();
     writeBarrierCurrentlyExecutingCodeBlocks();
 
index ae22ed7..064b5a4 100644 (file)
@@ -60,6 +60,7 @@ class FullGCActivityCallback;
 class GCActivityCallback;
 class GCAwareJITStubRoutine;
 class Heap;
+class HeapProfiler;
 class HeapRootVisitor;
 class HeapVerifier;
 class IncrementalSweeper;
@@ -329,7 +330,6 @@ private:
     void sweepArrayBuffers();
     void snapshotMarkedSpace();
     void deleteSourceProviderCaches();
-    void removeDeadHeapSnapshotNodes();
     void notifyIncrementalSweeper();
     void writeBarrierCurrentlyExecutingCodeBlocks();
     void resetAllocators();
@@ -344,6 +344,8 @@ private:
     void resumeCompilerThreads();
     void zombifyDeadObjects();
     void markDeadObjects();
+    void gatherExtraHeapSnapshotData(HeapProfiler&);
+    void removeDeadHeapSnapshotNodes(HeapProfiler&);
 
     void sweepAllLogicallyEmptyWeakBlocks();
     bool sweepNextLogicallyEmptyWeakBlock();
index cff9e13..d62f8dd 100644 (file)
@@ -70,7 +70,7 @@ void HeapSnapshotBuilder::appendNode(JSCell* cell)
     if (hasExistingNodeForCell(cell))
         return;
 
-    std::lock_guard<Lock> lock(m_appendingNodeMutex);
+    std::lock_guard<Lock> lock(m_buildingNodeMutex);
 
     m_snapshot->appendNode(HeapSnapshotNode(cell, getNextObjectIdentifier()));
 }
@@ -84,11 +84,41 @@ void HeapSnapshotBuilder::appendEdge(JSCell* from, JSCell* to)
     if (from == to)
         return;
 
-    std::lock_guard<Lock> lock(m_appendingEdgeMutex);
+    std::lock_guard<Lock> lock(m_buildingEdgeMutex);
 
     m_edges.append(HeapSnapshotEdge(from, to));
 }
 
+void HeapSnapshotBuilder::appendPropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName)
+{
+    ASSERT(m_profiler.activeSnapshotBuilder() == this);
+    ASSERT(to);
+
+    std::lock_guard<Lock> lock(m_buildingEdgeMutex);
+
+    m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Property, propertyName));
+}
+
+void HeapSnapshotBuilder::appendVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName)
+{
+    ASSERT(m_profiler.activeSnapshotBuilder() == this);
+    ASSERT(to);
+
+    std::lock_guard<Lock> lock(m_buildingEdgeMutex);
+
+    m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Variable, variableName));
+}
+
+void HeapSnapshotBuilder::appendIndexEdge(JSCell* from, JSCell* to, uint32_t index)
+{
+    ASSERT(m_profiler.activeSnapshotBuilder() == this);
+    ASSERT(to);
+
+    std::lock_guard<Lock> lock(m_buildingEdgeMutex);
+
+    m_edges.append(HeapSnapshotEdge(from, to, index));
+}
+
 bool HeapSnapshotBuilder::hasExistingNodeForCell(JSCell* cell)
 {
     if (!m_snapshot->previous())
@@ -221,6 +251,20 @@ String HeapSnapshotBuilder::json(std::function<bool (const HeapSnapshotNode&)> a
         json.appendNumber(toIdentifier);
         json.append(',');
         json.appendNumber(edgeTypeToNumber(edge.type));
+        switch (edge.type) {
+        case EdgeType::Property:
+        case EdgeType::Variable:
+            json.append(',');
+            json.appendQuotedJSONString(edge.u.name);
+            break;
+        case EdgeType::Index:
+            json.append(',');
+            json.appendNumber(edge.u.index);
+            break;
+        default:
+            // No data for this edge type.
+            break;
+        }
         json.append(']');
     };
 
index 5960036..80058ca 100644 (file)
@@ -29,6 +29,7 @@
 #include <functional>
 #include <wtf/Lock.h>
 #include <wtf/Vector.h>
+#include <wtf/text/UniquedStringImpl.h>
 #include <wtf/text/WTFString.h>
 
 namespace JSC {
@@ -62,9 +63,30 @@ struct HeapSnapshotEdge {
         , type(EdgeType::Internal)
     { }
 
+    HeapSnapshotEdge(JSCell* from, JSCell* to, EdgeType type, UniquedStringImpl* name)
+        : from(from)
+        , to(to)
+        , type(type)
+    {
+        ASSERT(type == EdgeType::Property || type == EdgeType::Variable);
+        u.name = name;
+    }
+
+    HeapSnapshotEdge(JSCell* from, JSCell* to, uint32_t index)
+        : from(from)
+        , to(to)
+        , type(EdgeType::Index)
+    {
+        u.index = index;
+    }
+
     JSCell* from;
     JSCell* to;
     EdgeType type;
+    union {
+        UniquedStringImpl* name;
+        uint32_t index;
+    } u;
 };
 
 class JS_EXPORT_PRIVATE HeapSnapshotBuilder {
@@ -84,6 +106,9 @@ public:
 
     // A reference from one cell to another.
     void appendEdge(JSCell* from, JSCell* to);
+    void appendPropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName);
+    void appendVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName);
+    void appendIndexEdge(JSCell* from, JSCell* to, uint32_t index);
 
     String json();
     String json(std::function<bool (const HeapSnapshotNode&)> allowNodeCallback);
@@ -96,9 +121,9 @@ private:
     HeapProfiler& m_profiler;
 
     // SlotVisitors run in parallel.
-    Lock m_appendingNodeMutex;
+    Lock m_buildingNodeMutex;
     std::unique_ptr<HeapSnapshot> m_snapshot;
-    Lock m_appendingEdgeMutex;
+    Lock m_buildingEdgeMutex;
     Vector<HeapSnapshotEdge> m_edges;
 };
 
index 641e590..9959af7 100644 (file)
@@ -129,12 +129,20 @@ void SlotVisitor::append(JSValue value)
     if (!value || !value.isCell())
         return;
 
-    if (m_heapSnapshotBuilder)
+    if (UNLIKELY(m_heapSnapshotBuilder))
         m_heapSnapshotBuilder->appendEdge(m_currentCell, value.asCell());
 
     setMarkedAndAppendToMarkStack(value.asCell());
 }
 
+void SlotVisitor::appendHidden(JSValue value)
+{
+    if (!value || !value.isCell())
+        return;
+
+    setMarkedAndAppendToMarkStack(value.asCell());
+}
+
 void SlotVisitor::setMarkedAndAppendToMarkStack(JSCell* cell)
 {
     ASSERT(!m_isCheckingForDefaultMarkViolation);
@@ -167,7 +175,7 @@ void SlotVisitor::appendToMarkStack(JSCell* cell)
     m_bytesVisited += MarkedBlock::blockFor(cell)->cellSize();
     m_stack.append(cell);
 
-    if (m_heapSnapshotBuilder)
+    if (UNLIKELY(m_heapSnapshotBuilder))
         m_heapSnapshotBuilder->appendNode(cell);
 }
 
index 26b615e..5590159 100644 (file)
@@ -67,8 +67,10 @@ public:
     
     template<typename T> void append(JITWriteBarrier<T>*);
     template<typename T> void append(WriteBarrierBase<T>*);
+    template<typename T> void appendHidden(WriteBarrierBase<T>*);
     template<typename Iterator> void append(Iterator begin , Iterator end);
     void appendValues(WriteBarrierBase<Unknown>*, size_t count);
+    void appendValuesHidden(WriteBarrierBase<Unknown>*, size_t count);
     
     template<typename T>
     void appendUnbarrieredPointer(T**);
@@ -119,6 +121,7 @@ private:
     friend class ParallelModeEnabler;
     
     JS_EXPORT_PRIVATE void append(JSValue); // This is private to encourage clients to use WriteBarrier<T>.
+    void appendHidden(JSValue);
 
     JS_EXPORT_PRIVATE void setMarkedAndAppendToMarkStack(JSCell*);
     void appendToMarkStack(JSCell*);
index 033872c..a379238 100644 (file)
@@ -69,6 +69,12 @@ inline void SlotVisitor::append(WriteBarrierBase<T>* slot)
     append(slot->get());
 }
 
+template<typename T>
+inline void SlotVisitor::appendHidden(WriteBarrierBase<T>* slot)
+{
+    appendHidden(slot->get());
+}
+
 template<typename Iterator>
 inline void SlotVisitor::append(Iterator begin, Iterator end)
 {
@@ -82,6 +88,12 @@ inline void SlotVisitor::appendValues(WriteBarrierBase<Unknown>* barriers, size_
         append(&barriers[i]);
 }
 
+inline void SlotVisitor::appendValuesHidden(WriteBarrierBase<Unknown>* barriers, size_t count)
+{
+    for (size_t i = 0; i < count; ++i)
+        appendHidden(&barriers[i]);
+}
+
 inline void SlotVisitor::addWeakReferenceHarvester(WeakReferenceHarvester* weakReferenceHarvester)
 {
     m_heap.m_weakReferenceHarvesters.addThreadSafe(weakReferenceHarvester);
index befeef9..63a7da3 100644 (file)
@@ -485,11 +485,6 @@ public:
         return simpleObject;
     }
 
-    void finishCreation(VM& vm)
-    {
-        Base::finishCreation(vm);
-    }
-
     static void visitChildren(JSCell* cell, SlotVisitor& visitor)
     {
         SimpleObject* thisObject = jsCast<SimpleObject*>(cell);
index 484e737..0450b53 100644 (file)
@@ -30,6 +30,7 @@
 
 namespace JSC {
 
+class HeapSnapshotBuilder;
 class JSArrayBufferView;
 struct HashTable;
 
@@ -118,6 +119,9 @@ struct MethodTable {
     typedef void (*DumpToStreamFunctionPtr)(const JSCell*, PrintStream&);
     DumpToStreamFunctionPtr dumpToStream;
 
+    typedef void (*HeapSnapshotFunctionPtr)(JSCell*, HeapSnapshotBuilder&);
+    HeapSnapshotFunctionPtr heapSnapshot;
+
     typedef size_t (*EstimatedSizeFunctionPtr)(JSCell*);
     EstimatedSizeFunctionPtr estimatedSize;
 };
@@ -171,6 +175,7 @@ struct MethodTable {
         &ClassName::setPrototype, \
         &ClassName::getPrototype, \
         &ClassName::dumpToStream, \
+        &ClassName::heapSnapshot, \
         &ClassName::estimatedSize \
     }, \
     ClassName::TypedArrayStorageType
index 243b805..8807a7d 100644 (file)
@@ -65,6 +65,10 @@ void JSCell::copyBackingStore(JSCell*, CopyVisitor&, CopyToken)
 {
 }
 
+void JSCell::heapSnapshot(JSCell*, HeapSnapshotBuilder&)
+{
+}
+
 bool JSCell::getString(ExecState* exec, String& stringValue) const
 {
     if (!isString())
index 43da049..1321745 100644 (file)
@@ -141,6 +141,8 @@ public:
     static void visitChildren(JSCell*, SlotVisitor&);
     JS_EXPORT_PRIVATE static void copyBackingStore(JSCell*, CopyVisitor&, CopyToken);
 
+    JS_EXPORT_PRIVATE static void heapSnapshot(JSCell*, HeapSnapshotBuilder&);
+
     // Object operations, with the toObject operation included.
     const ClassInfo* classInfo() const;
     const MethodTable* methodTable() const;
index f1d7695..923cd49 100644 (file)
@@ -29,6 +29,7 @@
 #include "config.h"
 #include "JSEnvironmentRecord.h"
 
+#include "HeapSnapshotBuilder.h"
 #include "JSCInlines.h"
 
 namespace JSC {
@@ -40,7 +41,27 @@ void JSEnvironmentRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
     JSEnvironmentRecord* thisObject = jsCast<JSEnvironmentRecord*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     Base::visitChildren(thisObject, visitor);
-    visitor.appendValues(thisObject->variables(), thisObject->symbolTable()->scopeSize());
+    visitor.appendValuesHidden(thisObject->variables(), thisObject->symbolTable()->scopeSize());
+}
+
+void JSEnvironmentRecord::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder)
+{
+    JSEnvironmentRecord* thisObject = jsCast<JSEnvironmentRecord*>(cell);
+    Base::heapSnapshot(cell, builder);
+
+    ConcurrentJITLocker locker(thisObject->symbolTable()->m_lock);
+    SymbolTable::Map::iterator end = thisObject->symbolTable()->end(locker);
+    for (SymbolTable::Map::iterator it = thisObject->symbolTable()->begin(locker); it != end; ++it) {
+        SymbolTableEntry::Fast entry = it->value;
+        ASSERT(!entry.isNull());
+        ScopeOffset offset = entry.scopeOffset();
+        if (!thisObject->isValidScopeOffset(offset))
+            continue;
+
+        JSValue toValue = thisObject->variableAt(offset).get();
+        if (toValue && toValue.isCell())
+            builder.appendVariableNameEdge(thisObject, toValue.asCell(), it->key.get());
+    }
 }
 
 } // namespace JSC
index 8cb3dd0..ca261db 100644 (file)
@@ -111,6 +111,7 @@ protected:
     }
 
     static void visitChildren(JSCell*, SlotVisitor&);
+    static void heapSnapshot(JSCell*, HeapSnapshotBuilder&);
 };
 
 } // namespace JSC
index 2167b4b..2e8f71e 100644 (file)
 #include "Exception.h"
 #include "Executable.h"
 #include "GetterSetter.h"
+#include "HeapSnapshotBuilder.h"
 #include "IndexingHeaderInlines.h"
 #include "JSBoundSlotBaseFunction.h"
+#include "JSCInlines.h"
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
 #include "Lookup.h"
 #include "NativeErrorConstructor.h"
 #include "Nodes.h"
 #include "ObjectPrototype.h"
-#include "JSCInlines.h"
 #include "PropertyDescriptor.h"
 #include "PropertyNameArray.h"
 #include "ProxyObject.h"
@@ -157,12 +158,11 @@ ALWAYS_INLINE void JSObject::copyButterfly(CopyVisitor& visitor, Butterfly* butt
     } 
 }
 
-ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* butterfly, size_t storageSize)
+ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* butterfly, Structure* structure)
 {
     ASSERT(butterfly);
     
-    Structure* structure = this->structure(visitor.vm());
-    
+    size_t storageSize = structure->outOfLineSize();
     size_t propertyCapacity = structure->outOfLineCapacity();
     size_t preCapacity;
     size_t indexingPayloadSizeInBytes;
@@ -177,7 +177,7 @@ ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* but
     size_t capacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes);
 
     // Mark the properties.
-    visitor.appendValues(butterfly->propertyStorage() - storageSize, storageSize);
+    visitor.appendValuesHidden(butterfly->propertyStorage() - storageSize, storageSize);
     visitor.copyLater(
         this, ButterflyCopyToken,
         butterfly->base(preCapacity, propertyCapacity), capacityInBytes);
@@ -185,10 +185,10 @@ ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* but
     // Mark the array if appropriate.
     switch (this->indexingType()) {
     case ALL_CONTIGUOUS_INDEXING_TYPES:
-        visitor.appendValues(butterfly->contiguous().data(), butterfly->publicLength());
+        visitor.appendValuesHidden(butterfly->contiguous().data(), butterfly->publicLength());
         break;
     case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-        visitor.appendValues(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength());
+        visitor.appendValuesHidden(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength());
         if (butterfly->arrayStorage()->m_sparseMap)
             visitor.append(&butterfly->arrayStorage()->m_sparseMap);
         break;
@@ -217,7 +217,7 @@ void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
 
     Butterfly* butterfly = thisObject->m_butterfly.getWithoutBarrier();
     if (butterfly)
-        thisObject->visitButterfly(visitor, butterfly, thisObject->structure(visitor.vm())->outOfLineSize());
+        thisObject->visitButterfly(visitor, butterfly, thisObject->structure(visitor.vm()));
 
 #if !ASSERT_DISABLED
     visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
@@ -237,6 +237,44 @@ void JSObject::copyBackingStore(JSCell* cell, CopyVisitor& visitor, CopyToken to
         thisObject->copyButterfly(visitor, butterfly, thisObject->structure()->outOfLineSize());
 }
 
+void JSObject::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder)
+{
+    JSObject* thisObject = jsCast<JSObject*>(cell);
+    Base::heapSnapshot(cell, builder);
+
+    Structure* structure = thisObject->structure();
+    for (auto& entry : structure->getPropertiesConcurrently()) {
+        JSValue toValue = thisObject->getDirect(entry.offset);
+        if (toValue && toValue.isCell())
+            builder.appendPropertyNameEdge(thisObject, toValue.asCell(), entry.key);
+    }
+
+    Butterfly* butterfly = thisObject->m_butterfly.getWithoutBarrier();
+    if (butterfly) {
+        WriteBarrier<Unknown>* data;
+        uint32_t count = 0;
+
+        switch (thisObject->indexingType()) {
+        case ALL_CONTIGUOUS_INDEXING_TYPES:
+            data = butterfly->contiguous().data();
+            count = butterfly->publicLength();
+            break;
+        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
+            data = butterfly->arrayStorage()->m_vector;
+            count = butterfly->arrayStorage()->vectorLength();
+            break;
+        default:
+            break;
+        }
+
+        for (uint32_t i = 0; i < count; ++i) {
+            JSValue toValue = data[i].get();
+            if (toValue && toValue.isCell())
+                builder.appendIndexEdge(thisObject, toValue.asCell(), i);
+        }
+    }
+}
+
 void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
 {
     JSFinalObject* thisObject = jsCast<JSFinalObject*>(cell);
@@ -248,13 +286,13 @@ void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
     
     JSCell::visitChildren(thisObject, visitor);
 
-    Structure* structure = thisObject->structure();
+    Structure* structure = thisObject->structure(visitor.vm());
     Butterfly* butterfly = thisObject->butterfly();
     if (butterfly)
-        thisObject->visitButterfly(visitor, butterfly, structure->outOfLineSize());
+        thisObject->visitButterfly(visitor, butterfly, structure);
 
     size_t storageSize = structure->inlineSize();
-    visitor.appendValues(thisObject->inlineStorage(), storageSize);
+    visitor.appendValuesHidden(thisObject->inlineStorage(), storageSize);
 
 #if !ASSERT_DISABLED
     visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
index 796c841..43b9906 100644 (file)
@@ -99,6 +99,7 @@ public:
     JS_EXPORT_PRIVATE static size_t estimatedSize(JSCell*);
     JS_EXPORT_PRIVATE static void visitChildren(JSCell*, SlotVisitor&);
     JS_EXPORT_PRIVATE static void copyBackingStore(JSCell*, CopyVisitor&, CopyToken);
+    JS_EXPORT_PRIVATE static void heapSnapshot(JSCell*, HeapSnapshotBuilder&);
 
     JS_EXPORT_PRIVATE static String className(const JSObject*);
     JS_EXPORT_PRIVATE static String calculatedClassName(JSObject*);
@@ -785,7 +786,7 @@ protected:
     // To create derived types you likely want JSNonFinalObject, below.
     JSObject(VM&, Structure*, Butterfly* = 0);
         
-    void visitButterfly(SlotVisitor&, Butterfly*, size_t storageSize);
+    void visitButterfly(SlotVisitor&, Butterfly*, Structure*);
     void copyButterfly(CopyVisitor&, Butterfly*, size_t storageSize);
 
     // Call this if you know that the object is in a mode where it has array
index 15f19f5..9d5bdf5 100644 (file)
@@ -29,6 +29,7 @@
 #include "config.h"
 #include "JSSegmentedVariableObject.h"
 
+#include "HeapSnapshotBuilder.h"
 #include "JSCInlines.h"
 
 namespace JSC {
@@ -63,10 +64,30 @@ void JSSegmentedVariableObject::visitChildren(JSCell* cell, SlotVisitor& slotVis
 {
     JSSegmentedVariableObject* thisObject = jsCast<JSSegmentedVariableObject*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
-    JSSymbolTableObject::visitChildren(thisObject, slotVisitor);
+    Base::visitChildren(thisObject, slotVisitor);
     
     for (unsigned i = thisObject->m_variables.size(); i--;)
-        slotVisitor.append(&thisObject->m_variables[i]);
+        slotVisitor.appendHidden(&thisObject->m_variables[i]);
+}
+
+void JSSegmentedVariableObject::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder)
+{
+    JSSegmentedVariableObject* thisObject = jsCast<JSSegmentedVariableObject*>(cell);
+    Base::heapSnapshot(cell, builder);
+
+    ConcurrentJITLocker locker(thisObject->symbolTable()->m_lock);
+    SymbolTable::Map::iterator end = thisObject->symbolTable()->end(locker);
+    for (SymbolTable::Map::iterator it = thisObject->symbolTable()->begin(locker); it != end; ++it) {
+        SymbolTableEntry::Fast entry = it->value;
+        ASSERT(!entry.isNull());
+        ScopeOffset offset = entry.scopeOffset();
+        if (!thisObject->isValidScopeOffset(offset))
+            continue;
+
+        JSValue toValue = thisObject->variableAt(offset).get();
+        if (toValue && toValue.isCell())
+            builder.appendVariableNameEdge(thisObject, toValue.asCell(), it->key.get());
+    }
 }
 
 } // namespace JSC
index 476c288..4b06bbd 100644 (file)
@@ -85,6 +85,7 @@ public:
     JS_EXPORT_PRIVATE ScopeOffset addVariables(unsigned numberOfVariablesToAdd, JSValue);
     
     JS_EXPORT_PRIVATE static void visitChildren(JSCell*, SlotVisitor&);
+    JS_EXPORT_PRIVATE static void heapSnapshot(JSCell*, HeapSnapshotBuilder&);
 
 protected:
     JSSegmentedVariableObject(VM& vm, Structure* structure, JSScope* scope)
index 5bc663d..b383f54 100644 (file)
@@ -1127,7 +1127,9 @@ void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor)
     if (thisObject->isPinnedPropertyTable()) {
         ASSERT(thisObject->m_propertyTableUnsafe);
         visitor.append(&thisObject->m_propertyTableUnsafe);
-    } else if (thisObject->m_propertyTableUnsafe)
+    } else if (visitor.isBuildingHeapSnapshot())
+        visitor.append(&thisObject->m_propertyTableUnsafe);
+    else if (thisObject->m_propertyTableUnsafe)
         thisObject->m_propertyTableUnsafe.clear();
 
     visitor.append(&thisObject->m_inferredTypeTable);
index c268480..8e456cb 100644 (file)
@@ -720,6 +720,7 @@ private:
     StructureTransitionTable m_transitionTable;
 
     // Should be accessed through propertyTable(). During GC, it may be set to 0 by another thread.
+    // During a Heap Snapshot GC we avoid clearing the table so it is safe to use.
     WriteBarrier<PropertyTable> m_propertyTableUnsafe;
 
     WriteBarrier<InferredTypeTable> m_inferredTypeTable;
index de75b4f..7e78329 100644 (file)
@@ -242,7 +242,7 @@ inline bool Structure::putWillGrowOutOfLineStorage()
 
 ALWAYS_INLINE WriteBarrier<PropertyTable>& Structure::propertyTable()
 {
-    ASSERT(!globalObject() || !globalObject()->vm().heap.isCollecting());
+    ASSERT(!globalObject() || (!globalObject()->vm().heap.isCollecting() || globalObject()->vm().heap.isHeapSnapshotting()));
     return m_propertyTableUnsafe;
 }
 
index d06ca82..82f80f0 100644 (file)
@@ -240,3 +240,17 @@ function createHeapSnapshot() {
 
     return new HeapSnapshot(json);
 }
+
+function followPath(node, path) {
+    let current = node;
+    for (let component of path) {
+        let edges = null;
+        if (component.edge)
+            edges = current.outgoingEdges.filter((e) => e.data === component.edge);
+        else if (component.node)
+            edges = current.outgoingEdges.filter((e) => e.to.className === component.node);
+        assert(edges.length === 1, "Ambiguous or bad path component: " + JSON.stringify(component));
+        current = edges[0].to;
+    }
+    return current;
+}
diff --git a/Source/JavaScriptCore/tests/heapProfiler/property-edge-types.js b/Source/JavaScriptCore/tests/heapProfiler/property-edge-types.js
new file mode 100644 (file)
index 0000000..a9baf46
--- /dev/null
@@ -0,0 +1,98 @@
+load("./driver/driver.js");
+
+let simpleObject = new SimpleObject;
+setHiddenValue(simpleObject, "hiddenValue"); // Internal
+simpleObject.propertyName1 = "propertyValue1"; // Property
+simpleObject["propertyName2"] = "propertyValue2"; // Property
+simpleObject[100] = "indexedValue"; // Index
+simpleObject[0xffffffff + 100] = "largeIndexValueBecomingProperty"; // Property
+simpleObject.point = {x:"x1", y:"y1"}; // Property => object with 2 inline properties.
+
+// ----------
+
+function excludeStructure(edges) {
+    return edges.filter((x) => x.to.className !== "Structure");
+}
+
+let snapshot = createHeapSnapshot();
+
+// Internal, Property, and Index edges on an Object.
+let nodes = snapshot.nodesWithClassName("SimpleObject");
+assert(nodes.length === 1, "Snapshot should contain 1 'SimpleObject' instance");
+let simpleObjectNode = nodes[0];
+let edges = excludeStructure(simpleObjectNode.outgoingEdges);
+let pointNode = null;
+
+let seenHiddenValue = false;
+let seenPropertyName1 = false;
+let seenPropertyName2 = false;
+let seenIndex100 = false;
+let seenLargeIndex = false;
+let seenObjectWithInlineStorage = false;
+let largeIndexName = (0xffffffff + 100).toString();
+
+for (let edge of edges) {
+    switch (edge.type) {
+    case "Internal":
+        assert(!seenHiddenValue);
+        seenHiddenValue = true;
+        break;
+    case "Property":
+        if (edge.data === "propertyName1")
+            seenPropertyName1 = true;
+        else if (edge.data === "propertyName2")
+            seenPropertyName2 = true;
+        else if (edge.data === largeIndexName)
+            seenLargeIndex = true;
+        else if (edge.data === "point") {
+            seenPoint = true;
+            pointNode = edge.to;
+        } else
+            assert(false, "Unexpected property name");
+        break;
+    case "Index":
+        if (edge.data === 100)
+            seenIndex100 = true;
+        break;
+    case "Variable":
+        assert(false, "Should not see a variable edge for SimpleObject instance");
+        break;
+    default:
+        assert(false, "Unexpected edge type");
+        break;
+    }
+}
+
+assert(seenHiddenValue, "Should see Internal edge for hidden value");
+assert(seenPropertyName1, "Should see Property edge for propertyName1");
+assert(seenPropertyName2, "Should see Property edge for propertyName2");
+assert(seenIndex100, "Should see Index edge for index 100");
+assert(seenLargeIndex, "Should see Property edge for index " + largeIndexName);
+
+
+// Property on an Object's inline storage.
+let pointEdges = excludeStructure(pointNode.outgoingEdges);
+
+let seenPropertyX = false;
+let seenPropertyY = false;
+
+for (let edge of pointEdges) {
+    switch (edge.type) {
+    case "Property":
+        if (edge.data === "x")
+            seenPropertyX = true;
+        else if (edge.data === "y")
+            seenPropertyY = true;
+        else
+            assert(false, "Unexpected property name");
+        break;
+    case "Index":
+    case "Variable":
+    case "Internal":
+        assert(false, "Unexpected edge type");
+        break;
+    }
+}
+
+assert(seenPropertyX, "Should see Property edge for x");
+assert(seenPropertyY, "Should see Property edge for y");
diff --git a/Source/JavaScriptCore/tests/heapProfiler/variable-edge-types.js b/Source/JavaScriptCore/tests/heapProfiler/variable-edge-types.js
new file mode 100644 (file)
index 0000000..92a675e
--- /dev/null
@@ -0,0 +1,69 @@
+load("./driver/driver.js");
+
+let globalScopeVariable = "globalScopeVariableValue";
+let simpleObject = new SimpleObject;
+
+(function() {
+    let closureVariable = {};
+    simpleObject.f = function() { closureVariable.x = 0; }
+})();
+
+// ----------
+
+let snapshot = createHeapSnapshot();
+
+// Global Scope => "globalScopeVariable"
+let nodes = snapshot.nodesWithClassName("JSGlobalLexicalEnvironment");
+assert(nodes.length === 1, "Should be only 1 'JSGlobalLexicalEnvironment' instance");
+let globalScopeNode = nodes[0];
+
+let seenGlobalScopeVariable = false;
+let seenSimpleObjectVariable = false;
+
+for (let edge of globalScopeNode.outgoingEdges) {
+    switch (edge.type) {
+    case "Variable":
+        if (edge.data === "globalScopeVariable")
+            seenGlobalScopeVariable = true;
+        else if (edge.data === "simpleObject")
+            seenSimpleObjectVariable = true;
+        else
+            assert(false, "Unexpected variable name: " + edge.data);
+        break;
+    case "Index":
+    case "Property":
+        assert(false, "Unexpected edge type");
+        break;
+    case "Internal":
+        break;
+    }
+}
+
+assert(seenGlobalScopeVariable, "Should see Variable edge for variable 'globalScopeVariable'");
+assert(seenSimpleObjectVariable, "Should see Variable edge for variable 'simpleObject'");
+
+// Function Scope => "closureVariable"
+nodes = snapshot.nodesWithClassName("SimpleObject");
+assert(nodes.length === 1, "Should be only 1 'SimpleObject' instance");
+let scopeNode = followPath(nodes[0], [{edge: "f"}, {node: "JSLexicalEnvironment"}]);
+
+let seenClosureVariable = false;
+
+for (let edge of scopeNode.outgoingEdges) {
+    switch (edge.type) {
+    case "Variable":
+        if (edge.data === "closureVariable")
+            seenClosureVariable = true;
+        else
+            assert(false, "Unexpected variable name: " + edge.data);
+        break;
+    case "Index":
+    case "Property":
+        assert(false, "Unexpected edge type");
+        break;
+    case "Internal":
+        break;
+    }
+}
+
+assert(seenClosureVariable, "Should see Variable edge for closure variable 'closureVariable'");