Skip to content

[libc++] Add __detected_or_t and use it to implement some of the allocator traits aliases #115654

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

Merged
merged 1 commit into from
Nov 26, 2024

Conversation

philnik777
Copy link
Contributor

This simplifies the implementation a bit, since we don't need a lot of the __has_x classes anymore. We just need two template aliases to implement the allocator_traits aliases now.

Copy link

github-actions bot commented Nov 10, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@philnik777 philnik777 force-pushed the use_detected_or branch 2 times, most recently from 064c370 to dc7abc0 Compare November 13, 2024 09:48
@philnik777 philnik777 marked this pull request as ready for review November 14, 2024 13:11
@philnik777 philnik777 requested a review from a team as a code owner November 14, 2024 13:11
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Nov 14, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 14, 2024

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes

This simplifies the implementation a bit, since we don't need a lot of the __has_x classes anymore. We just need two template aliases to implement the allocator_traits aliases now.


Full diff: https://github.com/llvm/llvm-project/pull/115654.diff

5 Files Affected:

  • (modified) libcxx/include/CMakeLists.txt (+1)
  • (modified) libcxx/include/__memory/allocator_traits.h (+40-55)
  • (modified) libcxx/include/__memory/unique_ptr.h (+2-2)
  • (added) libcxx/include/__type_traits/detected_or.h (+36)
  • (modified) libcxx/include/module.modulemap (+1)
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index dd774b25a81eda..98b9a19e4227ef 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -758,6 +758,7 @@ set(files
   __type_traits/decay.h
   __type_traits/dependent_type.h
   __type_traits/desugars_to.h
+  __type_traits/detected_or.h
   __type_traits/disjunction.h
   __type_traits/enable_if.h
   __type_traits/extent.h
diff --git a/libcxx/include/__memory/allocator_traits.h b/libcxx/include/__memory/allocator_traits.h
index 499b30b85b6c9b..62b454c9227523 100644
--- a/libcxx/include/__memory/allocator_traits.h
+++ b/libcxx/include/__memory/allocator_traits.h
@@ -15,6 +15,7 @@
 #include <__fwd/memory.h>
 #include <__memory/construct_at.h>
 #include <__memory/pointer_traits.h>
+#include <__type_traits/detected_or.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/is_constructible.h>
 #include <__type_traits/is_empty.h>
@@ -42,17 +43,11 @@ _LIBCPP_BEGIN_NAMESPACE_STD
   struct NAME<_Tp, __void_t<typename _Tp::PROPERTY > > : true_type {}
 
 // __pointer
-template <class _Tp,
-          class _Alloc,
-          class _RawAlloc = __libcpp_remove_reference_t<_Alloc>,
-          bool            = __has_pointer<_RawAlloc>::value>
-struct __pointer {
-  using type _LIBCPP_NODEBUG = typename _RawAlloc::pointer;
-};
-template <class _Tp, class _Alloc, class _RawAlloc>
-struct __pointer<_Tp, _Alloc, _RawAlloc, false> {
-  using type _LIBCPP_NODEBUG = _Tp*;
-};
+template <class _Tp>
+using __pointer_member = typename _Tp::pointer;
+
+template <class _Tp, class _Alloc>
+using __pointer = __detected_or_t<_Tp*, __pointer_member, __libcpp_remove_reference_t<_Alloc> >;
 
 // __const_pointer
 _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_const_pointer, const_pointer);
@@ -100,13 +95,11 @@ struct __const_void_pointer<_Ptr, _Alloc, false> {
 };
 
 // __size_type
-_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_size_type, size_type);
-template <class _Alloc, class _DiffType, bool = __has_size_type<_Alloc>::value>
-struct __size_type : make_unsigned<_DiffType> {};
+template <class _Tp>
+using __size_type_member = typename _Tp::size_type;
+
 template <class _Alloc, class _DiffType>
-struct __size_type<_Alloc, _DiffType, true> {
-  using type _LIBCPP_NODEBUG = typename _Alloc::size_type;
-};
+using __size_type = __detected_or_t<__make_unsigned_t<_DiffType>, __size_type_member, _Alloc>;
 
 // __alloc_traits_difference_type
 _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_alloc_traits_difference_type, difference_type);
@@ -120,40 +113,34 @@ struct __alloc_traits_difference_type<_Alloc, _Ptr, true> {
 };
 
 // __propagate_on_container_copy_assignment
-_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_propagate_on_container_copy_assignment, propagate_on_container_copy_assignment);
-template <class _Alloc, bool = __has_propagate_on_container_copy_assignment<_Alloc>::value>
-struct __propagate_on_container_copy_assignment : false_type {};
+template <class _Tp>
+using __propagate_on_container_copy_assignment_member = typename _Tp::propagate_on_container_copy_assignment;
+
 template <class _Alloc>
-struct __propagate_on_container_copy_assignment<_Alloc, true> {
-  using type _LIBCPP_NODEBUG = typename _Alloc::propagate_on_container_copy_assignment;
-};
+using __propagate_on_container_copy_assignment =
+    __detected_or_t<false_type, __propagate_on_container_copy_assignment_member, _Alloc>;
 
 // __propagate_on_container_move_assignment
-_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_propagate_on_container_move_assignment, propagate_on_container_move_assignment);
-template <class _Alloc, bool = __has_propagate_on_container_move_assignment<_Alloc>::value>
-struct __propagate_on_container_move_assignment : false_type {};
+template <class _Tp>
+using __propagate_on_container_move_assignment_member = typename _Tp::propagate_on_container_move_assignment;
+
 template <class _Alloc>
-struct __propagate_on_container_move_assignment<_Alloc, true> {
-  using type _LIBCPP_NODEBUG = typename _Alloc::propagate_on_container_move_assignment;
-};
+using __propagate_on_container_move_assignment =
+    __detected_or_t<false_type, __propagate_on_container_move_assignment_member, _Alloc>;
 
 // __propagate_on_container_swap
-_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_propagate_on_container_swap, propagate_on_container_swap);
-template <class _Alloc, bool = __has_propagate_on_container_swap<_Alloc>::value>
-struct __propagate_on_container_swap : false_type {};
+template <class _Tp>
+using __propagate_on_container_swap_member = typename _Tp::propagate_on_container_swap;
+
 template <class _Alloc>
-struct __propagate_on_container_swap<_Alloc, true> {
-  using type _LIBCPP_NODEBUG = typename _Alloc::propagate_on_container_swap;
-};
+using __propagate_on_container_swap = __detected_or_t<false_type, __propagate_on_container_swap_member, _Alloc>;
 
 // __is_always_equal
-_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_is_always_equal, is_always_equal);
-template <class _Alloc, bool = __has_is_always_equal<_Alloc>::value>
-struct __is_always_equal : is_empty<_Alloc> {};
+template <class _Tp>
+using __is_always_equal_member = typename _Tp::is_always_equal;
+
 template <class _Alloc>
-struct __is_always_equal<_Alloc, true> {
-  using type _LIBCPP_NODEBUG = typename _Alloc::is_always_equal;
-};
+using __is_always_equal = __detected_or_t<typename is_empty<_Alloc>::type, __is_always_equal_member, _Alloc>;
 
 // __allocator_traits_rebind
 _LIBCPP_SUPPRESS_DEPRECATED_PUSH
