[libcxx-commits] [libcxx] [libc++] Add __pointer_int_pair (PR #94324)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jun 9 03:51:30 PDT 2025


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/94324

>From c9cbd3734f625588c67d8d66689cf978b0777a2b Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Tue, 4 Jun 2024 09:04:47 +0200
Subject: [PATCH] [libc++] Add __pointer_int_pair

---
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__bit/bit_log2.h               |   2 +-
 libcxx/include/__bit/countl.h                 |   2 +-
 libcxx/include/__utility/pointer_int_pair.h   | 149 ++++++++++++++++++
 libcxx/include/module.modulemap.in            |   5 +
 .../assert.constructor.pass.cpp               |  42 +++++
 .../pointer_int_pair/constinit.verify.cpp     |  24 +++
 .../pointer_int_pair.pass.cpp                 | 110 +++++++++++++
 .../static_asserts.verify.cpp                 |  17 ++
 9 files changed, 350 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/include/__utility/pointer_int_pair.h
 create mode 100644 libcxx/test/libcxx/utilities/pointer_int_pair/assert.constructor.pass.cpp
 create mode 100644 libcxx/test/libcxx/utilities/pointer_int_pair/constinit.verify.cpp
 create mode 100644 libcxx/test/libcxx/utilities/pointer_int_pair/pointer_int_pair.pass.cpp
 create mode 100644 libcxx/test/libcxx/utilities/pointer_int_pair/static_asserts.verify.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 43cefd5600646..6c4551fb93fba 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -917,6 +917,7 @@ set(files
   __utility/no_destroy.h
   __utility/pair.h
   __utility/piecewise_construct.h
+  __utility/pointer_int_pair.h
   __utility/priority_tag.h
   __utility/private_constructor_tag.h
   __utility/rel_ops.h
diff --git a/libcxx/include/__bit/bit_log2.h b/libcxx/include/__bit/bit_log2.h
index b22e1ce1f84e6..63cc3232e76ae 100644
--- a/libcxx/include/__bit/bit_log2.h
+++ b/libcxx/include/__bit/bit_log2.h
@@ -21,7 +21,7 @@
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 template <class _Tp>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp __bit_log2(_Tp __t) _NOEXCEPT {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _Tp __bit_log2(_Tp __t) _NOEXCEPT {
   static_assert(__libcpp_is_unsigned_integer<_Tp>::value, "__bit_log2 requires an unsigned integer type");
   return numeric_limits<_Tp>::digits - 1 - std::__countl_zero(__t);
 }
diff --git a/libcxx/include/__bit/countl.h b/libcxx/include/__bit/countl.h
index 9499bf9b458ee..939cda6e71a08 100644
--- a/libcxx/include/__bit/countl.h
+++ b/libcxx/include/__bit/countl.h
@@ -24,7 +24,7 @@ _LIBCPP_PUSH_MACROS
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 template <class _Tp>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 int __countl_zero(_Tp __t) _NOEXCEPT {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int __countl_zero(_Tp __t) _NOEXCEPT {
   static_assert(__libcpp_is_unsigned_integer<_Tp>::value, "__countl_zero requires an unsigned integer type");
   return __builtin_clzg(__t, numeric_limits<_Tp>::digits);
 }
diff --git a/libcxx/include/__utility/pointer_int_pair.h b/libcxx/include/__utility/pointer_int_pair.h
new file mode 100644
index 0000000000000..30e1005e9d96c
--- /dev/null
+++ b/libcxx/include/__utility/pointer_int_pair.h
@@ -0,0 +1,149 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___UTILITY_POINTER_INT_PAIR_H
+#define _LIBCPP___UTILITY_POINTER_INT_PAIR_H
+
+#include <__assert>
+#include <__bit/bit_log2.h>
+#include <__config>
+#include <__cstddef/size_t.h>
+#include <__fwd/tuple.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/is_integral.h>
+#include <__type_traits/is_unsigned.h>
+#include <__type_traits/is_void.h>
+#include <cstdint>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+// A __pointer_int_pair is a pair of a pointer and an integral type. The lower bits of the pointer that are free
+// due to the alignment requirement of the pointee are used to store the integral type.
+//
+// This imposes a constraint on the number of bits available for the integral type -- the integral type can use
+// at most log2(alignof(T)) bits. This technique allows storing the integral type without additional storage
+// beyond that of the pointer itself, at the cost of some bit twiddling.
+
+#ifndef _LIBCPP_CXX03_LANG
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class, class = void>
+struct _PointerLikeTraits;
+
+template <class _Tp>
+struct _PointerLikeTraits<_Tp*, __enable_if_t<!is_void<_Tp>::value>> {
+  static constexpr size_t __low_bits_available = std::__bit_log2(alignof(_Tp));
+
+  static _LIBCPP_HIDE_FROM_ABI uintptr_t __to_uintptr(_Tp* __ptr) { return reinterpret_cast<uintptr_t>(__ptr); }
+  static _LIBCPP_HIDE_FROM_ABI _Tp* __to_pointer(uintptr_t __ptr) { return reinterpret_cast<_Tp*>(__ptr); }
+};
+
+template <class _Tp>
+struct _PointerLikeTraits<_Tp*, __enable_if_t<is_void<_Tp>::value>> {
+  static constexpr size_t __low_bits_available = 0;
+
+  static _LIBCPP_HIDE_FROM_ABI uintptr_t __to_uintptr(_Tp* __ptr) { return reinterpret_cast<uintptr_t>(__ptr); }
+  static _LIBCPP_HIDE_FROM_ABI _Tp* __to_pointer(uintptr_t __ptr) { return reinterpret_cast<_Tp*>(__ptr); }
+};
+
+enum class __integer_width : size_t {};
+
+template <class _Pointer, class _IntType, __integer_width __int_bit_count>
+class __pointer_int_pair {
+  using _PointerTraits = _PointerLikeTraits<_Pointer>;
+
+  static constexpr auto __int_width = static_cast<size_t>(__int_bit_count);
+
+  static_assert(__int_width <= _PointerTraits::__low_bits_available,
+                "Not enough bits available for requested bit count");
+  static_assert(is_integral<_IntType>::value, "_IntType has to be an integral type");
+  static_assert(is_unsigned<_IntType>::value,
+                "__pointer_int_pair doesn't work for signed types since that would require handling the sign bit");
+
+  static constexpr size_t __extra_bits  = _PointerTraits::__low_bits_available - __int_width;
+  static constexpr uintptr_t __int_mask = static_cast<uintptr_t>(1 << _PointerTraits::__low_bits_available) - 1;
+  static constexpr uintptr_t __ptr_mask = ~__int_mask;
+
+  uintptr_t __value_ = 0;
+
+public:
+  __pointer_int_pair()                                     = default;
+  __pointer_int_pair(const __pointer_int_pair&)            = default;
+  __pointer_int_pair(__pointer_int_pair&&)                 = default;
+  __pointer_int_pair& operator=(const __pointer_int_pair&) = default;
+  __pointer_int_pair& operator=(__pointer_int_pair&&)      = default;
+  ~__pointer_int_pair()                                    = default;
+
+  _LIBCPP_HIDE_FROM_ABI __pointer_int_pair(_Pointer __ptr_value, _IntType __int_value)
+      : __value_(_PointerTraits::__to_uintptr(__ptr_value) | (__int_value << __extra_bits)) {
+    _LIBCPP_ASSERT_INTERNAL((__int_value & (__int_mask >> __extra_bits)) == __int_value, "integer is too large!");
+    _LIBCPP_ASSERT_INTERNAL(
+        (_PointerTraits::__to_uintptr(__ptr_value) & __ptr_mask) == _PointerTraits::__to_uintptr(__ptr_value),
+        "Pointer alignment is too low!");
+  }
+
+  _LIBCPP_HIDE_FROM_ABI _IntType __get_value() const { return (__value_ & __int_mask) >> __extra_bits; }
+  _LIBCPP_HIDE_FROM_ABI _Pointer __get_ptr() const { return _PointerTraits::__to_pointer(__value_ & __ptr_mask); }
+
+  template <class, class>
+  friend struct _PointerLikeTraits;
+};
+
+template <class _Pointer, __integer_width __int_bit_count, class _IntType>
+struct _PointerLikeTraits<__pointer_int_pair<_Pointer, _IntType, __int_bit_count> > {
+private:
+  using _PointerIntPair = __pointer_int_pair<_Pointer, _IntType, __int_bit_count>;
+
+public:
+  static inline const size_t __low_bits_available = _PointerIntPair::__extra_bits;
+
+  static _LIBCPP_HIDE_FROM_ABI uintptr_t __to_uintptr(_PointerIntPair __ptr) { return __ptr.__value_; }
+
+  static _LIBCPP_HIDE_FROM_ABI _PointerIntPair __to_pointer(uintptr_t __ptr) {
+    _PointerIntPair __tmp = {};
+    __tmp.__value_        = __ptr;
+    return __tmp;
+  }
+};
+
+// Make __pointer_int_pair tuple-like
+
+template <class _Pointer, class _IntType, __integer_width __int_bit_count>
+struct tuple_size<__pointer_int_pair<_Pointer, _IntType, __int_bit_count>> : integral_constant<size_t, 2> {};
+
+template <class _Pointer, class _IntType, __integer_width __int_bit_count>
+struct tuple_element<0, __pointer_int_pair<_Pointer, _IntType, __int_bit_count>> {
+  using type = _Pointer;
+};
+
+template <class _Pointer, class _IntType, __integer_width __int_bit_count>
+struct tuple_element<1, __pointer_int_pair<_Pointer, _IntType, __int_bit_count>> {
+  using type = _IntType;
+};
+
+template <size_t __i, class _Pointer, class _IntType, __integer_width __int_bit_count>
+_LIBCPP_HIDE_FROM_ABI __conditional_t<__i == 0, _Pointer, _IntType>
+get(__pointer_int_pair<_Pointer, _IntType, __int_bit_count> __pair) {
+  if constexpr (__i == 0) {
+    return __pair.__get_ptr();
+  } else if constexpr (__i == 1) {
+    return __pair.__get_value();
+  } else {
+    static_assert(false, "Index out of bounds");
+  }
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_CXX03_LANG
+
+#endif // _LIBCPP___UTILITY_POINTER_INT_PAIR_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 7f625cefed1c2..a96c079f06427 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2141,6 +2141,11 @@ module std [system] {
     module no_destroy                 { header "__utility/no_destroy.h" }
     module pair                       { header "__utility/pair.h" }
     module piecewise_construct        { header "__utility/piecewise_construct.h" }
+    module pointer_int_pair           {
+      header "__utility/pointer_int_pair.h"
+
+      export std_core.fwd.tuple
+    }
     module priority_tag               { header "__utility/priority_tag.h" }
     module private_constructor_tag    { header "__utility/private_constructor_tag.h" }
     module rel_ops                    { header "__utility/rel_ops.h" }
diff --git a/libcxx/test/libcxx/utilities/pointer_int_pair/assert.constructor.pass.cpp b/libcxx/test/libcxx/utilities/pointer_int_pair/assert.constructor.pass.cpp
new file mode 100644
index 0000000000000..a8ea5e96c3df8
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/pointer_int_pair/assert.constructor.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: has-unix-headers
+// REQUIRES: libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+#include "test_macros.h"
+
+TEST_DIAGNOSTIC_PUSH
+TEST_CLANG_DIAGNOSTIC_IGNORED("-Wprivate-header")
+#include <__utility/pointer_int_pair.h>
+TEST_DIAGNOSTIC_POP
+
+#include <cassert>
+
+#include "check_assertion.h"
+
+struct [[gnu::packed]] Packed {
+  char c;
+  int i;
+};
+
+int main(int, char**) {
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::__pointer_int_pair<int*, size_t, std::__integer_width{1}>{nullptr, 2}), "integer is too large!");
+
+  TEST_DIAGNOSTIC_PUSH
+  TEST_CLANG_DIAGNOSTIC_IGNORED("-Waddress-of-packed-member") // That's what we're trying to test
+  TEST_GCC_DIAGNOSTIC_IGNORED("-Waddress-of-packed-member")
+  alignas(int) Packed p;
+  TEST_LIBCPP_ASSERT_FAILURE(
+      (std::__pointer_int_pair<int*, size_t, std::__integer_width{1}>{&p.i, 0}), "Pointer alignment is too low!");
+  TEST_DIAGNOSTIC_POP
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/utilities/pointer_int_pair/constinit.verify.cpp b/libcxx/test/libcxx/utilities/pointer_int_pair/constinit.verify.cpp
new file mode 100644
index 0000000000000..090df2b47d624
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/pointer_int_pair/constinit.verify.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Ensure that __pointer_int_pair cannot be constant initialized with a value.
+// This would mean that the constructor is `constexpr`, which should only be
+// possible with compiler magic.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// clang-format off
+
+#include <__utility/pointer_int_pair.h>
+#include <cstddef>
+
+template <class Ptr, class UnderlyingType>
+using single_bit_pair = std::__pointer_int_pair<Ptr, UnderlyingType, std::__integer_width{1}>;
+
+constinit int ptr = 0;
+constinit single_bit_pair<int*, size_t> continitiable_pointer_int_pair_values{&ptr, 0}; // expected-error {{variable does not have a constant initializer}}
diff --git a/libcxx/test/libcxx/utilities/pointer_int_pair/pointer_int_pair.pass.cpp b/libcxx/test/libcxx/utilities/pointer_int_pair/pointer_int_pair.pass.cpp
new file mode 100644
index 0000000000000..993eee7848b60
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/pointer_int_pair/pointer_int_pair.pass.cpp
@@ -0,0 +1,110 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+#include <__utility/pointer_int_pair.h>
+#include <cassert>
+#include <cstddef>
+#include <type_traits>
+
+template <class Ptr, class UnderlyingType>
+using single_bit_pair = std::__pointer_int_pair<Ptr, UnderlyingType, std::__integer_width(1)>;
+
+template <class Ptr, class UnderlyingType>
+using two_bit_pair = std::__pointer_int_pair<Ptr, UnderlyingType, std::__integer_width(2)>;
+
+#if _LIBCPP_STD_VER >= 20
+constinit single_bit_pair<int*, size_t> continitiable_pointer_int_pair;
+#endif
+
+int main(int, char**) {
+  { // __pointer_int_pair() constructor
+    single_bit_pair<int*, size_t> pair = {};
+    assert(pair.__get_value() == 0);
+    assert(pair.__get_ptr() == nullptr);
+  }
+
+  { // __pointer_int_pair(pointer, int) constructor
+    single_bit_pair<int*, size_t> pair(nullptr, 1);
+    assert(pair.__get_value() == 1);
+    assert(pair.__get_ptr() == nullptr);
+  }
+
+  { // pointer is correctly packed/unpacked (with different types and values)
+    int i;
+    single_bit_pair<int*, size_t> pair(&i, 0);
+    assert(pair.__get_value() == 0);
+    assert(pair.__get_ptr() == &i);
+  }
+  {
+    int i;
+    two_bit_pair<int*, size_t> pair(&i, 2);
+    assert(pair.__get_value() == 2);
+    assert(pair.__get_ptr() == &i);
+  }
+  {
+    short i;
+    single_bit_pair<short*, size_t> pair(&i, 1);
+    assert(pair.__get_value() == 1);
+    assert(pair.__get_ptr() == &i);
+  }
+
+  { // check that a __pointer_int_pair<__pointer_int_pair> works
+    int i;
+    single_bit_pair<single_bit_pair<int*, size_t>, size_t> pair({&i, 1}, 0);
+    assert(pair.__get_value() == 0);
+    assert(pair.__get_ptr().__get_ptr() == &i);
+    assert(pair.__get_ptr().__get_value() == 1);
+  }
+
+#if _LIBCPP_STD_VER >= 17
+  { // check that the tuple protocol is correctly implemented
+    int i;
+    two_bit_pair<int*, size_t> pair{&i, 3};
+    auto [ptr, value] = pair;
+    assert(ptr == &i);
+    assert(value == 3);
+  }
+#endif
+
+  { // check that the (pointer, int) constructor is implicit
+    int i;
+    two_bit_pair<int*, size_t> pair = {&i, 3};
+    assert(pair.__get_ptr() == &i);
+    assert(pair.__get_value() == 3);
+  }
+
+  { // check that overaligned types work as expected
+    struct alignas(32) Overaligned {
+      int i;
+    };
+
+    Overaligned i;
+    std::__pointer_int_pair<Overaligned*, size_t, std::__integer_width(4)> pair = {&i, 13};
+    assert(pair.__get_ptr() == &i);
+    assert(pair.__get_value() == 13);
+  }
+
+  { // check that types other than size_t work as well
+    int i;
+    single_bit_pair<int*, bool> pair = {&i, true};
+    assert(pair.__get_ptr() == &i);
+    assert(pair.__get_value());
+    static_assert(std::is_same<decltype(pair.__get_value()), bool>::value, "");
+  }
+  {
+    int i;
+    single_bit_pair<int*, unsigned char> pair = {&i, 1};
+    assert(pair.__get_ptr() == &i);
+    assert(pair.__get_value() == 1);
+    static_assert(std::is_same<decltype(pair.__get_value()), unsigned char>::value, "");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/utilities/pointer_int_pair/static_asserts.verify.cpp b/libcxx/test/libcxx/utilities/pointer_int_pair/static_asserts.verify.cpp
new file mode 100644
index 0000000000000..34de1dbfed940
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/pointer_int_pair/static_asserts.verify.cpp
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <__utility/pointer_int_pair.h>
+#include <cstddef>
+
+// expected-error@*:* {{Not enough bits available for requested bit count}}
+std::__pointer_int_pair<char*, size_t, std::__integer_width(2)> ptr1; // expected-note {{here}}
+// expected-error@*:* {{_IntType has to be an integral type}}
+std::__pointer_int_pair<int*, int*, std::__integer_width(2)> ptr2; // expected-note {{here}}
+// expected-error@*:* {{__pointer_int_pair doesn't work for signed types}}
+std::__pointer_int_pair<int*, signed, std::__integer_width(2)> ptr3; // expected-note {{here}}



More information about the libcxx-commits mailing list