Bug 155676

Summary: [WTF] Removing a smart pointer from HashTable issues two stores to the same location
Product: WebKit Reporter: Benjamin Poulain <benjamin>
Component: New BugsAssignee: Benjamin Poulain <benjamin>
Status: RESOLVED FIXED    
Severity: Normal CC: achristensen, andersca, buildbot, cdumez, cmarcelo, commit-queue, darin, rniwa
Priority: P2    
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Patch
none
Archive of layout-test-results from ews115 for mac-yosemite
none
Archive of layout-test-results from ews101 for mac-yosemite
none
Archive of layout-test-results from ews104 for mac-yosemite-wk2
none
Archive of layout-test-results from ews122 for ios-simulator-wk2
none
Patch
none
Patch
none
Patch
none
Patch
none
Example of SFINAE used to detect the presence of a static member function
none
Patch none

Benjamin Poulain
Reported 2016-03-18 20:50:08 PDT
[WTF] Remove a smart pointer from HashTable issues two stores to the same location
Attachments
Patch (11.06 KB, patch)
2016-03-18 21:11 PDT, Benjamin Poulain
no flags
Archive of layout-test-results from ews115 for mac-yosemite (347.03 KB, application/zip)
2016-03-18 22:30 PDT, Build Bot
no flags
Archive of layout-test-results from ews101 for mac-yosemite (1.13 MB, application/zip)
2016-03-18 22:33 PDT, Build Bot
no flags
Archive of layout-test-results from ews104 for mac-yosemite-wk2 (1.19 MB, application/zip)
2016-03-18 22:35 PDT, Build Bot
no flags
Archive of layout-test-results from ews122 for ios-simulator-wk2 (293.45 KB, application/zip)
2016-03-18 22:39 PDT, Build Bot
no flags
Patch (13.87 KB, patch)
2016-03-20 18:27 PDT, Benjamin Poulain
no flags
Patch (18.57 KB, patch)
2016-03-22 16:18 PDT, Benjamin Poulain
no flags
Patch (11.51 KB, patch)
2016-03-23 14:51 PDT, Benjamin Poulain
no flags
Patch (17.73 KB, patch)
2016-03-23 18:02 PDT, Benjamin Poulain
no flags
Example of SFINAE used to detect the presence of a static member function (931 bytes, text/plain)
2016-03-29 09:10 PDT, Darin Adler
no flags
Patch (17.87 KB, patch)
2016-03-29 19:21 PDT, Benjamin Poulain
no flags
Benjamin Poulain
Comment 1 2016-03-18 21:11:50 PDT
Build Bot
Comment 2 2016-03-18 22:30:43 PDT
Comment on attachment 274498 [details] Patch Attachment 274498 [details] did not pass mac-debug-ews (mac): Output: http://webkit-queues.webkit.org/results/1003189 Number of test failures exceeded the failure limit.
Build Bot
Comment 3 2016-03-18 22:30:46 PDT
Created attachment 274504 [details] Archive of layout-test-results from ews115 for mac-yosemite The attached test failures were seen while running run-webkit-tests on the mac-debug-ews. Bot: ews115 Port: mac-yosemite Platform: Mac OS X 10.10.5
Build Bot
Comment 4 2016-03-18 22:33:36 PDT
Comment on attachment 274498 [details] Patch Attachment 274498 [details] did not pass mac-ews (mac): Output: http://webkit-queues.webkit.org/results/1003199 Number of test failures exceeded the failure limit.
Build Bot
Comment 5 2016-03-18 22:33:39 PDT
Created attachment 274505 [details] Archive of layout-test-results from ews101 for mac-yosemite The attached test failures were seen while running run-webkit-tests on the mac-ews. Bot: ews101 Port: mac-yosemite Platform: Mac OS X 10.10.5
Build Bot
Comment 6 2016-03-18 22:35:09 PDT
Comment on attachment 274498 [details] Patch Attachment 274498 [details] did not pass mac-wk2-ews (mac-wk2): Output: http://webkit-queues.webkit.org/results/1003198 Number of test failures exceeded the failure limit.
Build Bot
Comment 7 2016-03-18 22:35:12 PDT
Created attachment 274506 [details] Archive of layout-test-results from ews104 for mac-yosemite-wk2 The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews. Bot: ews104 Port: mac-yosemite-wk2 Platform: Mac OS X 10.10.5
Build Bot
Comment 8 2016-03-18 22:39:25 PDT
Comment on attachment 274498 [details] Patch Attachment 274498 [details] did not pass ios-sim-ews (ios-simulator-wk2): Output: http://webkit-queues.webkit.org/results/1003202 Number of test failures exceeded the failure limit.
Build Bot
Comment 9 2016-03-18 22:39:29 PDT
Created attachment 274507 [details] Archive of layout-test-results from ews122 for ios-simulator-wk2 The attached test failures were seen while running run-webkit-tests on the ios-sim-ews. Bot: ews122 Port: ios-simulator-wk2 Platform: Mac OS X 10.10.5
Benjamin Poulain
Comment 10 2016-03-20 18:27:11 PDT
Benjamin Poulain
Comment 11 2016-03-22 16:18:14 PDT
Darin Adler
Comment 12 2016-03-22 21:36:43 PDT
Comment on attachment 274695 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=274695&action=review > Source/WTF/wtf/HashTable.h:458 > - static void deleteBucket(ValueType& bucket) { bucket.~ValueType(); Traits::constructDeletedValue(bucket); } > + static void deleteBucket(ValueType& bucket) { hashTraitsDeleteBucket<Traits>(bucket); } I think there’s a simpler solution using move semantics for any type that can be moved: static void deleteBucket(ValueType&& bucket) { auto valueToBeDeleted = WTFMove(bucket); Traits::constructDeletedValue(bucket); } Will that work? Can we try doing that to avoid all the other work below? Do we need to use it only for certain types? > Source/WTF/wtf/HashTraits.h:136 > + static void customDeleteBucket(std::unique_ptr<T, Deleter>& value) > + { > + T* pointer = value.release(); > + constructDeletedValue(value); > + if (LIKELY(pointer)) > + Deleter()(pointer); > + } I would have written this: static void customDeleteBucket(std::unique_ptr<T, Deleter>&& value) { auto pointer = WTFMove(value); constructDeletedValue(value); } Does your version create more efficient code? Maybe it should be like this: static void customDeleteBucket(std::unique_ptr<T, Deleter>&& value) { auto pointer = WTFMove(value); constructDeletedValue(value); } > Source/WTF/wtf/HashTraits.h:149 > + ASSERT(!SimpleClassHashTraits<RefPtr<P>>::isDeletedValue(value)); Why did you assert here but not in the std::unique_ptr version? > Source/WTF/wtf/HashTraits.h:152 > + P* pointer = value.leakRef(); > + SimpleClassHashTraits<RefPtr<P>>::constructDeletedValue(value); > + derefIfNotNull(pointer); Again, why can't we use move semantics to do this? The leakRef function nulls out the value, so we are not doing anything to avoid the multiple stores. > Source/WTF/wtf/HashTraits.h:161 > + static void customDeleteBucket(String& value); I’d suggest leaving out the word “value”.
Darin Adler
Comment 13 2016-03-22 21:37:20 PDT
Sorry, I meant to omit all the comments except the first one. Those were from an earlier review before I had that idea.
Benjamin Poulain
Comment 14 2016-03-22 23:54:58 PDT
Comment on attachment 274695 [details] Patch (In reply to comment #13) > Sorry, I meant to omit all the comments except the first one. Those were > from an earlier review before I had that idea. That's a good idea. I'll give it a try tomorrow. The reason the code is structured like this is to specialize cases where you can eliminate the null check. I can add what I need later if/when I have time to do that part.
Benjamin Poulain
Comment 15 2016-03-23 14:51:42 PDT
Benjamin Poulain
Comment 16 2016-03-23 14:58:40 PDT
The last patch is for reference only. It is actually significantly worse than the existing code. I suspect the slow down is caused by the non-trivial move constructor of some objects. C++ code segment sizes on x86_64: -Baseline: 19787776 bytes -Delete-Then-Store: 19779584 bytes -Store-Then-Delete: 19779584 bytes -Always WTFMove the bucket: 19791872 bytes The binary size of Delete->Store is smaller than Store->Delete. I am not sure why since I would not expect that to affect constant pools...
Benjamin Poulain
Comment 17 2016-03-23 18:02:03 PDT
Benjamin Poulain
Comment 18 2016-03-23 18:03:06 PDT
Comment on attachment 274805 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=274805&action=review I hoped I addressed everything. > Source/WTF/wtf/HashTraits.h:136 > + if (LIKELY(pointer)) > + Deleter()(pointer); I put this one back since the LIKELY(pointer) is helping keeping the codegen tight.
Alex Christensen
Comment 19 2016-03-23 21:43:20 PDT
Comment on attachment 274805 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=274805&action=review > Source/WTF/ChangeLog:39 > + This patch removes 13kb out of x86_64 WebCore. That's a lot of unnecessary storing! >> Source/WTF/wtf/HashTraits.h:136 >> + Deleter()(pointer); > > I put this one back since the LIKELY(pointer) is helping keeping the codegen tight. Is this ever not true? If we put a nullptr std::unique_ptr into a WTF::HashTable, it will cause big problems well before this because it is equal to the empty value. Would it be safe to do without this branch altogether? > Source/WTF/wtf/text/StringHash.h:36 > + inline void HashTraits<String>::customDeleteBucket(String& value) You didn't add any tests for strings or atomic strings.
Benjamin Poulain
Comment 20 2016-03-23 21:49:50 PDT
> > Source/WTF/ChangeLog:39 > > + This patch removes 13kb out of x86_64 WebCore. > > That's a lot of unnecessary storing! Yep! :) > >> Source/WTF/wtf/HashTraits.h:136 > >> + Deleter()(pointer); > > > > I put this one back since the LIKELY(pointer) is helping keeping the codegen tight. > > Is this ever not true? If we put a nullptr std::unique_ptr into a > WTF::HashTable, it will cause big problems well before this because it is > equal to the empty value. Would it be safe to do without this branch > altogether? That's a sad story. Initially, I had made so that all the null check are removed. But, there is code doing things like: iterator = set.find(foobar); Value myValue = WTFMove(*iterator); set.remove(iterator); So yep, it is possible to have a nullptr one you remove despite not being a valid thing to add in a table. :((In reply to comment #19)
Darin Adler
Comment 21 2016-03-24 19:17:51 PDT
Comment on attachment 274805 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=274805&action=review Unfortunate this ended up being so complicated, but it does seem worthwhile to to this. > Source/WTF/wtf/HashTraits.h:133 > + T* pointer = value.release(); Seems like there should be brief a comment explaining that we want to make sure the nulling and overwriting with the deleted value are not separated by a function call so we can avoid the dead store. Hard to make the comment brief, but I consider this non-obvious. >>> Source/WTF/wtf/HashTraits.h:136 >>> + Deleter()(pointer); >> >> I put this one back since the LIKELY(pointer) is helping keeping the codegen tight. > > Is this ever not true? If we put a nullptr std::unique_ptr into a WTF::HashTable, it will cause big problems well before this because it is equal to the empty value. Would it be safe to do without this branch altogether? Should add a comment explaining the non-obvious fact that the pointer can be null if someone just moved the value out of a set before deleting the bucket. > Source/WTF/wtf/HashTraits.h:150 > + ASSERT(!SimpleClassHashTraits<RefPtr<P>>::isDeletedValue(value)); I suggest writing isDeletedValue(value) instead of SimpleClassHashTraits<RefPtr<P>>::isDeletedValue(value). > Source/WTF/wtf/HashTraits.h:152 > + auto pointer = WTFMove(value); > + SimpleClassHashTraits<RefPtr<P>>::constructDeletedValue(value); Seems like this needs that same brief comment explaining that we want to make sure the nulling and overwriting with the deleted value are not separated by a function call so we can avoid the dead store. I think the local should be called smartPointerToBeDestroyed or valueToBeDestroyed or something like that. Otherwise the technique here is non-obvious. I suggest writing constructDeletedValue(value) instead of SimpleClassHashTraits<RefPtr<P>>::constructDeletedValue(value). > Source/WTF/wtf/HashTraits.h:195 > + HashTraitsCustomDeleteBucketChecker<Traits, Traits::hasCustomDeleteBucketFunction>::deleteBucket(value); We don’t need hasCustomDeleteBucketFunction because we can use instead use the SFINAE technique <http://en.cppreference.com/w/cpp/language/sfinae> to automatically chose the right version of deleteBucket based on the presence or absence of a customDeleteBucket function. Or we can do the even better and easier solution of just putting deleteBucket in GenericHashTraitsBase and overriding it instead of having customDeleteBucket. Either way, we don’t need the boolean. > Source/WTF/wtf/HashTraits.h:260 > + static_assert(std::is_trivially_destructible<KeyValuePair<int, int>>::value, > + "The wrapper itself has to be trivially destructible for customDeleteBucket() to make sense, since we do not destruct the wrapper itself."); Since this assertion is about the validity of the algorithm of customDeleteBucket, why not put it inside that function? Since it’s a compile time assertion it should be OK to have it in there instead of out here. > Source/WTF/wtf/text/AtomicStringHash.h:61 > + ASSERT(!SimpleClassHashTraits<AtomicString>::isDeletedValue(value)); I suggest writing isDeletedValue(value) instead of SimpleClassHashTraits<AtomicString>::isDeletedValue(value). > Source/WTF/wtf/text/AtomicStringHash.h:62 > + AtomicString valueToBeDeleted = WTFMove(value); Seems like this needs that same brief comment explaining that we want to make sure the nulling and overwriting with the deleted value are not separated by a function call so we can avoid the dead store. Otherwise the technique here is non-obvious. Also, thinking about C++ terminology, I think valueToBeDestroyed is accurate and valueToBeDeleted is inaccurate. > Source/WTF/wtf/text/AtomicStringHash.h:63 > + SimpleClassHashTraits<AtomicString>::constructDeletedValue(value); I suggest writing constructDeletedValue(value) instead of SimpleClassHashTraits<AtomicString>::constructDeletedValue(value). > Source/WTF/wtf/text/StringHash.h:38 > + ASSERT(!SimpleClassHashTraits<String>::isDeletedValue(value)); I suggest writing isDeletedValue(value) instead of SimpleClassHashTraits<String>::isDeletedValue(value). > Source/WTF/wtf/text/StringHash.h:39 > + String valueToBeDeleted = WTFMove(value); Seems like this needs that same brief comment explaining that we want to make sure the nulling and overwriting with the deleted value are not separated by a function call so we can avoid the dead store. Otherwise the technique here is non-obvious. Also, thinking about C++ terminology, I think valueToBeDestroyed is accurate and valueToBeDeleted is inaccurate. > Source/WTF/wtf/text/StringHash.h:40 > + SimpleClassHashTraits<String>::constructDeletedValue(value); I suggest writing constructDeletedValue(value) instead of SimpleClassHashTraits<String>::constructDeletedValue(value).
Darin Adler
Comment 22 2016-03-24 19:31:27 PDT
Comment on attachment 274805 [details] Patch All those places I suggested using f instead of SimpleHashTraits<xxx>::f, I might have been wrong because of template rules, but maybe we can at least just write HashTraits::f without the explicit template arguments? Do we really need to write it out like that?
Benjamin Poulain
Comment 23 2016-03-25 21:05:53 PDT
I am familiar with SFINAE but I have never used it with functions. What trick do you use to do type substitution based on a static function?
Darin Adler
Comment 24 2016-03-28 10:54:37 PDT
I’m not entirely sure it can be done based on a static function, but I think it can. I don’t have time to work out the details myself right now, but I will do it at some point when I get a chance. I might find I was mistaken. But I think we should do the better solution just after mentioning SFINAE. Unless I was mistaken about both.
Darin Adler
Comment 25 2016-03-28 10:55:04 PDT
(In reply to comment #24) > But I think we should do the better solution just after mentioning SFINAE. The better solution *I suggested* just after...
Benjamin Poulain
Comment 26 2016-03-28 15:36:38 PDT
(In reply to comment #25) > (In reply to comment #24) > > But I think we should do the better solution just after mentioning SFINAE. > > The better solution *I suggested* just after... I cannot define deleteBucket() before constructDeletedValue() is defined. That's the reason customDeleteBucket() exists.
Darin Adler
Comment 27 2016-03-29 09:10:21 PDT
Created attachment 275097 [details] Example of SFINAE used to detect the presence of a static member function OK, here’s how to use SFINAE to detect the presence of a static member function. There may be a more elegant version, but this is an existence proof that it can be done.
Darin Adler
Comment 28 2016-03-29 09:11:51 PDT
I did a search for "SFINAE static member function" and found various discussions of this. I attached an example that I got working. Can probably be streamlined and improved a bit.
Benjamin Poulain
Comment 29 2016-03-29 19:21:25 PDT
WebKit Commit Bot
Comment 30 2016-03-29 22:13:03 PDT
Comment on attachment 275163 [details] Patch Clearing flags on attachment: 275163 Committed r198827: <http://trac.webkit.org/changeset/198827>
WebKit Commit Bot
Comment 31 2016-03-29 22:13:09 PDT
All reviewed patches have been landed. Closing bug.
Note You need to log in before you can comment on or make changes to this bug.