@@ -245,20 +232,18 @@ _LIBCPP_CTAD_SUPPORTED_FOR_TYPE(allocation_result);
 
 template <class _Alloc>
 struct _LIBCPP_TEMPLATE_VIS allocator_traits {
-  using allocator_type     = _Alloc;
-  using value_type         = typename allocator_type::value_type;
-  using pointer            = typename __pointer<value_type, allocator_type>::type;
-  using const_pointer      = typename __const_pointer<value_type, pointer, allocator_type>::type;
-  using void_pointer       = typename __void_pointer<pointer, allocator_type>::type;
-  using const_void_pointer = typename __const_void_pointer<pointer, allocator_type>::type;
-  using difference_type    = typename __alloc_traits_difference_type<allocator_type, pointer>::type;
-  using size_type          = typename __size_type<allocator_type, difference_type>::type;
-  using propagate_on_container_copy_assignment =
-      typename __propagate_on_container_copy_assignment<allocator_type>::type;
-  using propagate_on_container_move_assignment =
-      typename __propagate_on_container_move_assignment<allocator_type>::type;
-  using propagate_on_container_swap = typename __propagate_on_container_swap<allocator_type>::type;
-  using is_always_equal             = typename __is_always_equal<allocator_type>::type;
+  using allocator_type                         = _Alloc;
+  using value_type                             = typename allocator_type::value_type;
+  using pointer                                = __pointer<value_type, allocator_type>;
+  using const_pointer                          = typename __const_pointer<value_type, pointer, allocator_type>::type;
+  using void_pointer                           = typename __void_pointer<pointer, allocator_type>::type;
+  using const_void_pointer                     = typename __const_void_pointer<pointer, allocator_type>::type;
+  using difference_type                        = typename __alloc_traits_difference_type<allocator_type, pointer>::type;
+  using size_type                              = __size_type<allocator_type, difference_type>;
+  using propagate_on_container_copy_assignment = __propagate_on_container_copy_assignment<allocator_type>;
+  using propagate_on_container_move_assignment = __propagate_on_container_move_assignment<allocator_type>;
+  using propagate_on_container_swap            = __propagate_on_container_swap<allocator_type>;
+  using is_always_equal                        = __is_always_equal<allocator_type>;
 
 #ifndef _LIBCPP_CXX03_LANG
   template <class _Tp>
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 28c62e13566e24..9526255583dd56 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -143,7 +143,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
 public:
   typedef _Tp element_type;
   typedef _Dp deleter_type;
-  typedef _LIBCPP_NODEBUG typename __pointer<_Tp, deleter_type>::type pointer;
+  using pointer _LIBCPP_NODEBUG = __pointer<_Tp, deleter_type>;
 
   static_assert(!is_rvalue_reference<deleter_type>::value, "the specified deleter type cannot be an rvalue reference");
 
@@ -410,7 +410,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
 public:
   typedef _Tp element_type;
   typedef _Dp deleter_type;
-  typedef typename __pointer<_Tp, deleter_type>::type pointer;
+  using pointer = __pointer<_Tp, deleter_type>;
 
   // A unique_ptr contains the following members which may be trivially relocatable:
   // - pointer: this may be trivially relocatable, so it's checked
diff --git a/libcxx/include/__type_traits/detected_or.h b/libcxx/include/__type_traits/detected_or.h
new file mode 100644
index 00000000000000..390f368411471e
--- /dev/null
+++ b/libcxx/include/__type_traits/detected_or.h
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___TYPE_TRAITS_DETECTED_OR_H
+#define _LIBCPP___TYPE_TRAITS_DETECTED_OR_H
+
+#include <__config>
+#include <__type_traits/void_t.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Default, class _Void, template <class...> class _Op, class... _Args>
+struct __detector {
+  using type = _Default;
+};
+
+template <class _Default, template <class...> class _Op, class... _Args>
+struct __detector<_Default, __void_t<_Op<_Args...> >, _Op, _Args...> {
+  using type = _Op<_Args...>;
+};
+
+template <class _Default, template <class...> class _Op, class... _Args>
+using __detected_or_t = typename __detector<_Default, void, _Op, _Args...>::type;
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_DETECTED_OR_H
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 9027c28632dcda..92b90db816ab03 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -87,6 +87,7 @@ module std_core [system] {
     module decay                                      { header "__type_traits/decay.h" }
     module dependent_type                             { header "__type_traits/dependent_type.h" }
     module desugars_to                                { header "__type_traits/desugars_to.h" }
+    module detected_or                                { header "__type_traits/detected_or.h" }
     module disjunction                                { header "__type_traits/disjunction.h" }
     module enable_if                                  { header "__type_traits/enable_if.h" }
     module extent                                     { header "__type_traits/extent.h" }

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot, nice simplification.

@philnik777 philnik777 merged commit 7ae61a3 into llvm:main Nov 26, 2024
59 of 61 checks passed
@philnik777 philnik777 deleted the use_detected_or branch November 26, 2024 22:53
@dwblaikie
Copy link
Collaborator

FWIW, seeing some substantial (multiple single-digit % of overall linked binary size, double digit % growth of the DWARF index (.debug_gnu_pubnames/.gdb_index in our case, but I expect we'll see something similar in .debug_names too (FYI @adrian-prantl ))).

The specific data we have for an easily shareable example is (though I probably don't have an exhaustive list of flags here - an estimate of flags that may be relevant to the specific numbers):
clang, debug build (-g, -gz=zstd, -gsplit-dwarf, -ggnu-pubnames):
In .o/.dwo files:
+15% .debug_gnu_pubnames
+1.12% .debug_abbrev.dwo

clang, opt build (-g, -gz=zstd, -gsplit-dwarf, -ggnu-pubnames, -fdebug-types-section):
in .o/.dwo files:
+16% .debug_gnu_pubnames
+2.7% .group (more comdat groups that aren't optimized away at compile-time?)
+1-1.5% growth in: .debug_abbrev.dwo, .crel.debug_line, .crel.eh_frame, .debug_line_str, .debug_rnglists, .eh_frame, .debug_abbrev.dwo

(and including a 1.58% increase in executable size, including a 1.17% increase in .debug_rnglists and 6% increase in .gdb_index)

Looking into it further/will update with more info as I have it.

Maybe a lot of this could be addressed by adding the nodebug attribute to this __detected_or_t, but that probably wouldn't account for the rangelist increases or non-debug changes like .group.

@dwblaikie
Copy link
Collaborator

(oh, and @cjdb who had asked me about the nodebug attribute on type traits previously)

@ldionne
Copy link
Member

ldionne commented Dec 3, 2024

Thanks for the heads up. Indeed, I strongly suspect that most of this would be alleviated by adding _LIBCPP_NODEBUG to the new aliases. If you are able to reproduce this locally, could you try it out to see how much that helps?

@dwblaikie
Copy link
Collaborator

Yeah (I was thinking of adding the attribute to __detected_or_t (& the __detected struct template (& its partial specialization)) itself, but yeah, any of the alias declarations should probably have it too (but its class/struct type information that tends to be much heavier weight than aliases/typedefs) if they're part of metaprogramming - they're probably not much use to people debugging code, I think (because you can see the specific thing they were resolved to easily)) - I've handed this off to @cmtice internally to try exactly that (applying the attribute to the __detected* stuff, and see if that accounts for most/all of the change)

I do worry a bit about the increase to non-debug (& debug-but-not-type) information, like .group (and .debug_rnglists).

@philnik777
Copy link
Contributor Author

Is there a reason we should ever not put a _LIBCPP_NODEBUG on internal aliases?

I don't really see how this could have anything to do with anything but aliases, since the number of struct instantiations shouldn't change.

@dwblaikie
Copy link
Collaborator

Is there a reason we should ever not put a _LIBCPP_NODEBUG on internal aliases?

Don't think so?

I don't really see how this could have anything to do with anything but aliases, since the number of struct instantiations shouldn't change.

Instantiations don't directly cause DWARF to be emitted - the instantiation has to be referenced by something, so avoiding references to the struct instantiation can reduce the debug info size cost.

Specifically, putting _LIBCPP_NODEBUG on the type member of a type trait struct avoids needing to describe the struct (to put the type member inside of it), which saves a bunch of DWARF. Non-member aliases are nice to attribute too/nice to avoid describing in the DWARF - but the struct types are especially expensive/more verbose than non-member aliases.

@cmtice
Copy link
Contributor

cmtice commented Dec 4, 2024

I have verified that adding _LIBCPP_NODEBUG on all the newly introduced 'using' statements, reduces the size increase (on my particular test case) from 9.5% down to 0.2%, Should I send a PR with this change?

@cmtice
Copy link
Contributor

cmtice commented Dec 4, 2024

(I could add it to some of the other 'using' statements, too, and reduce it further)

@ldionne
Copy link
Member

ldionne commented Dec 4, 2024

Yes, a PR would be great!

@cmtice
Copy link
Contributor

cmtice commented Dec 4, 2024

Quick question: I don't usually work in libcxx: What's the best way to officially test my change? And do I need to try to write a test case for this?

@philnik777
Copy link
Contributor Author

We don't have any infrastructure for this kind of test currently, so I don't think you need to add a test. If you have a good idea I'd be interested though.

Let's tackle the regression for now. If we have some general guidelines when to add [[gnu::nodebug]] we should add that in a separate PR. I've looked into adding a clang-tidy check to add the attribute to all libc++-internal aliases, so a discussion on any heuristic we can use would be nice.

@ldionne
Copy link
Member

ldionne commented Dec 4, 2024

@cmtice If you open a PR, our CI will tell you if you messed up badly enough to break something else :). That should be sufficient for a patch of this kind.

@cmtice
Copy link
Contributor

cmtice commented Dec 5, 2024

I've created a PR with my fixes: #118835

ldionne pushed a commit that referenced this pull request Dec 6, 2024
…8835)

Put _LIBCPP_NODEBUG on the new allocator trait aliases introduced in
#115654. This prevents a large
increase in the gdb_index size that was introduced by that PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants