• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
addReferencesForNodeWithEventListeners(v8::Isolate * isolate,Node * node,const v8::Persistent<v8::Object> & wrapper)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 
opaqueRootForGC(Node * node,v8::Isolate *)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:
MinorGCWrapperVisitor(v8::Isolate * isolate)105     explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
106         : m_isolate(isolate)
107     { }
108 
VisitPersistentHandle(v8::Persistent<v8::Value> * value,uint16_t classId)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 
notifyFinished()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:
traverseTree(Node * rootNode,Vector<Node *,initialNodeVectorSize> * partiallyDependentNodes)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 
gcTree(v8::Isolate * isolate,Node * startNode)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:
MajorGCWrapperVisitor(v8::Isolate * isolate,bool constructRetainedObjectInfos)249     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
250         : m_isolate(isolate)
251         , m_liveRootGroupIdSet(false)
252         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
253     {
254     }
255 
VisitPersistentHandle(v8::Persistent<v8::Value> * value,uint16_t classId)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 
notifyFinished()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:
liveRootId()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 
usedHeapSize(v8::Isolate * isolate)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 
gcPrologue(v8::GCType type,v8::GCCallbackFlags flags)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 
minorGCPrologue(v8::Isolate * isolate)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.
majorGCPrologue(bool constructRetainedObjectInfos,v8::Isolate * isolate)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 
gcEpilogue(v8::GCType type,v8::GCCallbackFlags flags)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 
minorGCEpilogue(v8::Isolate * isolate)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 
majorGCEpilogue(v8::Isolate * isolate)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 
collectGarbage(v8::Isolate * isolate)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 
reportDOMMemoryUsageToV8(v8::Isolate * isolate)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