Skip to content

[libc++] Add some _LIBCPP_ASSUMEs for bounded iterators #109033

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions libcxx/include/__assert
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
// optimization intent. See https://discourse.llvm.org/t/llvm-assume-blocks-optimization/71609 for a
// discussion.
#if __has_builtin(__builtin_assume)
# define _LIBCPP_ASSUME(expression) \
(_LIBCPP_DIAGNOSTIC_PUSH _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wassume") \
__builtin_assume(static_cast<bool>(expression)) _LIBCPP_DIAGNOSTIC_POP)
# define _LIBCPP_ASSUME(expression) __builtin_assume(static_cast<bool>(expression))
#else
# define _LIBCPP_ASSUME(expression) ((void)0)
#endif
Expand Down
16 changes: 16 additions & 0 deletions libcxx/include/__iterator/bounded_iter.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,26 @@ struct __bounded_iter {
_LIBCPP_HIDE_FROM_ABI
_LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __bounded_iter(_Iterator __current, _Iterator __begin, _Iterator __end)
: __current_(__current), __begin_(__begin), __end_(__end) {
// These are internal checks rather than hardening checks because the STL container is expected to ensure they are
// in order.
_LIBCPP_ASSERT_INTERNAL(
__begin <= __current, "__bounded_iter(current, begin, end): current and begin are inconsistent");
_LIBCPP_ASSERT_INTERNAL(
__current <= __end, "__bounded_iter(current, begin, end): current and end are inconsistent");

// However, this order is important to help the compiler reason about bounds checks. For example, `std::vector` sets
// `__end_ptr` to the capacity, not the true container end. To translate container-end fenceposts into hardening-end
// fenceposts, we must know that container-end <= hardening-end. `std::__to_address` is needed because `_Iterator`
// may be wrapped type, such that `operator<=` has side effects.
pointer __begin_ptr = std::__to_address(__begin);
pointer __current_ptr = std::__to_address(__current);
pointer __end_ptr = std::__to_address(__end);
_LIBCPP_ASSUME(__begin_ptr <= __current_ptr);
_LIBCPP_ASSUME(__current_ptr <= __end_ptr);
// Silence warnings when assumptions are disabled.
(void)__begin_ptr;
(void)__current_ptr;
(void)__end_ptr;
}

template <class _It>
Expand Down
12 changes: 11 additions & 1 deletion libcxx/include/__vector/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,11 @@ class _LIBCPP_TEMPLATE_VIS vector {

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator __make_iter(pointer __p) _NOEXCEPT {
#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
// `__bounded_iter` will tell the compiler that `__p` is bounded by `__begin_` and `__end_cap`, but nothing a priori
// relates `__p` to `__end_`.
_LIBCPP_ASSERT_INTERNAL(__p <= this->__end_, "vector::__make_iter passed an invalid pointer");
_LIBCPP_ASSUME(__p <= this->__end_);

// Bound the iterator according to the capacity, rather than the size.
//
// Vector guarantees that iterators stay valid as long as no reallocation occurs even if new elements are inserted
Expand All @@ -673,7 +678,12 @@ class _LIBCPP_TEMPLATE_VIS vector {

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator __make_iter(const_pointer __p) const _NOEXCEPT {
#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
// Bound the iterator according to the capacity, rather than the size.
// `__bounded_iter` will tell the compiler that `__p` is bounded by `__begin_` and `__end_cap`, but nothing a priori
// relates `__p` to `__end_`.
_LIBCPP_ASSERT_INTERNAL(__p <= this->__end_, "vector::__make_iter passed an invalid pointer");
_LIBCPP_ASSUME(__p <= this->__end_);

// Bound the iterator according to the capacity, rather than the size. See above.
return std::__make_bounded_iter(
std::__wrap_iter<const_pointer>(__p),
std::__wrap_iter<const_pointer>(this->__begin_),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,27 @@ void sequence_container_benchmarks(std::string container) {
}
});
}

/////////////////////////
// General usage patterns
/////////////////////////
bench("iterate-whole-container", [cheap](auto& st) {
auto const size = st.range(0);
std::vector<ValueType> in;
std::generate_n(std::back_inserter(in), size, cheap);
DoNotOptimizeData(in);

Container c(in.begin(), in.end());
DoNotOptimizeData(c);

auto use = [](auto& element) { benchmark::DoNotOptimize(element); };

for ([[maybe_unused]] auto _ : st) {
for (auto it = c.begin(); it != c.end(); ++it) {
use(*it);
}
}
});
}

} // namespace support
Expand Down
Loading