V8GCController.cpp [plain text]
#include "config.h"
#include "V8GCController.h"
#include "ActiveDOMObject.h"
#include "Attr.h"
#include "DOMDataStore.h"
#include "DOMImplementation.h"
#include "HTMLImageElement.h"
#include "HTMLNames.h"
#include "MemoryUsageSupport.h"
#include "MessagePort.h"
#include "PlatformSupport.h"
#include "RetainedDOMInfo.h"
#include "RetainedObjectInfo.h"
#include "V8Binding.h"
#include "V8CSSRule.h"
#include "V8CSSRuleList.h"
#include "V8CSSStyleDeclaration.h"
#include "V8DOMImplementation.h"
#include "V8MessagePort.h"
#include "V8StyleSheet.h"
#include "V8StyleSheetList.h"
#include "WrapperTypeInfo.h"
#include <algorithm>
#include <utility>
#include <v8-debug.h>
#include <wtf/HashMap.h>
#include <wtf/StdLibExtras.h>
#include <wtf/UnusedParam.h>
namespace WebCore {
#ifndef NDEBUG
static GlobalHandleMap& currentGlobalHandleMap()
{
return V8BindingPerIsolateData::current()->globalHandleMap();
}
static void enumerateGlobalHandles()
{
GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
for (GlobalHandleMap::iterator it = globalHandleMap.begin(), end = globalHandleMap.end(); it != end; ++it) {
GlobalHandleInfo* info = it->second;
UNUSED_PARAM(info);
v8::Value* handle = it->first;
UNUSED_PARAM(handle);
}
}
void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
{
GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
ASSERT(!globalHandleMap.contains(*handle));
globalHandleMap.set(*handle, new GlobalHandleInfo(host, type));
}
void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
{
GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
ASSERT(globalHandleMap.contains(*handle));
GlobalHandleInfo* info = globalHandleMap.take(*handle);
ASSERT(info->m_host == host);
delete info;
}
#endif // ifndef NDEBUG
typedef HashMap<Node*, v8::Object*> DOMNodeMap;
typedef HashMap<void*, v8::Object*> DOMObjectMap;
#ifndef NDEBUG
class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
public:
void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
{
WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
UNUSED_PARAM(type);
UNUSED_PARAM(object);
}
};
class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
public:
void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
{
UNUSED_PARAM(object);
ASSERT(wrapper.IsWeak());
}
};
#endif // NDEBUG
class SpecialCasePrologueObjectHandler {
public:
static bool process(void* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
{
if (V8MessagePort::info.equals(typeInfo)) {
MessagePort* port1 = static_cast<MessagePort*>(object);
if (port1->isEntangled() || port1->hasPendingActivity())
wrapper.ClearWeak();
return true;
}
return false;
}
};
class SpecialCasePrologueNodeHandler {
public:
static bool process(Node* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
{
UNUSED_PARAM(object);
UNUSED_PARAM(wrapper);
UNUSED_PARAM(typeInfo);
return false;
}
};
template<typename T, typename S>
class GCPrologueVisitor : public DOMWrapperMap<T>::Visitor {
public:
void visitDOMWrapper(DOMDataStore* store, T* object, v8::Persistent<v8::Object> wrapper)
{
WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
if (!S::process(object, wrapper, typeInfo)) {
ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
if (activeDOMObject && activeDOMObject->hasPendingActivity())
wrapper.ClearWeak();
}
}
};
class UnspecifiedGroup : public RetainedObjectInfo {
public:
explicit UnspecifiedGroup(void* object)
: m_object(object)
{
ASSERT(m_object);
}
virtual void Dispose() { delete this; }
virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
{
ASSERT(other);
return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
}
virtual intptr_t GetHash()
{
return PtrHash<void*>::hash(m_object);
}
virtual const char* GetLabel()
{
return "Object group";
}
virtual intptr_t GetEquivalenceClass()
{
return reinterpret_cast<intptr_t>(m_object);
}
private:
void* m_object;
};
class GroupId {
public:
GroupId() : m_type(NullType), m_groupId(0) {}
GroupId(Node* node) : m_type(NodeType), m_node(node) {}
GroupId(void* other) : m_type(OtherType), m_other(other) {}
bool operator!() const { return m_type == NullType; }
uintptr_t groupId() const { return m_groupId; }
RetainedObjectInfo* createRetainedObjectInfo() const
{
switch (m_type) {
case NullType:
return 0;
case NodeType:
return new RetainedDOMInfo(m_node);
case OtherType:
return new UnspecifiedGroup(m_other);
default:
return 0;
}
}
private:
enum Type {
NullType,
NodeType,
OtherType
};
Type m_type;
union {
uintptr_t m_groupId;
Node* m_node;
void* m_other;
};
};
class GrouperItem {
public:
GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
uintptr_t groupId() const { return m_groupId.groupId(); }
RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
private:
GroupId m_groupId;
v8::Persistent<v8::Object> m_wrapper;
};
bool operator<(const GrouperItem& a, const GrouperItem& b)
{
return a.groupId() < b.groupId();
}
typedef Vector<GrouperItem> GrouperList;
static GroupId calculateGroupId(Node* node)
{
if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && static_cast<HTMLImageElement*>(node)->hasPendingLoadEvent()))
return GroupId(node->document());
Node* root = node;
if (node->isAttributeNode()) {
root = static_cast<Attr*>(node)->ownerElement();
if (!root)
return GroupId();
} else {
while (Node* parent = root->parentOrHostNode())
root = parent;
}
return GroupId(root);
}
class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
public:
void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
{
if (node->hasEventListeners()) {
Vector<v8::Persistent<v8::Value> > listeners;
EventListenerIterator iterator(node);
while (EventListener* listener = iterator.nextListener()) {
if (listener->type() != EventListener::JSEventListenerType)
continue;
V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
if (!v8listener->hasExistingListenerObject())
continue;
listeners.append(v8listener->existingListenerObjectPersistentHandle());
}
if (!listeners.isEmpty())
v8::V8::AddImplicitReferences(wrapper, listeners.data(), listeners.size());
}
GroupId groupId = calculateGroupId(node);
if (!groupId)
return;
m_grouper.append(GrouperItem(groupId, wrapper));
}
void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
{
}
void applyGrouping()
{
std::sort(m_grouper.begin(), m_grouper.end());
for (size_t i = 0; i < m_grouper.size(); ) {
size_t nextKeyIndex = m_grouper.size();
for (size_t j = i; j < m_grouper.size(); ++j) {
if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
nextKeyIndex = j;
break;
}
}
ASSERT(nextKeyIndex > i);
if (nextKeyIndex - i <= 1) {
i = nextKeyIndex;
continue;
}
size_t rootIndex = i;
Vector<v8::Persistent<v8::Value> > group;
group.reserveCapacity(nextKeyIndex - i);
for (; i < nextKeyIndex; ++i) {
v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
if (!wrapper.IsEmpty())
group.append(wrapper);
}
if (group.size() > 1)
v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
ASSERT(i == nextKeyIndex);
}
}
private:
GrouperList m_grouper;
};
void V8GCController::gcPrologue()
{
v8::HandleScope scope;
#ifndef NDEBUG
DOMObjectVisitor domObjectVisitor;
visitDOMObjects(&domObjectVisitor);
#endif
GCPrologueVisitor<void, SpecialCasePrologueObjectHandler> prologueObjectVisitor;
visitActiveDOMObjects(&prologueObjectVisitor);
GCPrologueVisitor<Node, SpecialCasePrologueNodeHandler> prologueNodeVisitor;
visitActiveDOMNodes(&prologueNodeVisitor);
GrouperVisitor grouperVisitor;
visitDOMNodes(&grouperVisitor);
visitActiveDOMNodes(&grouperVisitor);
visitDOMObjects(&grouperVisitor);
grouperVisitor.applyGrouping();
V8BindingPerIsolateData* data = V8BindingPerIsolateData::current();
data->stringCache()->clearOnGC();
}
class SpecialCaseEpilogueObjectHandler {
public:
static bool process(void* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
{
if (V8MessagePort::info.equals(typeInfo)) {
MessagePort* port1 = static_cast<MessagePort*>(object);
if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
return true;
}
return false;
}
};
class SpecialCaseEpilogueNodeHandler {
public:
static bool process(Node* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
{
UNUSED_PARAM(object);
UNUSED_PARAM(wrapper);
UNUSED_PARAM(typeInfo);
return false;
}
};
template<typename T, typename S, v8::WeakReferenceCallback callback>
class GCEpilogueVisitor : public DOMWrapperMap<T>::Visitor {
public:
void visitDOMWrapper(DOMDataStore* store, T* object, v8::Persistent<v8::Object> wrapper)
{
WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
if (!S::process(object, wrapper, typeInfo)) {
ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
ASSERT(!wrapper.IsWeak());
wrapper.MakeWeak(object, callback);
}
}
}
};
int V8GCController::workingSetEstimateMB = 0;
namespace {
int getMemoryUsageInMB()
{
#if PLATFORM(CHROMIUM)
return MemoryUsageSupport::memoryUsageMB();
#else
return 0;
#endif
}
int getActualMemoryUsageInMB()
{
#if PLATFORM(CHROMIUM)
return MemoryUsageSupport::actualMemoryUsageMB();
#else
return 0;
#endif
}
}
void V8GCController::gcEpilogue()
{
v8::HandleScope scope;
GCEpilogueVisitor<void, SpecialCaseEpilogueObjectHandler, &DOMDataStore::weakActiveDOMObjectCallback> epilogueObjectVisitor;
visitActiveDOMObjects(&epilogueObjectVisitor);
GCEpilogueVisitor<Node, SpecialCaseEpilogueNodeHandler, &DOMDataStore::weakNodeCallback> epilogueNodeVisitor;
visitActiveDOMNodes(&epilogueNodeVisitor);
workingSetEstimateMB = getActualMemoryUsageInMB();
#ifndef NDEBUG
DOMObjectVisitor domObjectVisitor;
visitDOMObjects(&domObjectVisitor);
EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
visitDOMNodes(&weakDOMNodeVisitor);
enumerateGlobalHandles();
#endif
}
void V8GCController::checkMemoryUsage()
{
#if PLATFORM(CHROMIUM) || PLATFORM(QT)
const int lowMemoryUsageMB = MemoryUsageSupport::lowMemoryUsageMB();
const int highMemoryUsageMB = MemoryUsageSupport::highMemoryUsageMB();
const int highUsageDeltaMB = MemoryUsageSupport::highUsageDeltaMB();
int memoryUsageMB = getMemoryUsageInMB();
if ((memoryUsageMB > lowMemoryUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highMemoryUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
v8::V8::LowMemoryNotification();
#endif
}
}