diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index b40f586161e62..02d83c13fb5fd 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -527,6 +527,7 @@ set(files __iterator/size.h __iterator/sortable.h __iterator/static_bounded_iter.h + __iterator/static_packed_bounded_iter.h __iterator/unreachable_sentinel.h __iterator/wrap_iter.h __locale diff --git a/libcxx/include/__iterator/static_packed_bounded_iter.h b/libcxx/include/__iterator/static_packed_bounded_iter.h new file mode 100644 index 0000000000000..f094ab511a6b9 --- /dev/null +++ b/libcxx/include/__iterator/static_packed_bounded_iter.h @@ -0,0 +1,254 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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__STATIC_PACKED_BOUNDED_ITER_H +#define _LIBCPP__STATIC_PACKED_BOUNDED_ITER_H + +#include <__assert> +#include <__bit/bit_cast.h> +#include <__bit/countr.h> +#include <__compare/ordering.h> +#include <__compare/three_way_comparable.h> +#include <__config> +#include <__cstddef/size_t.h> +#include <__iterator/concepts.h> +#include <__iterator/incrementable_traits.h> +#include <__iterator/iterator_traits.h> +#include <__type_traits/is_constructible.h> +#include <__type_traits/is_pointer.h> + +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 26 + +// static_packed_bounded_iter is a bounded, contiguous iterator that is aware of its container's (compile-time) maximum +// capacity. It reuses the bottom unused bits of a pointer to keep track of its current position. +// This only applies if the container's maximum range can fit inside (2 ^ available_bits) - 1 + +_LIBCPP_BEGIN_NAMESPACE_STD + +consteval bool __range_fits_in_alignment(size_t __alignment, size_t __num_elems) { + size_t __bits = std::countr_zero(__alignment); + + // Example: For alignof(T) == 4, we have two bits free, which has a range of 0-3. We need to + // reserve one for the end position, so __num_elems must be < 3. + size_t __allowed_range = (1 << __bits) - 1; + return __allowed_range > __num_elems; +} + +template + requires(is_pointer_v<_Ptr> && std::__range_fits_in_alignment(_LIBCPP_ALIGNOF(iter_value_t<_Ptr>), _RangeCapacity)) +class __static_packed_bounded_iterator { +public: + using iterator_category = iterator_traits<_Ptr>::iterator_category; + using iterator_concept = contiguous_iterator_tag; + using difference_type = iter_difference_t<_Ptr>; + using pointer = iterator_traits<_Ptr>::pointer; + using reference = iter_reference_t<_Ptr>; + using value_type = iter_value_t<_Ptr>; + +private: + static constexpr uintptr_t __count_mask_ = (1 << std::countr_zero(_LIBCPP_ALIGNOF(value_type))) - 1; + static constexpr uintptr_t __ptr_mask_ = ~__count_mask_; + + union { + pointer __ptr_; + alignas(pointer) uintptr_t __data_; + }; + + uintptr_t __count() const { return __data_ & __count_mask_; } + + constexpr _Ptr __current() const { + if consteval { + return __ptr_; + } else { + return std::bit_cast(__data_ & __ptr_mask_) + __count(); + } + } + + constexpr void __update(difference_type __n) { + if consteval { + __ptr_ += __n; + } else { + __data_ += __n; + } + } + + constexpr explicit __static_packed_bounded_iterator(_Ptr __p) noexcept : __ptr_(__p) { + if !consteval { + _LIBCPP_ASSERT_INTERNAL( + (__data_ & __count_mask_) == 0, "__static_packed_bounded_iterator: Expected alignment bits of ptr to be 0"); + } + } + +public: + template + friend constexpr auto __make_static_packed_bounded_iter(_Ptr2) noexcept; + + constexpr __static_packed_bounded_iterator() + requires is_default_constructible_v<_Ptr> + = default; + + template + requires is_convertible_v<_Ptr2, _Ptr> + constexpr __static_packed_bounded_iterator(const __static_packed_bounded_iterator<_Ptr2, _Tag, _RangeCapacity>& __y) + : __ptr_(__y.__ptr_) {} + + [[nodiscard]] constexpr decltype(auto) operator*() const noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __count() != _RangeCapacity, + "__static_packed_bounded_iterator::operator*: Attempt to dereference an iterator at the end"); + } + + return *(__current()); + } + + constexpr decltype(auto) operator->() const noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __count() != _RangeCapacity, + "__static_packed_bounded_iterator::operator->: Attempt to dereference an iterator at the end"); + } + + return __current(); + } + + constexpr __static_packed_bounded_iterator& operator++() noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __count() != _RangeCapacity, + "__static_packed_bounded_iterator::operator++: Attempt to advance an iterator past the end"); + } + + __update(1); + + return *this; + } + + constexpr __static_packed_bounded_iterator operator++(int) noexcept { + __static_packed_bounded_iterator __tmp(*this); + ++*this; + return __tmp; + } + + constexpr __static_packed_bounded_iterator& operator--() noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __count() != 0u, + "__static_packed_bounded_iterator::operator--: Attempt to rewind an iterator past the start"); + } + + __update(-1); + + return *this; + } + + constexpr __static_packed_bounded_iterator operator--(int) noexcept { + __static_packed_bounded_iterator __tmp(*this); + --*this; + return __tmp; + } + + constexpr __static_packed_bounded_iterator& operator+=(difference_type __n) noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + (static_cast(__count()) + __n) >= 0, + "__static_packed_bounded_iterator::operator+=: Attempt to rewind an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + static_cast(__count() + __n) <= _RangeCapacity, + "__static_packed_bounded_iterator::operator+=: Attempt to advance an iterator past the end"); + } + + __update(__n); + + return *this; + } + + constexpr __static_packed_bounded_iterator& operator-=(difference_type __n) noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + (static_cast(__count()) - __n) >= 0, + "__static_packed_bounded_iterator::operator-=: Attempt to rewind an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + static_cast(__count() - __n) <= _RangeCapacity, + "__static_packed_bounded_iterator::operator-=: Attempt to advance an iterator past the end"); + } + + __update(-__n); + + return *this; + } + + [[nodiscard]] constexpr decltype(auto) operator[](difference_type __n) const noexcept { + if !consteval { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + (static_cast(__count()) + __n) >= 0, + "__static_packed_bounded_iterator::operator[]: Attempt to index an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + static_cast(__count() + __n) < _RangeCapacity, + "__static_packed_bounded_iterator::operator[]: Attempt to index an iterator at or past the end"); + } + return *(*this + __n); + } + + friend constexpr bool + operator==(const __static_packed_bounded_iterator& __x, const __static_packed_bounded_iterator& __y) noexcept { + return __x.__current() == __y.__current(); + } + + friend constexpr auto + operator<=>(const __static_packed_bounded_iterator& __x, const __static_packed_bounded_iterator& __y) noexcept { + return __x.__current() <=> __y.__current(); + } + + [[nodiscard]] friend constexpr __static_packed_bounded_iterator + operator+(const __static_packed_bounded_iterator& __i, difference_type __n) noexcept { + auto __tmp = __i; + __tmp += __n; + return __tmp; + } + + [[nodiscard]] friend constexpr __static_packed_bounded_iterator + operator+(difference_type __n, const __static_packed_bounded_iterator& __i) noexcept { + auto __tmp = __i; + __tmp += __n; + return __tmp; + } + + [[nodiscard]] friend constexpr __static_packed_bounded_iterator + operator-(const __static_packed_bounded_iterator& __i, difference_type __n) noexcept { + auto __tmp = __i; + __tmp -= __n; + return __tmp; + } + + [[nodiscard]] friend constexpr difference_type + operator-(const __static_packed_bounded_iterator& __x, const __static_packed_bounded_iterator& __y) noexcept { + return difference_type(__x.__current() - __y.__current()); + } +}; + +template +constexpr auto __make_static_packed_bounded_iter(_Ptr __p) noexcept { + return __static_packed_bounded_iterator<_Ptr, _Tag, _RangeCapacity>(__p); +} + +_LIBCPP_END_NAMESPACE_STD + +#endif +_LIBCPP_POP_MACROS +#endif diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 39b4e0bb986c6..f8cf79d81245c 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1565,6 +1565,7 @@ module std { module size { header "__iterator/size.h" } module sortable { header "__iterator/sortable.h" } module static_bounded_iter { header "__iterator/static_bounded_iter.h" } + module static_packed_bounded_iter { header "__iterator/static_packed_bounded_iter.h" } module unreachable_sentinel { header "__iterator/unreachable_sentinel.h" } module capacity_aware_iterator { header "__iterator/capacity_aware_iterator.h" } module wrap_iter { header "__iterator/wrap_iter.h" } diff --git a/libcxx/test/libcxx/iterators/static_packed_bounded_iter/arithmetic.pass.cpp b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/arithmetic.pass.cpp new file mode 100644 index 0000000000000..3b9fb92cc31fe --- /dev/null +++ b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/arithmetic.pass.cpp @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// 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++26 + +// template +// class __static_packed_bounded_iter; +// +// Arithmetic operators + +#include <__iterator/static_packed_bounded_iter.h> +#include +#include +#include + +#include "test_macros.h" + +struct TEST_ALIGNAS(8) Foo { + int x; + + constexpr Foo(int y) : x(y) {} + + constexpr bool operator==(const Foo& rhs) const { return x == rhs.x; } +}; + +template +TEST_CONSTEXPR_CXX14 bool tests() { + Foo array[] = {40, 41, 42, 43, 44}; + Foo* b = array + 0; + Foo* e = array + 5; + using BoundedIter = std::__static_packed_bounded_iterator; + // ++it + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter& result = ++iter; + assert(&result == &iter); + assert(*iter == 41); + } + // it++ + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter result = iter++; + assert(*result == 40); + assert(*iter == 41); + } + // --it + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)) + 3; + BoundedIter& result = --iter; + assert(&result == &iter); + assert(*iter == 42); + } + // it-- + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)) + 3; + BoundedIter result = iter--; + assert(*result == 43); + assert(*iter == 42); + } + // it += n + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter& result = (iter += 3); + assert(&result == &iter); + assert(*iter == 43); + } + // it + n + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter result = iter + 3; + assert(*iter == 40); + assert(*result == 43); + } + // n + it + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter result = 3 + iter; + assert(*iter == 40); + assert(*result == 43); + } + // it -= n + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)) + 3; + BoundedIter& result = (iter -= 3); + assert(&result == &iter); + assert(*iter == 40); + } + // it - n + { + BoundedIter iter = std::__make_static_packed_bounded_iter(Iter(b)) + 3; + BoundedIter result = iter - 3; + assert(*iter == 43); + assert(*result == 40); + } + // it - it + { + BoundedIter iter1 = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter iter2 = std::__make_static_packed_bounded_iter(Iter(e)); + std::ptrdiff_t result = iter2 - iter1; + assert(result == 5); + } + + return true; +} + +int main(int, char**) { + tests(); + static_assert(tests()); + + return 0; +} diff --git a/libcxx/test/libcxx/iterators/static_packed_bounded_iter/assert.pass.cpp b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/assert.pass.cpp new file mode 100644 index 0000000000000..2ca740570418c --- /dev/null +++ b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/assert.pass.cpp @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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++26 +// UNSUPPORTED: libcpp-hardening-mode=none + +// template +// class __static_packed_bounded_iterator; + +// Check assert failure if advancing, rewinding past start or past end, and dereferencing if at end + +#include <__iterator/static_packed_bounded_iter.h> +#include + +#include "check_assertion.h" +#include "test_macros.h" + +struct TEST_ALIGNAS(4) Foo { + char x; + Foo(int y) : x(y) {} +}; + +template +void test() { + Ty arr[] = {1, 2}; + + constexpr long sz = std::size(arr); + + using BoundedIter = std::__static_packed_bounded_iterator; + + BoundedIter it = std::__make_static_packed_bounded_iter(Ptr(arr)); + + TEST_LIBCPP_ASSERT_FAILURE( + it--, "__static_packed_bounded_iterator::operator--: Attempt to rewind an iterator past the start"); + TEST_LIBCPP_ASSERT_FAILURE( + --it, "__static_packed_bounded_iterator::operator--: Attempt to rewind an iterator past the start"); + + TEST_LIBCPP_ASSERT_FAILURE( + it += -1, "__static_packed_bounded_iterator::operator+=: Attempt to rewind an iterator past the start"); + TEST_LIBCPP_ASSERT_FAILURE( + it += (sz + 1), "__static_packed_bounded_iterator::operator+=: Attempt to advance an iterator past the end"); + + TEST_LIBCPP_ASSERT_FAILURE( + it -= 1, "__static_packed_bounded_iterator::operator-=: Attempt to rewind an iterator past the start"); + TEST_LIBCPP_ASSERT_FAILURE( + it -= -(sz + 1), "__static_packed_bounded_iterator::operator-=: Attempt to advance an iterator past the end"); + + TEST_LIBCPP_ASSERT_FAILURE( + it[sz], "__static_packed_bounded_iterator::operator[]: Attempt to index an iterator at or past the end"); + TEST_LIBCPP_ASSERT_FAILURE( + it[-1], "__static_packed_bounded_iterator::operator[]: Attempt to index an iterator past the start"); + + it += sz; + + TEST_LIBCPP_ASSERT_FAILURE( + it++, "__static_packed_bounded_iterator::operator++: Attempt to advance an iterator past the end"); + TEST_LIBCPP_ASSERT_FAILURE( + ++it, "__static_packed_bounded_iterator::operator++: Attempt to advance an iterator past the end"); + + TEST_LIBCPP_ASSERT_FAILURE( + *it, "__static_packed_bounded_iterator::operator*: Attempt to dereference an iterator at the end"); + TEST_LIBCPP_ASSERT_FAILURE( + it.operator->(), "__static_packed_bounded_iterator::operator->: Attempt to dereference an iterator at the end"); +} + +int main(int, char**) { + test(); + + return 0; +} diff --git a/libcxx/test/libcxx/iterators/static_packed_bounded_iter/comparison.pass.cpp b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/comparison.pass.cpp new file mode 100644 index 0000000000000..ffe1fad5b26f6 --- /dev/null +++ b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/comparison.pass.cpp @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// 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++26 + +// template +// class __static_packed_bounded_iter; +// +// Comparison operators + +#include <__iterator/static_packed_bounded_iter.h> +#include +#include +#include +#include + +#include "test_macros.h" + +struct alignas(4) Foo { + int x; + constexpr Foo(int y) : x(y) {} +}; + +template +TEST_CONSTEXPR_CXX14 bool tests() { + Foo array[] = {0, 1}; + Foo* b = array + 0; + using BoundedIter = std::__static_packed_bounded_iterator; + BoundedIter const iter1 = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter const iter2 = + std::__make_static_packed_bounded_iter(Iter(b)) + 2; + + // operator== + { + assert(iter1 == iter1); + assert(!(iter1 == iter2)); + } + // operator!= + { + assert(iter1 != iter2); + assert(!(iter1 != iter1)); + } + // operator< + { + assert(iter1 < iter2); + assert(!(iter2 < iter1)); + assert(!(iter1 < iter1)); + } + // operator> + { + assert(iter2 > iter1); + assert(!(iter1 > iter2)); + assert(!(iter1 > iter1)); + } + // operator<= + { + assert(iter1 <= iter2); + assert(!(iter2 <= iter1)); + assert(iter1 <= iter1); + } + // operator>= + { + assert(iter2 >= iter1); + assert(!(iter1 >= iter2)); + assert(iter1 >= iter1); + } + +#if TEST_STD_VER >= 20 + // P1614 + std::same_as decltype(auto) r1 = iter1 <=> iter2; + assert(r1 == std::strong_ordering::less); +#endif + + return true; +} + +int main(int, char**) { + tests(); + static_assert(tests(), ""); + + return 0; +} diff --git a/libcxx/test/libcxx/iterators/static_packed_bounded_iter/dereference.pass.cpp b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/dereference.pass.cpp new file mode 100644 index 0000000000000..be05946c62cc0 --- /dev/null +++ b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/dereference.pass.cpp @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// template +// class __static_packed_bounded_iter; +// +// Dereference and indexing operators + +// REQUIRES: std-at-least-c++26 + +#include <__iterator/static_packed_bounded_iter.h> +#include +#include + +#include "test_macros.h" + +struct alignas(8) Foo { + char x; + constexpr bool operator==(Foo const& other) const { return x == other.x; } +}; + +template +TEST_CONSTEXPR_CXX14 bool tests() { + Foo array[] = {Foo{40}, Foo{41}, Foo{42}, Foo{43}, Foo{44}}; + Foo* b = array + 0; + + using BoundedIter = std::__static_packed_bounded_iterator; + + BoundedIter const iter1 = std::__make_static_packed_bounded_iter(Iter(b)); + BoundedIter const iter2 = + std::__make_static_packed_bounded_iter(Iter(b)) + 5; + + // operator* + assert(*iter1 == Foo{40}); + // operator-> + assert(iter1->x == 40); + // operator[] + assert(iter1[0] == Foo{40}); + assert(iter1[1] == Foo{41}); + assert(iter1[2] == Foo{42}); + assert(iter2[-1] == Foo{44}); + assert(iter2[-2] == Foo{43}); + + return true; +} + +int main(int, char**) { + tests(); + static_assert(tests(), ""); + + return 0; +} diff --git a/libcxx/test/libcxx/iterators/static_packed_bounded_iter/types.compile.pass.cpp b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/types.compile.pass.cpp new file mode 100644 index 0000000000000..d3987ffbd0c6a --- /dev/null +++ b/libcxx/test/libcxx/iterators/static_packed_bounded_iter/types.compile.pass.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// 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++26 + +// template +// class __packed_static_bounded_iterator; +// +// Nested types + +#include <__iterator/static_packed_bounded_iter.h> +#include +#include +#include + +using Iter = std::__static_packed_bounded_iterator; + +static_assert(std::is_same_v, ""); +static_assert(std::is_same_v, ""); +static_assert(std::is_same_v, ""); +static_assert(std::is_same_v, ""); +static_assert(std::is_same_v, ""); +static_assert(std::is_same_v, ""); + +static_assert(sizeof(Iter) == sizeof(void*));