From 270ba25a01bfa19f3511bd70f994cfbd6473863d Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Tue, 6 Feb 2024 08:15:34 +0100 Subject: compat/intrusive-ptr: WIP --- compat/intrusive-ptr.hpp | 326 +++++++++++++++++++++++++++++++++++++++++++++++ compat/intrusive-ptr.inl | 8 ++ test/app.hpp | 1 + test/intrusive-ptr.cpp | 169 ++++++++++++++++++++++++ test/main.cpp | 1 + 5 files changed, 505 insertions(+) create mode 100644 compat/intrusive-ptr.hpp create mode 100644 compat/intrusive-ptr.inl create mode 100644 test/intrusive-ptr.cpp diff --git a/compat/intrusive-ptr.hpp b/compat/intrusive-ptr.hpp new file mode 100644 index 00000000..0d09a366 --- /dev/null +++ b/compat/intrusive-ptr.hpp @@ -0,0 +1,326 @@ +#pragma once +#include "compat/assert.hpp" +#include +#include + +namespace floormat::iptr { struct non_atomic_u32_tag{}; } + +namespace floormat { + +template class basic_iptr; +template using local_iptr = basic_iptr; + +} // namespace floormat + +namespace floormat::iptr { + +template struct refcount_traits; +template struct refcount_access; +template struct refcount_ops; + +template +struct refcount_traits +{ + refcount_traits() = delete; + using counter_type = uint32_t; + using size_type = size_t; + using access_t = refcount_access; +}; + +template +struct refcount_ops +{ + refcount_ops() = delete; + + using traits = refcount_traits; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = typename traits::access_t; + +#ifndef FM_NO_DEBUG + using Tref = T*&; +#else + using Tref = T*; +#endif + + static constexpr inline auto incr(T* ptr) noexcept -> size_type; + static constexpr inline auto decr(Tref ptr) noexcept -> size_type; + static constexpr inline auto count(T* ptr) noexcept -> size_type; + static constexpr inline void init_to_1(T* ptr) noexcept; +}; + +template +struct refcount_access +{ + using counter_type = refcount_traits::counter_type; + refcount_access() = delete; + + static constexpr auto access(T* ptr) noexcept -> counter_type&; + template static constexpr Y* checked_cast(const T* ptr) noexcept; // todo! +}; + +template +constexpr auto refcount_ops::incr(T* ptr) noexcept -> size_type +{ + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{++ctr}; + return ret; +} + +template +constexpr auto refcount_ops::decr(Tref ptr) noexcept -> size_type +{ + static_assert(std::is_nothrow_destructible_v); + + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{--ctr}; + if (ret == size_type{0}) + { + delete ptr; + if constexpr(std::is_reference_v) + ptr = reinterpret_cast((uintptr_t)-1); + } + return ret; +} + +template +constexpr auto refcount_ops::count(T* ptr) noexcept -> size_type +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)ptr != (void*)-1); + if (!ptr) [[unlikely]] + return 0; + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{ctr}; + return ret; +} + +template +constexpr void refcount_ops::init_to_1(T* ptr) noexcept +{ + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} == size_type{0}); + ctr = size_type{1}; +} + +template +consteval bool check_traits() +{ + using traits = ::floormat::iptr::refcount_traits; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = ::floormat::iptr::refcount_access; + using ops_t = ::floormat::iptr::refcount_ops; + + static_assert(requires (counter_type& ctr) { + requires std::is_arithmetic_v; + requires std::is_unsigned_v; + requires std::is_fundamental_v; + requires noexcept(size_type{ctr}); + { size_t{size_type{ctr}} }; + { ctr = size_type{1} }; + { size_type{++ctr} }; + { size_type{--ctr} }; + requires noexcept(size_t{size_type{ctr}}); + requires noexcept(ctr = size_type{1}); + requires noexcept(size_type{++ctr}); + requires noexcept(size_type{--ctr}); + requires std::is_nothrow_destructible_v; + requires sizeof(access_t) != 0; + requires sizeof(ops_t) != 0; + }); + + return true; +} + +} // namespace floormat::iptr + + + +// ----- macros ----- + +#define fm_template template +#define fm_basic_iptr basic_iptr + +#ifndef FM_IPTR_USE_CONSTRUCT_AT // todo! finish it +#define FM_IPTR_USE_CONSTRUCT_AT 0 +#endif + +// ----- basic_iptr ----- + +namespace floormat { + +template +class basic_iptr final +{ + static_assert(!std::is_reference_v); + static_assert(!std::is_const_v); // todo, modify only refcount + + using traits = ::floormat::iptr::refcount_traits; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = ::floormat::iptr::refcount_access; + using ops_t = ::floormat::iptr::refcount_ops; + static_assert(::floormat::iptr::check_traits()); + + T* _ptr{nullptr}; + + // todo use std::construct_at as it has a constexpr exception. + // ...but it requires :( — maybe use an ifdef? + +public: + constexpr basic_iptr() noexcept; + constexpr basic_iptr(std::nullptr_t) noexcept; + constexpr basic_iptr& operator=(std::nullptr_t) noexcept; + explicit constexpr basic_iptr(T* ptr) noexcept; + constexpr basic_iptr(basic_iptr&& other) noexcept; + constexpr basic_iptr(const basic_iptr& other) noexcept; + constexpr basic_iptr& operator=(basic_iptr&& other) noexcept; + constexpr basic_iptr& operator=(const basic_iptr& other) noexcept; + constexpr ~basic_iptr() noexcept; + + template requires std::is_constructible_v + constexpr basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward) + noexcept(std::is_nothrow_constructible_v); + + constexpr inline void reset() noexcept; + constexpr void reset(T* ptr) noexcept; // todo casts + constexpr void swap(basic_iptr& other) noexcept; + constexpr T* get() const noexcept; + constexpr inline T& operator*() const noexcept; + constexpr inline T* operator->() const noexcept; + + constexpr size_type use_count() const noexcept; + explicit constexpr operator bool() const noexcept; + + template + friend constexpr bool operator==(const basic_iptr& a, const basic_iptr& b) noexcept; + + template + friend constexpr std::strong_ordering operator<=>(const basic_iptr& a, const basic_iptr& b) noexcept; +}; + +// ----- constructors ----- + +template constexpr fm_basic_iptr::basic_iptr() noexcept = default; +template constexpr fm_basic_iptr::basic_iptr(std::nullptr_t) noexcept {} + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(std::nullptr_t) noexcept +{ + if (_ptr) + { + ops_t::decr(_ptr); + _ptr = nullptr; + } + return *this; +} + +fm_template constexpr fm_basic_iptr::basic_iptr(fm_basic_iptr&& other) noexcept: _ptr{other._ptr} { other._ptr = nullptr; } + +fm_template constexpr fm_basic_iptr::basic_iptr(const fm_basic_iptr& other) noexcept: + _ptr{other._ptr} +{ + if (_ptr) + ops_t::incr(_ptr); +} + +fm_template constexpr fm_basic_iptr::basic_iptr(T* ptr) noexcept +{ + ops_t::init_to_1(ptr); +} + +// ----- destructor ----- + +fm_template constexpr fm_basic_iptr::~basic_iptr() noexcept { reset(); } + +// ----- assignment operators ----- + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(fm_basic_iptr&& other) noexcept +{ + if (_ptr != other._ptr) + { + reset(); + _ptr = other._ptr; + other._ptr = nullptr; + } + return *this; +} + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(const fm_basic_iptr& other) noexcept +{ +#ifdef __CLION_IDE__ + if (&other == this) + return *this; +#endif + if (other._ptr != _ptr) [[likely]] + { + if (_ptr) + ops_t::decr(_ptr); + _ptr = other._ptr; + if (_ptr) + ops_t::incr(_ptr); + } + return *this; +} + +fm_template template +requires std::is_constructible_v +constexpr fm_basic_iptr::basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward) +noexcept(std::is_nothrow_constructible_v): + _ptr{new T{Utility::forward(args...)}} +{ + ops_t::init_to_1(_ptr); +} + +fm_template constexpr void fm_basic_iptr::reset() noexcept { if (_ptr) ops_t::decr(_ptr); } +fm_template constexpr void fm_basic_iptr::reset(T* ptr) noexcept { reset(); _ptr = ptr; } +fm_template constexpr void fm_basic_iptr::swap(basic_iptr& other) noexcept { auto p = _ptr; _ptr = other._ptr; other._ptr = p; } + +fm_template constexpr T* fm_basic_iptr::get() const noexcept +{ + fm_debug_assert((void*)_ptr != (void*)-1); // NOLINT(*-no-int-to-ptr) + return _ptr; +} + +fm_template constexpr T& fm_basic_iptr::operator*() const noexcept { return *get(); } +fm_template constexpr T* fm_basic_iptr::operator->() const noexcept { return get(); } + +fm_template constexpr auto fm_basic_iptr::use_count() const noexcept -> size_type +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)_ptr != (void*)-1); + return ops_t::count(_ptr); +} + +fm_template constexpr fm_basic_iptr::operator bool() const noexcept +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)_ptr != (void*)-1); + return _ptr != nullptr; +} + +fm_template constexpr bool operator==(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept +{ + if (!CORRADE_CONSTEVAL) + { + fm_debug_assert((void*)a._ptr != (void*)-1); + fm_debug_assert((void*)b._ptr != (void*)-1); + } + return a._ptr == b._ptr; +} + +fm_template constexpr std::strong_ordering operator<=>(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept +{ + return a._ptr <=> b._ptr; +} + +#undef fm_template +#undef fm_basic_iptr + +} // namespace floormat diff --git a/compat/intrusive-ptr.inl b/compat/intrusive-ptr.inl new file mode 100644 index 00000000..b8d40e3b --- /dev/null +++ b/compat/intrusive-ptr.inl @@ -0,0 +1,8 @@ +#pragma once +#include "intrusive-ptr.hpp" + +namespace floormat { + + + +} // namespace floormat diff --git a/test/app.hpp b/test/app.hpp index fc0bc90c..78794cd3 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -39,6 +39,7 @@ struct test_app final : private FM_APPLICATION static void test_wall_atlas(); static void test_wall_atlas2(); static void test_raycast(); + static void test_intrusive_ptr(); static void zzz_test_misc(); }; } // namespace floormat diff --git a/test/intrusive-ptr.cpp b/test/intrusive-ptr.cpp new file mode 100644 index 00000000..c37d5b2b --- /dev/null +++ b/test/intrusive-ptr.cpp @@ -0,0 +1,169 @@ +#include "app.hpp" +#include "compat/intrusive-ptr.hpp" +#include "compat/defs.hpp" + +#ifdef __CLION_IDE__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma ide diagnostic ignored "performance-unnecessary-copy-initialization" +#endif + +namespace floormat { + +namespace { struct Test2 { int val = 0; uint32_t _counter = 0; }; } + +template<> +constexpr auto +iptr::refcount_access::access(Test2* ptr) noexcept -> counter_type& +{ + return ptr->_counter; +} + +namespace { + +struct non_copyable +{ + int value = 0; + explicit constexpr non_copyable(DirectInitT) {} + explicit constexpr non_copyable(DirectInitT, int value) : value{value} {} + fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT(non_copyable); + fm_DECLARE_DELETED_COPY_ASSIGNMENT(non_copyable); +}; + +struct S { // todo fm_assert_equal + int instances; + unsigned allocs, frees; + bool operator==(const S&) const = default; +} s = {}; + +struct Test1 +{ + friend struct iptr::refcount_access; + Test1(non_copyable nc) noexcept : value{nc.value} { s.instances++; s.allocs++; } + ~Test1() noexcept { s.instances--; fm_assert(s.instances >= 0); s.frees++; } + + int value = 0; + +private: + iptr::refcount_traits::counter_type counter{0}; +}; + +} // namespace + +template<> +constexpr auto iptr::refcount_access::access(Test1* ptr) noexcept -> counter_type& +{ + return ptr->counter; +} + +template class basic_iptr; + +namespace { + +void test_copy() +{ + using myptr = local_iptr; + + s = {}; + + { (void)myptr{nullptr}; + fm_assert(s == S{}); + (void)myptr{}; + fm_assert(s == S{}); + { Test1 t1{non_copyable{DirectInit}}; } + fm_assert(s != S{}); + fm_assert(s == S{0, 1, 1}); + + { Test1 t1{non_copyable{DirectInit}}; + fm_assert(s == S{1, 2, 1}); + } fm_assert(s == S{0, 2, 2}); + } + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit}}; + fm_assert(s == S{1, 3, 2}); + auto b = a; + fm_assert(s == S{1, 3, 2}); + fm_assert(b.get() == a.get()); + } fm_assert(s == S{0, 3, 3}); + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit}}; + fm_assert(s == S{1, 4, 3}); + a = {}; + fm_assert(s == S{0, 4, 4}); + } fm_assert(s == S{0, 4, 4}); + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}}; + auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}}; + { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}}; + auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}}; + } + fm_assert(a.use_count() == 1); + fm_assert(a->value == 1); + fm_assert(b->value == 2); + fm_assert(s == S{2, 8, 6}); + b = a; + fm_assert(b.use_count() == 2); + fm_assert(a.use_count() == 2); + b = nullptr; + fm_assert(b.use_count() == 0); + fm_assert(a.use_count() == 1); + fm_assert(s == S{1, 8, 7}); + fm_assert(a->value == 1); + } fm_assert(s == S{0, 8, 8}); +} + +#define fm_assert_free() do { fm_assert(s.allocs == s.frees); fm_assert(s.instances == 0); } while (false) + +void test_move() +{ + using myptr = local_iptr; + using Utility::move; + + fm_assert_free(); + s = {}; + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}}; + auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}}; + fm_assert(s == S{2, 2, 0}); + { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}}; + auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}}; + fm_assert(s == S{4, 4, 0}); + } fm_assert(s == S{2, 4, 2}); + + { a = move(b); + fm_assert(s == S{1, 4, 3}); + fm_assert(!b); + fm_assert(a); + fm_assert(b.use_count() == 0); + fm_assert(a.use_count() == 1); + } + } fm_assert(s == S{0, 4, 4}); +} + +constexpr bool test_cexpr() // todo +{ + using myptr = local_iptr; + using Utility::move; + + // construct + auto foo1 = myptr{}; + auto foo2 = myptr{nullptr}; + + fm_assert(foo1.use_count() == 0); + fm_assert(foo2.use_count() == 0); + foo1 = move(foo2); + fm_assert(foo1.use_count() == 0); + fm_assert(foo2.use_count() == 0); + + return true; +} + +} // namespace + +void test_app::test_iptr() +{ + static_assert(test_cexpr()); + test_copy(); + test_move(); +} + +} // namespace floormat diff --git a/test/main.cpp b/test/main.cpp index 3b77de46..7eb2b6af 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -28,6 +28,7 @@ int test_app::exec() test_entity(); test_math(); test_hash(); + test_intrusive_ptr(); test_loader(); test_bitmask(); test_wall_atlas(); -- cgit v1.2.3