-
Notifications
You must be signed in to change notification settings - Fork 15.5k
[libc++] Optimize __hash_table::erase(iterator, iterator) #152471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
Member
|
@llvm/pr-subscribers-libcxx Author: Nikolas Klauser (philnik777) Changes1 Files Affected:
diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index dacc152030e14..2f0f9457f1416 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1848,12 +1848,56 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __p) {
template <class _Tp, class _Hash, class _Equal, class _Alloc>
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator
__hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __first, const_iterator __last) {
- for (const_iterator __p = __first; __first != __last; __p = __first) {
- ++__first;
- erase(__p);
+ if (__first == __last)
+ return iterator(__last.__node_);
+
+ // current node
+ __next_pointer __current = __first.__node_;
+ size_type __bucket_count = bucket_count();
+ size_t __chash = std::__constrain_hash(__current->__hash(), __bucket_count);
+ // find previous node
+ __next_pointer __before_first = __bucket_list_[__chash];
+ for (; __before_first->__next_ != __current; __before_first = __before_first->__next_)
+ ;
+
+ __next_pointer __end = __last.__node_;
+
+ // If __before_first is in the same bucket, clear this bucket first without re-linking it
+ if (__before_first != __first_node_.__ptr() &&
+ std::__constrain_hash(__before_first->__hash(), __bucket_count) == __chash) {
+ while (__current != __end) {
+ if (auto __next_chash = std::__constrain_hash(__current->__hash(), __bucket_count); __next_chash != __chash) {
+ __chash = __next_chash;
+ break;
+ }
+ auto __next = __current->__next_;
+ __node_traits::deallocate(__node_alloc(), __current->__upcast(), 1);
+ __current = __next;
+ --__size_;
+ }
}
- __next_pointer __np = __last.__node_;
- return iterator(__np);
+
+ while (__current != __end) {
+ auto __next = __current->__next_;
+ __node_traits::deallocate(__node_alloc(), __current->__upcast(), 1);
+ __current = __next;
+ --__size_;
+
+ // When switching buckets, set the old bucket to be empty and update the next bucket to have __before_first as its
+ // before-first element
+ if (__next) {
+ if (auto __next_chash = std::__constrain_hash(__next->__hash(), __bucket_count); __next_chash != __chash) {
+ __bucket_list_[__chash] = nullptr;
+ __chash = __next_chash;
+ __bucket_list_[__chash] = __before_first;
+ }
+ }
+ }
+
+ // re-link __before_start with __last
+ __before_first->__next_ = __current;
+
+ return iterator(__last.__node_);
}
template <class _Tp, class _Hash, class _Equal, class _Alloc>
|
ldionne
reviewed
Aug 13, 2025
40ad452 to
ff5a8ec
Compare
ldionne
approved these changes
Aug 22, 2025
ff5a8ec to
e006595
Compare
e006595 to
7bc9893
Compare
Contributor
|
@philnik777 I'm seeing a regression under ASan (use after free after this patch). The following code snippet throws a use-after-free for me in final call to #include <unordered_map>
#include <utility>
typedef std::unordered_multimap<void*, void*> mapType;
typedef std::pair<mapType::iterator, mapType::iterator> erasePair;
int main(int argc, char** argv) {
mapType map;
map.insert(mapType::value_type((void*)0x7e4df9645ab8, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9649868, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9649cf0, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964a200, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964c700, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964cad8, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964cda0, (void*)5));
// equal_range: 0x7e4df9645ab8
erasePair pair1 = map.equal_range((void*)0x7e4df9645ab8);
map.erase(pair1.first, pair1.second);
map.insert(mapType::value_type((void*)0x7e4df9645f60, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df96505a8, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964c700, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df964c700, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9649cf0, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9650078, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9652258, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9652638, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df96509d0, (void*)5));
// equal_range: 0x7e4df9649868
erasePair pair2 = map.equal_range((void*)0x7e4df9649868);
map.erase(pair2.first, pair2.second);
map.insert(mapType::value_type((void*)0x7e4df964def0, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df96598b0, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df965a160, (void*)5));
// equal_range: 0x7e4df96505a8
erasePair pair3 = map.equal_range((void*)0x7e4df96505a8);
map.erase(pair3.first, pair3.second);
map.insert(mapType::value_type((void*)0x7e4df9653810, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df9666340, (void*)5));
// equal_range: 0x7e4df964c700
erasePair pair4 = map.equal_range((void*)0x7e4df964c700);
map.erase(pair4.first, pair4.second);
map.insert(mapType::value_type((void*)0x7e4df965a880, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df965a880, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df965a880, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df96680c8, (void*)5));
// equal_range: 0x7e4df9652258
erasePair pair5 = map.equal_range((void*)0x7e4df9652258);
map.erase(pair5.first, pair5.second);
map.insert(mapType::value_type((void*)0x7e4df9666d98, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df966c608, (void*)5));
map.insert(mapType::value_type((void*)0x7e4df966c9e0, (void*)5));
erasePair test_pair = map.equal_range((void*)0x7e4df9649cf0);
if (test_pair.first == test_pair.second) {
return 1;
}
return 0;
}Are you able to take a look? |
boomanaiden154
added a commit
to boomanaiden154/llvm-project
that referenced
this pull request
Sep 16, 2025
…vm#152471)" This reverts commit e4eccd6. This was causing ASan failures in some situations involving unordered multimap containers. Details and a reproducer were posted on the original PR (llvm#152471).
boomanaiden154
added a commit
that referenced
this pull request
Sep 17, 2025
Contributor
|
(This relanded in #162850.) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Instead of just calling the single element
eraseon every element of the range, we can combine some of the operations in a custom implementation. Specifically, we don't need to search for the previous node or re-link the list every iteration. Removing this unnecessary work results in some nice performance improvements: