Skip to content

Commit 61afcf7

Browse files
philnik777Icohedron
authored andcommitted
Reapply "[libc++] Simplify the implementation of reserve() and shrink_to_fit() (llvm#113453)" (llvm#125888)
The capacity is now passed correctly and a test for this path is added. Since we changed the implementation of `reserve(size_type)` to only ever extend, it doesn't make a ton of sense anymore to have `__shrink_or_extend`, since the code paths of `reserve` and `shrink_to_fit` are now almost completely separate. This patch splits up `__shrink_or_extend` so that the individual parts are in `reserve` and `shrink_to_fit` depending on where they are needed. This reverts commit 59f57be.
1 parent 0f66666 commit 61afcf7

File tree

2 files changed

+68
-60
lines changed

2 files changed

+68
-60
lines changed

libcxx/include/string

Lines changed: 59 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,8 +1882,6 @@ public:
18821882
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __clear_and_shrink() _NOEXCEPT;
18831883

18841884
private:
1885-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __shrink_or_extend(size_type __target_capacity);
1886-
18871885
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS bool
18881886
__is_long() const _NOEXCEPT {
18891887
if (__libcpp_is_constant_evaluated() && __builtin_constant_p(__rep_.__l.__is_long_)) {
@@ -2079,6 +2077,21 @@ private:
20792077
# endif
20802078
}
20812079

2080+
// Disable ASan annotations and enable them again when going out of scope. It is assumed that the string is in a valid
2081+
// state at that point, so `size()` can be called safely.
2082+
struct [[__nodiscard__]] __annotation_guard {
2083+
__annotation_guard(const __annotation_guard&) = delete;
2084+
__annotation_guard& operator=(const __annotation_guard&) = delete;
2085+
2086+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __annotation_guard(basic_string& __str) : __str_(__str) {
2087+
__str_.__annotate_delete();
2088+
}
2089+
2090+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 ~__annotation_guard() { __str_.__annotate_new(__str_.size()); }
2091+
2092+
basic_string& __str_;
2093+
};
2094+
20822095
template <size_type __a>
20832096
static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type __align_it(size_type __s) _NOEXCEPT {
20842097
return (__s + (__a - 1)) & ~(__a - 1);
@@ -3357,7 +3370,16 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::re
33573370
if (__requested_capacity <= capacity())
33583371
return;
33593372

3360-
__shrink_or_extend(__recommend(__requested_capacity));
3373+
__annotation_guard __g(*this);
3374+
auto __allocation = std::__allocate_at_least(__alloc_, __recommend(__requested_capacity) + 1);
3375+
auto __size = size();
3376+
__begin_lifetime(__allocation.ptr, __allocation.count);
3377+
traits_type::copy(std::__to_address(__allocation.ptr), data(), __size + 1);
3378+
if (__is_long())
3379+
__alloc_traits::deallocate(__alloc_, __get_long_pointer(), __get_long_cap());
3380+
__set_long_cap(__allocation.count);
3381+
__set_long_size(__size);
3382+
__set_long_pointer(__allocation.ptr);
33613383
}
33623384

33633385
template <class _CharT, class _Traits, class _Allocator>
@@ -3366,69 +3388,46 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocat
33663388
if (__target_capacity == capacity())
33673389
return;
33683390

3369-
__shrink_or_extend(__target_capacity);
3370-
}
3391+
_LIBCPP_ASSERT_INTERNAL(__is_long(), "Trying to shrink small string");
33713392

3372-
template <class _CharT, class _Traits, class _Allocator>
3373-
inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void
3374-
basic_string<_CharT, _Traits, _Allocator>::__shrink_or_extend(size_type __target_capacity) {
3375-
__annotate_delete();
3376-
auto __guard = std::__make_scope_guard(__annotate_new_size(*this));
3377-
size_type __cap = capacity();
3378-
size_type __sz = size();
3379-
3380-
pointer __new_data, __p;
3381-
bool __was_long, __now_long;
3393+
// We're a long string and we're shrinking into the small buffer.
33823394
if (__fits_in_sso(__target_capacity)) {
3383-
__was_long = true;
3384-
__now_long = false;
3385-
__new_data = __get_short_pointer();
3386-
__p = __get_long_pointer();
3387-
} else {
3388-
if (__target_capacity > __cap) {
3389-
// Extend
3390-
// - called from reserve should propagate the exception thrown.
3391-
auto __allocation = std::__allocate_at_least(__alloc_, __target_capacity + 1);
3392-
__new_data = __allocation.ptr;
3393-
__target_capacity = __allocation.count - 1;
3394-
} else {
3395-
// Shrink
3396-
// - called from shrink_to_fit should not throw.
3397-
// - called from reserve may throw but is not required to.
3395+
__annotation_guard __g(*this);
3396+
auto __ptr = __get_long_pointer();
3397+
auto __size = __get_long_size();
3398+
auto __cap = __get_long_cap();
3399+
traits_type::copy(std::__to_address(__get_short_pointer()), std::__to_address(__ptr), __size + 1);
3400+
__set_short_size(__size);
3401+
__alloc_traits::deallocate(__alloc_, __ptr, __cap);
3402+
return;
3403+
}
3404+
33983405
# if _LIBCPP_HAS_EXCEPTIONS
3399-
try {
3406+
try {
34003407
# endif // _LIBCPP_HAS_EXCEPTIONS
3401-
auto __allocation = std::__allocate_at_least(__alloc_, __target_capacity + 1);
3402-
3403-
// The Standard mandates shrink_to_fit() does not increase the capacity.
3404-
// With equal capacity keep the existing buffer. This avoids extra work
3405-
// due to swapping the elements.
3406-
if (__allocation.count - 1 > capacity()) {
3407-
__alloc_traits::deallocate(__alloc_, __allocation.ptr, __allocation.count);
3408-
return;
3409-
}
3410-
__new_data = __allocation.ptr;
3411-
__target_capacity = __allocation.count - 1;
3408+
__annotation_guard __g(*this);
3409+
auto __size = size();
3410+
auto __allocation = std::__allocate_at_least(__alloc_, __target_capacity + 1);
3411+
3412+
// The Standard mandates shrink_to_fit() does not increase the capacity.
3413+
// With equal capacity keep the existing buffer. This avoids extra work
3414+
// due to swapping the elements.
3415+
if (__allocation.count - 1 > capacity()) {
3416+
__alloc_traits::deallocate(__alloc_, __allocation.ptr, __allocation.count);
3417+
return;
3418+
}
3419+
3420+
__begin_lifetime(__allocation.ptr, __allocation.count);
3421+
auto __ptr = __get_long_pointer();
3422+
traits_type::copy(std::__to_address(__allocation.ptr), std::__to_address(__ptr), __size + 1);
3423+
__alloc_traits::deallocate(__alloc_, __ptr, __get_long_cap());
3424+
__set_long_cap(__allocation.count);
3425+
__set_long_pointer(__allocation.ptr);
34123426
# if _LIBCPP_HAS_EXCEPTIONS
3413-
} catch (...) {
3414-
return;
3415-
}
3427+
} catch (...) {
3428+
return;
3429+
}
34163430
# endif // _LIBCPP_HAS_EXCEPTIONS
3417-
}
3418-
__begin_lifetime(__new_data, __target_capacity + 1);
3419-
__now_long = true;
3420-
__was_long = __is_long();
3421-
__p = __get_pointer();
3422-
}
3423-
traits_type::copy(std::__to_address(__new_data), std::__to_address(__p), size() + 1);
3424-
if (__was_long)
3425-
__alloc_traits::deallocate(__alloc_, __p, __cap + 1);
3426-
if (__now_long) {
3427-
__set_long_cap(__target_capacity + 1);
3428-
__set_long_size(__sz);
3429-
__set_long_pointer(__new_data);
3430-
} else
3431-
__set_short_size(__sz);
34323431
}
34333432

34343433
template <class _CharT, class _Traits, class _Allocator>

libcxx/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ TEST_CONSTEXPR_CXX20 void test_string() {
7575
test<S>(100, 50, 1000);
7676
test<S>(100, 50, S::npos);
7777
}
78+
79+
{ // Check that growing twice works as expected
80+
S str;
81+
str.reserve(50);
82+
assert(str.capacity() >= 50);
83+
size_t old_cap = str.capacity();
84+
str.reserve(str.capacity() + 1);
85+
assert(str.capacity() > old_cap);
86+
}
7887
}
7988

8089
TEST_CONSTEXPR_CXX20 bool test() {

0 commit comments

Comments
 (0)