Skip to content

[libc++] Implement <bit> parts of P3793R2 Better shifting - #219389

Merged
frederick-vs-ja merged 15 commits into
llvm:mainfrom
eisenwave:p3793r2
Sep 15, 2026
Merged

frederick-vs-ja merged 15 commits into
llvm:mainfrom
eisenwave:p3793r2

Conversation

@eisenwave

@eisenwave eisenwave commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

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.

@eisenwave
eisenwave requested a review from a team as a code owner August 28, 2026 06:45
@llvmorg-github-actions llvmorg-github-actions Bot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Aug 28, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-libcxx

Author: Jan Schultke (eisenwave)

Changes

Closes #204401

The implementations of shl and shr are based on the reference implementation in the paper. The test code is largely AI-generated with the instruction to model it after that for rotl and rotr.

No idea how to run any of these tests locally though because the build seems to be messed up for me:

$ninja check-cxx
ninja: error: dependency cycle: /home/user/projects/llvm-project/build-llvm/lib/clang/24/include/stddef.h -&gt; bin/clang-tblgen -&gt; tools/clang/lib/Support/CMakeFiles/obj.clangSupport.dir/RISCVVIntrinsicUtils.cpp.o -&gt; lib/Support/CMakeFiles/LLVMSupport.dir/cmake_pch.hxx.pch -&gt; /home/user/projects/llvm-project/build-llvm/lib/clang/24/include/stddef.h

Patch is 33.03 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219389.diff

11 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+2)
  • (modified) libcxx/docs/ReleaseNotes/23.rst (+1)
  • (modified) libcxx/docs/Status/Cxx29Papers.csv (+1-1)
  • (added) libcxx/include/__bit/shift.h (+62)
  • (modified) libcxx/include/bit (+10)
  • (modified) libcxx/include/version (+4-1)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp (+2-2)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp (+2-2)
  • (added) libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp (+307)
  • (added) libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp (+310)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (+1-1)
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index cabd30847b2af..ea9157141cef6 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -558,6 +558,8 @@ Status
     ---------------------------------------------------------- -----------------
     **C++29**
     ----------------------------------------------------------------------------
+    ``__cpp_lib_bitops``                                       ``202606L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_map_lookup``                                   *unimplemented*
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_mdspan_copy``                                  *unimplemented*
diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index f7a8e5b04f1ea..3210b6a1a6af7 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -55,6 +55,7 @@ Implemented Papers
 - P3369R0: constexpr for ``uninitialized_default_construct`` (`Github <https://llvm.org/PR118380>`__)
 - P3508R0: Wording for "constexpr for specialized memory algorithms" (`Github <https://llvm.org/PR118379>`__)
 - P4206R0: Revert string support in ``std::constant_wrapper`` (`Github <https://llvm.org/PR203338>`__)
+- P3793R2: Better shifting (`Github <https://llvm.org/PR????>`__)
 
 Improvements and New Features
 -----------------------------
