diff --git a/libcxx/include/__memory/valid_range.h b/libcxx/include/__memory/valid_range.h index 32c98588fe39a..6edd68d53c49c 100644 --- a/libcxx/include/__memory/valid_range.h +++ b/libcxx/include/__memory/valid_range.h @@ -50,7 +50,9 @@ __is_valid_range(const _Tp* __first, const _Tp* __last) { // This function allows the compiler to assume that [__first, __last) is a valid range as defined above. // // In practice, we only add explicit assumptions for bullets (1) and (3). These assumptions allow (currently only -// clang-based compilers) to auto-vectorize algorithms that contain early returns. +// clang-based compilers) to auto-vectorize algorithms that contain early returns. Note that we don't enforce +// alignment assumptions for empty ranges since it is a common occurence to use unaligned sentinel values to +// implement iterators of empty ranges. template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]] _Sent&& __last) { @@ -60,10 +62,12 @@ __assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]] _LIBCPP_ASSERT_INTERNAL(std::__is_valid_range(std::__to_address(__first), std::__to_address(__last)), "Valid range assumption does not hold"); if (!__libcpp_is_constant_evaluated()) { - using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type; - __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type)); - (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first)); - (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last)); + if (__first != __last) { + using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type; + __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type)); + (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first)); + (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last)); + } } } #endif diff --git a/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp b/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp new file mode 100644 index 0000000000000..8cd9bcd67f870 --- /dev/null +++ b/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Some algorithms make alignment assumptions for optimization purposes. However, it is +// fairly common for data structures to implement iterators for empty ranges using +// sentinel values, which may not be properly aligned. This test ensures that we don't +// make alignment assumptions for empty ranges, which would break that use case. + +// UNSUPPORTED: c++03 + +#include +#include +#include +#include + +#include "test_macros.h" + +// Returns a pointer that doesn't point to any object at all and that is not suitably +// aligned for T. This is the sort of sentinel value that user code sometimes uses to +// represent an empty range. +template +T* unaligned_sentinel() { + return reinterpret_cast(static_cast(1)); +} + +template +void test() { + T* first = unaligned_sentinel(); + T* last = first; + T value{}; + auto pred = [](T const&) { return true; }; + + assert(std::find(first, last, value) == last); + assert(std::find_if(first, last, pred) == last); + assert(std::find_if_not(first, last, pred) == last); + assert(std::any_of(first, last, pred) == false); + assert(std::all_of(first, last, pred) == true); + assert(std::none_of(first, last, pred) == true); + assert(std::remove(first, last, value) == last); + assert(std::remove_if(first, last, pred) == last); + +#if TEST_STD_VER >= 20 + // (iterator, sentinel) overloads + assert(std::ranges::find(first, last, value) == last); + assert(std::ranges::find_if(first, last, pred) == last); + assert(std::ranges::find_if_not(first, last, pred) == last); + assert(std::ranges::any_of(first, last, pred) == false); + assert(std::ranges::all_of(first, last, pred) == true); + assert(std::ranges::none_of(first, last, pred) == true); + assert(std::ranges::remove(first, last, value).begin() == last); + assert(std::ranges::remove_if(first, last, pred).begin() == last); + + // (range) overloads + std::ranges::subrange range(first, last); + assert(std::ranges::find(range, value) == last); + assert(std::ranges::find_if(range, pred) == last); + assert(std::ranges::find_if_not(range, pred) == last); + assert(std::ranges::any_of(range, pred) == false); + assert(std::ranges::all_of(range, pred) == true); + assert(std::ranges::none_of(range, pred) == true); + assert(std::ranges::remove(range, value).begin() == last); + assert(std::ranges::remove_if(range, pred).begin() == last); +#endif +} + +struct alignas(16) Overaligned { + int value = 0; + friend bool operator==(Overaligned const& x, Overaligned const& y) { return x.value == y.value; } +}; + +int main(int, char**) { + test(); + test(); // not trivially equality comparable + test(); + + return 0; +}