Home | History | Annotate | Download | only in v8
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "bindings/v8/V8GCController.h"
     33 
     34 #include "bindings/core/v8/V8MutationObserver.h"
     35 #include "bindings/core/v8/V8Node.h"
     36 #include "bindings/v8/RetainedDOMInfo.h"
     37 #include "bindings/v8/V8AbstractEventListener.h"
     38 #include "bindings/v8/V8Binding.h"
     39 #include "bindings/v8/V8ScriptRunner.h"
     40 #include "bindings/v8/WrapperTypeInfo.h"
     41 #include "core/dom/Attr.h"
     42 #include "core/dom/NodeTraversal.h"
     43 #include "core/dom/TemplateContentDocumentFragment.h"
     44 #include "core/dom/shadow/ElementShadow.h"
     45 #include "core/dom/shadow/ShadowRoot.h"
     46 #include "core/html/HTMLImageElement.h"
     47 #include "core/html/HTMLTemplateElement.h"
     48 #include "core/html/imports/HTMLImportsController.h"
     49 #include "core/inspector/InspectorTraceEvents.h"
     50 #include "core/svg/SVGElement.h"
     51 #include "platform/Partitions.h"
     52 #include "platform/TraceEvent.h"
     53 #include <algorithm>
     54 
     55 namespace WebCore {
     56 
     57 // FIXME: This should use opaque GC roots.
     58 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
     59 {
     60     ASSERT(node->hasEventListeners());
     61 
     62     EventListenerIterator iterator(node);
     63     while (EventListener* listener = iterator.nextListener()) {
     64         if (listener->type() != EventListener::JSEventListenerType)
     65             continue;
     66         V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
     67         if (!v8listener->hasExistingListenerObject())
     68             continue;
     69 
     70         isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
     71     }
     72 }
     73 
     74 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
     75 {
     76     ASSERT(node);
     77     // FIXME: Remove the special handling for image elements.
     78     // The same special handling is in V8GCController::gcTree().
     79     // Maybe should image elements be active DOM nodes?
     80     // See https://code.google.com/p/chromium/issues/detail?id=164882
     81     if (node->inDocument() || (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())) {
     82         Document& document = node->document();
     83         if (HTMLImportsController* controller = document.importsController())
     84             return controller->master();
     85         return &document;
     86     }
     87 
     88     if (node->isAttributeNode()) {
     89         Node* ownerElement = toAttr(node)->ownerElement();
     90         if (!ownerElement)
     91             return node;
     92         node = ownerElement;
     93     }
     94 
     95     while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
     96         node = parent;
     97 
     98     return node;
     99 }
    100 
    101 // Regarding a minor GC algorithm for DOM nodes, see this document:
    102 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
    103 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
    104 public:
    105     explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
    106         : m_isolate(isolate)
    107     { }
    108 
    109     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    110     {
    111         // A minor DOM GC can collect only Nodes.
    112         if (classId != v8DOMNodeClassId)
    113             return;
    114 
    115         // To make minor GC cycle time bounded, we limit the number of wrappers handled
    116         // by each minor GC cycle to 10000. This value was selected so that the minor
    117         // GC cycle time is bounded to 20 ms in a case where the new space size
    118         // is 16 MB and it is full of wrappers (which is almost the worst case).
    119         // Practically speaking, as far as I crawled real web applications,
    120         // the number of wrappers handled by each minor GC cycle is at most 3000.
    121         // So this limit is mainly for pathological micro benchmarks.
    122         const unsigned wrappersHandledByEachMinorGC = 10000;
    123         if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
    124             return;
    125 
    126         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
    127         // during the GC prologue.
    128         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    129         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    130         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
    131         ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
    132         Node* node = V8Node::toNative(*wrapper);
    133         // A minor DOM GC can handle only node wrappers in the main world.
    134         // Note that node->wrapper().IsEmpty() returns true for nodes that
    135         // do not have wrappers in the main world.
    136         if (node->containsWrapper()) {
    137             const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    138             ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    139             if (activeDOMObject && activeDOMObject->hasPendingActivity())
    140                 return;
    141             // FIXME: Remove the special handling for image elements.
    142             // The same special handling is in V8GCController::opaqueRootForGC().
    143             // Maybe should image elements be active DOM nodes?
    144             // See https://code.google.com/p/chromium/issues/detail?id=164882
    145             if (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())
    146                 return;
    147             // FIXME: Remove the special handling for SVG context elements.
    148             if (node->isSVGElement() && toSVGElement(node)->isContextElement())
    149                 return;
    150 
    151             m_nodesInNewSpace.append(node);
    152             node->markV8CollectableDuringMinorGC();
    153         }
    154     }
    155 
    156     void notifyFinished()
    157     {
    158         Node** nodeIterator = m_nodesInNewSpace.begin();
    159         Node** nodeIteratorEnd = m_nodesInNewSpace.end();
    160         for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) {
    161             Node* node = *nodeIterator;
    162             ASSERT(node->containsWrapper());
    163             if (node->isV8CollectableDuringMinorGC()) { // This branch is just for performance.
    164                 gcTree(m_isolate, node);
    165                 node->clearV8CollectableDuringMinorGC();
    166             }
    167         }
    168     }
    169 
    170 private:
    171     bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* partiallyDependentNodes)
    172     {
    173         // To make each minor GC time bounded, we might need to give up
    174         // traversing at some point for a large DOM tree. That being said,
    175         // I could not observe the need even in pathological test cases.
    176         for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) {
    177             if (node->containsWrapper()) {
    178                 if (!node->isV8CollectableDuringMinorGC()) {
    179                     // This node is not in the new space of V8. This indicates that
    180                     // the minor GC cannot anyway judge reachability of this DOM tree.
    181                     // Thus we give up traversing the DOM tree.
    182                     return false;
    183                 }
    184                 node->clearV8CollectableDuringMinorGC();
    185                 partiallyDependentNodes->append(node);
    186             }
    187             if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
    188                 if (!traverseTree(shadowRoot, partiallyDependentNodes))
    189                     return false;
    190             } else if (node->isShadowRoot()) {
    191                 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
    192                     if (!traverseTree(shadowRoot, partiallyDependentNodes))
    193                         return false;
    194                 }
    195             }
    196             // <template> has a |content| property holding a DOM fragment which we must traverse,
    197             // just like we do for the shadow trees above.
    198             if (isHTMLTemplateElement(*node)) {
    199                 if (!traverseTree(toHTMLTemplateElement(*node).content(), partiallyDependentNodes))
    200                     return false;
    201             }
    202 
    203             // Document maintains the list of imported documents through HTMLImportsController.
    204             if (node->isDocumentNode()) {
    205                 Document* document = toDocument(node);
    206                 HTMLImportsController* controller = document->importsController();
    207                 if (controller && document == controller->master()) {
    208                     for (unsigned i = 0; i < controller->loaderCount(); ++i) {
    209                         if (!traverseTree(controller->loaderDocumentAt(i), partiallyDependentNodes))
    210                             return false;
    211                     }
    212                 }
    213             }
    214         }
    215         return true;
    216     }
    217 
    218     void gcTree(v8::Isolate* isolate, Node* startNode)
    219     {
    220         Vector<Node*, initialNodeVectorSize> partiallyDependentNodes;
    221 
    222         Node* node = startNode;
    223         while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
    224             node = parent;
    225 
    226         if (!traverseTree(node, &partiallyDependentNodes))
    227             return;
    228 
    229         // We completed the DOM tree traversal. All wrappers in the DOM tree are
    230         // stored in partiallyDependentNodes and are expected to exist in the new space of V8.
    231         // We report those wrappers to V8 as an object group.
    232         Node** nodeIterator = partiallyDependentNodes.begin();
    233         Node** const nodeIteratorEnd = partiallyDependentNodes.end();
    234         if (nodeIterator == nodeIteratorEnd)
    235             return;
    236 
    237         Node* groupRoot = *nodeIterator;
    238         for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) {
    239             (*nodeIterator)->markAsDependentGroup(groupRoot, isolate);
    240         }
    241     }
    242 
    243     Vector<Node*> m_nodesInNewSpace;
    244     v8::Isolate* m_isolate;
    245 };
    246 
    247 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
    248 public:
    249     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
    250         : m_isolate(isolate)
    251         , m_liveRootGroupIdSet(false)
    252         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
    253     {
    254     }
    255 
    256     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    257     {
    258         if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId)
    259             return;
    260 
    261         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
    262         // during the GC prologue.
    263         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    264         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    265         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
    266 
    267         if (value->IsIndependent())
    268             return;
    269 
    270         const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    271         void* object = toNative(*wrapper);
    272 
    273         ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    274         if (activeDOMObject && activeDOMObject->hasPendingActivity())
    275             m_isolate->SetObjectGroupId(*value, liveRootId());
    276 
    277         if (classId == v8DOMNodeClassId) {
    278             ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
    279             Node* node = static_cast<Node*>(object);
    280             if (node->hasEventListeners())
    281                 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
    282             Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
    283             m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
    284             if (m_constructRetainedObjectInfos)
    285                 m_groupsWhichNeedRetainerInfo.append(root);
    286         } else if (classId == v8DOMObjectClassId) {
    287             type->visitDOMWrapper(object, v8::Persistent<v8::Object>::Cast(*value), m_isolate);
    288         } else {
    289             ASSERT_NOT_REACHED();
    290         }
    291     }
    292 
    293     void notifyFinished()
    294     {
    295         if (!m_constructRetainedObjectInfos)
    296             return;
    297         std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
    298         Node* alreadyAdded = 0;
    299         v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
    300         for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
    301             Node* root = m_groupsWhichNeedRetainerInfo[i];
    302             if (root != alreadyAdded) {
    303                 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
    304                 alreadyAdded = root;
    305             }
    306         }
    307     }
    308 
    309 private:
    310     v8::UniqueId liveRootId()
    311     {
    312         const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
    313         const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
    314         v8::UniqueId id(*idPointer);
    315         if (!m_liveRootGroupIdSet) {
    316             m_isolate->SetObjectGroupId(liveRoot, id);
    317             m_liveRootGroupIdSet = true;
    318         }
    319         return id;
    320     }
    321 
    322     v8::Isolate* m_isolate;
    323     Vector<Node*> m_groupsWhichNeedRetainerInfo;
    324     bool m_liveRootGroupIdSet;
    325     bool m_constructRetainedObjectInfos;
    326 };
    327 
    328 static unsigned long long usedHeapSize(v8::Isolate* isolate)
    329 {
    330     v8::HeapStatistics heapStatistics;
    331     isolate->GetHeapStatistics(&heapStatistics);
    332     return heapStatistics.used_heap_size();
    333 }
    334 
    335 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
    336 {
    337     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    338     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    339     TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeBefore", usedHeapSize(isolate));
    340     if (type == v8::kGCTypeScavenge)
    341         minorGCPrologue(isolate);
    342     else if (type == v8::kGCTypeMarkSweepCompact)
    343         majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
    344 }
    345 
    346 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
    347 {
    348     TRACE_EVENT_BEGIN0("v8", "minorGC");
    349     if (isMainThread()) {
    350         {
    351             TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "DOMMinorGC");
    352             v8::HandleScope scope(isolate);
    353             MinorGCWrapperVisitor visitor(isolate);
    354             v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
    355             visitor.notifyFinished();
    356         }
    357         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    358         TRACE_EVENT_SET_SAMPLING_STATE("V8", "V8MinorGC");
    359     }
    360 }
    361 
    362 // Create object groups for DOM tree nodes.
    363 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
    364 {
    365     v8::HandleScope scope(isolate);
    366     TRACE_EVENT_BEGIN0("v8", "majorGC");
    367     if (isMainThread()) {
    368         {
    369             TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "DOMMajorGC");
    370             MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    371             v8::V8::VisitHandlesWithClassIds(&visitor);
    372             visitor.notifyFinished();
    373         }
    374         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    375         TRACE_EVENT_SET_SAMPLING_STATE("V8", "V8MajorGC");
    376     } else {
    377         MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    378         v8::V8::VisitHandlesWithClassIds(&visitor);
    379         visitor.notifyFinished();
    380     }
    381 }
    382 
    383 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
    384 {
    385     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    386     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    387     if (type == v8::kGCTypeScavenge)
    388         minorGCEpilogue(isolate);
    389     else if (type == v8::kGCTypeMarkSweepCompact)
    390         majorGCEpilogue(isolate);
    391 
    392     // Forces a Blink heap garbage collection when a garbage collection
    393     // was forced from V8. This is used for tests that force GCs from
    394     // JavaScript to verify that objects die when expected.
    395     if (flags & v8::kGCCallbackFlagForced) {
    396         // This single GC is not enough for two reasons:
    397         //   (1) The GC is not precise because the GC scans on-stack pointers conservatively.
    398         //   (2) One GC is not enough to break a chain of persistent handles. It's possible that
    399         //       some heap allocated objects own objects that contain persistent handles
    400         //       pointing to other heap allocated objects. To break the chain, we need multiple GCs.
    401         //
    402         // Regarding (1), we force a precise GC at the end of the current event loop. So if you want
    403         // to collect all garbage, you need to wait until the next event loop.
    404         // Regarding (2), it would be OK in practice to trigger only one GC per gcEpilogue, because
    405         // GCController.collectAll() forces 7 V8's GC.
    406         Heap::collectGarbage(ThreadState::HeapPointersOnStack);
    407 
    408         // Forces a precise GC at the end of the current event loop.
    409         Heap::setForcePreciseGCForTesting();
    410     }
    411 
    412     TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeAfter", usedHeapSize(isolate));
    413     TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data());
    414 }
    415 
    416 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
    417 {
    418     TRACE_EVENT_END0("v8", "minorGC");
    419     if (isMainThread())
    420         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    421 }
    422 
    423 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
    424 {
    425     v8::HandleScope scope(isolate);
    426 
    427     TRACE_EVENT_END0("v8", "majorGC");
    428     if (isMainThread())
    429         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    430 }
    431 
    432 void V8GCController::collectGarbage(v8::Isolate* isolate)
    433 {
    434     v8::HandleScope handleScope(isolate);
    435     RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create());
    436     ScriptState::Scope scope(scriptState.get());
    437     V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
    438     scriptState->disposePerContextData();
    439 }
    440 
    441 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
    442 {
    443     if (!isMainThread())
    444         return;
    445 
    446     static size_t lastUsageReportedToV8 = 0;
    447 
    448     size_t currentUsage = Partitions::currentDOMMemoryUsage();
    449     int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
    450     isolate->AdjustAmountOfExternalAllocatedMemory(diff);
    451 
    452     lastUsageReportedToV8 = currentUsage;
    453 }
    454 
    455 }  // namespace WebCore
    456