#pragma once // No BOM and only basic ASCII in this header, or a neko will die #include #include #include "atomic.hpp" #include "bless.hpp" namespace stx { template constexpr bool same_ptr_implicit_v = std::is_convertible_v ? PtrSame : false; template class single_ptr; template class shared_ptr; template class atomic_ptr; // Basic assumption of userspace pointer size constexpr uint c_ptr_size = 48; // Use lower 16 bits as atomic_ptr internal counter of borrowed refs (pointer itself is shifted) constexpr uint c_ref_mask = 0xffff, c_ref_size = 16; // Remaining pointer bits constexpr uptr c_ptr_mask = static_cast(-1) << c_ref_size; struct shared_counter { // Stored destructor atomic_t destroy{}; // Reference counter atomic_t refs{1}; }; template struct align_filler { }; template requires(Align > Size) struct align_filler { char dummy[Align - Size]; }; // Control block with data and reference counter template class shared_data final : align_filler { public: shared_counter m_ctr{}; T m_data; template explicit constexpr shared_data(Args&&... args) noexcept : m_data(std::forward(args)...) { } }; template class shared_data final : align_filler { public: usz m_count{}; shared_counter m_ctr{}; constexpr shared_data() noexcept = default; }; struct null_ptr_t; // Simplified unique pointer. In some cases, std::unique_ptr is preferred. // This one is shared_ptr counterpart, it has a control block with refs and deleter. // It's trivially convertible to shared_ptr, and back if refs == 1. template class single_ptr { std::remove_extent_t* m_ptr{}; shared_counter* d() const noexcept { // Shared counter, deleter, should be at negative offset return std::launder(reinterpret_cast(reinterpret_cast(m_ptr) - sizeof(shared_counter))); } template friend class single_ptr; template friend class shared_ptr; template friend class atomic_ptr; public: using element_type = std::remove_extent_t; constexpr single_ptr() noexcept = default; single_ptr(const single_ptr&) = delete; // Default constructor or null_ptr should be used instead [[deprecated("Use null_ptr")]] single_ptr(std::nullptr_t) = delete; explicit single_ptr(shared_data&, element_type* ptr) noexcept : m_ptr(ptr) { } single_ptr(single_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } template requires same_ptr_implicit_v single_ptr(single_ptr&& r) noexcept { m_ptr = r.m_ptr; r.m_ptr = nullptr; } ~single_ptr() noexcept { reset(); } single_ptr& operator=(const single_ptr&) = delete; [[deprecated("Use null_ptr")]] single_ptr& operator=(std::nullptr_t) = delete; single_ptr& operator=(single_ptr&& r) noexcept { single_ptr(std::move(r)).swap(*this); return *this; } template requires same_ptr_implicit_v single_ptr& operator=(single_ptr&& r) noexcept { single_ptr(std::move(r)).swap(*this); return *this; } void reset() noexcept { if (m_ptr) [[likely]] { const auto o = d(); ensure(o->refs == 1); o->destroy.load()(o); m_ptr = nullptr; } } void swap(single_ptr& r) noexcept { std::swap(m_ptr, r.m_ptr); } element_type* get() const noexcept { return m_ptr; } element_type& operator*() const noexcept requires(!std::is_void_v) { return *m_ptr; } element_type* operator->() const noexcept { return m_ptr; } element_type& operator[](std::ptrdiff_t idx) const noexcept requires(!std::is_void_v && std::is_array_v) { return m_ptr[idx]; } template requires(std::is_invocable_v) decltype(auto) operator()(Args&&... args) const noexcept { return std::invoke(*m_ptr, std::forward(args)...); } explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; } // "Moving" "static cast" template requires PtrSame explicit operator single_ptr() && noexcept { single_ptr r; r.m_ptr = static_cast(std::exchange(m_ptr, nullptr)); return r; } template requires same_ptr_implicit_v bool operator==(const single_ptr& r) const noexcept { return get() == r.get(); } }; #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winvalid-offsetof" #endif template requires(!std::is_unbounded_array_v && (Init || std::is_array_v) && (Init || !sizeof...(Args))) static single_ptr make_single(Args&&... args) noexcept { static_assert(offsetof(shared_data, m_data) - offsetof(shared_data, m_ctr) == sizeof(shared_counter)); using etype = std::remove_extent_t; shared_data* ptr = nullptr; if constexpr (!std::is_array_v) { ptr = new shared_data(std::forward(args)...); } else { ptr = new shared_data; if constexpr (Init && std::is_array_v) { // Weird case, destroy and reinitialize every fixed array arg (fill) for (auto& e : ptr->m_data) { e.~etype(); new (&e) etype(std::forward(args)...); } } } ptr->m_ctr.destroy.raw() = [](shared_counter* _this) noexcept { delete reinterpret_cast*>(reinterpret_cast(_this) - offsetof(shared_data, m_ctr)); }; return single_ptr(*ptr, &ptr->m_data); } template )> requires(std::is_unbounded_array_v && std::is_default_constructible_v>) static single_ptr make_single(usz count) noexcept { static_assert(sizeof(shared_data) - offsetof(shared_data, m_ctr) == sizeof(shared_counter)); using etype = std::remove_extent_t; const usz size = sizeof(shared_data) + count * sizeof(etype); std::byte* bytes = nullptr; if constexpr (Align > (__STDCPP_DEFAULT_NEW_ALIGNMENT__)) { bytes = static_cast(::operator new(size, std::align_val_t{Align})); } else { bytes = new std::byte[size]; } // Initialize control block shared_data* ptr = new (reinterpret_cast*>(bytes)) shared_data(); // Initialize array next to the control block etype* arr = reinterpret_cast(bytes + sizeof(shared_data)); if constexpr (Init) { std::uninitialized_value_construct_n(arr, count); } else { std::uninitialized_default_construct_n(arr, count); } ptr->m_count = count; ptr->m_ctr.destroy.raw() = [](shared_counter* _this) noexcept { shared_data* ptr = reinterpret_cast*>(reinterpret_cast(_this) - offsetof(shared_data, m_ctr)); std::byte* bytes = reinterpret_cast(ptr); std::destroy_n(std::launder(reinterpret_cast(bytes + sizeof(shared_data))), ptr->m_count); ptr->~shared_data(); if constexpr (Align > (__STDCPP_DEFAULT_NEW_ALIGNMENT__)) { ::operator delete[](bytes, std::align_val_t{Align}); } else { delete[] bytes; } }; return single_ptr(*ptr, std::launder(arr)); } template static single_ptr> make_single_value(T&& value) { return make_single>(std::forward(value)); } #ifndef _MSC_VER #pragma GCC diagnostic pop #endif // Simplified shared pointer template class shared_ptr { std::remove_extent_t* m_ptr{}; shared_counter* d() const noexcept { // Shared counter, deleter, should be at negative offset return std::launder(reinterpret_cast(reinterpret_cast(m_ptr) - sizeof(shared_counter))); } template friend class shared_ptr; template friend class atomic_ptr; public: using element_type = std::remove_extent_t; constexpr shared_ptr() noexcept = default; shared_ptr(const shared_ptr& r) noexcept : m_ptr(r.m_ptr) { if (m_ptr) d()->refs++; } // Default constructor or null_ptr constant should be used instead [[deprecated("Use null_ptr")]] shared_ptr(std::nullptr_t) = delete; // Not-so-aliasing constructor: emulates std::enable_shared_from_this without its overhead template friend shared_ptr make_shared_from_this(const Type* _this) noexcept; template requires same_ptr_implicit_v shared_ptr(const shared_ptr& r) noexcept { m_ptr = r.m_ptr; if (m_ptr) d()->refs++; } shared_ptr(shared_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } template requires same_ptr_implicit_v shared_ptr(shared_ptr&& r) noexcept { m_ptr = r.m_ptr; r.m_ptr = nullptr; } template requires same_ptr_implicit_v shared_ptr(single_ptr&& r) noexcept { m_ptr = r.m_ptr; r.m_ptr = nullptr; } ~shared_ptr() noexcept { reset(); } shared_ptr& operator=(const shared_ptr& r) noexcept { shared_ptr(r).swap(*this); return *this; } [[deprecated("Use null_ptr")]] shared_ptr& operator=(std::nullptr_t) = delete; template requires same_ptr_implicit_v shared_ptr& operator=(const shared_ptr& r) noexcept { shared_ptr(r).swap(*this); return *this; } shared_ptr& operator=(shared_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } template requires same_ptr_implicit_v shared_ptr& operator=(shared_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } template requires same_ptr_implicit_v shared_ptr& operator=(single_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } // Set to null void reset() noexcept { if (m_ptr) [[unlikely]] { const auto o = d(); if (!--o->refs) { o->destroy(o); } m_ptr = nullptr; } } // Converts to unique (single) ptr if reference is 1. Nullifies self on success. template requires PtrSame single_ptr try_convert_to_single_ptr() noexcept { if (const auto o = m_ptr ? d() : nullptr; o && o->refs == 1u) { // Convert last reference to single_ptr instance. single_ptr r; r.m_ptr = static_cast(std::exchange(m_ptr, nullptr)); return r; } return {}; } void swap(shared_ptr& r) noexcept { std::swap(this->m_ptr, r.m_ptr); } element_type* get() const noexcept { return m_ptr; } element_type& operator*() const noexcept requires(!std::is_void_v) { return *m_ptr; } element_type* operator->() const noexcept { return m_ptr; } element_type& operator[](std::ptrdiff_t idx) const noexcept requires(!std::is_void_v && std::is_array_v) { return m_ptr[idx]; } template requires(std::is_invocable_v) decltype(auto) operator()(Args&&... args) const noexcept { return std::invoke(*m_ptr, std::forward(args)...); } usz use_count() const noexcept { if (m_ptr) { return d()->refs; } else { return 0; } } explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; } // Basic "static cast" support template requires PtrSame explicit operator shared_ptr() const& noexcept { if (m_ptr) { d()->refs++; } shared_ptr r; r.m_ptr = static_cast(m_ptr); return r; } // "Moving" "static cast" template requires PtrSame explicit operator shared_ptr() && noexcept { shared_ptr r; r.m_ptr = static_cast(std::exchange(m_ptr, nullptr)); return r; } template requires same_ptr_implicit_v bool operator==(const shared_ptr& r) const noexcept { return get() == r.get(); } }; template requires(!std::is_unbounded_array_v && std::is_constructible_v, Args && ...>) static shared_ptr make_shared(Args&&... args) noexcept { return make_single(std::forward(args)...); } template requires(std::is_unbounded_array_v && std::is_default_constructible_v>) static shared_ptr make_shared(usz count) noexcept { return make_single(count); } template requires(std::is_constructible_v, T &&>) static shared_ptr> make_shared_value(T&& value) noexcept { return make_single_value(std::forward(value)); } // Not-so-aliasing constructor: emulates std::enable_shared_from_this without its overhead template static shared_ptr make_shared_from_this(const T* _this) noexcept { shared_ptr r; r.m_ptr = const_cast(_this); if (!_this) [[unlikely]] { return r; } // Random checks which may fail on invalid pointer ensure((reinterpret_cast(r.d()->destroy.load()) - 0x10000) >> 47 == 0); ensure((r.d()->refs++ - 1) >> 58 == 0); return r; } // Atomic simplified shared pointer template class atomic_ptr { mutable atomic_t m_val{0}; static shared_counter* d(uptr val) noexcept { return std::launder(reinterpret_cast((val >> c_ref_size) - sizeof(shared_counter))); } shared_counter* d() const noexcept { return d(m_val); } static uptr to_val(const volatile std::remove_extent_t* ptr) noexcept { return (reinterpret_cast(ptr) << c_ref_size); } static std::remove_extent_t* ptr_to(uptr val) noexcept { return reinterpret_cast*>(val >> c_ref_size); } template friend class atomic_ptr; // Helper struct to check if a type is an instance of a template template class Template> struct is_instance_of : std::false_type { }; template class Template> struct is_instance_of, Template> : std::true_type { }; template static constexpr bool is_stx_pointer = false || is_instance_of, shared_ptr>::value || is_instance_of, single_ptr>::value || is_instance_of, atomic_ptr>::value || std::is_same_v, null_ptr_t>; public: using element_type = std::remove_extent_t; using shared_type = shared_ptr; constexpr atomic_ptr() noexcept = default; // Optimized value construct template requires(true && sizeof...(Args) != 0 && !(sizeof...(Args) == 1 && (is_stx_pointer || ...)) && std::is_constructible_v) explicit atomic_ptr(Args&&... args) noexcept { shared_type r = make_single(std::forward(args)...); m_val.raw() = to_val(std::exchange(r.m_ptr, nullptr)); d()->refs.raw() += c_ref_mask; } template requires same_ptr_implicit_v atomic_ptr(const shared_ptr& r) noexcept { // Obtain a ref + as many refs as an atomic_ptr can additionally reference if (uptr rval = to_val(r.m_ptr)) { m_val.raw() = rval; d(rval)->refs += c_ref_mask + 1; } } template requires same_ptr_implicit_v atomic_ptr(shared_ptr&& r) noexcept { if (uptr rval = to_val(r.m_ptr)) { m_val.raw() = rval; d(rval)->refs += c_ref_mask; } r.m_ptr = nullptr; } template requires same_ptr_implicit_v atomic_ptr(single_ptr&& r) noexcept { if (uptr rval = to_val(r.m_ptr)) { m_val.raw() = rval; d(rval)->refs += c_ref_mask; } r.m_ptr = nullptr; } ~atomic_ptr() noexcept { const uptr v = m_val.raw(); if (v >> c_ref_size) { const auto o = d(v); if (!o->refs.sub_fetch(c_ref_mask + 1 - (v & c_ref_mask))) { o->destroy.load()(o); } } } // Optimized value assignment atomic_ptr& operator=(std::remove_cv_t value) noexcept requires(!is_stx_pointer) { shared_type r = make_single(std::move(value)); r.d()->refs.raw() += c_ref_mask; atomic_ptr old; old.m_val.raw() = m_val.exchange(to_val(std::exchange(r.m_ptr, nullptr))); return *this; } template requires same_ptr_implicit_v atomic_ptr& operator=(const shared_ptr& r) noexcept { store(r); return *this; } template requires same_ptr_implicit_v atomic_ptr& operator=(shared_ptr&& r) noexcept { store(std::move(r)); return *this; } template requires same_ptr_implicit_v atomic_ptr& operator=(single_ptr&& r) noexcept { store(std::move(r)); return *this; } void reset() noexcept { store(shared_type{}); } shared_type load() const noexcept { shared_type r; // Add reference const auto [prev, did_ref] = m_val.fetch_op([](uptr& val) { if (val >> c_ref_size) { val++; return true; } return false; }); if (!did_ref) { // Null pointer return r; } // Set referenced pointer r.m_ptr = std::launder(ptr_to(prev)); r.d()->refs++; // Dereference if still the same pointer const auto [_, did_deref] = m_val.fetch_op([prev = prev](uptr& val) { if (val >> c_ref_size == prev >> c_ref_size) { val--; return true; } return false; }); if (!did_deref) { // Otherwise fix ref count (atomic_ptr has been overwritten) r.d()->refs--; } return r; } // Atomically inspect pointer with the possibility to reference it if necessary template > RT peek_op(F op) const noexcept { shared_type r; // Add reference const auto [prev, did_ref] = m_val.fetch_op([](uptr& val) { if (val >> c_ref_size) { val++; return true; } return false; }); // Set fake unreferenced pointer if (did_ref) { r.m_ptr = std::launder(ptr_to(prev)); } // Result temp storage [[maybe_unused]] std::conditional_t, int, RT> result; // Invoke if constexpr (std::is_void_v) { std::invoke(op, std::as_const(r)); if (!did_ref) { return; } } else { result = std::invoke(op, std::as_const(r)); if (!did_ref) { return result; } } // Dereference if still the same pointer const auto [_, did_deref] = m_val.fetch_op([prev = prev](uptr& val) { if (val >> c_ref_size == prev >> c_ref_size) { val--; return true; } return false; }); if (did_deref) { // Deactivate fake pointer r.m_ptr = nullptr; } if constexpr (std::is_void_v) { return; } else { return result; } } // Create an object from variadic args // If a type needs shared_type to be constructed, std::reference_wrapper can be used template requires(true && sizeof...(Args) != 0 && !(sizeof...(Args) == 1 && (is_stx_pointer || ...)) && std::is_constructible_v) void store(Args&&... args) noexcept { shared_type r = make_single(std::forward(args)...); r.d()->refs.raw() += c_ref_mask; atomic_ptr old; old.m_val.raw() = m_val.exchange(to_val(std::exchange(r.m_ptr, nullptr))); } void store(shared_type value) noexcept { if (value.m_ptr) { // Consume value and add refs value.d()->refs += c_ref_mask; } atomic_ptr old; old.m_val.raw() = m_val.exchange(to_val(std::exchange(value.m_ptr, nullptr))); } template requires(true && sizeof...(Args) != 0 && !(sizeof...(Args) == 1 && (is_stx_pointer || ...)) && std::is_constructible_v) [[nodiscard]] shared_type exchange(Args&&... args) noexcept { shared_type r = make_single(std::forward(args)...); r.d()->refs.raw() += c_ref_mask; atomic_ptr old; old.m_val.raw() = m_val.exchange(to_val(r.m_ptr)); old.m_val.raw() += 1; r.m_ptr = std::launder(ptr_to(old.m_val)); return r; } [[nodiscard]] shared_type exchange(shared_type value) noexcept { if (value.m_ptr) { // Consume value and add refs value.d()->refs += c_ref_mask; } atomic_ptr old; old.m_val.raw() = m_val.exchange(to_val(value.m_ptr)); old.m_val.raw() += 1; value.m_ptr = std::launder(ptr_to(old.m_val)); return value; } // Ineffective [[nodiscard]] bool compare_exchange(shared_type& cmp_and_old, shared_type exch) { const uptr _old = reinterpret_cast(cmp_and_old.m_ptr); const uptr _new = reinterpret_cast(exch.m_ptr); if (exch.m_ptr) { exch.d()->refs += c_ref_mask; } atomic_ptr old; const uptr _val = m_val.fetch_op([&](uptr& val) { if (val >> c_ref_size == _old) { // Set new value val = _new << c_ref_size; } else if (val) { // Reference previous value val++; } }); if (_val >> c_ref_size == _old) { // Success (exch is consumed, cmp_and_old is unchanged) if (exch.m_ptr) { exch.m_ptr = nullptr; } // Cleanup old.m_val.raw() = _val; return true; } atomic_ptr old_exch; old_exch.m_val.raw() = to_val(std::exchange(exch.m_ptr, nullptr)); // Set to reset old cmp_and_old value old.m_val.raw() = to_val(cmp_and_old.m_ptr) | c_ref_mask; if (!_val) { return false; } // Set referenced pointer cmp_and_old.m_ptr = std::launder(ptr_to(_val)); cmp_and_old.d()->refs++; // Dereference if still the same pointer const auto [_, did_deref] = m_val.fetch_op([_val](uptr& val) { if (val >> c_ref_size == _val >> c_ref_size) { val--; return true; } return false; }); if (!did_deref) { // Otherwise fix ref count (atomic_ptr has been overwritten) cmp_and_old.d()->refs--; } return false; } // Unoptimized template requires same_ptr_implicit_v shared_type compare_and_swap(const shared_ptr& cmp, shared_type exch) { shared_type old = cmp; static_cast(compare_exchange(old, std::move(exch))); return old; } // More lightweight than compare_exchange template requires same_ptr_implicit_v bool compare_and_swap_test(const shared_ptr& cmp, shared_type exch) { const uptr _old = reinterpret_cast(cmp.m_ptr); const uptr _new = reinterpret_cast(exch.m_ptr); if (exch.m_ptr) { exch.d()->refs += c_ref_mask; } atomic_ptr old; const auto [_val, ok] = m_val.fetch_op([&](uptr& val) { if (val >> c_ref_size == _old) { // Set new value val = _new << c_ref_size; return true; } return false; }); if (ok) { // Success (exch is consumed, cmp_and_old is unchanged) exch.m_ptr = nullptr; old.m_val.raw() = _val; return true; } // Failure (return references) old.m_val.raw() = to_val(std::exchange(exch.m_ptr, nullptr)); return false; } // Unoptimized template requires same_ptr_implicit_v shared_type compare_and_swap(const single_ptr& cmp, shared_type exch) { shared_type old = cmp; static_cast(compare_exchange(old, std::move(exch))); return old; } // Supplementary template requires same_ptr_implicit_v bool compare_and_swap_test(const single_ptr& cmp, shared_type exch) { return compare_and_swap_test(reinterpret_cast&>(cmp), std::move(exch)); } // Helper utility void push_head(shared_type& next, shared_type exch) noexcept { if (exch.m_ptr) [[likely]] { // Add missing references first exch.d()->refs += c_ref_mask; } if (next.m_ptr) [[unlikely]] { // Just in case next.reset(); } atomic_ptr old; old.m_val.raw() = m_val.load(); do { // Update old head with current value next.m_ptr = std::launder(ptr_to(old.m_val.raw())); } while (!m_val.compare_exchange(old.m_val.raw(), to_val(exch.m_ptr))); // This argument is consumed (moved from) exch.m_ptr = nullptr; if (next.m_ptr) { // Compensation for `next` assignment old.m_val.raw() += 1; } } // Simple atomic load is much more effective than load(), but it's a non-owning reference T* observe() const noexcept { return std::launder(ptr_to(m_val)); } explicit constexpr operator bool() const noexcept { return m_val != 0; } template requires same_ptr_implicit_v bool is_equal(const shared_ptr& r) const noexcept { return static_cast(observe()) == r.get(); } template requires same_ptr_implicit_v bool is_equal(const single_ptr& r) const noexcept { return static_cast(observe()) == r.get(); } void wait(std::nullptr_t, atomic_wait_timeout timeout = atomic_wait_timeout::inf) { utils::bless>(&m_val)[1].wait(0, timeout); } void notify_one() { utils::bless>(&m_val)[1].notify_one(); } void notify_all() { utils::bless>(&m_val)[1].notify_all(); } }; // Some nullptr replacement for few cases constexpr struct null_ptr_t { template constexpr operator single_ptr() const noexcept { return {}; } template constexpr operator shared_ptr() const noexcept { return {}; } template constexpr operator atomic_ptr() const noexcept { return {}; } explicit constexpr operator bool() const noexcept { return false; } } null_ptr; } // namespace stx template struct std::hash> { usz operator()(const stx::single_ptr& x) const noexcept { return std::hash()(x.get()); } }; template struct std::hash> { usz operator()(const stx::shared_ptr& x) const noexcept { return std::hash()(x.get()); } }; using stx::atomic_ptr; using stx::make_shared; using stx::make_shared_value; using stx::make_single; using stx::make_single_value; using stx::null_ptr; using stx::shared_ptr; using stx::single_ptr;