RESOLVED FIXED 195776
Reduce the size of Node::deref by eliminating an explicit parentNode check
https://bugs.webkit.org/show_bug.cgi?id=195776
Summary Reduce the size of Node::deref by eliminating an explicit parentNode check
Ryosuke Niwa
Reported 2019-03-14 16:02:12 PDT
As discussed in https://bugs.webkit.org/show_bug.cgi?id=195661, we can use the same strategy as String and use the lowest bit of Node::m_refCount to indicate the pretense of a parent node. This will reduce the load in Node::def. We can also avoid writing to m_refCount in the case of calling removedLastRef() as we've done for RefCounted<T> in https://trac.webkit.org/r30042.
Attachments
Patch (7.59 KB, patch)
2019-03-14 16:33 PDT, Ryosuke Niwa
no flags
Patch (8.14 KB, patch)
2019-03-14 16:58 PDT, Ryosuke Niwa
ews-watchlist: commit-queue-
Archive of layout-test-results from ews103 for mac-highsierra (2.83 MB, application/zip)
2019-03-14 17:58 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews104 for mac-highsierra-wk2 (2.88 MB, application/zip)
2019-03-14 18:40 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews113 for mac-highsierra (2.50 MB, application/zip)
2019-03-14 18:59 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews204 for win-future (12.88 MB, application/zip)
2019-03-14 21:03 PDT, EWS Watchlist
no flags
Fixed the tests (10.94 KB, patch)
2019-03-14 22:16 PDT, Ryosuke Niwa
no flags
Fixed the tests (8.75 KB, patch)
2019-03-14 22:17 PDT, Ryosuke Niwa
no flags
Patch (8.74 KB, patch)
2019-03-14 22:51 PDT, Ryosuke Niwa
no flags
Patch for landing (9.04 KB, patch)
2019-03-15 19:30 PDT, Ryosuke Niwa
no flags
Wait for EWS (9.03 KB, patch)
2019-03-18 11:22 PDT, Ryosuke Niwa
no flags
Address Darin's comments (4.54 KB, patch)
2019-03-29 21:44 PDT, Ryosuke Niwa
no flags
Ryosuke Niwa
Comment 1 2019-03-14 16:03:05 PDT
This seems to reduce the binary size of WebCore from 61260928 to 61213824 in my local builds at r242965.
Ryosuke Niwa
Comment 2 2019-03-14 16:33:03 PDT
Ryosuke Niwa
Comment 3 2019-03-14 16:58:29 PDT
EWS Watchlist
Comment 4 2019-03-14 17:58:19 PDT
Comment on attachment 364721 [details] Patch Attachment 364721 [details] did not pass mac-ews (mac): Output: https://webkit-queues.webkit.org/results/11510518 New failing tests: fast/repaint/simple-line-layout-shrinking-content.html accessibility/mac/aria-liveregions-addedelement.html
EWS Watchlist
Comment 5 2019-03-14 17:58:26 PDT
Created attachment 364738 [details] Archive of layout-test-results from ews103 for mac-highsierra The attached test failures were seen while running run-webkit-tests on the mac-ews. Bot: ews103 Port: mac-highsierra Platform: Mac OS X 10.13.6
Ryosuke Niwa
Comment 6 2019-03-14 17:58:42 PDT
Simon and I discussed the idea of using the highest bit instead to avoid having to hard-code 2 everywhere, and in fact I wrote such a patch but it has one downside that the highest bit can also be overridden by the regular ref-counting and we may make m_refCount to 0 in the case of overflow. Using the lowest bit is slightly more secure as long as the node has a parent, which is more common case anyway. So let's just define a constexpr somewhere for 2 and use the lowest bit.
EWS Watchlist
Comment 7 2019-03-14 18:40:09 PDT
Comment on attachment 364721 [details] Patch Attachment 364721 [details] did not pass mac-wk2-ews (mac-wk2): Output: https://webkit-queues.webkit.org/results/11511189 New failing tests: fast/repaint/simple-line-layout-shrinking-content.html accessibility/mac/aria-liveregions-addedelement.html
EWS Watchlist
Comment 8 2019-03-14 18:40:12 PDT
Created attachment 364748 [details] Archive of layout-test-results from ews104 for mac-highsierra-wk2 The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews. Bot: ews104 Port: mac-highsierra-wk2 Platform: Mac OS X 10.13.6
EWS Watchlist
Comment 9 2019-03-14 18:59:31 PDT
Comment on attachment 364721 [details] Patch Attachment 364721 [details] did not pass mac-debug-ews (mac): Output: https://webkit-queues.webkit.org/results/11511165 New failing tests: fast/repaint/simple-line-layout-shrinking-content.html accessibility/mac/aria-liveregions-addedelement.html
EWS Watchlist
Comment 10 2019-03-14 18:59:34 PDT
Created attachment 364752 [details] Archive of layout-test-results from ews113 for mac-highsierra The attached test failures were seen while running run-webkit-tests on the mac-debug-ews. Bot: ews113 Port: mac-highsierra Platform: Mac OS X 10.13.6
Darin Adler
Comment 11 2019-03-14 19:12:50 PDT
Comment on attachment 364721 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=364721&action=review > Source/WebCore/dom/Node.h:723 > - return m_refCount == 1; > + return m_refCount >= 2; This seems wrong. The point of this is function to return true only if Node has no more than one ref and no less than one ref. Not to return 1 if it has more than one ref. I think the correct implementation is one of these: return (m_refCount >> 1) == 1; return (m_refCount & ~1) == 2;
EWS Watchlist
Comment 12 2019-03-14 21:03:10 PDT
Comment on attachment 364721 [details] Patch Attachment 364721 [details] did not pass win-ews (win): Output: https://webkit-queues.webkit.org/results/11512677 New failing tests: fast/repaint/simple-line-layout-shrinking-content.html
EWS Watchlist
Comment 13 2019-03-14 21:03:24 PDT
Created attachment 364767 [details] Archive of layout-test-results from ews204 for win-future The attached test failures were seen while running run-webkit-tests on the win-ews. Bot: ews204 Port: win-future Platform: CYGWIN_NT-6.1-2.10.0-0.325-5-3-x86_64-64bit
Ryosuke Niwa
Comment 14 2019-03-14 21:21:31 PDT
Comment on attachment 364721 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=364721&action=review >> Source/WebCore/dom/Node.h:723 >> + return m_refCount >= 2; > > This seems wrong. The point of this is function to return true only if Node has no more than one ref and no less than one ref. Not to return 1 if it has more than one ref. I think the correct implementation is one of these: > > return (m_refCount >> 1) == 1; > return (m_refCount & ~1) == 2; Oh oops, you're right. > Source/WebCore/dom/Node.h:728 > return m_refCount; This also needs to be fixed.
Ryosuke Niwa
Comment 15 2019-03-14 22:14:28 PDT
Comment on attachment 364721 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=364721&action=review >> Source/WebCore/dom/Node.h:728 >> return m_refCount; > > This also needs to be fixed. This was the cause of the test failures. With refCount() returning non-zero value, the optimization in replaceChildrenWithFragment to avoid replacing the text node was disabled.
Ryosuke Niwa
Comment 16 2019-03-14 22:16:00 PDT
Created attachment 364771 [details] Fixed the tests
Ryosuke Niwa
Comment 17 2019-03-14 22:17:09 PDT
Created attachment 364772 [details] Fixed the tests
Ryosuke Niwa
Comment 18 2019-03-14 22:37:35 PDT
Comment on attachment 364772 [details] Fixed the tests Oh what the heck. I forgot to remove parentNode() check I added back for debugging :(
Ryosuke Niwa
Comment 19 2019-03-14 22:51:52 PDT
Geoffrey Garen
Comment 20 2019-03-15 16:35:43 PDT
Comment on attachment 364773 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=364773&action=review > Source/WebCore/dom/Node.cpp:2533 > + m_refCount = 0; // Document needs m_refCount to be 0 here due to m_referencingNodeCount. I didn't fully understand this comment or the related ChangeLog explanation. Can we make a nice API so that Document's use of m_refCount works without this unique behavior? We are calling Document::removedLastRef(). Is that a strong enough signal to the Document that it really has no referencing nodes? > Source/WebCore/dom/Node.cpp:-2537 > - 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.) Why don't we need this anymore? Why does Document still do this if Node doesn't? > Source/WebCore/dom/Node.h:621 > + static constexpr uint32_t s_refCountIncrementUnit = 2; Let's call this s_refCountIncrement to match StringImpl. > Source/WebCore/dom/Node.h:669 > + uint32_t m_refCount { s_refCountIncrementUnit }; This probably needs a better name, like m_refCountAndParentBit. > Source/WebCore/dom/Node.h:730 > + return m_refCount >> 1; We probably want an s_refCountShift instead of a hard-coded 1. > Source/WebCore/dom/Node.h:744 > + auto refCountWithoutParentBit = m_refCount & ~0x1; We probably want an s_refCountMask instead of a hard-coded 1.
Ryosuke Niwa
Comment 21 2019-03-15 16:42:24 PDT
(In reply to Geoffrey Garen from comment #20) > Comment on attachment 364773 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=364773&action=review > > > Source/WebCore/dom/Node.cpp:2533 > > + m_refCount = 0; // Document needs m_refCount to be 0 here due to m_referencingNodeCount. > > I didn't fully understand this comment or the related ChangeLog explanation. > > Can we make a nice API so that Document's use of m_refCount works without > this unique behavior? We can't because if m_refCount is not 0 here when m_referencingNodeCount is not zero, Document::removedLastRef doesn't delete document. Instead, it gets deleted when m_referencingNodeCount becomes 0 in decrementReferencingNodeCount. But it deletes Document only when m_refCount is also zero. If we didn't decrement m_refCount here, then we won't be able to tell in decrementReferencingNodeCount whether m_refCount is actually 2, or it would have been reached 0 but we simply didn't decrement. > We are calling Document::removedLastRef(). Is that a strong enough signal to > the Document that it really has no referencing nodes? No. There could be nodes in the document which still references such a Document. This is kept track by m_referencingNodeCount but Document::removedLastRef still needs to run some cleanup steps so that it can remove any Ref/RefPtr Document itself holds. > > Source/WebCore/dom/Node.cpp:-2537 > > - 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.) > > Why don't we need this anymore? Because we would never decrement m_refCount to 0 in Node::Derek. > Why does Document still do this if Node doesn't? Because Document has the issue above. That means we still DO need to decrement m_refCount to 0 when the last de-ref is called so we'd have to set it back to 2 before calling delete. > > Source/WebCore/dom/Node.h:621 > > + static constexpr uint32_t s_refCountIncrementUnit = 2; > > Let's call this s_refCountIncrement to match StringImpl. Sure. > > Source/WebCore/dom/Node.h:669 > > + uint32_t m_refCount { s_refCountIncrementUnit }; > > This probably needs a better name, like m_refCountAndParentBit. Will rename. > > Source/WebCore/dom/Node.h:730 > > + return m_refCount >> 1; > > We probably want an s_refCountShift instead of a hard-coded 1. Sure. Will do. > > Source/WebCore/dom/Node.h:744 > > + auto refCountWithoutParentBit = m_refCount & ~0x1; > > We probably want an s_refCountMask instead of a hard-coded 1. Okay, will do.
Geoffrey Garen
Comment 22 2019-03-15 16:59:50 PDT
Comment on attachment 364773 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=364773&action=review r=me with comments already discussed, and one new suggestion >>> Source/WebCore/dom/Node.cpp:2533 >>> + m_refCount = 0; // Document needs m_refCount to be 0 here due to m_referencingNodeCount. >> >> I didn't fully understand this comment or the related ChangeLog explanation. >> >> Can we make a nice API so that Document's use of m_refCount works without this unique behavior? >> >> We are calling Document::removedLastRef(). Is that a strong enough signal to the Document that it really has no referencing nodes? > > We can't because if m_refCount is not 0 here when m_referencingNodeCount is not zero, Document::removedLastRef doesn't delete document. Instead, it gets deleted when m_referencingNodeCount becomes 0 in decrementReferencingNodeCount. But it deletes Document only when m_refCount is also zero. If we didn't decrement m_refCount here, then we won't be able to tell in decrementReferencingNodeCount whether m_refCount is actually 2, or it would have been reached 0 but we simply didn't decrement. Would it work to move this assignment of 0 into Document::removedLastRef? Document::removedLastRef could include a comment that said "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()." How does that sound?
Ryosuke Niwa
Comment 23 2019-03-15 19:11:20 PDT
(In reply to Geoffrey Garen from comment #22) > Comment on attachment 364773 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=364773&action=review > > r=me with comments already discussed, and one new suggestion > > >>> Source/WebCore/dom/Node.cpp:2533 > >>> + m_refCount = 0; // Document needs m_refCount to be 0 here due to m_referencingNodeCount. > >> > >> I didn't fully understand this comment or the related ChangeLog explanation. > >> > >> Can we make a nice API so that Document's use of m_refCount works without this unique behavior? > >> > >> We are calling Document::removedLastRef(). Is that a strong enough signal to the Document that it really has no referencing nodes? > > > > We can't because if m_refCount is not 0 here when m_referencingNodeCount is not zero, Document::removedLastRef doesn't delete document. Instead, it gets deleted when m_referencingNodeCount becomes 0 in decrementReferencingNodeCount. But it deletes Document only when m_refCount is also zero. If we didn't decrement m_refCount here, then we won't be able to tell in decrementReferencingNodeCount whether m_refCount is actually 2, or it would have been reached 0 but we simply didn't decrement. > > Would it work to move this assignment of 0 into Document::removedLastRef? > > Document::removedLastRef could include a comment that said > "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()." Sure, will do.
Ryosuke Niwa
Comment 24 2019-03-15 19:30:56 PDT
Created attachment 364900 [details] Patch for landing
Ryosuke Niwa
Comment 25 2019-03-15 19:31:15 PDT
Comment on attachment 364900 [details] Patch for landing Wait for EWS.
WebKit Commit Bot
Comment 26 2019-03-16 00:50:30 PDT
Comment on attachment 364900 [details] Patch for landing Clearing flags on attachment: 364900 Committed r243037: <https://trac.webkit.org/changeset/243037>
WebKit Commit Bot
Comment 27 2019-03-16 00:50:32 PDT
All reviewed patches have been landed. Closing bug.
Radar WebKit Bug Importer
Comment 28 2019-03-16 00:51:30 PDT
Ryan Haddad
Comment 29 2019-03-18 08:48:06 PDT
This change broke the Windows build: c:\cygwin\home\buildbot\worker\win10-release\build\source\webcore\dom\node.h(622): error C2220: warning treated as error - no 'object' file generated (compiling source file C:\cygwin\home\buildbot\worker\win10-release\build\Source\WebCore\html\track\TextTrackCueGeneric.cpp) [C:\cygwin\home\buildbot\worker\win10-release\build\WebKitBuild\Release\Source\WebCore\WebCore.vcxproj] c:\cygwin\home\buildbot\worker\win10-release\build\source\webcore\dom\node.h(622): warning C4245: 'initializing': conversion from 'int' to 'const uint32_t', signed/unsigned mismatch (compiling source file C:\cygwin\home\buildbot\worker\win10-release\build\Source\WebCore\html\track\TextTrackCueGeneric.cpp) [C:\cygwin\home\buildbot\worker\win10-release\build\WebKitBuild\Release\Source\WebCore\WebCore.vcxproj] https://build.webkit.org/builders/Apple%20Win%2010%20Release%20%28Build%29/builds/2928
Ryan Haddad
Comment 30 2019-03-18 10:06:00 PDT
Reverted r243037 for reason: Broke the Windows build Committed r243075: <https://trac.webkit.org/changeset/243075>
Ryosuke Niwa
Comment 31 2019-03-18 11:22:13 PDT
Created attachment 365036 [details] Wait for EWS
Ryosuke Niwa
Comment 32 2019-03-18 11:22:42 PDT
Why is EWS so insanely slow?
Ryosuke Niwa
Comment 33 2019-03-18 18:32:05 PDT
Comment on attachment 365036 [details] Wait for EWS Clearing flags on attachment: 365036 Committed r243122: <https://trac.webkit.org/changeset/243122>
Ryosuke Niwa
Comment 34 2019-03-18 18:32:08 PDT
All reviewed patches have been landed. Closing bug.
Darin Adler
Comment 35 2019-03-26 20:24:48 PDT
Comment on attachment 365036 [details] Wait for EWS View in context: https://bugs.webkit.org/attachment.cgi?id=365036&action=review A few things I noticed after this was landed. > Source/WebCore/dom/Document.cpp:673 > + // But we need to remember that our refCount reached zero in subsequent calls to decrementReferencingNodeCount() No period at the end of this line. > Source/WebCore/dom/Node.cpp:335 > // This is a security mitigation in case of programmer errorm (caught by a debug assertion). "errorm" A little peculiar that this is not worded the same as the comment with identical purpose in Document.h. > Source/WebCore/dom/Node.cpp:2527 > + // 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); Really confusing that this comment claims that an *assertion* avoids double destruction. The correct claim is that this strategy avoids double destruction, but it’s strange to have the comment on an assertion. > Source/WebCore/dom/Node.h:622 > + static constexpr uint32_t s_refCountIncrement = 2; > + static constexpr uint32_t s_refCountMask = ~static_cast<uint32_t>(0x1); Kind of funny to use just "2" but then "0x1". Why not just "1"? Or could write the increment as "1 << 1" and the mask as "~0 << 1" and even consider having a constant for the shift amount of 1, and use that instead of division by s_refCountIncrement. > Source/WebCore/dom/Node.h:711 > + auto tempRefCount = m_refCountAndParentBit - s_refCountIncrement; I think this should be called "updatedRefCount" rather than "tempRefCount". > Source/WebCore/dom/Node.h:717 > + return; This seems more like the place we should have the comment about making double destruction safe, not elsewhere in the functions that make assertions. > Source/WebCore/dom/Node.h:746 > + auto refCountWithoutParentBit = m_refCountAndParentBit & s_refCountMask; > + m_refCountAndParentBit = refCountWithoutParentBit | !!parent; I think I would find this more readable either in a single expression or with a much shorter variable name.
Ryosuke Niwa
Comment 36 2019-03-29 21:40:39 PDT
(In reply to Darin Adler from comment #35) > Comment on attachment 365036 [details] > Wait for EWS > > View in context: > https://bugs.webkit.org/attachment.cgi?id=365036&action=review > > A few things I noticed after this was landed. > > > Source/WebCore/dom/Document.cpp:673 > > + // But we need to remember that our refCount reached zero in subsequent calls to decrementReferencingNodeCount() > > No period at the end of this line. Fixed. > > Source/WebCore/dom/Node.cpp:335 > > // This is a security mitigation in case of programmer errorm (caught by a debug assertion). > > "errorm" > > A little peculiar that this is not worded the same as the comment with > identical purpose in Document.h. I removed this comment in favor of the one in Node.h > > Source/WebCore/dom/Node.cpp:2527 > > + // 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); > > Really confusing that this comment claims that an *assertion* avoids double > destruction. The correct claim is that this strategy avoids double > destruction, but it’s strange to have the comment on an assertion. I removed this comment and added the same one as Document.h in Node.h where we avoid decrementing m_refCountAndParentBit. > > Source/WebCore/dom/Node.h:622 > > + static constexpr uint32_t s_refCountIncrement = 2; > > + static constexpr uint32_t s_refCountMask = ~static_cast<uint32_t>(0x1); > > Kind of funny to use just "2" but then "0x1". Why not just "1"? Sure, I'd just use 2 & 1. > > Source/WebCore/dom/Node.h:711 > > + auto tempRefCount = m_refCountAndParentBit - s_refCountIncrement; > > I think this should be called "updatedRefCount" rather than "tempRefCount". Done. > > Source/WebCore/dom/Node.h:717 > > + return; > > This seems more like the place we should have the comment about making > double destruction safe, not elsewhere in the functions that make assertions. Done that. > > Source/WebCore/dom/Node.h:746 > > + auto refCountWithoutParentBit = m_refCountAndParentBit & s_refCountMask; > > + m_refCountAndParentBit = refCountWithoutParentBit | !!parent; > > I think I would find this more readable either in a single expression or > with a much shorter variable name. Sure, replaced it with a single line expression.
Ryosuke Niwa
Comment 37 2019-03-29 21:44:47 PDT
Reopening to attach new patch.
Ryosuke Niwa
Comment 38 2019-03-29 21:44:48 PDT
Created attachment 366352 [details] Address Darin's comments
WebKit Commit Bot
Comment 39 2019-03-31 19:03:19 PDT
Comment on attachment 366352 [details] Address Darin's comments Clearing flags on attachment: 366352 Committed r243686: <https://trac.webkit.org/changeset/243686>
WebKit Commit Bot
Comment 40 2019-03-31 19:03:22 PDT
All reviewed patches have been landed. Closing bug.
Darin Adler
Comment 41 2019-04-01 08:39:36 PDT
Comment on attachment 366352 [details] Address Darin's comments View in context: https://bugs.webkit.org/attachment.cgi?id=366352&action=review > Source/WebCore/dom/Node.h:621 > + static constexpr uint32_t s_refCountMask = ~static_cast<uint32_t>(1); Did you try just ~1 or did that result in a warning?
Ryosuke Niwa
Comment 42 2019-04-01 11:15:26 PDT
(In reply to Darin Adler from comment #41) > Comment on attachment 366352 [details] > Address Darin's comments > > View in context: > https://bugs.webkit.org/attachment.cgi?id=366352&action=review > > > Source/WebCore/dom/Node.h:621 > > + static constexpr uint32_t s_refCountMask = ~static_cast<uint32_t>(1); > > Did you try just ~1 or did that result in a warning? That doesn't work on Windows :(
Note You need to log in before you can comment on or make changes to this bug.