diff --git a/libcxx/docs/Status/Cxx29Papers.csv b/libcxx/docs/Status/Cxx29Papers.csv
index 3c48fe89be33d..30f0588b49e60 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|","24","`#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/__bit/shift.h b/libcxx/include/__bit/shift.h
new file mode 100644
index 0000000000000..75bafe9731125
--- /dev/null
+++ b/libcxx/include/__bit/shift.h
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// Writing two full functions for shl and shr makes it easier for the compiler
+// to optimize the code.
+
+#if _LIBCPP_STD_VER >= 29
+
+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_v<_Shift>) {
+    if (__cnt < 0) {
+      if (__cnt <= -__n)
+        return static_cast<_Tp>(__t < 0 ? -1 : 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_v<_Shift>) {
+    if (__cnt < 0) {
+      if (__cnt <= -__n)
+        return static_cast<_Tp>(0);
+      return __t << -__cnt;
+    }
+  }
+  if (__cnt >= __n)
+    return static_cast<_Tp>(__t < 0 ? -1 : 0);
+  return __t >> __cnt;
+}
+
+#endif // _LIBCPP_STD_VER >= 29
+
+_LIBCPP_END_NAMESPACE_STD
+
+#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/version b/libcxx/include/version
index 589ce789ffa7c..421ceb20b270e 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>
@@ -661,6 +662,8 @@ __cpp_lib_void_t                                        201411L <type_traits>
 #endif
 
 #if _LIBCPP_STD_VER >= 29
+# undef  __cpp_lib_bitops
+# define __cpp_lib_bitops                               202606L
 // # define __cpp_lib_map_lookup                           202606L
 // # define __cpp_lib_mdspan_copy                          202606L
 // # define __cpp_lib_pointer_tag_pair                     202606L
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 769b79c7924d2..480b8692cfe5e 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
@@ -8829,8 +8829,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..d63a547226ba7
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
@@ -0,0 +1,307 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23, c++26
+
+// 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 <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+
+struct A {};
+enum       E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+constexpr bool test()
+{
+    ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+    ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+    using U = std::make_unsigned_t<T>;
+    constexpr int width = std::numeric_limits<U>::digits;
+
+    // shl(1, n) for n = 0..7 — basic left shifts
+    assert(std::shl(T(1), 0) == T(1));
+    assert(std::shl(T(1), 1) == T(2));
+    assert(std::shl(T(1), 2) == T(4));
+    assert(std::shl(T(1), 3) == T(8));
+    assert(std::shl(T(1), 4) == T(16));
+    assert(std::shl(T(1), 5) == T(32));
+    assert(std::shl(T(1), 6) == T(64));
+    assert(std::shl(T(1), 7) == T(128));
+
+    // Overlong shifts return 0
+    assert(std::shl(T(1), width) == T(0));
+    assert(std::shl(T(1), width + 1) == T(0));
+    assert(std::shl(T(1), width + 100) == T(0));
+    assert(std::shl(T(1), std::numeric_limits<int>::max()) == T(0));
+
+    // Test all-ones pattern shifted out
+    if constexpr (std::is_signed_v<T>) {
+        assert(std::shl(T(-1), width) == T(0));
+        assert(std::shl(T(-1), width + 5) == T(0));
+    } else {
+        assert(std::shl(T(~T(0)), width) == T(0));
+        assert(std::shl(T(~T(0)), width + 5) == T(0));
+    }
+
+    // Negative shift amounts shift right (bidirectional)
+    U highbit_u = U(1) << (width - 1);
+    T highbit = static_cast<T>(highbit_u);
+    assert(std::shl(highbit, -1) == static_cast<T>(highbit_u >> 1));
+    assert(std::shl(highbit, -2) == static_cast<T>(highbit_u >> 2));
+    assert(std::shl(highbit, -3) == static_cast<T>(highbit_u >> 3));
+    assert(std::shl(highbit, -4) == static_cast<T>(highbit_u >> 4));
+    assert(std::shl(highbit, -5) == static_cast<T>(highbit_u >> 5));
+    assert(std::shl(highbit, -6) == static_cast<T>(highbit_u >> 6));
+    assert(std::shl(highbit, -7) == static_cast<T>(highbit_u >> 7));
+    assert(std::shl(highbit, -(width - 1)) == T(1));
+
+    // Negative overlong shifts: 0 for unsigned, sign-extended for signed
+    if constexpr (std::is_signed_v<T>) {
+        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));
+    } else {
+        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));
+    }
+
+#ifndef TEST_HAS_NO_INT128
+    if constexpr (std::is_same_v<T, __uint128_t>) {
+        assert(std::shl(T(1), 0) == T(1));
+        assert(std::shl(T(1), 64) == T(1) << 64);
+        assert(std::shl(T(1), 127) == T(1) << 127);
+        assert(std::shl(T(1), 128) == T(0));
+        assert(std::shl(T(1), 200) == 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));
+    }
+#endif
+
+    return true;
+}
+
+int main(int, char**)
+{
+    {
+    // Constraint: T must be a signed or unsigned integer type
+    auto lambda = [](auto x) -> decltype(std::shl(x, 1U)) {};
+    using L = decltype(lambda);
+
+    // Signed integer types are accepted
+    static_assert(std::is_invocable_v<L, signed char>);
+    static_assert(std::is_invocable_v<L, short>);
+    static_assert(std::is_invocable_v<L, int>);
+    static_assert(std::is_invocable_v<L, long>);
+    static_assert(std::is_invocable_v<L, long long>);
+#ifndef TEST_HAS_NO_INT128
+    static_assert(std::is_invocable_v<L, __int128_t>);
+#endif
+
+    static_assert(std::is_invocable_v<L, std::int8_t>);
+    static_assert(std::is_invocable_v<L, std::int16_t>);
+    static_assert(std::is_invocable_v<L, std::int32_t>);
+    static_assert(std::is_invocable_v<L, std::int64_t>);
+    static_assert(std::is_invocable_v<L, std::intmax_t>);
+    static_assert(std::is_invocable_v<L, std::intptr_t>);
+    static_assert(std::is_invocable_v<L, std::ptrdiff_t>);
+
+    // Unsigned integer types are accepted
+    static_assert(std::is_invocable_v<L, unsigned char>);
+    static_assert(std::is_invocable_v<L, unsigned short>);
+    static_assert(std::is_invocable_v<L, unsigned int>);
+    static_assert(std::is_invocable_v<L, unsigned long>);
+    static_assert(std::is_invocable_v<L, unsigned long long>);
+
+    // bool, character types, enums, classes, and pointers are rejected
+    static_assert(!std::is_invocable_v<L, bool>);
+    static_assert(!std::is_invocable_v<L, char>);
+    static_assert(!std::is_invocable_v<L, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+    static_assert(!std::is_invocable_v<L, char8_t>);
+#endif
+    static_assert(!std::is_invocable_v<L, char16_t>);
+    static_assert(!std::is_invocable_v<L, char32_t>);
+
+    static_assert(!std::is_invocable_v<L, A>);
+    static_assert(!std::is_invocable_v<L, A*>);
+    static_assert(!std::is_invocable_v<L, E1>);
+    static_assert(!std::is_invocable_v<L, E2>);
+    }
+    {
+    // Constraint: S must be a signed or unsigned integer type
+    auto lambda_s = [](auto s) -> decltype(std::shl(1, s)) {};
+    using LS = decltype(lambda_s);
+
+    static_assert(std::is_invocable_v<LS, signed char>);
+    static_assert(std::is_invocable_v<LS, short>);
+    static_assert(std::is_invocable_v<LS, int>);
+    static_assert(std::is_invocable_v<LS, long>);
+    static_assert(std::is_invocable_v<LS, long long>);
+
+    static_assert(std::is_invocable_v<LS, unsigned char>);
+    static_assert(std::is_invocable_v<LS, unsigned short>);
+    static_assert(std::is_invocable_v<LS, unsigned int>);
+    static_assert(std::is_invocable_v<LS, unsigned long>);
+    static_assert(std::is_invocable_v<LS, unsigned long long>);
+
+    static_assert(!std::is_invocable_v<LS, bool>);
+    static_assert(!std::is_invocable_v<LS, char>);
+    static_assert(!std::is_invocable_v<LS, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+    static_assert(!std::is_invocable_v<LS, char8_t>);
+#endif
+    static_assert(!std::is_invocable_v<LS, char16_t>);
+    static_assert(!std::is_invocable_v<LS, char32_t>);
+
+    static_assert(!std::is_invocable_v<LS, A>);
+    static_assert(!std::is_invocable_v<LS, A*>);
+    static_assert(!std::is_invocable_v<LS, E1>);
+    static_assert(!std::is_invocable_v<LS, E2>);
+    }
+
+    static_assert(test<unsigned char>());
+    static_assert(test<unsigned short>());
+    static_assert(test<unsigned int>());
+    static_assert(test<unsigned long>());
+    static_assert(test<unsigned long long>());
+#ifndef TEST_HAS_NO_INT128
+    static_assert(test<__uint128_t>());
+#endif
+
+    static_assert(test<signed char>());
+    static_assert(test<short>());
+    static_assert(test<int>());
+    static_assert(test<long>());
+    static_assert(test<long long>());
+
+    static_assert(test<std::uint8_t>());
+    static_assert(test<std::uint16_t>());
+    static_assert(test<std::uint32_t>());
+    static_assert(test<std::uint64_t>());
+    static_assert(test<std::uintmax_t>());
+    static_assert(test<std::uintptr_t>());
+    static_assert(test<std::size_t>());
+
+    static_assert(test<std::int8_t>());
+    static_assert(test<std::int16_t>());
+    static_assert(test<std::int32_t>());
+    static_assert(test<std::int64_t>());
+    static_assert(test<std::intmax_t>());
+    static_assert(test<std::intptr_t>());
+    static_assert(test<std::ptrdiff_t>());
+
+    test<unsigned char>();
+    test<unsigned short>();
+    test<unsigned int>();
+    test<unsigned long>();
+    test<unsigned long long>();
+#ifndef TEST_HAS_NO_INT128
+    test<__uint128_t>();
+#endif
+
+    test<signed char>();
+    test<short>();
+    test<int>();
+    test<long>();
+    test<long long>();
+
+    test<std::uint8_t>();
+    test<std::uint16_t>();
+    test<std::uint32_t>();
+    test<std::uint64_t>();
+    test<std::uintmax_t>();
+    test<std::uintptr_t>();
+    test<std::size_t>();
+
+    test<std::int8_t>();
+    test<std::int16_t>();
+    test<std::int32_t>();
+    test<std::int64_t>();
+    test<std::intmax_t>();
+    test<std::intptr_t>();
+    test<std::ptrdiff_t>();
+
+#if TEST_HAS_EXTENSION(bit_int)
+    {
+      using T32 = unsigned _BitInt(32);
+      using T64 = unsigned _BitInt(64);
+
+      assert(std::shl(T32(1), 0) == T32(1));
+      assert(std::shl(T32(1), 1) == T32(2));
+      assert(std::shl(T32(1), 4) == T32(16));
+      assert(std::shl(T32(1), 7) == T32(128));
+      assert(std::shl(T32(1), 31) == T32(1) << 31);
+      // Overlong shift returns 0
+      assert(std::shl(T32(1), 32) == T32(0));
+      assert(std::shl(T32(~T32(0)), 32) == T32(0));
+      // Negative shift (bidirectional)
+      assert(std::shl(T32(1) << 31, -1) == T32(1) << 30);
+      assert(std::shl(T32(1) << 31, -31) == T32(1));
+      assert(std::shl(T32(1) << 31, -32) == T32(0));
+
+      assert(std::shl(T64(1), 0) == T64(1));
+      assert(std::shl(T64(1), 4) == T64(16));
+      assert(std::shl(T64(1), 63) == T64(1) << 63);
+      assert(std::shl(T64(1), 64) == T64(0));
+      assert(std::shl(T64(1) << 63, -1) == T64(1) << 62);
+      assert(std::shl(T64(1) << 63, -63) == T64(1));
+      assert(std::shl(T64(1) << 63, -64) == T64(0));
+    }
+#  if __BITINT_MAXWIDTH__ >= 128
+    {
+      using T128 = unsigned _BitInt(128);
+      assert(std::shl(T128(1), 0) == T128(1));
+      assert(std::shl(T128(1), 4) == T128(16));
+      assert(std::shl(T128(1), 63) == T128(1) << 63);
+      assert(std::shl(T128(1), 64) == T128(1) << 64);
+      assert(std::shl(T128(1), 127) == T128(1) << 127);
+      // Overlong shifts return 0
+      assert(std::shl(T128(1), 128) == T128(0));
+      assert(std::shl(T128(1), 200) == T128(0));
+      // Negative shift (bidirectional)
+      assert(std::shl(T128(1) << 127, -1) == T128(1) << 126);
+      assert(std::shl(T128(1) << 127, -127) == T128(1));
+      assert(std::shl(T128(1) << 127, -128) == T128(0));
+    }
+#  endif
+#  if __BITINT_MAXWIDTH__ >= 256
+    {
+      using T256 = unsigned _BitInt(256);
+      assert(std::shl(T256(1), 0) == T256(1));
+      assert(std::shl(T256(1), 4) == T256(16));
+      assert(std::shl(T256(1), 200) == T256(1) << 200);
+      // Overlong shift returns 0
+      assert(std::sh...
[truncated]

@github-actions

github-actions Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

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

@frederick-vs-ja frederick-vs-ja changed the title P3793R2 Better shifting [libc++] Implement <bit> parts of P3793R2 Better shifting Aug 28, 2026

@philnik777 philnik777 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The commit message shouldn't be closing the issue, since the paper isn't fully implemented.

Comment thread libcxx/docs/Status/Cxx29Papers.csv Outdated
Comment thread libcxx/include/__bit/shift.h
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/utils/generate_feature_test_macro_components.py
Comment thread libcxx/include/__bit/shift.h
Comment thread libcxx/include/__bit/shift.h
Comment thread libcxx/docs/ReleaseNotes/24.rst Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/include/__bit/shift.h

@philnik777 philnik777 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Basically looks good, just a few nits.

Comment thread libcxx/include/__bit/shift.h Outdated
Comment thread libcxx/include/__bit/shift.h
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
@eisenwave

Copy link
Copy Markdown
Contributor Author

Ok yeah, it's definitely incorrect now. It was correct before the "fixes". I'm also realizing that the tests only cover signed shift amounts because they always use int, so that needs some more coverage.

@Zingam

Zingam commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

No idea how to run any of these tests locally though because the build seems to be messed up for me:

$ninja check-cxx
ninja: error: dependency cycle: /home/user/projects/llvm-project/build-llvm/lib/clang/24/include/stddef.h -> bin/clang-

@eisenwave AFAIK These lines would considered inappropriate to include in the commit message. Please delete this from the top comment or move this to a separate message.

The commit message should contain info about the implementation only.

@eisenwave
eisenwave requested a review from philnik777 August 28, 2026 15:46
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/docs/FeatureTestMacroTable.rst Outdated

@frederick-vs-ja frederick-vs-ja left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! The CI failure is probably unrelated.

Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
Comment thread libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp Outdated
@eisenwave

Copy link
Copy Markdown
Contributor Author

@frederick-vs-ja @philnik777 can you merge?

@frederick-vs-ja
frederick-vs-ja merged commit 058ca12 into llvm:main Sep 15, 2026
85 checks passed
anilkund pushed a commit to anilkund/llvm-project that referenced this pull request Sep 16, 2026
)

Partially implements llvm#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++29 libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants