-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[libc++] Simplify vector<bool>::__construct_at_end (Reopend) #119632
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -552,36 +552,29 @@ vector<bool, _Allocator>::__recommend(size_type __new_size) const { | |
} | ||
|
||
// Default constructs __n objects starting at __end_ | ||
// Precondition: __n > 0 | ||
// Precondition: size() + __n <= capacity() | ||
// Postcondition: size() == size() + __n | ||
template <class _Allocator> | ||
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void | ||
vector<bool, _Allocator>::__construct_at_end(size_type __n, bool __x) { | ||
size_type __old_size = this->__size_; | ||
_LIBCPP_ASSERT_INTERNAL( | ||
capacity() >= size() + __n, "vector<bool>::__construct_at_end called with insufficient capacity"); | ||
std::fill_n(end(), __n, __x); | ||
this->__size_ += __n; | ||
if (__old_size == 0 || ((__old_size - 1) / __bits_per_word) != ((this->__size_ - 1) / __bits_per_word)) { | ||
if (this->__size_ <= __bits_per_word) | ||
this->__begin_[0] = __storage_type(0); | ||
else | ||
this->__begin_[(this->__size_ - 1) / __bits_per_word] = __storage_type(0); | ||
} | ||
std::fill_n(__make_iter(__old_size), __n, __x); | ||
if (end().__ctz_ != 0) // Ensure uninitialized leading bits in the last word are set to zero | ||
std::fill_n(end(), __bits_per_word - end().__ctz_, 0); | ||
} | ||
|
||
template <class _Allocator> | ||
template <class _InputIterator, class _Sentinel> | ||
_LIBCPP_CONSTEXPR_SINCE_CXX20 void | ||
vector<bool, _Allocator>::__construct_at_end(_InputIterator __first, _Sentinel __last, size_type __n) { | ||
ldionne marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a test for this function where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand your concern here. That's why I explicitly check if we have uninitialized bits, which could only exist in the last partial word. Previously, we pre-initialize the entire last word, whether it is a partial word or not. This patch implements it in a more compact form which initializes the bits to zero only when the last word is a partial word. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I have basically the same question here: How does the |
||
size_type __old_size = this->__size_; | ||
_LIBCPP_ASSERT_INTERNAL( | ||
capacity() >= size() + __n, "vector<bool>::__construct_at_end called with insufficient capacity"); | ||
std::__copy(std::move(__first), std::move(__last), end()); | ||
this->__size_ += __n; | ||
if (__old_size == 0 || ((__old_size - 1) / __bits_per_word) != ((this->__size_ - 1) / __bits_per_word)) { | ||
if (this->__size_ <= __bits_per_word) | ||
this->__begin_[0] = __storage_type(0); | ||
else | ||
this->__begin_[(this->__size_ - 1) / __bits_per_word] = __storage_type(0); | ||
} | ||
std::__copy(std::move(__first), std::move(__last), __make_iter(__old_size)); | ||
if (end().__ctz_ != 0) // Ensure uninitialized leading bits in the last word are set to zero | ||
std::fill_n(end(), __bits_per_word - end().__ctz_, 0); | ||
} | ||
|
||
template <class _Allocator> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures that the unused leading bits in the last word are initialized, which serves the same purpose as the pre-initialization logic in the previous implementation:
llvm-project/libcxx/include/__vector/vector_bool.h
Lines 554 to 561 in 743c84b
Without this, we would have a Msan CI failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But how is the previous
fill_n
not reading uninitialized elements? It has to load the bits of the last word after all.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In constant evaluation, we have no uninitialized bits at all because
__vallocate
guarantees that the allocated memory is default-constructed. In non-constant evaluation, we effectively only reads from bits within the bounds ofvector<bool>
since we utilize bit masks. So in this case, the purpose of the initialization is to make sanitizer tools happy. According to my test, sanitizers do not complain as long as the unused bits in the last word are initialized to 0 within__construct_at_end
, regardless of whether this occurs before or after the initialization of other words. However, without the initialization of unused bits, subsequent read fromvector<bool>
would trigger a Msan CI failure.