Reapply "[libc++] Optimize std::find_if" (#175903) (#175921)

#175913 removed that `__builtin_assume_dereferenceable(ptr, 0)` implies
`ptr != nullptr`, which should allow us to use the builtin with LLVM 23.

This reverts commit 776c09c212.
This commit is contained in:
Nikolas Klauser 2026-01-15 10:16:45 +01:00 committed by GitHub
parent 808a6ba345
commit 4304106dc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 92 additions and 58 deletions

View file

@ -607,6 +607,7 @@ set(files
__memory/unique_temporary_buffer.h
__memory/uses_allocator.h
__memory/uses_allocator_construction.h
__memory/valid_range.h
__memory_resource/memory_resource.h
__memory_resource/monotonic_buffer_resource.h
__memory_resource/polymorphic_allocator.h
@ -931,7 +932,6 @@ set(files
__utility/in_place.h
__utility/integer_sequence.h
__utility/is_pointer_in_range.h
__utility/is_valid_range.h
__utility/lazy_synth_three_way_comparator.h
__utility/move.h
__utility/no_destroy.h

View file

@ -11,6 +11,7 @@
#define _LIBCPP___ALGORITHM_FIND_IF_H
#include <__config>
#include <__memory/valid_range.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@ -21,6 +22,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, class _Predicate>
[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
find_if(_InputIterator __first, _InputIterator __last, _Predicate __pred) {
std::__assume_valid_range(__first, __last);
for (; __first != __last; ++__first)
if (__pred(*__first))
break;

View file

@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___MEMORY_VALID_RANGE_H
#define _LIBCPP___MEMORY_VALID_RANGE_H
#include <__algorithm/comp.h>
#include <__assert>
#include <__config>
#include <__iterator/iterator_traits.h>
#include <__memory/assume_aligned.h>
#include <__memory/pointer_traits.h>
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/is_same.h>
#include <__type_traits/remove_cvref.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
// A valid range as defined by the C++ Standard has the following constraints:
// - [__first, __last) is dereferenceable
// - __last is reachable from __first
// - if __first and __last are contiguous iterators, the pointers they "decay to" are correctly aligned according to the
// language rules for pointers
// This function attempts to detect invalid ranges as defined above. Specifically, it checks bullet (2). This also means
// that it doesn't return whether a range is actually valid, but only whether a range is definitely not valid.
// The checks may be extended in the future.
template <class _Tp>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool
__is_valid_range(const _Tp* __first, const _Tp* __last) {
if (__libcpp_is_constant_evaluated()) {
// If this is not a constant during constant evaluation, that is because __first and __last are not
// part of the same allocation. If they are part of the same allocation, we must still make sure they
// are ordered properly.
return __builtin_constant_p(__first <= __last) && __first <= __last;
}
return !__less<>()(__last, __first);
}
// 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.
template <class _Iter, class _Sent>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
__assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]] _Sent&& __last) {
#if defined(_LIBCPP_CLANG_VER) && _LIBCPP_CLANG_VER >= 2300 && !defined(_LIBCPP_CXX03_LANG)
if constexpr (__libcpp_is_contiguous_iterator<__remove_cvref_t<_Iter>>::value &&
is_same<__remove_cvref_t<_Iter>, __remove_cvref_t<_Sent>>::value) {
_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));
}
}
#endif
}
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___MEMORY_VALID_RANGE_H

View file

@ -12,12 +12,12 @@
#include <__algorithm/comp.h>
#include <__assert>
#include <__config>
#include <__memory/valid_range.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/void_t.h>
#include <__utility/declval.h>
#include <__utility/is_valid_range.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header

View file

@ -1,37 +0,0 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___UTILITY_IS_VALID_RANGE_H
#define _LIBCPP___UTILITY_IS_VALID_RANGE_H
#include <__algorithm/comp.h>
#include <__config>
#include <__type_traits/is_constant_evaluated.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
template <class _Tp>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool
__is_valid_range(const _Tp* __first, const _Tp* __last) {
if (__libcpp_is_constant_evaluated()) {
// If this is not a constant during constant evaluation, that is because __first and __last are not
// part of the same allocation. If they are part of the same allocation, we must still make sure they
// are ordered properly.
return __builtin_constant_p(__first <= __last) && __first <= __last;
}
return !__less<>()(__last, __first);
}
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___UTILITY_IS_VALID_RANGE_H

View file

@ -1690,6 +1690,7 @@ module std [system] {
}
module uses_allocator { header "__memory/uses_allocator.h" }
module uses_allocator_construction { header "__memory/uses_allocator_construction.h" }
module valid_range { header "__memory/valid_range.h" }
header "memory"
export *
@ -2172,7 +2173,6 @@ module std [system] {
}
module integer_sequence { header "__utility/integer_sequence.h" }
module is_pointer_in_range { header "__utility/is_pointer_in_range.h" }
module is_valid_range { header "__utility/is_valid_range.h" }
module lazy_synth_three_way_comparator { header "__utility/lazy_synth_three_way_comparator.h" }
module move { header "__utility/move.h" }
module no_destroy { header "__utility/no_destroy.h" }

View file

@ -117,8 +117,8 @@ protected:
# include <__assert>
# include <__fwd/streambuf.h>
# include <__locale>
# include <__memory/valid_range.h>
# include <__type_traits/is_same.h>
# include <__utility/is_valid_range.h>
# include <__utility/scope_guard.h>
# include <climits>
# include <ios>

View file

@ -22,30 +22,18 @@
int main(int argc, char** argv) {
auto std_find = [](auto first, auto last, auto const& value) { return std::find(first, last, value); };
auto std_find_if = [](auto first, auto last, auto const& value) {
return std::find_if(first, last, [&](auto element) {
benchmark::DoNotOptimize(element);
return element == value;
});
return std::find_if(first, last, [&](auto element) { return element == value; });
};
auto std_find_if_not = [](auto first, auto last, auto const& value) {
return std::find_if_not(first, last, [&](auto element) {
benchmark::DoNotOptimize(element);
return element != value;
});
return std::find_if_not(first, last, [&](auto element) { return element != value; });
};
auto ranges_find = [](auto first, auto last, auto const& value) { return std::ranges::find(first, last, value); };
auto ranges_find_if = [](auto first, auto last, auto const& value) {
return std::ranges::find_if(first, last, [&](auto element) {
benchmark::DoNotOptimize(element);
return element == value;
});
return std::ranges::find_if(first, last, [&](auto element) { return element == value; });
};
auto ranges_find_if_not = [](auto first, auto last, auto const& value) {
return std::ranges::find_if_not(first, last, [&](auto element) {
benchmark::DoNotOptimize(element);
return element != value;
});
return std::ranges::find_if_not(first, last, [&](auto element) { return element != value; });
};
auto register_benchmarks = [&](auto bm, std::string comment) {

View file

@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include <__utility/is_valid_range.h>
#include <__memory/valid_range.h>
#include <cassert>
#include "test_macros.h"

View file

@ -56,6 +56,12 @@ TEST_CONSTEXPR_CXX17 bool test() {
test_iter<forward_iterator<int*> >();
test_iter<bidirectional_iterator<int*> >();
test_iter<random_access_iterator<int*> >();
{ // Check that [nullptr, nullptr) is an accepted range
auto result = std::find_if(static_cast<int*>(nullptr), static_cast<int*>(nullptr), [](int i) { return i == 0; });
assert(result == nullptr);
}
return true;
}