Skip to content
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

[libc++][C++26] P2562R1: constexpr Stable Sorting #110320

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

PaulXiCao
Copy link
Contributor

@PaulXiCao PaulXiCao commented Sep 27, 2024

Implementing constexpr std::stable_sort.

This is part of P2562R1, tracked via issue #105360.

(EDIT: This comment has been edited.)

@PaulXiCao PaulXiCao requested a review from a team as a code owner September 27, 2024 19:39
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Sep 27, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Sep 27, 2024

@llvm/pr-subscribers-libcxx

Author: None (PaulXiCao)

Changes

Closes #105360.


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

4 Files Affected:

  • (modified) libcxx/include/__algorithm/sort.h (+1-1)
  • (modified) libcxx/include/__algorithm/stable_sort.h (+26-22)
  • (modified) libcxx/include/__memory/destruct_n.h (+10-10)
  • (modified) libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp (+163-70)
diff --git a/libcxx/include/__algorithm/sort.h b/libcxx/include/__algorithm/sort.h
index 0b2137dee2f77e..2e858fd15022d8 100644
--- a/libcxx/include/__algorithm/sort.h
+++ b/libcxx/include/__algorithm/sort.h
@@ -283,7 +283,7 @@ __selection_sort(_BidirectionalIterator __first, _BidirectionalIterator __last,
 // Sort the iterator range [__first, __last) using the comparator __comp using
 // the insertion sort algorithm.
 template <class _AlgPolicy, class _Compare, class _BidirectionalIterator>
-_LIBCPP_HIDE_FROM_ABI void
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
 __insertion_sort(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp) {
   using _Ops = _IterOps<_AlgPolicy>;
 
diff --git a/libcxx/include/__algorithm/stable_sort.h b/libcxx/include/__algorithm/stable_sort.h
index ec556aad82e8d8..4f7f8b7615781c 100644
--- a/libcxx/include/__algorithm/stable_sort.h
+++ b/libcxx/include/__algorithm/stable_sort.h
@@ -68,7 +68,7 @@ _LIBCPP_HIDE_FROM_ABI void __insertion_sort_move(
 }
 
 template <class _AlgPolicy, class _Compare, class _InputIterator1, class _InputIterator2>
-_LIBCPP_HIDE_FROM_ABI void __merge_move_construct(
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __merge_move_construct(
     _InputIterator1 __first1,
     _InputIterator1 __last1,
     _InputIterator2 __first2,
@@ -106,7 +106,7 @@ _LIBCPP_HIDE_FROM_ABI void __merge_move_construct(
 }
 
 template <class _AlgPolicy, class _Compare, class _InputIterator1, class _InputIterator2, class _OutputIterator>
-_LIBCPP_HIDE_FROM_ABI void __merge_move_assign(
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __merge_move_assign(
     _InputIterator1 __first1,
     _InputIterator1 __last1,
     _InputIterator2 __first2,
@@ -134,19 +134,21 @@ _LIBCPP_HIDE_FROM_ABI void __merge_move_assign(
 }
 
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
-void __stable_sort(_RandomAccessIterator __first,
-                   _RandomAccessIterator __last,
-                   _Compare __comp,
-                   typename iterator_traits<_RandomAccessIterator>::difference_type __len,
-                   typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
-                   ptrdiff_t __buff_size);
+_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
+    _RandomAccessIterator __first,
+    _RandomAccessIterator __last,
+    _Compare __comp,
+    typename iterator_traits<_RandomAccessIterator>::difference_type __len,
+    typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
+    ptrdiff_t __buff_size);
 
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
-void __stable_sort_move(_RandomAccessIterator __first1,
-                        _RandomAccessIterator __last1,
-                        _Compare __comp,
-                        typename iterator_traits<_RandomAccessIterator>::difference_type __len,
-                        typename iterator_traits<_RandomAccessIterator>::value_type* __first2) {
+_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort_move(
+    _RandomAccessIterator __first1,
+    _RandomAccessIterator __last1,
+    _Compare __comp,
+    typename iterator_traits<_RandomAccessIterator>::difference_type __len,
+    typename iterator_traits<_RandomAccessIterator>::value_type* __first2) {
   using _Ops = _IterOps<_AlgPolicy>;
 
   typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
@@ -190,12 +192,13 @@ struct __stable_sort_switch {
 };
 
 template <class _AlgPolicy, class _Compare, class _RandomAccessIterator>
-void __stable_sort(_RandomAccessIterator __first,
-                   _RandomAccessIterator __last,
-                   _Compare __comp,
-                   typename iterator_traits<_RandomAccessIterator>::difference_type __len,
-                   typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
-                   ptrdiff_t __buff_size) {
+_LIBCPP_CONSTEXPR_SINCE_CXX26 void __stable_sort(
+    _RandomAccessIterator __first,
+    _RandomAccessIterator __last,
+    _Compare __comp,
+    typename iterator_traits<_RandomAccessIterator>::difference_type __len,
+    typename iterator_traits<_RandomAccessIterator>::value_type* __buff,
+    ptrdiff_t __buff_size) {
   typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
   typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
   switch (__len) {
@@ -235,7 +238,7 @@ void __stable_sort(_RandomAccessIterator __first,
 }
 
 template <class _AlgPolicy, class _RandomAccessIterator, class _Compare>
-inline _LIBCPP_HIDE_FROM_ABI void
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
 __stable_sort_impl(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare& __comp) {
   using value_type      = typename iterator_traits<_RandomAccessIterator>::value_type;
   using difference_type = typename iterator_traits<_RandomAccessIterator>::difference_type;
@@ -254,13 +257,14 @@ __stable_sort_impl(_RandomAccessIterator __first, _RandomAccessIterator __last,
 }
 
 template <class _RandomAccessIterator, class _Compare>
-inline _LIBCPP_HIDE_FROM_ABI void
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
 stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) {
   std::__stable_sort_impl<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __comp);
 }
 
 template <class _RandomAccessIterator>
-inline _LIBCPP_HIDE_FROM_ABI void stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last) {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
+stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last) {
   std::stable_sort(__first, __last, __less<>());
 }
 
diff --git a/libcxx/include/__memory/destruct_n.h b/libcxx/include/__memory/destruct_n.h
index 78635ad0af04bd..1445a909162d32 100644
--- a/libcxx/include/__memory/destruct_n.h
+++ b/libcxx/include/__memory/destruct_n.h
@@ -25,35 +25,35 @@ struct __destruct_n {
   size_t __size_;
 
   template <class _Tp>
-  _LIBCPP_HIDE_FROM_ABI void __process(_Tp* __p, false_type) _NOEXCEPT {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __process(_Tp* __p, false_type) _NOEXCEPT {
     for (size_t __i = 0; __i < __size_; ++__i, ++__p)
       __p->~_Tp();
   }
 
   template <class _Tp>
-  _LIBCPP_HIDE_FROM_ABI void __process(_Tp*, true_type) _NOEXCEPT {}
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __process(_Tp*, true_type) _NOEXCEPT {}
 
-  _LIBCPP_HIDE_FROM_ABI void __incr(false_type) _NOEXCEPT { ++__size_; }
-  _LIBCPP_HIDE_FROM_ABI void __incr(true_type) _NOEXCEPT {}
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr(false_type) _NOEXCEPT { ++__size_; }
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr(true_type) _NOEXCEPT {}
 
-  _LIBCPP_HIDE_FROM_ABI void __set(size_t __s, false_type) _NOEXCEPT { __size_ = __s; }
-  _LIBCPP_HIDE_FROM_ABI void __set(size_t, true_type) _NOEXCEPT {}
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t __s, false_type) _NOEXCEPT { __size_ = __s; }
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t, true_type) _NOEXCEPT {}
 
 public:
-  _LIBCPP_HIDE_FROM_ABI explicit __destruct_n(size_t __s) _NOEXCEPT : __size_(__s) {}
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit __destruct_n(size_t __s) _NOEXCEPT : __size_(__s) {}
 
   template <class _Tp>
-  _LIBCPP_HIDE_FROM_ABI void __incr() _NOEXCEPT {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __incr() _NOEXCEPT {
     __incr(integral_constant<bool, is_trivially_destructible<_Tp>::value>());
   }
 
   template <class _Tp>
-  _LIBCPP_HIDE_FROM_ABI void __set(size_t __s, _Tp*) _NOEXCEPT {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __set(size_t __s, _Tp*) _NOEXCEPT {
     __set(__s, integral_constant<bool, is_trivially_destructible<_Tp>::value>());
   }
 
   template <class _Tp>
-  _LIBCPP_HIDE_FROM_ABI void operator()(_Tp* __p) _NOEXCEPT {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void operator()(_Tp* __p) _NOEXCEPT {
     __process(__p, integral_constant<bool, is_trivially_destructible<_Tp>::value>());
   }
 };
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
index 4301d22027de85..92803c89dbcb7c 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp
@@ -14,7 +14,9 @@
 //   void
 //   stable_sort(Iter first, Iter last);
 
+#include <__config>
 #include <algorithm>
+#include <array>
 #include <cassert>
 #include <iterator>
 #include <random>
@@ -23,8 +25,6 @@
 #include "count_new.h"
 #include "test_macros.h"
 
-std::mt19937 randomness;
-
 template <class RI>
 void
 test_sort_helper(RI f, RI l)
@@ -80,66 +80,149 @@ test_sort_()
     }
 }
 
-void
-test_larger_sorts(int N, int M)
-{
-    assert(N != 0);
-    assert(M != 0);
-    // create array length N filled with M different numbers
-    int* array = new int[N];
-    int x = 0;
-    for (int i = 0; i < N; ++i)
-    {
-        array[i] = x;
-        if (++x == M)
-            x = 0;
+template <int N, int M>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> init_saw_tooth_pattern() {
+  std::array<int, N> array;
+  for (int i = 0, x = 0; i < N; ++i) {
+    array[i] = x;
+    if (++x == M)
+      x = 0;
+  }
+  return array;
+}
+
+template <int N, int M>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> sort_saw_tooth_pattern() {
+  std::array<int, N> array = init_saw_tooth_pattern<N, M>();
+  std::stable_sort(array.begin(), array.end());
+  return array;
+}
+
+template <int N, int M>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> sort_already_sorted() {
+  std::array<int, N> array = sort_saw_tooth_pattern<N, M>();
+  std::stable_sort(array.begin(), array.end());
+  return array;
+}
+
+template <int N, int M>
+std::array<int, N> sort_reversely_sorted() {
+  std::array<int, N> array = sort_saw_tooth_pattern<N, M>();
+  std::reverse(array.begin(), array.end());
+  std::stable_sort(array.begin(), array.end());
+  return array;
+}
+
+template <int N, int M>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> sort_swapped_sorted_ranges() {
+  std::array<int, N> array = sort_saw_tooth_pattern<N, M>();
+  std::swap_ranges(array.begin(), array.begin() + N / 2, array.begin() + N / 2);
+  std::stable_sort(array.begin(), array.end());
+  return array;
+}
+
+template <int N, int M>
+std::array<int, N> sort_reversely_swapped_sorted_ranges() {
+  std::array<int, N> array = sort_saw_tooth_pattern<N, M>();
+  std::reverse(array.begin(), array.end());
+  std::swap_ranges(array.begin(), array.begin() + N / 2, array.begin() + N / 2);
+  std::stable_sort(array.begin(), array.end());
+  return array;
+}
+
+#if _LIBCPP_STD_VER >= 26
+#  define COMPILE_OR_RUNTIME_ASSERT(func)                                                                              \
+    if consteval {                                                                                                     \
+      static_assert(func);                                                                                             \
+    } else {                                                                                                           \
+      assert(func);                                                                                                    \
     }
-    // test saw tooth pattern
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    // test random pattern
-    std::shuffle(array, array+N, randomness);
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    // test sorted pattern
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    // test reverse sorted pattern
-    std::reverse(array, array+N);
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    // test swap ranges 2 pattern
-    std::swap_ranges(array, array+N/2, array+N/2);
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    // test reverse swap ranges 2 pattern
-    std::reverse(array, array+N);
-    std::swap_ranges(array, array+N/2, array+N/2);
-    std::stable_sort(array, array+N);
-    assert(std::is_sorted(array, array+N));
-    delete [] array;
+#else
+#  define COMPILE_OR_RUNTIME_ASSERT(func) assert(func);
+#endif
+
+template <int N, int M>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 void test_larger_sorts() {
+  static_assert(N > 0, "");
+  static_assert(M > 0, "");
+
+  { // test saw tooth pattern
+    _LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> array = sort_saw_tooth_pattern<N, M>();
+    COMPILE_OR_RUNTIME_ASSERT(std::is_sorted(array.begin(), array.end()))
+  }
+
+#if _LIBCPP_STD_VER >= 26
+  if !consteval
+#endif
+  { // test random pattern
+    // random-number generators not constexpr-friendly
+    static std::mt19937 randomness;
+    std::array<int, N> array = init_saw_tooth_pattern<N, M>();
+    std::shuffle(array.begin(), array.end(), randomness);
+    std::stable_sort(array.begin(), array.end());
+    assert(std::is_sorted(array.begin(), array.end()));
+  }
+
+  { // test sorted pattern
+    _LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> array = sort_already_sorted<N, M>();
+    COMPILE_OR_RUNTIME_ASSERT(std::is_sorted(array.begin(), array.end()))
+  }
+
+#if _LIBCPP_STD_VER >= 26
+  if !consteval
+#endif
+  { // test reverse sorted pattern
+    // consteval error: "constexpr evaluation hit maximum step limit"
+    std::array<int, N> array = sort_reversely_sorted<N, M>();
+    assert(std::is_sorted(array.begin(), array.end()));
+  }
+
+  { // test swap ranges 2 pattern
+    _LIBCPP_CONSTEXPR_SINCE_CXX26 std::array<int, N> array = sort_swapped_sorted_ranges<N, M>();
+    COMPILE_OR_RUNTIME_ASSERT(std::is_sorted(array.begin(), array.end()))
+  }
+
+#if _LIBCPP_STD_VER >= 26
+  if !consteval
+#endif
+  { // test reverse swap ranges 2 pattern
+    // consteval error: "constexpr evaluation hit maximum step limit"
+    std::array<int, N> array = sort_reversely_swapped_sorted_ranges<N, M>();
+    assert(std::is_sorted(array.begin(), array.end()));
+  }
 }
 
-void
-test_larger_sorts(int N)
-{
-    test_larger_sorts(N, 1);
-    test_larger_sorts(N, 2);
-    test_larger_sorts(N, 3);
-    test_larger_sorts(N, N/2-1);
-    test_larger_sorts(N, N/2);
-    test_larger_sorts(N, N/2+1);
-    test_larger_sorts(N, N-2);
-    test_larger_sorts(N, N-1);
-    test_larger_sorts(N, N);
+template <int N>
+_LIBCPP_CONSTEXPR_SINCE_CXX26 void test_larger_sorts() {
+  test_larger_sorts<N, 1>();
+  test_larger_sorts<N, 2>();
+  test_larger_sorts<N, 3>();
+  test_larger_sorts<N, N / 2 - 1>();
+  test_larger_sorts<N, N / 2>();
+  test_larger_sorts<N, N / 2 + 1>();
+  test_larger_sorts<N, N - 2>();
+  test_larger_sorts<N, N - 1>();
+  test_larger_sorts<N, N>();
 }
 
-int main(int, char**)
-{
-    // test null range
+#if _LIBCPP_STD_VER >= 26
+#  define COMPILE_AND_RUNTIME_CALL(func)                                                                               \
+    func;                                                                                                              \
+    static_assert((func, true));
+#else
+#  define COMPILE_AND_RUNTIME_CALL(func) func;
+#endif
+
+int main(int, char**) {
+  { // test null range
     int d = 0;
     std::stable_sort(&d, &d);
-    // exhaustively test all possibilities up to length 8
+#if _LIBCPP_STD_VER >= 26
+    static_assert((std::stable_sort(&d, &d), true));
+#endif
+  }
+
+  { // exhaustively test all possibilities up to length 8
     test_sort_<1>();
     test_sort_<2>();
     test_sort_<3>();
@@ -148,22 +231,32 @@ int main(int, char**)
     test_sort_<6>();
     test_sort_<7>();
     test_sort_<8>();
+  }
 
-    test_larger_sorts(256);
-    test_larger_sorts(257);
-    test_larger_sorts(499);
-    test_larger_sorts(500);
-    test_larger_sorts(997);
-    test_larger_sorts(1000);
-    test_larger_sorts(1009);
-
-#if !defined(TEST_HAS_NO_EXCEPTIONS)
-    { // check that the algorithm works without memory
-        std::vector<int> vec(150, 3);
-        getGlobalMemCounter()->throw_after = 0;
-        std::stable_sort(vec.begin(), vec.end());
-    }
-#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
+  { // larger sorts
+    // run- and conditionally compile-time tests
+    test_larger_sorts<256>();
+    test_larger_sorts<257>();
+#if _LIBCPP_STD_VER >= 26
+    static_assert((test_larger_sorts<256>(), true));
+    static_assert((test_larger_sorts<257>(), true));
+#endif
+
+    // only runtime tests bc. error: "constexpr evaluation hit maximum step limit"
+    test_larger_sorts<499>();
+    test_larger_sorts<500>();
+    test_larger_sorts<997>();
+    test_larger_sorts<1000>();
+    test_larger_sorts<1009>();
+  }
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  { // check that the algorithm works without memory
+    std::vector<int> vec(150, 3);
+    getGlobalMemCounter()->throw_after = 0;
+    std::stable_sort(vec.begin(), vec.end());
+  }
+#endif
 
   return 0;
 }

@PaulXiCao PaulXiCao marked this pull request as draft September 27, 2024 19:42
@PaulXiCao
Copy link
Contributor Author

This comment by Jiang An on the discord channel for reference:

I think we may need to use std::__construct_at for older compilers. Once compiler versions that don't support constexpr placement-new are no longer supported, the workaround can be removed.

@Zingam
Copy link
Contributor

Zingam commented Sep 29, 2024

@PaulXiCao If this PR addresses the paper only partially, maybe you shouldn't use "Closes #", because it will close the issue? Is that so?
If your intention is to list the subtasks, you should do it in the issue.

@PaulXiCao PaulXiCao marked this pull request as ready for review October 2, 2024 20:22
Copy link

github-actions bot commented Oct 6, 2024

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

array[i] = x;
if (++x == M)
x = 0;
template <int N, int M>
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't move the individual test into different functions. If you really want to you can do

{ // description
  <test code>
}

but I'd just keep it as-is if there isn't a reason to change things here.

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