[libcxx-commits] [libcxx] 058ca12 - [libc++] Implement `<bit>` parts of P3793R2 Better shifting (#219389)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Sep 15 07:03:05 PDT 2026


Author: Jan Schultke
Date: 2026-09-15T22:03:00+08:00
New Revision: 058ca1274446c7fa27b193237a90c21dddf7833c

URL: https://github.com/llvm/llvm-project/commit/058ca1274446c7fa27b193237a90c21dddf7833c
DIFF: https://github.com/llvm/llvm-project/commit/058ca1274446c7fa27b193237a90c21dddf7833c.diff

LOG: [libc++] Implement `<bit>` parts of P3793R2 Better shifting (#219389)

Partially implements #204401

That is, the scalar `<bit>` parts are implemented, but the `<simd>`
overloads are missing. Notice that only `__cpp_lib_bitops` is set to
`202606L`. `__cpp_lib_simd_bitops` is missing.

The implementations of `shl` and `shr` are based on the reference
implementation in the paper. The test code is partially AI-generated and
inspired by the test code for `rotl` or `rotr`, but comes with
significant modernizations.

Added: 
    libcxx/include/__bit/shift.h
    libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
    libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/ReleaseNotes/24.rst
    libcxx/docs/Status/Cxx29Papers.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/bit
    libcxx/include/module.modulemap.in
    libcxx/include/version
    libcxx/modules/std/bit.inc
    libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 7035b01904a19..8329c6114d212 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -560,6 +560,8 @@ Status
     ---------------------------------------------------------- -----------------
     **C++29**
     ----------------------------------------------------------------------------
+    ``__cpp_lib_bitops``                                       ``202606L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_expected``                                     ``202606L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_map_lookup``                                   *unimplemented*

diff  --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index a7b9e8731d555..80c437e868bcb 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -52,6 +52,7 @@ Implemented Papers
 - P2830R10: Standardized Constexpr Type Ordering (`Github <https://llvm.org/PR216616>`__)
 - P3778R0: Fix for ``type_order`` template definition (`Github <https://llvm.org/PR216616>`__)
 - P4140R0: Proposed resolution for US70-126: allow incomplete types in type_order (`Github <https://llvm.org/PR216616>`__)
+- P3793R2: Better shifting (`Github <https://llvm.org/PR219389>`__) (no ``std::simd`` overloads)
 
 Improvements and New Features
 -----------------------------

diff  --git a/libcxx/docs/Status/Cxx29Papers.csv b/libcxx/docs/Status/Cxx29Papers.csv
index 56fb1540e47fc..3fccdf486fb63 100644
--- a/libcxx/docs/Status/Cxx29Papers.csv
+++ b/libcxx/docs/Status/Cxx29Papers.csv
@@ -11,7 +11,7 @@
 "`P3154R3 <https://wg21.link/P3154R3>`__","Deprecating signed character types in iostreams","2026-06 (Brno)","","","`#204398 <https://github.com/llvm/llvm-project/issues/204398>`__",""
 "`P3428R4 <https://wg21.link/P3428R4>`__","Hazard Pointer Batches","2026-06 (Brno)","","","`#204399 <https://github.com/llvm/llvm-project/issues/204399>`__",""
 "`P3248R5 <https://wg21.link/P3248R5>`__","Require ``[u]intptr_t``","2026-06 (Brno)","","","`#204400 <https://github.com/llvm/llvm-project/issues/204400>`__",""
-"`P3793R2 <https://wg21.link/P3793R2>`__","Better shifting","2026-06 (Brno)","","","`#204401 <https://github.com/llvm/llvm-project/issues/204401>`__",""
+"`P3793R2 <https://wg21.link/P3793R2>`__","Better shifting","2026-06 (Brno)","|Partial|","","`#204401 <https://github.com/llvm/llvm-project/issues/204401>`__","No `std::simd` overloads provided yet."
 "`P3242R4 <https://wg21.link/P3242R4>`__","Copy and fill for ``mdspan``","2026-06 (Brno)","","","`#204402 <https://github.com/llvm/llvm-project/issues/204402>`__",""
 "`P3692R4 <https://wg21.link/P3692R4>`__","How to Avoid OOTA Without Really Trying","2026-06 (Brno)","","","`#204403 <https://github.com/llvm/llvm-project/issues/204403>`__",""
 "`P3104R6 <https://wg21.link/P3104R6>`__","Bit permutations","2026-06 (Brno)","","","`#204404 <https://github.com/llvm/llvm-project/issues/204404>`__",""

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index bc11bda33724b..bc6499c6e2bbd 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -244,6 +244,7 @@ set(files
   __bit/invert_if.h
   __bit/popcount.h
   __bit/rotate.h
+  __bit/shift.h
   __bit_reference
   __charconv/chars_format.h
   __charconv/from_chars_floating_point.h

diff  --git a/libcxx/include/__bit/shift.h b/libcxx/include/__bit/shift.h
new file mode 100644
index 0000000000000..02032dc658c87
--- /dev/null
+++ b/libcxx/include/__bit/shift.h
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___BIT_SHIFT_H
+#define _LIBCPP___BIT_SHIFT_H
+
+#include <__config>
+#include <__type_traits/integer_traits.h>
+#include <__type_traits/make_unsigned.h>
+#include <limits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 29
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Shift>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp shl(_Tp __t, _Shift __cnt) noexcept {
+  constexpr auto __n = static_cast<_Shift>(numeric_limits<make_unsigned_t<_Tp>>::digits);
+  if constexpr (__is_signed_integer_v<_Shift>) {
+    if (__cnt < 0) {
+      if (__cnt <= -__n) {
+        if constexpr (__is_signed_integer_v<_Tp>)
+          return static_cast<_Tp>(__t < 0 ? -1 : 0);
+        else
+          return static_cast<_Tp>(0);
+      }
+      return __t >> -__cnt;
+    }
+  }
+  if (__cnt >= __n)
+    return static_cast<_Tp>(0);
+  return __t << __cnt;
+}
+
+template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Shift>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp shr(_Tp __t, _Shift __cnt) noexcept {
+  constexpr auto __n = static_cast<_Shift>(numeric_limits<make_unsigned_t<_Tp>>::digits);
+  if constexpr (__is_signed_integer_v<_Shift>) {
+    if (__cnt < 0) {
+      if (__cnt <= -__n)
+        return static_cast<_Tp>(0);
+      return __t << -__cnt;
+    }
+  }
+  if (__cnt >= __n) {
+    if constexpr (__is_signed_integer_v<_Tp>)
+      return static_cast<_Tp>(__t < 0 ? -1 : 0);
+    else
+      return static_cast<_Tp>(0);
+  }
+  return __t >> __cnt;
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 29
+
+#endif // _LIBCPP___BIT_SHIFT_H

diff  --git a/libcxx/include/bit b/libcxx/include/bit
index 40fcda27856bb..be3dfdc451b3d 100644
--- a/libcxx/include/bit
+++ b/libcxx/include/bit
@@ -32,6 +32,12 @@ namespace std {
   template <class T>
     constexpr int bit_width(T x) noexcept;       // C++20
 
+  // [bit.shift], shifting
+  template<class T, class S>
+    constexpr T shl(T x, S s) noexcept; // C++29
+  template<class T, class S>
+    constexpr T shr(T x, S s) noexcept; // C++29
+
   // [bit.rotate], rotating
   template<class T>
     constexpr T rotl(T x, int s) noexcept; // C++20
@@ -84,6 +90,10 @@ namespace std {
 #    include <__bit/byteswap.h>
 #  endif
 
+#  if _LIBCPP_STD_VER >= 29
+#    include <__bit/shift.h>
+#  endif
+
 #  include <version>
 
 #  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 214020750e1af..9ddac1013c954 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -931,6 +931,7 @@ module std {
     module invert_if        { header "__bit/invert_if.h" }
     module popcount         { header "__bit/popcount.h" }
     module rotate           { header "__bit/rotate.h" }
+    module shift            { header "__bit/shift.h" }
 
     header "bit"
     export *

diff  --git a/libcxx/include/version b/libcxx/include/version
index c89c56fce45f5..de14038550c1c 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -47,7 +47,8 @@ __cpp_lib_bind_back                                     202202L <functional>
 __cpp_lib_bind_front                                    202306L <functional>
                                                         201907L // C++20
 __cpp_lib_bit_cast                                      201806L <bit>
-__cpp_lib_bitops                                        201907L <bit>
+__cpp_lib_bitops                                        202606L <bit>
+                                                        201907L // C++20
 __cpp_lib_bitset                                        202306L <bitset>
 __cpp_lib_bool_constant                                 201505L <type_traits>
 __cpp_lib_bounded_array_traits                          201902L <type_traits>
@@ -666,6 +667,8 @@ __cpp_lib_void_t                                        201411L <type_traits>
 #endif
 
 #if _LIBCPP_STD_VER >= 29
+# undef  __cpp_lib_bitops
+# define __cpp_lib_bitops                               202606L
 # undef  __cpp_lib_expected
 # define __cpp_lib_expected                             202606L
 // # define __cpp_lib_map_lookup                           202606L

diff  --git a/libcxx/modules/std/bit.inc b/libcxx/modules/std/bit.inc
index 027a6c17ad6a6..dd05b3963db33 100644
--- a/libcxx/modules/std/bit.inc
+++ b/libcxx/modules/std/bit.inc
@@ -22,6 +22,12 @@ export namespace std {
   using std::bit_width;
   using std::has_single_bit;
 
+#if _LIBCPP_STD_VER >= 29
+  // [bit.shift], shifting
+  using std::shl;
+  using std::shr;
+#endif
+
   // [bit.rotate], rotating
   using std::rotl;
   using std::rotr;

diff  --git a/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp b/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
index 4d3c9e41b783e..6915728843717 100644
--- a/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
@@ -31,6 +31,12 @@ void func() {
   std::has_single_bit(0u); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::popcount(0u); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
 
+#if TEST_STD_VER >= 29
+  // [bit.shift]
+  std::shl(1, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::shr(1, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+#endif
+
   // [bit.rotate]
   std::rotl(0u, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::rotr(0u, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
index 39df8f7ed649e..28a7debf546fe 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
@@ -204,8 +204,8 @@
 #  ifndef __cpp_lib_bitops
 #    error "__cpp_lib_bitops should be defined in c++29"
 #  endif
-#  if __cpp_lib_bitops != 201907L
-#    error "__cpp_lib_bitops should have the value 201907L in c++29"
+#  if __cpp_lib_bitops != 202606L
+#    error "__cpp_lib_bitops should have the value 202606L in c++29"
 #  endif
 
 #  ifndef __cpp_lib_byteswap

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 34900f78a8e92..9c89124f6b65d 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -8862,8 +8862,8 @@
 #  ifndef __cpp_lib_bitops
 #    error "__cpp_lib_bitops should be defined in c++29"
 #  endif
-#  if __cpp_lib_bitops != 201907L
-#    error "__cpp_lib_bitops should have the value 201907L in c++29"
+#  if __cpp_lib_bitops != 202606L
+#    error "__cpp_lib_bitops should have the value 202606L in c++29"
 #  endif
 
 #  ifndef __cpp_lib_bitset

diff  --git a/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
new file mode 100644
index 0000000000000..2cd26243f0d1d
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
@@ -0,0 +1,285 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++29
+
+// template <class T, class S>
+//   constexpr T shl(T x, S s) noexcept;
+
+// Constraints: Each of T and S is a signed or unsigned integer type.
+
+#include <bit>
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+concept can_shl_value = requires(T value) { std::shl(value, 1); };
+
+static_assert(can_shl_value<int>, "Concept is broken. We should be able to shift int.");
+static_assert(!can_shl_value<bool>);
+static_assert(!can_shl_value<char>);
+static_assert(!can_shl_value<wchar_t>);
+static_assert(!can_shl_value<char8_t>);
+static_assert(!can_shl_value<char16_t>);
+static_assert(!can_shl_value<char32_t>);
+static_assert(!can_shl_value<A>);
+static_assert(!can_shl_value<A*>);
+static_assert(!can_shl_value<E1>);
+static_assert(!can_shl_value<E2>);
+
+template <class T>
+concept can_shl_shift = requires(T shift) { std::shl(1, shift); };
+
+static_assert(can_shl_shift<int>, "Concept is broken. We should be able to shift by int.");
+static_assert(!can_shl_shift<bool>);
+static_assert(!can_shl_shift<char>);
+static_assert(!can_shl_shift<wchar_t>);
+static_assert(!can_shl_shift<char8_t>);
+static_assert(!can_shl_shift<char16_t>);
+static_assert(!can_shl_shift<char32_t>);
+static_assert(!can_shl_shift<A>);
+static_assert(!can_shl_shift<A*>);
+static_assert(!can_shl_shift<E1>);
+static_assert(!can_shl_shift<E2>);
+
+template <class T>
+constexpr bool test_signed() {
+  using U               = std::make_unsigned_t<T>;
+  constexpr int width   = std::numeric_limits<U>::digits;
+  constexpr U highbit_u = U(1) << (width - 1);
+
+  ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+  ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+  assert(std::shl(T(1), 0) == T(1));
+  assert(std::shl(T(1), 0u) == T(1));
+  assert(std::shl(T(1), 1) == T(2));
+  assert(std::shl(T(1), 1u) == T(2));
+  assert(std::shl(T(1), 2) == T(4));
+  assert(std::shl(T(1), 2u) == T(4));
+  assert(std::shl(T(1), 3) == T(8));
+  assert(std::shl(T(1), 3u) == T(8));
+  assert(std::shl(T(1), 4) == T(16));
+  assert(std::shl(T(1), 4u) == T(16));
+  assert(std::shl(T(1), 5) == T(32));
+  assert(std::shl(T(1), 5u) == T(32));
+  assert(std::shl(T(1), 6) == T(64));
+  assert(std::shl(T(1), 6u) == T(64));
+  assert(std::shl(T(1), 7) == T(128));
+  assert(std::shl(T(1), 7u) == T(128));
+
+  // Overlong shifts return 0
+  assert(std::shl(T(1), width) == T(0));
+  assert(std::shl(T(1), static_cast<U>(width)) == T(0));
+  assert(std::shl(T(1), width + 1) == T(0));
+  assert(std::shl(T(1), static_cast<U>(width + 1)) == T(0));
+  assert(std::shl(T(1), width + 100) == T(0));
+  assert(std::shl(T(1), static_cast<U>(width + 100)) == T(0));
+  assert(std::shl(T(1), std::numeric_limits<int>::max()) == T(0));
+  assert(std::shl(T(1), static_cast<U>(std::numeric_limits<int>::max())) == T(0));
+
+  assert(std::shl(T(-1), width) == T(0));
+  assert(std::shl(T(-1), static_cast<U>(width)) == T(0));
+  assert(std::shl(T(-1), width + 5) == T(0));
+  assert(std::shl(T(-1), static_cast<U>(width + 5)) == T(0));
+
+  // Negative shift amounts shift right arithmetically (bidirectional)
+  T highbit = static_cast<T>(highbit_u);
+  assert(std::shl(highbit, -1) == (highbit >> 1));
+  assert(std::shl(highbit, -2) == (highbit >> 2));
+  assert(std::shl(highbit, -3) == (highbit >> 3));
+  assert(std::shl(highbit, -4) == (highbit >> 4));
+  assert(std::shl(highbit, -5) == (highbit >> 5));
+  assert(std::shl(highbit, -6) == (highbit >> 6));
+  assert(std::shl(highbit, -7) == (highbit >> 7));
+  assert(std::shl(highbit, -(width - 1)) == (highbit >> (width - 1)));
+
+  // Negative overlong shifts: sign-extended for negative values, 0 otherwise
+  assert(std::shl(T(-1), -width) == T(-1));
+  assert(std::shl(T(-1), -(width + 1)) == T(-1));
+  assert(std::shl(T(-1), std::numeric_limits<int>::min()) == T(-1));
+  assert(std::shl(T(42), -width) == T(0));
+  assert(std::shl(T(42), -(width + 1)) == T(0));
+
+  if constexpr (width == 128) {
+    assert(std::shl(T(1), 64) == U(1) << 64);
+    assert(std::shl(T(1), 64u) == U(1) << 64);
+    assert(std::shl(T(1), 127) == U(1) << 127);
+    assert(std::shl(T(1), 127u) == U(1) << 127);
+    assert(std::shl(T(1), 128) == T(0));
+    assert(std::shl(T(1), 128u) == T(0));
+    assert(std::shl(T(1), 200) == T(0));
+    assert(std::shl(T(1), 200u) == T(0));
+
+    assert(std::shl(highbit, -64) == (highbit >> 64));
+    assert(std::shl(highbit, -127) == (highbit >> 127));
+    assert(std::shl(highbit, -128) == T(-1));
+    assert(std::shl(highbit, -200) == T(-1));
+  } else if constexpr (width == 256) {
+    assert(std::shl(T(1), 64) == U(1) << 64);
+    assert(std::shl(T(1), 64u) == U(1) << 64);
+    assert(std::shl(T(1), 127) == U(1) << 127);
+    assert(std::shl(T(1), 127u) == U(1) << 127);
+    assert(std::shl(T(1), 128) == U(1) << 128);
+    assert(std::shl(T(1), 128u) == U(1) << 128);
+    assert(std::shl(T(1), 200) == U(1) << 200);
+    assert(std::shl(T(1), 200u) == U(1) << 200);
+    assert(std::shl(T(1), 255) == U(1) << 255);
+    assert(std::shl(T(1), 255u) == U(1) << 255);
+    assert(std::shl(T(1), 256) == T(0));
+    assert(std::shl(T(1), 256u) == T(0));
+    assert(std::shl(T(1), 300) == T(0));
+    assert(std::shl(T(1), 300u) == T(0));
+
+    assert(std::shl(highbit, -64) == (highbit >> 64));
+    assert(std::shl(highbit, -127) == (highbit >> 127));
+    assert(std::shl(highbit, -128) == (highbit >> 128));
+    assert(std::shl(highbit, -200) == (highbit >> 200));
+    assert(std::shl(highbit, -255) == T(-1));
+    assert(std::shl(highbit, -256) == T(-1));
+  }
+
+  return true;
+}
+
+template <class T>
+constexpr bool test_unsigned() {
+  constexpr int width = std::numeric_limits<T>::digits;
+  constexpr T highbit = T(1) << (width - 1);
+
+  ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+  ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+  assert(std::shl(T(1), 0) == T(1));
+  assert(std::shl(T(1), 0u) == T(1));
+  assert(std::shl(T(1), 1) == T(2));
+  assert(std::shl(T(1), 1u) == T(2));
+  assert(std::shl(T(1), 2) == T(4));
+  assert(std::shl(T(1), 2u) == T(4));
+  assert(std::shl(T(1), 3) == T(8));
+  assert(std::shl(T(1), 3u) == T(8));
+  assert(std::shl(T(1), 4) == T(16));
+  assert(std::shl(T(1), 4u) == T(16));
+  assert(std::shl(T(1), 5) == T(32));
+  assert(std::shl(T(1), 5u) == T(32));
+  assert(std::shl(T(1), 6) == T(64));
+  assert(std::shl(T(1), 6u) == T(64));
+  assert(std::shl(T(1), 7) == T(128));
+  assert(std::shl(T(1), 7u) == T(128));
+
+  // Overlong shifts return 0
+  assert(std::shl(T(1), width) == T(0));
+  assert(std::shl(T(1), static_cast<T>(width)) == T(0));
+  assert(std::shl(T(1), width + 1) == T(0));
+  assert(std::shl(T(1), static_cast<T>(width + 1)) == T(0));
+  assert(std::shl(T(1), width + 100) == T(0));
+  assert(std::shl(T(1), static_cast<T>(width + 100)) == T(0));
+  assert(std::shl(T(1), std::numeric_limits<int>::max()) == T(0));
+  assert(std::shl(T(1), static_cast<T>(std::numeric_limits<int>::max())) == T(0));
+
+  assert(std::shl(T(~T(0)), width) == T(0));
+  assert(std::shl(T(~T(0)), static_cast<T>(width)) == T(0));
+  assert(std::shl(T(~T(0)), width + 5) == T(0));
+  assert(std::shl(T(~T(0)), static_cast<T>(width + 5)) == T(0));
+
+  // Negative shift amounts shift right (bidirectional)
+  assert(std::shl(highbit, -1) == (highbit >> 1));
+  assert(std::shl(highbit, -2) == (highbit >> 2));
+  assert(std::shl(highbit, -3) == (highbit >> 3));
+  assert(std::shl(highbit, -4) == (highbit >> 4));
+  assert(std::shl(highbit, -5) == (highbit >> 5));
+  assert(std::shl(highbit, -6) == (highbit >> 6));
+  assert(std::shl(highbit, -7) == (highbit >> 7));
+  assert(std::shl(highbit, -(width - 1)) == T(1));
+
+  // Negative overlong shifts: 0 for unsigned
+  assert(std::shl(T(~T(0)), -width) == T(0));
+  assert(std::shl(T(~T(0)), -(width + 1)) == T(0));
+  assert(std::shl(T(1), std::numeric_limits<int>::min()) == T(0));
+
+  if constexpr (width == 128) {
+    assert(std::shl(T(1), 64) == T(1) << 64);
+    assert(std::shl(T(1), 64u) == T(1) << 64);
+    assert(std::shl(T(1), 127) == T(1) << 127);
+    assert(std::shl(T(1), 127u) == T(1) << 127);
+    assert(std::shl(T(1), 128) == T(0));
+    assert(std::shl(T(1), 128u) == T(0));
+    assert(std::shl(T(1), 200) == T(0));
+    assert(std::shl(T(1), 200u) == T(0));
+
+    assert(std::shl(T(1) << 127, -1) == T(1) << 126);
+    assert(std::shl(T(1) << 127, -127) == T(1));
+    assert(std::shl(T(1) << 127, -128) == T(0));
+    assert(std::shl(T(1) << 127, -200) == T(0));
+  } else if constexpr (width == 256) {
+    assert(std::shl(T(1), 64) == T(1) << 64);
+    assert(std::shl(T(1), 64u) == T(1) << 64);
+    assert(std::shl(T(1), 127) == T(1) << 127);
+    assert(std::shl(T(1), 127u) == T(1) << 127);
+    assert(std::shl(T(1), 128) == T(1) << 128);
+    assert(std::shl(T(1), 128u) == T(1) << 128);
+    assert(std::shl(T(1), 200) == T(1) << 200);
+    assert(std::shl(T(1), 200u) == T(1) << 200);
+
+    assert(std::shl(T(1) << 127, -1) == T(1) << 126);
+    assert(std::shl(T(1) << 127, -127) == T(1));
+    assert(std::shl(T(1) << 127, -128) == T(0));
+    assert(std::shl(T(1) << 127, -200) == T(0));
+    assert(std::shl(T(1), 256) == T(0));
+    assert(std::shl(T(1), 256u) == T(0));
+    assert(std::shl(T(1), 300) == T(0));
+    assert(std::shl(T(1), 300u) == T(0));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  constexpr auto test_type = []<class T> {
+    if constexpr (std::is_signed_v<T>) {
+      static_assert(test_signed<T>());
+      test_signed<T>();
+    } else {
+      static_assert(test_unsigned<T>());
+      test_unsigned<T>();
+    }
+  };
+
+  types::for_each(types::unsigned_integer_types{}, test_type);
+  types::for_each(types::signed_integer_types{}, test_type);
+
+#if TEST_HAS_BITINT
+  using bitint_types = types::type_list<
+      signed _BitInt(32),
+      unsigned _BitInt(32),
+      signed _BitInt(64),
+      unsigned _BitInt(64)
+#  if __BITINT_MAXWIDTH__ >= 128
+          ,
+      signed _BitInt(128),
+      unsigned _BitInt(128)
+#  endif
+#  if __BITINT_MAXWIDTH__ >= 256
+          ,
+      signed _BitInt(256),
+      unsigned _BitInt(256)
+#  endif
+      >;
+  types::for_each(bitint_types{}, test_type);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp b/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
new file mode 100644
index 0000000000000..2ff38b849660a
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
@@ -0,0 +1,279 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++29
+
+// template <class T, class S>
+//   constexpr T shr(T x, S s) noexcept;
+
+// Constraints: Each of T and S is a signed or unsigned integer type.
+
+#include <bit>
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+concept can_shr_value = requires(T value) { std::shr(value, 1); };
+
+static_assert(can_shr_value<int>, "Concept is broken. We should be able to shift int.");
+static_assert(!can_shr_value<bool>);
+static_assert(!can_shr_value<char>);
+static_assert(!can_shr_value<wchar_t>);
+static_assert(!can_shr_value<char8_t>);
+static_assert(!can_shr_value<char16_t>);
+static_assert(!can_shr_value<char32_t>);
+static_assert(!can_shr_value<A>);
+static_assert(!can_shr_value<A*>);
+static_assert(!can_shr_value<E1>);
+static_assert(!can_shr_value<E2>);
+
+template <class T>
+concept can_shr_shift = requires(T shift) { std::shr(1, shift); };
+
+static_assert(can_shr_shift<int>, "Concept is broken. We should be able to shift by int.");
+static_assert(!can_shr_shift<bool>);
+static_assert(!can_shr_shift<char>);
+static_assert(!can_shr_shift<wchar_t>);
+static_assert(!can_shr_shift<char8_t>);
+static_assert(!can_shr_shift<char16_t>);
+static_assert(!can_shr_shift<char32_t>);
+static_assert(!can_shr_shift<A>);
+static_assert(!can_shr_shift<A*>);
+static_assert(!can_shr_shift<E1>);
+static_assert(!can_shr_shift<E2>);
+
+template <class T>
+constexpr bool test_signed() {
+  using U             = std::make_unsigned_t<T>;
+  constexpr int width = std::numeric_limits<U>::digits;
+  constexpr T highbit = static_cast<T>(T(1) << (width - 1));
+
+  ASSERT_SAME_TYPE(decltype(std::shr(T(), 0)), T);
+  ASSERT_NOEXCEPT(std::shr(T(), 0));
+
+  assert(std::shr(highbit, 0) == highbit);
+  assert(std::shr(highbit, 0u) == highbit);
+  assert(std::shr(highbit, 1) == (highbit >> 1));
+  assert(std::shr(highbit, 1u) == (highbit >> 1));
+  assert(std::shr(highbit, 2) == (highbit >> 2));
+  assert(std::shr(highbit, 2u) == (highbit >> 2));
+  assert(std::shr(highbit, 3) == (highbit >> 3));
+  assert(std::shr(highbit, 3u) == (highbit >> 3));
+  assert(std::shr(highbit, 4) == (highbit >> 4));
+  assert(std::shr(highbit, 4u) == (highbit >> 4));
+  assert(std::shr(highbit, 5) == (highbit >> 5));
+  assert(std::shr(highbit, 5u) == (highbit >> 5));
+  assert(std::shr(highbit, 6) == (highbit >> 6));
+  assert(std::shr(highbit, 6u) == (highbit >> 6));
+  assert(std::shr(highbit, 7) == (highbit >> 7));
+  assert(std::shr(highbit, 7u) == (highbit >> 7));
+  assert(std::shr(highbit, width - 1) == (highbit >> (width - 1)));
+  assert(std::shr(highbit, static_cast<unsigned>(width - 1)) == (highbit >> (width - 1)));
+
+  // Overlong right shifts sign-extend negative values and return 0 otherwise
+  assert(std::shr(T(-1), width) == T(-1));
+  assert(std::shr(T(-1), static_cast<unsigned>(width)) == T(-1));
+  assert(std::shr(T(-1), width + 1) == T(-1));
+  assert(std::shr(T(-1), static_cast<unsigned>(width + 1)) == T(-1));
+  assert(std::shr(T(-1), std::numeric_limits<int>::max()) == T(-1));
+  assert(std::shr(T(-1), static_cast<unsigned>(std::numeric_limits<int>::max())) == T(-1));
+  assert(std::shr(T(42), width) == T(0));
+  assert(std::shr(T(42), static_cast<unsigned>(width)) == T(0));
+  assert(std::shr(T(42), width + 1) == T(0));
+  assert(std::shr(T(42), static_cast<unsigned>(width + 1)) == T(0));
+
+  // Negative shift amounts shift left (bidirectional)
+  assert(std::shr(T(1), -1) == static_cast<T>(U(1) << 1));
+  assert(std::shr(T(1), -2) == static_cast<T>(U(1) << 2));
+  assert(std::shr(T(1), -3) == static_cast<T>(U(1) << 3));
+  assert(std::shr(T(1), -4) == static_cast<T>(U(1) << 4));
+  assert(std::shr(T(1), -5) == static_cast<T>(U(1) << 5));
+  assert(std::shr(T(1), -6) == static_cast<T>(U(1) << 6));
+  assert(std::shr(T(1), -7) == static_cast<T>(U(1) << 7));
+  assert(std::shr(T(1), -(width - 1)) == static_cast<T>(U(1) << (width - 1)));
+
+  // Negative overlong shifts always return 0
+  assert(std::shr(T(-1), -width) == T(0));
+  assert(std::shr(T(-1), -(width + 1)) == T(0));
+  assert(std::shr(T(-1), std::numeric_limits<int>::min()) == T(0));
+
+  if constexpr (width == 128) {
+    assert(std::shr(highbit, 64) == (highbit >> 64));
+    assert(std::shr(highbit, 64u) == (highbit >> 64));
+    assert(std::shr(highbit, 127) == (highbit >> 127));
+    assert(std::shr(highbit, 127u) == (highbit >> 127));
+    assert(std::shr(highbit, 128) == T(-1));
+    assert(std::shr(highbit, 128u) == T(-1));
+    assert(std::shr(highbit, 200) == T(-1));
+    assert(std::shr(highbit, 200u) == T(-1));
+
+    assert(std::shr(T(1), -64) == static_cast<T>(U(1) << 64));
+    assert(std::shr(T(1), -127) == static_cast<T>(U(1) << 127));
+    assert(std::shr(T(1), -128) == T(0));
+    assert(std::shr(T(1), -200) == T(0));
+  } else if constexpr (width == 256) {
+    assert(std::shr(highbit, 64) == (highbit >> 64));
+    assert(std::shr(highbit, 64u) == (highbit >> 64));
+    assert(std::shr(highbit, 127) == (highbit >> 127));
+    assert(std::shr(highbit, 127u) == (highbit >> 127));
+    assert(std::shr(highbit, 128) == (highbit >> 128));
+    assert(std::shr(highbit, 128u) == (highbit >> 128));
+    assert(std::shr(highbit, 200) == (highbit >> 200));
+    assert(std::shr(highbit, 200u) == (highbit >> 200));
+    assert(std::shr(highbit, 255) == T(-1));
+    assert(std::shr(highbit, 255u) == T(-1));
+    assert(std::shr(highbit, 256) == T(-1));
+    assert(std::shr(highbit, 256u) == T(-1));
+
+    assert(std::shr(T(1), -64) == static_cast<T>(U(1) << 64));
+    assert(std::shr(T(1), -127) == static_cast<T>(U(1) << 127));
+    assert(std::shr(T(1), -128) == static_cast<T>(U(1) << 128));
+    assert(std::shr(T(1), -200) == static_cast<T>(U(1) << 200));
+    assert(std::shr(T(1), -256) == T(0));
+    assert(std::shr(T(1), -300) == T(0));
+  }
+
+  return true;
+}
+
+template <class T>
+constexpr bool test_unsigned() {
+  constexpr int width = std::numeric_limits<T>::digits;
+  constexpr T highbit = T(1) << (width - 1);
+
+  ASSERT_SAME_TYPE(decltype(std::shr(T(), 0)), T);
+  ASSERT_NOEXCEPT(std::shr(T(), 0));
+
+  assert(std::shr(highbit, 0) == highbit);
+  assert(std::shr(highbit, 0u) == highbit);
+  assert(std::shr(highbit, 1) == (highbit >> 1));
+  assert(std::shr(highbit, 1u) == (highbit >> 1));
+  assert(std::shr(highbit, 2) == (highbit >> 2));
+  assert(std::shr(highbit, 2u) == (highbit >> 2));
+  assert(std::shr(highbit, 3) == (highbit >> 3));
+  assert(std::shr(highbit, 3u) == (highbit >> 3));
+  assert(std::shr(highbit, 4) == (highbit >> 4));
+  assert(std::shr(highbit, 4u) == (highbit >> 4));
+  assert(std::shr(highbit, 5) == (highbit >> 5));
+  assert(std::shr(highbit, 5u) == (highbit >> 5));
+  assert(std::shr(highbit, 6) == (highbit >> 6));
+  assert(std::shr(highbit, 6u) == (highbit >> 6));
+  assert(std::shr(highbit, 7) == (highbit >> 7));
+  assert(std::shr(highbit, 7u) == (highbit >> 7));
+  assert(std::shr(highbit, width - 1) == T(1));
+  assert(std::shr(highbit, static_cast<unsigned>(width - 1)) == T(1));
+
+  // Overlong right shifts return 0
+  assert(std::shr(T(~T(0)), width) == T(0));
+  assert(std::shr(T(~T(0)), static_cast<unsigned>(width)) == T(0));
+  assert(std::shr(T(~T(0)), width + 1) == T(0));
+  assert(std::shr(T(~T(0)), static_cast<unsigned>(width + 1)) == T(0));
+  assert(std::shr(T(~T(0)), std::numeric_limits<int>::max()) == T(0));
+  assert(std::shr(T(~T(0)), static_cast<unsigned>(std::numeric_limits<int>::max())) == T(0));
+
+  // Negative shift amounts shift left (bidirectional)
+  assert(std::shr(T(1), -1) == T(2));
+  assert(std::shr(T(1), -2) == T(4));
+  assert(std::shr(T(1), -3) == T(8));
+  assert(std::shr(T(1), -4) == T(16));
+  assert(std::shr(T(1), -5) == T(32));
+  assert(std::shr(T(1), -6) == T(64));
+  assert(std::shr(T(1), -7) == T(128));
+  assert(std::shr(T(1), -(width - 1)) == (T(1) << (width - 1)));
+
+  // Negative overlong shifts always return 0
+  assert(std::shr(T(~T(0)), -width) == T(0));
+  assert(std::shr(T(~T(0)), -(width + 1)) == T(0));
+  assert(std::shr(T(1), std::numeric_limits<int>::min()) == T(0));
+
+  if constexpr (width == 128) {
+    assert(std::shr(highbit, 64) == (highbit >> 64));
+    assert(std::shr(highbit, 64u) == (highbit >> 64));
+    assert(std::shr(highbit, 127) == T(1));
+    assert(std::shr(highbit, 127u) == T(1));
+    assert(std::shr(highbit, 128) == T(0));
+    assert(std::shr(highbit, 128u) == T(0));
+    assert(std::shr(highbit, 200) == T(0));
+    assert(std::shr(highbit, 200u) == T(0));
+
+    assert(std::shr(T(1), -64) == T(1) << 64);
+    assert(std::shr(T(1), -127) == T(1) << 127);
+    assert(std::shr(T(1), -128) == T(0));
+    assert(std::shr(T(1), -200) == T(0));
+  } else if constexpr (width == 256) {
+    assert(std::shr(highbit, 64) == (highbit >> 64));
+    assert(std::shr(highbit, 64u) == (highbit >> 64));
+    assert(std::shr(highbit, 127) == (highbit >> 127));
+    assert(std::shr(highbit, 127u) == (highbit >> 127));
+    assert(std::shr(highbit, 128) == (highbit >> 128));
+    assert(std::shr(highbit, 128u) == (highbit >> 128));
+    assert(std::shr(highbit, 200) == (highbit >> 200));
+    assert(std::shr(highbit, 200u) == (highbit >> 200));
+    assert(std::shr(highbit, 255) == T(1));
+    assert(std::shr(highbit, 255u) == T(1));
+    assert(std::shr(highbit, 256) == T(0));
+    assert(std::shr(highbit, 256u) == T(0));
+    assert(std::shr(highbit, 300) == T(0));
+    assert(std::shr(highbit, 300u) == T(0));
+
+    assert(std::shr(T(1), -64) == T(1) << 64);
+    assert(std::shr(T(1), -127) == T(1) << 127);
+    assert(std::shr(T(1), -128) == T(1) << 128);
+    assert(std::shr(T(1), -200) == T(1) << 200);
+    assert(std::shr(T(1), -255) == T(1) << 255);
+    assert(std::shr(T(1), -256) == T(0));
+    assert(std::shr(T(1), -300) == T(0));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  constexpr auto test_type = []<class T> {
+    if constexpr (std::is_signed_v<T>) {
+      static_assert(test_signed<T>());
+      test_signed<T>();
+    } else {
+      static_assert(test_unsigned<T>());
+      test_unsigned<T>();
+    }
+  };
+
+  types::for_each(types::unsigned_integer_types{}, test_type);
+  types::for_each(types::signed_integer_types{}, test_type);
+
+#if TEST_HAS_BITINT
+  using bitint_types = types::type_list<
+      signed _BitInt(32),
+      unsigned _BitInt(32),
+      signed _BitInt(64),
+      unsigned _BitInt(64)
+#  if __BITINT_MAXWIDTH__ >= 128
+          ,
+      signed _BitInt(128),
+      unsigned _BitInt(128)
+#  endif
+#  if __BITINT_MAXWIDTH__ >= 256
+          ,
+      signed _BitInt(256),
+      unsigned _BitInt(256)
+#  endif
+      >;
+  types::for_each(bitint_types{}, test_type);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 8cc54971c57c9..f97488ddca59d 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -248,7 +248,7 @@ def add_version_header(tc):
             "name": "__cpp_lib_bitops",
             "values": {
                 "c++20": 201907,
-                # "c++29": 202606,  # P3793R2 Better shifting
+                "c++29": 202606,  # P3793R2 Better shifting
                 # "c++29": 202607,  # P3104R6 Bit permutations
             },
             "headers": ["bit"],


        


More information about the libcxx-commits mailing list