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 "V8MessagePort.h"
     35 #include "V8MutationObserver.h"
     36 #include "V8Node.h"
     37 #include "V8ScriptRunner.h"
     38 #include "bindings/v8/RetainedDOMInfo.h"
     39 #include "bindings/v8/V8AbstractEventListener.h"
     40 #include "bindings/v8/V8Binding.h"
     41 #include "bindings/v8/WrapperTypeInfo.h"
     42 #include "core/dom/Attr.h"
     43 #include "core/dom/NodeTraversal.h"
     44 #include "core/dom/shadow/ElementShadow.h"
     45 #include "core/dom/shadow/ShadowRoot.h"
     46 #include "core/html/HTMLImageElement.h"
     47 #include "core/platform/chromium/TraceEvent.h"
     48 #include <algorithm>
     49 
     50 namespace WebCore {
     51 
     52 // FIXME: This should use opaque GC roots.
     53 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
     54 {
     55     ASSERT(node->hasEventListeners());
     56 
     57     EventListenerIterator iterator(node);
     58     while (EventListener* listener = iterator.nextListener()) {
     59         if (listener->type() != EventListener::JSEventListenerType)
     60             continue;
     61         V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
     62         if (!v8listener->hasExistingListenerObject())
     63             continue;
     64 
     65         // FIXME: update this to use the upcasting function which v8 will provide.
     66         v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(&(v8listener->existingListenerObjectPersistentHandle()));
     67         isolate->SetReference(wrapper, *value);
     68     }
     69 }
     70 
     71 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
     72 {
     73     // FIXME: Remove the special handling for image elements.
     74     // The same special handling is in V8GCController::gcTree().
     75     // Maybe should image elements be active DOM nodes?
     76     // See https://code.google.com/p/chromium/issues/detail?id=164882
     77     if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity()))
     78         return node->document();
     79 
     80     if (node->isAttributeNode()) {
     81         Node* ownerElement = toAttr(node)->ownerElement();
     82         if (!ownerElement)
     83             return node;
     84         node = ownerElement;
     85     }
     86 
     87     while (Node* parent = node->parentOrShadowHostNode())
     88         node = parent;
     89 
     90     return node;
     91 }
     92 
     93 // Regarding a minor GC algorithm for DOM nodes, see this document:
     94 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
     95 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
     96 public:
     97     explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
     98         : m_isolate(isolate)
     99     {
    100         UNUSED_PARAM(m_isolate);
    101     }
    102 
    103     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    104     {
    105         // A minor DOM GC can collect only Nodes.
    106         if (classId != v8DOMNodeClassId)
    107             return;
    108 
    109         // To make minor GC cycle time bounded, we limit the number of wrappers handled
    110         // by each minor GC cycle to 10000. This value was selected so that the minor
    111         // GC cycle time is bounded to 20 ms in a case where the new space size
    112         // is 16 MB and it is full of wrappers (which is almost the worst case).
    113         // Practically speaking, as far as I crawled real web applications,
    114         // the number of wrappers handled by each minor GC cycle is at most 3000.
    115         // So this limit is mainly for pathological micro benchmarks.
    116         const unsigned wrappersHandledByEachMinorGC = 10000;
    117         if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
    118             return;
    119 
    120         // Casting to a Handle is safe here, since the Persistent cannot get GCd
    121         // during the GC prologue.
    122         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    123         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    124         ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
    125         ASSERT(V8Node::HasInstanceInAnyWorld(*wrapper, m_isolate));
    126         Node* node = V8Node::toNative(*wrapper);
    127         // A minor DOM GC can handle only node wrappers in the main world.
    128         // Note that node->wrapper().IsEmpty() returns true for nodes that
    129         // do not have wrappers in the main world.
    130         if (node->containsWrapper()) {
    131             WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    132             ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    133             if (activeDOMObject && activeDOMObject->hasPendingActivity())
    134                 return;
    135             m_nodesInNewSpace.append(node);
    136             node->setV8CollectableDuringMinorGC(true);
    137         }
    138     }
    139 
    140     void notifyFinished()
    141     {
    142         Node** nodeIterator = m_nodesInNewSpace.begin();
    143         Node** nodeIteratorEnd = m_nodesInNewSpace.end();
    144         for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) {
    145             Node* node = *nodeIterator;
    146             ASSERT(node->containsWrapper());
    147             if (node->isV8CollectableDuringMinorGC()) // This branch is just for performance.
    148                 gcTree(m_isolate, node);
    149         }
    150     }
    151 
    152 private:
    153     bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* newSpaceNodes)
    154     {
    155         // To make each minor GC time bounded, we might need to give up
    156         // traversing at some point for a large DOM tree. That being said,
    157         // I could not observe the need even in pathological test cases.
    158         for (Node* node = rootNode; node; node = NodeTraversal::next(node)) {
    159             if (node->containsWrapper()) {
    160                 // FIXME: Remove the special handling for image elements.
    161                 // The same special handling is in V8GCController::opaqueRootForGC().
    162                 // Maybe should image elements be active DOM nodes?
    163                 // See https://code.google.com/p/chromium/issues/detail?id=164882
    164                 if (!node->isV8CollectableDuringMinorGC() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity())) {
    165                     // This node is not in the new space of V8. This indicates that
    166                     // the minor GC cannot anyway judge reachability of this DOM tree.
    167                     // Thus we give up traversing the DOM tree.
    168                     return false;
    169                 }
    170                 node->setV8CollectableDuringMinorGC(false);
    171                 newSpaceNodes->append(node);
    172             }
    173             if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
    174                 if (!traverseTree(shadowRoot, newSpaceNodes))
    175                     return false;
    176             } else if (node->isShadowRoot()) {
    177                 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
    178                     if (!traverseTree(shadowRoot, newSpaceNodes))
    179                         return false;
    180                 }
    181             }
    182         }
    183         return true;
    184     }
    185 
    186     void gcTree(v8::Isolate* isolate, Node* startNode)
    187     {
    188         Vector<Node*, initialNodeVectorSize> newSpaceNodes;
    189 
    190         Node* node = startNode;
    191         while (node->parentOrShadowHostNode())
    192             node = node->parentOrShadowHostNode();
    193 
    194         if (!traverseTree(node, &newSpaceNodes))
    195             return;
    196 
    197         // We completed the DOM tree traversal. All wrappers in the DOM tree are
    198         // stored in newSpaceNodes and are expected to exist in the new space of V8.
    199         // We report those wrappers to V8 as an object group.
    200         Node** nodeIterator = newSpaceNodes.begin();
    201         Node** const nodeIteratorEnd = newSpaceNodes.end();
    202         if (nodeIterator == nodeIteratorEnd)
    203             return;
    204         v8::UniqueId id(reinterpret_cast<intptr_t>((*nodeIterator)->unsafePersistent().value()));
    205         for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) {
    206             // This is safe because we know that GC won't happen before we
    207             // dispose the UnsafePersistent (we're just preparing a GC). Though,
    208             // we need to keep the UnsafePersistent alive until we're done with
    209             // v8::Persistent.
    210             UnsafePersistent<v8::Object> unsafeWrapper = (*nodeIterator)->unsafePersistent();
    211             v8::Persistent<v8::Object>* wrapper = unsafeWrapper.persistent();
    212             wrapper->MarkPartiallyDependent(isolate);
    213             // FIXME: update this to use the upcasting function which v8 will provide
    214             v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(wrapper);
    215             isolate->SetObjectGroupId(*value, id);
    216         }
    217     }
    218 
    219     Vector<Node*> m_nodesInNewSpace;
    220     v8::Isolate* m_isolate;
    221 };
    222 
    223 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
    224 public:
    225     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
    226         : m_isolate(isolate)
    227         , m_liveRootGroupIdSet(false)
    228         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
    229     {
    230     }
    231 
    232     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    233     {
    234         // Casting to a Handle is safe here, since the Persistent cannot get GCd
    235         // during the GC prologue.
    236         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    237 
    238         if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId)
    239             return;
    240 
    241         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    242 
    243         ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
    244 
    245         if (value->IsIndependent(m_isolate))
    246             return;
    247 
    248         WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    249         void* object = toNative(*wrapper);
    250 
    251         if (V8MessagePort::info.equals(type)) {
    252             // Mark each port as in-use if it's entangled. For simplicity's sake,
    253             // we assume all ports are remotely entangled, since the Chromium port
    254             // implementation can't tell the difference.
    255             MessagePort* port = static_cast<MessagePort*>(object);
    256             if (port->isEntangled() || port->hasPendingActivity())
    257                 m_isolate->SetObjectGroupId(*value, liveRootId());
    258         } else if (V8MutationObserver::info.equals(type)) {
    259             // FIXME: Allow opaqueRootForGC to operate on multiple roots and move this logic into V8MutationObserverCustom.
    260             MutationObserver* observer = static_cast<MutationObserver*>(object);
    261             HashSet<Node*> observedNodes = observer->getObservedNodes();
    262             for (HashSet<Node*>::iterator it = observedNodes.begin(); it != observedNodes.end(); ++it) {
    263                 v8::UniqueId id(reinterpret_cast<intptr_t>(V8GCController::opaqueRootForGC(*it, m_isolate)));
    264                 m_isolate->SetReferenceFromGroup(id, *value);
    265             }
    266         } else {
    267             ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    268             if (activeDOMObject && activeDOMObject->hasPendingActivity())
    269                 m_isolate->SetObjectGroupId(*value, liveRootId());
    270         }
    271 
    272         if (classId == v8DOMNodeClassId) {
    273             UNUSED_PARAM(m_isolate);
    274             ASSERT(V8Node::HasInstanceInAnyWorld(*wrapper, m_isolate));
    275             ASSERT(!value->IsIndependent(m_isolate));
    276 
    277             Node* node = static_cast<Node*>(object);
    278 
    279             if (node->hasEventListeners())
    280                 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
    281             Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
    282             m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
    283             if (m_constructRetainedObjectInfos)
    284                 m_groupsWhichNeedRetainerInfo.append(root);
    285         } else if (classId == v8DOMObjectClassId) {
    286             ASSERT(!value->IsIndependent(m_isolate));
    287             void* root = type->opaqueRootForGC(object, m_isolate);
    288             m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
    289         } else {
    290             ASSERT_NOT_REACHED();
    291         }
    292     }
    293 
    294     void notifyFinished()
    295     {
    296         if (!m_constructRetainedObjectInfos)
    297             return;
    298         std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
    299         Node* alreadyAdded = 0;
    300         v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
    301         for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
    302             Node* root = m_groupsWhichNeedRetainerInfo[i];
    303             if (root != alreadyAdded) {
    304                 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
    305                 alreadyAdded = root;
    306             }
    307         }
    308     }
    309 
    310 private:
    311     v8::UniqueId liveRootId()
    312     {
    313         const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
    314         const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
    315         v8::UniqueId id(*idPointer);
    316         if (!m_liveRootGroupIdSet) {
    317             m_isolate->SetObjectGroupId(liveRoot, id);
    318             m_liveRootGroupIdSet = true;
    319         }
    320         return id;
    321     }
    322 
    323     v8::Isolate* m_isolate;
    324     Vector<Node*> m_groupsWhichNeedRetainerInfo;
    325     bool m_liveRootGroupIdSet;
    326     bool m_constructRetainedObjectInfos;
    327 };
    328 
    329 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
    330 {
    331     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    332     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    333     if (type == v8::kGCTypeScavenge)
    334         minorGCPrologue(isolate);
    335     else if (type == v8::kGCTypeMarkSweepCompact)
    336         majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
    337 }
    338 
    339 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
    340 {
    341     TRACE_EVENT_BEGIN0("v8", "minorGC");
    342     if (isMainThread()) {
    343         {
    344             TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MinorGC");
    345             v8::HandleScope scope(isolate);
    346             MinorGCWrapperVisitor visitor(isolate);
    347             v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
    348             visitor.notifyFinished();
    349         }
    350         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    351         TRACE_EVENT_SET_SAMPLING_STATE("V8", "MinorGC");
    352     }
    353 }
    354 
    355 // Create object groups for DOM tree nodes.
    356 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
    357 {
    358     v8::HandleScope scope(isolate);
    359     TRACE_EVENT_BEGIN0("v8", "majorGC");
    360     if (isMainThread()) {
    361         {
    362             TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MajorGC");
    363             MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    364             v8::V8::VisitHandlesWithClassIds(&visitor);
    365             visitor.notifyFinished();
    366         }
    367         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    368         TRACE_EVENT_SET_SAMPLING_STATE("V8", "MajorGC");
    369     } else {
    370         MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    371         v8::V8::VisitHandlesWithClassIds(&visitor);
    372         visitor.notifyFinished();
    373     }
    374 }
    375 
    376 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
    377 {
    378     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    379     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    380     if (type == v8::kGCTypeScavenge)
    381         minorGCEpilogue(isolate);
    382     else if (type == v8::kGCTypeMarkSweepCompact)
    383         majorGCEpilogue(isolate);
    384 }
    385 
    386 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
    387 {
    388     TRACE_EVENT_END0("v8", "minorGC");
    389     if (isMainThread())
    390         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    391 }
    392 
    393 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
    394 {
    395     v8::HandleScope scope(isolate);
    396 
    397     TRACE_EVENT_END0("v8", "majorGC");
    398     if (isMainThread())
    399         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    400 }
    401 
    402 void V8GCController::hintForCollectGarbage()
    403 {
    404     V8PerIsolateData* data = V8PerIsolateData::current();
    405     if (!data->shouldCollectGarbageSoon())
    406         return;
    407     const int longIdlePauseInMS = 1000;
    408     data->clearShouldCollectGarbageSoon();
    409     v8::V8::ContextDisposedNotification();
    410     v8::V8::IdleNotification(longIdlePauseInMS);
    411 }
    412 
    413 void V8GCController::collectGarbage(v8::Isolate* isolate)
    414 {
    415     v8::HandleScope handleScope(isolate);
    416     v8::Local<v8::Context> context = v8::Context::New(isolate);
    417     if (context.IsEmpty())
    418         return;
    419     v8::Context::Scope contextScope(context);
    420     V8ScriptRunner::compileAndRunInternalScript(v8String("if (gc) gc();", isolate), isolate);
    421 }
    422 
    423 }  // namespace WebCore
    424