[Clang] Add __builtin_invoke and use it in libc++ (#116709)

`std::invoke` is currently quite heavy compared to a function call,
since it involves quite heavy SFINAE. This can be done significantly
more efficient by the compiler, since most calls to `std::invoke` are
simple function calls and 6 out of the seven overloads for `std::invoke`
exist only to support member pointers. Even these boil down to a few
relatively simple checks.

Some real-world testing with this patch revealed some significant
results. For example, instantiating `std::format("Banane")` (and its
callees) went down from ~125ms on my system to ~104ms.
This commit is contained in:
Nikolas Klauser
2025-06-29 17:52:50 +02:00
committed by GitHub
parent 43ab5bb921
commit 713839729c
10 changed files with 624 additions and 82 deletions

View File

@@ -22,6 +22,7 @@
#include <__type_traits/is_same.h>
#include <__type_traits/is_void.h>
#include <__type_traits/nat.h>
#include <__type_traits/void_t.h>
#include <__utility/declval.h>
#include <__utility/forward.h>
@@ -61,6 +62,112 @@
_LIBCPP_BEGIN_NAMESPACE_STD
#if __has_builtin(__builtin_invoke)
template <class... _Args>
using __invoke_result_t = decltype(__builtin_invoke(std::declval<_Args>()...));
template <class, class... _Args>
struct __invoke_result_impl {};
template <class... _Args>
struct __invoke_result_impl<__void_t<__invoke_result_t<_Args...> >, _Args...> {
using type _LIBCPP_NODEBUG = __invoke_result_t<_Args...>;
};
template <class... _Args>
using __invoke_result = __invoke_result_impl<void, _Args...>;
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __invoke_result_t<_Args...> __invoke(_Args&&... __args)
_NOEXCEPT_(noexcept(__builtin_invoke(std::forward<_Args>(__args)...))) {
return __builtin_invoke(std::forward<_Args>(__args)...);
}
template <class _Void, class... _Args>
inline const bool __is_invocable_impl = false;
template <class... _Args>
inline const bool __is_invocable_impl<__void_t<__invoke_result_t<_Args...> >, _Args...> = true;
template <class... _Args>
inline const bool __is_invocable_v = __is_invocable_impl<void, _Args...>;
template <class... _Args>
struct __is_invocable : integral_constant<bool, __is_invocable_v<_Args...> > {};
template <class _Ret, bool, class... _Args>
inline const bool __is_invocable_r_impl = false;
template <class _Ret, class... _Args>
inline const bool __is_invocable_r_impl<_Ret, true, _Args...> =
__is_core_convertible<__invoke_result_t<_Args...>, _Ret>::value || is_void<_Ret>::value;
template <class _Ret, class... _Args>
inline const bool __is_invocable_r_v = __is_invocable_r_impl<_Ret, __is_invocable_v<_Args...>, _Args...>;
template <bool __is_invocable, class... _Args>
inline const bool __is_nothrow_invocable_impl = false;
template <class... _Args>
inline const bool __is_nothrow_invocable_impl<true, _Args...> = noexcept(__builtin_invoke(std::declval<_Args>()...));
template <class... _Args>
inline const bool __is_nothrow_invocable_v = __is_nothrow_invocable_impl<__is_invocable_v<_Args...>, _Args...>;
template <bool __is_invocable, class _Ret, class... _Args>
inline const bool __is_nothrow_invocable_r_impl = false;
template <class _Ret, class... _Args>
inline const bool __is_nothrow_invocable_r_impl<true, _Ret, _Args...> =
__is_nothrow_core_convertible_v<__invoke_result_t<_Args...>, _Ret> || is_void<_Ret>::value;
template <class _Ret, class... _Args>
inline const bool __is_nothrow_invocable_r_v =
__is_nothrow_invocable_r_impl<__is_nothrow_invocable_v<_Args...>, _Ret, _Args...>;
# if _LIBCPP_STD_VER >= 17
// is_invocable
template <class _Fn, class... _Args>
struct _LIBCPP_NO_SPECIALIZATIONS is_invocable : bool_constant<__is_invocable_v<_Fn, _Args...> > {};
template <class _Ret, class _Fn, class... _Args>
struct _LIBCPP_NO_SPECIALIZATIONS is_invocable_r : bool_constant<__is_invocable_r_v<_Ret, _Fn, _Args...>> {};
template <class _Fn, class... _Args>
_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_invocable_v = __is_invocable_v<_Fn, _Args...>;
template <class _Ret, class _Fn, class... _Args>
_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_invocable_r_v = is_invocable_r<_Ret, _Fn, _Args...>::value;
// is_nothrow_invocable
template <class _Fn, class... _Args>
struct _LIBCPP_NO_SPECIALIZATIONS is_nothrow_invocable : bool_constant<__is_nothrow_invocable_v<_Fn, _Args...> > {};
template <class _Ret, class _Fn, class... _Args>
struct _LIBCPP_NO_SPECIALIZATIONS is_nothrow_invocable_r
: integral_constant<bool, __is_nothrow_invocable_r_v<_Ret, _Fn, _Args...>> {};
template <class _Fn, class... _Args>
_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_nothrow_invocable_v = __is_nothrow_invocable_v<_Fn, _Args...>;
template <class _Ret, class _Fn, class... _Args>
_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_nothrow_invocable_r_v =
__is_nothrow_invocable_r_v<_Ret, _Fn, _Args...>;
template <class _Fn, class... _Args>
struct _LIBCPP_NO_SPECIALIZATIONS invoke_result : __invoke_result<_Fn, _Args...> {};
template <class _Fn, class... _Args>
using invoke_result_t = __invoke_result_t<_Fn, _Args...>;
# endif // _LIBCPP_STD_VER >= 17
#else // __has_builtin(__builtin_invoke)
template <class _DecayedFp>
struct __member_pointer_class_type {};
@@ -211,21 +318,21 @@ struct __nothrow_invokable_r_imp<true, false, _Ret, _Fp, _Args...> {
template <class _Tp>
static void __test_noexcept(_Tp) _NOEXCEPT;
#ifdef _LIBCPP_CXX03_LANG
# ifdef _LIBCPP_CXX03_LANG
static const bool value = false;
#else
# else
static const bool value =
noexcept(_ThisT::__test_noexcept<_Ret>(std::__invoke(std::declval<_Fp>(), std::declval<_Args>()...)));
#endif
# endif
};
template <class _Ret, class _Fp, class... _Args>
struct __nothrow_invokable_r_imp<true, true, _Ret, _Fp, _Args...> {
#ifdef _LIBCPP_CXX03_LANG
# ifdef _LIBCPP_CXX03_LANG
static const bool value = false;
#else
# else
static const bool value = noexcept(std::__invoke(std::declval<_Fp>(), std::declval<_Args>()...));
#endif
# endif
};
template <class _Ret, class _Fp, class... _Args>
@@ -236,22 +343,6 @@ template <class _Fp, class... _Args>
using __nothrow_invokable _LIBCPP_NODEBUG =
__nothrow_invokable_r_imp<__is_invocable<_Fp, _Args...>::value, true, void, _Fp, _Args...>;
template <class _Ret, bool = is_void<_Ret>::value>
struct __invoke_void_return_wrapper {
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static _Ret __call(_Args&&... __args) {
return std::__invoke(std::forward<_Args>(__args)...);
}
};
template <class _Ret>
struct __invoke_void_return_wrapper<_Ret, true> {
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void __call(_Args&&... __args) {
std::__invoke(std::forward<_Args>(__args)...);
}
};
template <class _Func, class... _Args>
inline const bool __is_invocable_v = __is_invocable<_Func, _Args...>::value;
@@ -268,12 +359,7 @@ struct __invoke_result
template <class _Func, class... _Args>
using __invoke_result_t _LIBCPP_NODEBUG = typename __invoke_result<_Func, _Args...>::type;
template <class _Ret, class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Ret __invoke_r(_Args&&... __args) {
return __invoke_void_return_wrapper<_Ret>::__call(std::forward<_Args>(__args)...);
}
#if _LIBCPP_STD_VER >= 17
# if _LIBCPP_STD_VER >= 17
// is_invocable
@@ -311,7 +397,30 @@ struct _LIBCPP_NO_SPECIALIZATIONS invoke_result : __invoke_result<_Fn, _Args...>
template <class _Fn, class... _Args>
using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
#endif // _LIBCPP_STD_VER >= 17
# endif // _LIBCPP_STD_VER >= 17
#endif // __has_builtin(__builtin_invoke_r)
template <class _Ret, bool = is_void<_Ret>::value>
struct __invoke_void_return_wrapper {
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static _Ret __call(_Args&&... __args) {
return std::__invoke(std::forward<_Args>(__args)...);
}
};
template <class _Ret>
struct __invoke_void_return_wrapper<_Ret, true> {
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void __call(_Args&&... __args) {
std::__invoke(std::forward<_Args>(__args)...);
}
};
template <class _Ret, class... _Args>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Ret __invoke_r(_Args&&... __args) {
return __invoke_void_return_wrapper<_Ret>::__call(std::forward<_Args>(__args)...);
}
_LIBCPP_END_NAMESPACE_STD

View File

@@ -37,6 +37,15 @@ concept __core_convertible_to = __is_core_convertible<_Tp, _Up>::value;
#endif // _LIBCPP_STD_VER >= 20
template <class _Tp, class _Up, bool = __is_core_convertible<_Tp, _Up>::value>
inline const bool __is_nothrow_core_convertible_v = false;
#ifndef _LIBCPP_CXX03_LANG
template <class _Tp, class _Up>
inline const bool __is_nothrow_core_convertible_v<_Tp, _Up, true> =
noexcept(static_cast<void (*)(_Up) noexcept>(0)(static_cast<_Tp (*)() noexcept>(0)()));
#endif
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___TYPE_TRAITS_IS_CORE_CONVERTIBLE_H