Reduce the size of Node::deref by eliminating an explicit parentNode check
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Mar 2019 01:32:04 +0000 (01:32 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Mar 2019 01:32:04 +0000 (01:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195776

Reviewed by Geoffrey Garen.

This patch eliminates the nullity check of m_parentNode in Node::deref as well as the store to
m_refCount in the case of invoking Node::removedLastRef() as done for RefCounted in r30042.
Together, this patch shrinks WebCore's size by 46KB or ~0.7%.

To do this, we take we take a similar approach as WTF::String by using the lowest bit of m_refCount
to indicate whether a node has a parent or not. Regular ref-counting is done on the upper 31 bits.
Node::setParentNode updates this flag, and Node::deref() would only `delete this` if m_refCount
is identically equal to 0.

For a Document, we set m_refCounted to 0 before in the case of non-zero m_referencingNodeCount
since decrementReferencingNodeCount needs to be able to tell if there is an outstanding Ref/RefPtr
or not when m_referencingNodeCount becomes 0.

No new tests since there should be no behavioral change.

* dom/Document.cpp:
(WebCore::Document::removedLastRef):
* dom/Document.h:
(WebCore::Document::decrementReferencingNodeCount):
* dom/Node.cpp:
(WebCore::Node::Node): Moved the initialization of m_refCount to the member variable declaration.
(WebCore::Node::~Node):
(WebCore::Node::removedLastRef):
* dom/Node.h:
(WebCore::Node): Changed the type of m_refCount from signed int to uint32_t. It was changed from
unsigned int to signed int back in r11492 but I don't think the signedness is needed.
(WebCore::Node::ref): Increment the ref count by 2 (upper 31-bit).
(WebCore::Node::deref): Implemented the optimization. This is what shrinks the WebCore binary size.
(WebCore::Node::hasOneRef const):
(WebCore::Node::refCount const): Ignore the lowest bit. Without this fix, the optimization in
replaceChildrenWithFragment to avoid replacing the text node is disabled whenever there is a parent.
(WebCore::Node::setParentNode): Sets the lowest bit to 1 if the node has a parent and 0 otherwise.

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

Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/Node.cpp
Source/WebCore/dom/Node.h

index 9e8b4b2..14e157f 100644 (file)
@@ -1,3 +1,43 @@
+2019-03-18  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reduce the size of Node::deref by eliminating an explicit parentNode check
+        https://bugs.webkit.org/show_bug.cgi?id=195776
+
+        Reviewed by Geoffrey Garen.
+
+        This patch eliminates the nullity check of m_parentNode in Node::deref as well as the store to
+        m_refCount in the case of invoking Node::removedLastRef() as done for RefCounted in r30042.
+        Together, this patch shrinks WebCore's size by 46KB or ~0.7%.
+
+        To do this, we take we take a similar approach as WTF::String by using the lowest bit of m_refCount
+        to indicate whether a node has a parent or not. Regular ref-counting is done on the upper 31 bits.
+        Node::setParentNode updates this flag, and Node::deref() would only `delete this` if m_refCount
+        is identically equal to 0.
+
+        For a Document, we set m_refCounted to 0 before in the case of non-zero m_referencingNodeCount
+        since decrementReferencingNodeCount needs to be able to tell if there is an outstanding Ref/RefPtr
+        or not when m_referencingNodeCount becomes 0.
+
+        No new tests since there should be no behavioral change.
+
+        * dom/Document.cpp:
+        (WebCore::Document::removedLastRef):
+        * dom/Document.h:
+        (WebCore::Document::decrementReferencingNodeCount):
+        * dom/Node.cpp:
+        (WebCore::Node::Node): Moved the initialization of m_refCount to the member variable declaration.
+        (WebCore::Node::~Node):
+        (WebCore::Node::removedLastRef):
+        * dom/Node.h:
+        (WebCore::Node): Changed the type of m_refCount from signed int to uint32_t. It was changed from
+        unsigned int to signed int back in r11492 but I don't think the signedness is needed.
+        (WebCore::Node::ref): Increment the ref count by 2 (upper 31-bit).
+        (WebCore::Node::deref): Implemented the optimization. This is what shrinks the WebCore binary size.
+        (WebCore::Node::hasOneRef const):
+        (WebCore::Node::refCount const): Ignore the lowest bit. Without this fix, the optimization in
+        replaceChildrenWithFragment to avoid replacing the text node is disabled whenever there is a parent.
+        (WebCore::Node::setParentNode): Sets the lowest bit to 1 if the node has a parent and 0 otherwise.
+
 2019-03-18  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
         Remove the SVG property tear off objects for SVGAnimatedBoolean
 2019-03-18  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
         Remove the SVG property tear off objects for SVGAnimatedBoolean
index d9abd47..77d0631 100644 (file)
@@ -669,6 +669,10 @@ void Document::removedLastRef()
 {
     ASSERT(!m_deletionHasBegun);
     if (m_referencingNodeCount) {
 {
     ASSERT(!m_deletionHasBegun);
     if (m_referencingNodeCount) {
+        // Node::removedLastRef doesn't set refCount() to zero because it's not observable.
+        // But we need to remember that our refCount reached zero in subsequent calls to decrementReferencingNodeCount()
+        m_refCountAndParentBit = 0;
+
         // If removing a child removes the last node reference, we don't want the scope to be destroyed
         // until after removeDetachedChildren returns, so we protect ourselves.
         incrementReferencingNodeCount();
         // If removing a child removes the last node reference, we don't want the scope to be destroyed
         // until after removeDetachedChildren returns, so we protect ourselves.
         incrementReferencingNodeCount();
@@ -718,7 +722,6 @@ void Document::removedLastRef()
         m_inRemovedLastRefFunction = false;
         m_deletionHasBegun = true;
 #endif
         m_inRemovedLastRefFunction = false;
         m_deletionHasBegun = true;
 #endif
-        m_refCount = 1; // Avoid double destruction through use of RefPtr<T>. (This is a security mitigation in case of programmer error. It will ASSERT in debug builds.)
         delete this;
     }
 }
         delete this;
     }
 }
index f1dbd48..3a3553e 100644 (file)
@@ -376,7 +376,7 @@ public:
 #if !ASSERT_DISABLED
             m_deletionHasBegun = true;
 #endif
 #if !ASSERT_DISABLED
             m_deletionHasBegun = true;
 #endif
-            m_refCount = 1; // Avoid double destruction through use of RefPtr<T>. (This is a security mitigation in case of programmer error. It will ASSERT in debug builds.)
+            m_refCountAndParentBit = s_refCountIncrement; // Avoid double destruction through use of Ref<T>/RefPtr<T>. (This is a security mitigation in case of programmer error. It will ASSERT in debug builds.)
             delete this;
         }
     }
             delete this;
         }
     }
index 7729179..e9ff1b3 100644 (file)
@@ -316,8 +316,7 @@ void Node::trackForDebugging()
 }
 
 Node::Node(Document& document, ConstructionType type)
 }
 
 Node::Node(Document& document, ConstructionType type)
-    : m_refCount(1)
-    , m_nodeFlags(type)
+    : m_nodeFlags(type)
     , m_treeScope(&document)
 {
     ASSERT(isMainThread());
     , m_treeScope(&document)
 {
     ASSERT(isMainThread());
@@ -332,9 +331,9 @@ Node::Node(Document& document, ConstructionType type)
 Node::~Node()
 {
     ASSERT(isMainThread());
 Node::~Node()
 {
     ASSERT(isMainThread());
-    // We set m_refCount to 1 before calling delete to avoid double destruction through use of Ref<T>/RefPtr<T>.
+    // We set m_refCount to 2 before calling delete to avoid double destruction through use of Ref<T>/RefPtr<T>.
     // This is a security mitigation in case of programmer errorm (caught by a debug assertion).
     // This is a security mitigation in case of programmer errorm (caught by a debug assertion).
-    ASSERT(m_refCount == 1);
+    ASSERT(m_refCountAndParentBit == s_refCountIncrement);
     ASSERT(m_deletionHasBegun);
     ASSERT(!m_adoptionIsRequired);
 
     ASSERT(m_deletionHasBegun);
     ASSERT(!m_adoptionIsRequired);
 
@@ -2523,6 +2522,10 @@ bool Node::willRespondToMouseWheelEvents()
 // delete a Node at each deref call site.
 void Node::removedLastRef()
 {
 // delete a Node at each deref call site.
 void Node::removedLastRef()
 {
+    // This avoids double destruction even when there is a programming error to use Ref<T> / RefPtr<T> on this node.
+    // There are debug assertions in Node::ref() / Node::deref() to catch such a programming error.
+    ASSERT(m_refCountAndParentBit == s_refCountIncrement);
+
     // An explicit check for Document here is better than a virtual function since it is
     // faster for non-Document nodes, and because the call to removedLastRef that is inlined
     // at all deref call sites is smaller if it's a non-virtual function.
     // An explicit check for Document here is better than a virtual function since it is
     // faster for non-Document nodes, and because the call to removedLastRef that is inlined
     // at all deref call sites is smaller if it's a non-virtual function.
@@ -2534,7 +2537,6 @@ void Node::removedLastRef()
 #ifndef NDEBUG
     m_deletionHasBegun = true;
 #endif
 #ifndef NDEBUG
     m_deletionHasBegun = true;
 #endif
-    m_refCount = 1; // Avoid double destruction through use of RefPtr<T>. (This is a security mitigation in case of programmer error. It will ASSERT in debug builds.)
     delete this;
 }
 
     delete this;
 }
 
index 2369ea2..3aa597e 100644 (file)
@@ -500,7 +500,7 @@ public:
     void ref();
     void deref();
     bool hasOneRef() const;
     void ref();
     void deref();
     bool hasOneRef() const;
-    int refCount() const;
+    unsigned refCount() const;
 
 #ifndef NDEBUG
     bool m_deletionHasBegun { false };
 
 #ifndef NDEBUG
     bool m_deletionHasBegun { false };
@@ -618,6 +618,9 @@ protected:
     };
     Node(Document&, ConstructionType);
 
     };
     Node(Document&, ConstructionType);
 
+    static constexpr uint32_t s_refCountIncrement = 2;
+    static constexpr uint32_t s_refCountMask = ~static_cast<uint32_t>(0x1);
+
     virtual void addSubresourceAttributeURLs(ListHashSet<URL>&) const { }
 
     bool hasRareData() const { return getFlag(HasRareDataFlag); }
     virtual void addSubresourceAttributeURLs(ListHashSet<URL>&) const { }
 
     bool hasRareData() const { return getFlag(HasRareDataFlag); }
@@ -664,7 +667,7 @@ private:
     static void moveTreeToNewScope(Node&, TreeScope& oldScope, TreeScope& newScope);
     void moveNodeToNewDocument(Document& oldDocument, Document& newDocument);
 
     static void moveTreeToNewScope(Node&, TreeScope& oldScope, TreeScope& newScope);
     void moveNodeToNewDocument(Document& oldDocument, Document& newDocument);
 
-    int m_refCount;
+    uint32_t m_refCountAndParentBit { s_refCountIncrement };
     mutable uint32_t m_nodeFlags;
 
     ContainerNode* m_parentNode { nullptr };
     mutable uint32_t m_nodeFlags;
 
     ContainerNode* m_parentNode { nullptr };
@@ -695,34 +698,37 @@ ALWAYS_INLINE void Node::ref()
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
     ASSERT(!m_adoptionIsRequired);
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
     ASSERT(!m_adoptionIsRequired);
-    ++m_refCount;
+    m_refCountAndParentBit += s_refCountIncrement;
 }
 
 ALWAYS_INLINE void Node::deref()
 {
     ASSERT(isMainThread());
 }
 
 ALWAYS_INLINE void Node::deref()
 {
     ASSERT(isMainThread());
-    ASSERT(m_refCount >= 0);
+    ASSERT(refCount());
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
     ASSERT(!m_adoptionIsRequired);
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
     ASSERT(!m_adoptionIsRequired);
-    if (--m_refCount <= 0 && !parentNode()) {
+    auto tempRefCount = m_refCountAndParentBit - s_refCountIncrement;
+    if (!tempRefCount) {
 #ifndef NDEBUG
         m_inRemovedLastRefFunction = true;
 #endif
         removedLastRef();
 #ifndef NDEBUG
         m_inRemovedLastRefFunction = true;
 #endif
         removedLastRef();
+        return;
     }
     }
+    m_refCountAndParentBit = tempRefCount;
 }
 
 ALWAYS_INLINE bool Node::hasOneRef() const
 {
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
 }
 
 ALWAYS_INLINE bool Node::hasOneRef() const
 {
     ASSERT(!m_deletionHasBegun);
     ASSERT(!m_inRemovedLastRefFunction);
-    return m_refCount == 1;
+    return refCount() == 1;
 }
 
 }
 
-ALWAYS_INLINE int Node::refCount() const
+ALWAYS_INLINE unsigned Node::refCount() const
 {
 {
-    return m_refCount;
+    return m_refCountAndParentBit / s_refCountIncrement;
 }
 
 // Used in Node::addSubresourceAttributeURLs() and in addSubresourceStyleURLs()
 }
 
 // Used in Node::addSubresourceAttributeURLs() and in addSubresourceStyleURLs()
@@ -736,6 +742,8 @@ inline void Node::setParentNode(ContainerNode* parent)
 {
     ASSERT(isMainThread());
     m_parentNode = parent;
 {
     ASSERT(isMainThread());
     m_parentNode = parent;
+    auto refCountWithoutParentBit = m_refCountAndParentBit & s_refCountMask;
+    m_refCountAndParentBit = refCountWithoutParentBit | !!parent;
 }
 
 inline ContainerNode* Node::parentNode() const
 }
 
 inline ContainerNode* Node::parentNode() const