diff options
| author | Nikolas Klauser <nikolasklauser@berlin.de> | 2024-06-04 09:04:47 +0200 |
|---|---|---|
| committer | Nikolas Klauser <nikolasklauser@berlin.de> | 2025-10-30 14:34:28 +0100 |
| commit | 722ab37c219793d3c92720ce5c1fede502f07e64 (patch) | |
| tree | b70a330dcf9298c4912e41b20f9ed125eb5fe271 | |
| parent | 932fa0e0871acce4f68fab504527f5b4e46f16f9 (diff) | |
[libc++] Add __pointer_int_pairusers/philnik777/spr/add_pointer_int_pair
7 files changed, 364 insertions, 0 deletions
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index de9819cf5346..65aa6f60037f 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -930,6 +930,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/__utility/pointer_int_pair.h b/libcxx/include/__utility/pointer_int_pair.h new file mode 100644 index 000000000000..eeb924694629 --- /dev/null +++ b/libcxx/include/__utility/pointer_int_pair.h @@ -0,0 +1,159 @@ +//===----------------------------------------------------------------------===// +// +// 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 <__config> +#include <__cstddef/size_t.h> +#include <__fwd/tuple.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> +#include <limits> + +#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. + +_LIBCPP_BEGIN_NAMESPACE_STD + +template <class, class = void> +struct _PointerLikeTraits; + +template <class _Tp> +struct _PointerLikeTraits<_Tp*, __enable_if_t<!is_void<_Tp>::value> > { + // This is really `__bit_log2`, but we need this to be a constant expression even in C++03, so we can't use that + static const size_t __low_bits_available = numeric_limits<size_t>::digits - 1 - __builtin_clzg(_LIBCPP_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 const 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 __integer_width : size_t {}; + +template <class _Pointer, class _IntType, __integer_width __int_bit_count> +class __pointer_int_pair { + using _PointerTraits = _PointerLikeTraits<_Pointer>; + + static const 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 const size_t __extra_bits = _PointerTraits::__low_bits_available - __int_width; + static const uintptr_t __int_mask = static_cast<uintptr_t>(1 << _PointerTraits::__low_bits_available) - 1; + static const uintptr_t __ptr_mask = ~__int_mask; + + uintptr_t __value_ = 0; + +public: + __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; + } +}; + +#ifndef _LIBCPP_CXX03_LANG + +// 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> +struct __pointer_int_pair_getter; + +template <> +struct __pointer_int_pair_getter<0> { + template <class _Pointer, class _IntType, __integer_width __int_bit_count> + static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _Pointer + __get(__pointer_int_pair<_Pointer, _IntType, __int_bit_count> __pair) { + return __pair.__get_ptr(); + } +}; + +template <> +struct __pointer_int_pair_getter<1> { + template <class _Pointer, class _IntType, __integer_width __int_bit_count> + static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _IntType + __get(__pointer_int_pair<_Pointer, _IntType, __int_bit_count> __pair) { + return __pair.__get_value(); + } +}; + +template <size_t __i, class _Pointer, class _IntType, __integer_width __int_bit_count> +_LIBCPP_HIDE_FROM_ABI typename tuple_element<__i, __pointer_int_pair<_Pointer, _IntType, __int_bit_count> >::type +get(__pointer_int_pair<_Pointer, _IntType, __int_bit_count> __pair) { + return __pointer_int_pair_getter<__i>::__get(__pair); +} + +#endif // _LIBCPP_CXX03_LANG + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___UTILITY_POINTER_INT_PAIR_H diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 11ab61d959e2..9b3767a8fdc0 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -2176,6 +2176,11 @@ module std [system] { export std.utility.piecewise_construct } 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 000000000000..a8ea5e96c3df --- /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 000000000000..090df2b47d62 --- /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 000000000000..5fc10301bb37 --- /dev/null +++ b/libcxx/test/libcxx/utilities/pointer_int_pair/pointer_int_pair.pass.cpp @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + +#include <__utility/pointer_int_pair.h> +#include <cassert> +#include <cstddef> +#include <type_traits> + +#include "test_macros.h" + +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**) { +#if TEST_STD_VER >= 11 + { // __pointer_int_pair() constructor + single_bit_pair<int*, size_t> pair = {}; + assert(pair.__get_value() == 0); + assert(pair.__get_ptr() == nullptr); + } +#endif + + { // __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(single_bit_pair<int*, size_t>(&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 TEST_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 000000000000..748a5096e0d1 --- /dev/null +++ b/libcxx/test/libcxx/utilities/pointer_int_pair/static_asserts.verify.cpp @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + +#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}} |
