mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
Homebrew atomic_ptr rewritten (util/shared_ptr.hpp)
It's analogous to C++20 atomic std::shared_ptr The following things brought into global namespace: single_ptr shared_ptr atomic_ptr make_single
This commit is contained in:
parent
bd90e3e37f
commit
b5d498ffda
@ -380,7 +380,7 @@ void cfg::_bool::from_default()
|
||||
|
||||
void cfg::string::from_default()
|
||||
{
|
||||
m_value = m_value.make(def);
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
void cfg::set_entry::from_default()
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "util/atomic.hpp"
|
||||
#include "util/shared_cptr.hpp"
|
||||
#include "util/shared_ptr.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
@ -393,7 +393,7 @@ namespace cfg
|
||||
{
|
||||
const std::string m_name;
|
||||
|
||||
stx::atomic_cptr<std::string> m_value;
|
||||
atomic_ptr<std::string> m_value;
|
||||
|
||||
public:
|
||||
std::string def;
|
||||
@ -401,7 +401,7 @@ namespace cfg
|
||||
string(node* owner, std::string name, std::string def = {}, bool dynamic = false)
|
||||
: _base(type::string, owner, name, dynamic)
|
||||
, m_name(std::move(name))
|
||||
, m_value(m_value.make(def))
|
||||
, m_value(def)
|
||||
, def(std::move(def))
|
||||
{
|
||||
}
|
||||
@ -411,7 +411,7 @@ namespace cfg
|
||||
return *m_value.load().get();
|
||||
}
|
||||
|
||||
std::pair<const std::string&, stx::shared_cptr<std::string>> get() const
|
||||
std::pair<const std::string&, shared_ptr<std::string>> get() const
|
||||
{
|
||||
auto v = m_value.load();
|
||||
|
||||
@ -440,7 +440,7 @@ namespace cfg
|
||||
|
||||
bool from_string(const std::string& value, bool /*dynamic*/ = false) override
|
||||
{
|
||||
m_value = m_value.make(value);
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
@ -2242,7 +2242,7 @@ std::string thread_ctrl::get_name_cached()
|
||||
return {};
|
||||
}
|
||||
|
||||
static thread_local stx::shared_cptr<std::string> name_cache;
|
||||
static thread_local shared_ptr<std::string> name_cache;
|
||||
|
||||
if (!_this->m_tname.is_equal(name_cache)) [[unlikely]]
|
||||
{
|
||||
@ -2254,7 +2254,7 @@ std::string thread_ctrl::get_name_cached()
|
||||
|
||||
thread_base::thread_base(native_entry entry, std::string_view name)
|
||||
: entry_point(entry)
|
||||
, m_tname(stx::shared_cptr<std::string>::make(name))
|
||||
, m_tname(make_single<std::string>(name))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include "types.h"
|
||||
#include "util/atomic.hpp"
|
||||
#include "util/shared_cptr.hpp"
|
||||
#include "util/shared_ptr.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@ -110,7 +110,7 @@ private:
|
||||
atomic_t<u64> m_sync{0};
|
||||
|
||||
// Thread name
|
||||
stx::atomic_cptr<std::string> m_tname;
|
||||
atomic_ptr<std::string> m_tname;
|
||||
|
||||
// Start thread
|
||||
void start();
|
||||
@ -191,14 +191,14 @@ public:
|
||||
// Set current thread name (not recommended)
|
||||
static void set_name(std::string_view name)
|
||||
{
|
||||
g_tls_this_thread->m_tname.store(stx::shared_cptr<std::string>::make(name));
|
||||
g_tls_this_thread->m_tname.store(make_single<std::string>(name));
|
||||
}
|
||||
|
||||
// Set thread name (not recommended)
|
||||
template <typename T>
|
||||
static void set_name(named_thread<T>& thread, std::string_view name)
|
||||
{
|
||||
static_cast<thread_base&>(thread).m_tname.store(stx::shared_cptr<std::string>::make(name));
|
||||
static_cast<thread_base&>(thread).m_tname.store(make_single<std::string>(name));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -34,7 +34,6 @@ target_include_directories(rpcs3_emu
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
../util/atomic.cpp
|
||||
../util/atomic2.cpp
|
||||
../util/shared_cptr.cpp
|
||||
../util/fixed_typemap.cpp
|
||||
../util/logs.cpp
|
||||
../util/yaml.cpp
|
||||
|
@ -915,7 +915,7 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3
|
||||
, joiner(detached != 0 ? ppu_join_status::detached : ppu_join_status::joinable)
|
||||
, entry_func(param.entry)
|
||||
, start_time(get_guest_system_time())
|
||||
, ppu_tname(stx::shared_cptr<std::string>::make(name))
|
||||
, ppu_tname(make_single<std::string>(name))
|
||||
{
|
||||
gpr[1] = stack_addr + stack_size - ppu_stack_start_offset;
|
||||
|
||||
@ -1020,7 +1020,7 @@ void ppu_thread::fast_call(u32 addr, u32 rtoc)
|
||||
{
|
||||
const auto _this = static_cast<ppu_thread*>(get_current_cpu_thread());
|
||||
|
||||
static thread_local stx::shared_cptr<std::string> name_cache;
|
||||
static thread_local shared_ptr<std::string> name_cache;
|
||||
|
||||
if (!_this->ppu_tname.is_equal(name_cache)) [[unlikely]]
|
||||
{
|
||||
|
@ -215,7 +215,7 @@ public:
|
||||
const char* last_function{}; // Sticky copy of current_function, is not cleared on function return
|
||||
|
||||
// Thread name
|
||||
stx::atomic_cptr<std::string> ppu_tname;
|
||||
atomic_ptr<std::string> ppu_tname;
|
||||
|
||||
u64 last_ftsc = 0;
|
||||
u64 last_ftime = 0;
|
||||
|
@ -1572,7 +1572,7 @@ void spu_thread::cpu_task()
|
||||
{
|
||||
const auto cpu = static_cast<spu_thread*>(get_current_cpu_thread());
|
||||
|
||||
static thread_local stx::shared_cptr<std::string> name_cache;
|
||||
static thread_local shared_ptr<std::string> name_cache;
|
||||
|
||||
if (!cpu->spu_tname.is_equal(name_cache)) [[unlikely]]
|
||||
{
|
||||
@ -1692,7 +1692,7 @@ spu_thread::spu_thread(lv2_spu_group* group, u32 index, std::string_view name, u
|
||||
, group(group)
|
||||
, option(option)
|
||||
, lv2_id(lv2_id)
|
||||
, spu_tname(stx::shared_cptr<std::string>::make(name))
|
||||
, spu_tname(make_single<std::string>(name))
|
||||
{
|
||||
if (g_cfg.core.spu_decoder == spu_decoder_type::asmjit)
|
||||
{
|
||||
|
@ -747,7 +747,7 @@ public:
|
||||
const u32 lv2_id; // The actual id that is used by syscalls
|
||||
|
||||
// Thread name
|
||||
stx::atomic_cptr<std::string> spu_tname;
|
||||
atomic_ptr<std::string> spu_tname;
|
||||
|
||||
std::unique_ptr<class spu_recompiler_base> jit; // Recompiler instance
|
||||
|
||||
|
@ -548,7 +548,7 @@ error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr<char>
|
||||
const auto pname = name.get_ptr();
|
||||
|
||||
// Make valid name
|
||||
auto _name = stx::shared_cptr<std::string>::make(pname, std::find(pname, pname + max_size, '\0'));
|
||||
auto _name = make_single<std::string>(pname, std::find(pname, pname + max_size, '\0'));
|
||||
|
||||
// thread_ctrl name is not changed (TODO)
|
||||
sys_ppu_thread.warning(u8"sys_ppu_thread_rename(): Thread renamed to “%s”", *_name);
|
||||
|
@ -130,9 +130,6 @@
|
||||
<ClCompile Include="util\cereal.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\shared_cptr.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\fixed_typemap.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@ -778,7 +775,7 @@
|
||||
<ClInclude Include="restore_new.h" />
|
||||
<ClInclude Include="rpcs3_version.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="util\shared_cptr.hpp" />
|
||||
<ClInclude Include="util\shared_ptr.hpp" />
|
||||
<ClInclude Include="util\typeindices.hpp" />
|
||||
<ClInclude Include="util\yaml.hpp" />
|
||||
</ItemGroup>
|
||||
|
@ -878,9 +878,6 @@
|
||||
<ClCompile Include="util\cereal.cpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\shared_cptr.cpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\fixed_typemap.cpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClCompile>
|
||||
@ -944,9 +941,6 @@
|
||||
<ClCompile Include="util\logs.cpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="util\shared_cptr.cpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
|
||||
<Filter>Emu\Audio\FAudio</Filter>
|
||||
</ClCompile>
|
||||
@ -1837,7 +1831,7 @@
|
||||
<ClInclude Include="util\logs.hpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\shared_cptr.hpp">
|
||||
<ClInclude Include="util\shared_ptr.hpp">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\typeindices.hpp">
|
||||
|
@ -1,73 +0,0 @@
|
||||
#include "shared_cptr.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
stx::atomic_base::ptr_type stx::atomic_base::ref_halve() const noexcept
|
||||
{
|
||||
ptr_type v = val_load();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!(v & c_ref_mask))
|
||||
{
|
||||
// Nullptr or depleted reference pool
|
||||
return 0;
|
||||
}
|
||||
else if (val_compare_exchange(v, (v & ~c_ref_mask) | (v & c_ref_mask) >> 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return acquired references (rounded towards zero)
|
||||
return (v & ~c_ref_mask) | ((v & c_ref_mask) - ((v & c_ref_mask) >> 1) - 1);
|
||||
}
|
||||
|
||||
stx::atomic_base::ptr_type stx::atomic_base::ref_load() const noexcept
|
||||
{
|
||||
ptr_type v = val_load();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!(v & c_ref_mask))
|
||||
{
|
||||
if (v == 0)
|
||||
{
|
||||
// Null pointer
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Busy wait
|
||||
std::this_thread::yield();
|
||||
v = val_load();
|
||||
}
|
||||
else if (val_compare_exchange(v, v - 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtained 1 reference from the atomic pointer
|
||||
return v & ~c_ref_mask;
|
||||
}
|
||||
|
||||
void stx::atomic_base::ref_fix(stx::atomic_base::ptr_type& _old) const noexcept
|
||||
{
|
||||
ptr_type old = _old & ~c_ref_mask;
|
||||
ptr_type v = val_load();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if ((v & ~c_ref_mask) != old || (v & c_ref_mask) == c_ref_mask)
|
||||
{
|
||||
// Can't return a reference to the original atomic pointer, so keep it
|
||||
_old += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (val_compare_exchange(v, v + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,493 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace stx
|
||||
{
|
||||
template <typename T>
|
||||
class shared_data;
|
||||
|
||||
template <typename T>
|
||||
class unique_data;
|
||||
|
||||
// Common internal layout
|
||||
class atomic_base
|
||||
{
|
||||
public:
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
using ptr_type = long long;
|
||||
|
||||
static const long long c_ref_init = 0x10000;
|
||||
static const ptr_type c_ref_mask = 0xffff;
|
||||
static const ptr_type c_ptr_mask = ~0;
|
||||
static const ptr_type c_ptr_shift = 16;
|
||||
static const auto c_ptr_align = alignof(long long);
|
||||
#else
|
||||
using ptr_type = unsigned long long;
|
||||
|
||||
static const long long c_ref_init = 0x10;
|
||||
static const ptr_type c_ref_mask = 0xf;
|
||||
static const ptr_type c_ptr_mask = ~c_ref_mask;
|
||||
static const ptr_type c_ptr_shift = 0;
|
||||
static const auto c_ptr_align = 16;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// Combined borrowed refcounter and object pointer
|
||||
mutable ptr_type m_val;
|
||||
|
||||
template <typename T, bool Const>
|
||||
friend class atomic_cptr;
|
||||
|
||||
constexpr atomic_base() noexcept
|
||||
: m_val(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit constexpr atomic_base(ptr_type val) noexcept
|
||||
: m_val(val)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
explicit atomic_base(shared_data<T>* ptr) noexcept
|
||||
: m_val(reinterpret_cast<ptr_type>(ptr) << c_ptr_shift)
|
||||
{
|
||||
if (ptr)
|
||||
{
|
||||
// Fixup reference counter
|
||||
m_val |= (ptr->m_ref_count - 1) & c_ref_mask;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
shared_data<T>* ptr_get() const noexcept
|
||||
{
|
||||
return reinterpret_cast<shared_data<T>*>(val_load() >> c_ptr_shift & c_ptr_mask);
|
||||
}
|
||||
|
||||
ptr_type val_load() const noexcept
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return const_cast<const volatile ptr_type&>(m_val);
|
||||
#else
|
||||
return __atomic_load_n(&m_val, __ATOMIC_SEQ_CST);
|
||||
#endif
|
||||
}
|
||||
|
||||
ptr_type val_exchange(ptr_type val) noexcept
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedExchange64(&m_val, val);
|
||||
#else
|
||||
return __atomic_exchange_n(&m_val, val, __ATOMIC_SEQ_CST);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool val_compare_exchange(ptr_type& expected, ptr_type val) const noexcept
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
ptr_type x = expected;
|
||||
expected = _InterlockedCompareExchange64(&m_val, val, x);
|
||||
return x == expected;
|
||||
#else
|
||||
return __atomic_compare_exchange_n(&m_val, &expected, val, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Load, acquiring half of the references from the pointer
|
||||
ptr_type ref_halve() const noexcept;
|
||||
|
||||
// Load, actively acquiring a reference from the pointer
|
||||
ptr_type ref_load() const noexcept;
|
||||
|
||||
// Return acquired reference if applicable
|
||||
void ref_fix(ptr_type& old) const noexcept;
|
||||
};
|
||||
|
||||
// Control block with data and reference counter
|
||||
template <typename T>
|
||||
class alignas(atomic_base::c_ptr_align) shared_data final
|
||||
{
|
||||
public:
|
||||
// Immutable data
|
||||
T m_data;
|
||||
|
||||
// Main reference counter
|
||||
long long m_ref_count = atomic_base::c_ref_init;
|
||||
|
||||
template <typename... Args>
|
||||
explicit constexpr shared_data(Args&&... args) noexcept
|
||||
: m_data(std::forward<Args>(args)...)
|
||||
{
|
||||
static_assert(offsetof(shared_data, m_data) == 0);
|
||||
}
|
||||
|
||||
// Atomic access to the ref counter
|
||||
long long fetch_add(long long value)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _InterlockedExchangeAdd64(&m_ref_count, value);
|
||||
#else
|
||||
return __atomic_fetch_add(&m_ref_count, value, __ATOMIC_SEQ_CST);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// Unique ownership pointer to mutable data, suitable for conversion to shared_cptr
|
||||
template <typename T>
|
||||
class unique_data : protected atomic_base
|
||||
{
|
||||
using cb = atomic_base;
|
||||
|
||||
public:
|
||||
constexpr unique_data() noexcept
|
||||
: atomic_base()
|
||||
{
|
||||
}
|
||||
|
||||
explicit unique_data(shared_data<T>* data) noexcept
|
||||
: atomic_base(data)
|
||||
{
|
||||
}
|
||||
|
||||
unique_data(const unique_data&) = delete;
|
||||
|
||||
unique_data(unique_data&& r) noexcept
|
||||
: atomic_base(r.m_val)
|
||||
{
|
||||
r.m_val = 0;
|
||||
}
|
||||
|
||||
unique_data& operator=(const unique_data&) = delete;
|
||||
|
||||
unique_data& operator=(unique_data&& r) noexcept
|
||||
{
|
||||
unique_data(std::move(r)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~unique_data()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
delete get();
|
||||
this->m_val = 0;
|
||||
}
|
||||
|
||||
void swap(unique_data& r) noexcept
|
||||
{
|
||||
std::swap(this->m_val, r.m_val);
|
||||
}
|
||||
|
||||
[[nodiscard]] shared_data<T>* release() noexcept
|
||||
{
|
||||
auto result = this->ptr_get<T>();
|
||||
this->m_val = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
T* get() const noexcept
|
||||
{
|
||||
return &this->ptr_get<T>()->m_data;
|
||||
}
|
||||
|
||||
T& operator*() const noexcept
|
||||
{
|
||||
return *get();
|
||||
}
|
||||
|
||||
T* operator->() const noexcept
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return this->m_val != 0;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static unique_data make(Args&&... args) noexcept
|
||||
{
|
||||
return unique_data(new shared_data<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
// Shared pointer to immutable data
|
||||
template <typename T, bool Const = true>
|
||||
class shared_cptr : protected atomic_base
|
||||
{
|
||||
using cb = atomic_base;
|
||||
|
||||
protected:
|
||||
using atomic_base::m_val;
|
||||
|
||||
public:
|
||||
constexpr shared_cptr() noexcept
|
||||
: atomic_base()
|
||||
{
|
||||
}
|
||||
|
||||
explicit shared_cptr(shared_data<T>* data) noexcept
|
||||
: atomic_base(data)
|
||||
{
|
||||
}
|
||||
|
||||
shared_cptr(const shared_cptr& r) noexcept
|
||||
: atomic_base()
|
||||
{
|
||||
if (const auto old_val = r.val_load())
|
||||
{
|
||||
// Try to take references from the former pointer first
|
||||
if (const auto new_val = r.ref_halve())
|
||||
{
|
||||
this->m_val = new_val;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it fails, fallback to the control block and take max amount
|
||||
this->m_val = old_val | cb::c_ref_mask;
|
||||
this->ptr_get<T>()->fetch_add(cb::c_ref_init);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_cptr(shared_cptr&& r) noexcept
|
||||
: atomic_base(r.m_val)
|
||||
{
|
||||
r.m_val = 0;
|
||||
}
|
||||
|
||||
shared_cptr(unique_data<T> r) noexcept
|
||||
: atomic_base(r.m_val)
|
||||
{
|
||||
r.m_val = 0;
|
||||
}
|
||||
|
||||
shared_cptr& operator=(const shared_cptr& r) noexcept
|
||||
{
|
||||
shared_cptr(r).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
shared_cptr& operator=(shared_cptr&& r) noexcept
|
||||
{
|
||||
shared_cptr(std::move(r)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~shared_cptr()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
// Set to null
|
||||
void reset() noexcept
|
||||
{
|
||||
if (const auto pdata = this->ptr_get<T>())
|
||||
{
|
||||
// Remove references
|
||||
const auto count = (cb::c_ref_mask & this->m_val) + 1;
|
||||
|
||||
this->m_val = 0;
|
||||
|
||||
if (pdata->fetch_add(-count) == count)
|
||||
{
|
||||
// Destroy if reference count became zero
|
||||
delete pdata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly return reference(s) to specified shared pointer instance
|
||||
void reset_hint(const shared_cptr& r) noexcept
|
||||
{
|
||||
// TODO
|
||||
reset();
|
||||
}
|
||||
|
||||
// Set to null, possibly returning a unique instance of shared data
|
||||
unique_data<T> release_unique() noexcept
|
||||
{
|
||||
if (const auto pdata = this->ptr_get<T>())
|
||||
{
|
||||
// Remove references
|
||||
const auto count = (cb::c_ref_mask & this->m_val) + 1;
|
||||
|
||||
this->m_val = 0;
|
||||
|
||||
if (pdata->fetch_add(-count) == count)
|
||||
{
|
||||
// Return data if reference count became zero
|
||||
pdata->m_ref_count = cb::c_ref_init;
|
||||
return unique_data<T>(pdata);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void swap(shared_cptr& r) noexcept
|
||||
{
|
||||
std::swap(this->m_val, r.m_val);
|
||||
}
|
||||
|
||||
std::conditional_t<Const, const T*, T*> get() const noexcept
|
||||
{
|
||||
return &this->ptr_get<T>()->m_data;
|
||||
}
|
||||
|
||||
std::conditional_t<Const, const T&, T&> operator*() const noexcept
|
||||
{
|
||||
return *get();
|
||||
}
|
||||
|
||||
std::conditional_t<Const, const T*, T*> operator->() const noexcept
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return this->val_load() != 0;
|
||||
}
|
||||
|
||||
bool operator ==(const shared_cptr& rhs) const noexcept
|
||||
{
|
||||
return get() == rhs.get();
|
||||
}
|
||||
|
||||
bool operator !=(const shared_cptr& rhs) const noexcept
|
||||
{
|
||||
return get() != rhs.get();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static shared_cptr make(Args&&... args) noexcept
|
||||
{
|
||||
return shared_cptr(new shared_data<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using shared_mptr = shared_cptr<T, false>;
|
||||
|
||||
// Atomic shared pointer to immutable data
|
||||
template <typename T, bool Const = true>
|
||||
class atomic_cptr : shared_cptr<T, Const>
|
||||
{
|
||||
using cb = atomic_base;
|
||||
using base = shared_cptr<T, Const>;
|
||||
|
||||
public:
|
||||
constexpr atomic_cptr() noexcept
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
atomic_cptr(base value)
|
||||
: base(std::move(value))
|
||||
{
|
||||
if (const auto diff = cb::c_ref_mask - (this->m_val & cb::c_ref_mask); this->m_val && diff)
|
||||
{
|
||||
// Obtain max amount of references
|
||||
this->template ptr_get<T>()->fetch_add(diff);
|
||||
this->m_val |= cb::c_ref_mask;
|
||||
}
|
||||
}
|
||||
|
||||
atomic_cptr(const atomic_cptr&) = delete;
|
||||
|
||||
atomic_cptr& operator=(const atomic_cptr&) = delete;
|
||||
|
||||
atomic_cptr& operator=(base value) noexcept
|
||||
{
|
||||
exchange(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void store(base value) noexcept
|
||||
{
|
||||
exchange(std::move(value));
|
||||
}
|
||||
|
||||
base load() const noexcept
|
||||
{
|
||||
base result;
|
||||
static_cast<cb&>(result).m_val = this->ref_load();
|
||||
|
||||
if (result)
|
||||
{
|
||||
// TODO: obtain max-1 and try to return as much as possible
|
||||
this->template ptr_get<T>()->fetch_add(1);
|
||||
this->ref_fix(static_cast<cb&>(result).m_val);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
operator base() const noexcept
|
||||
{
|
||||
return load();
|
||||
}
|
||||
|
||||
base exchange(base value) noexcept
|
||||
{
|
||||
static_cast<cb&>(value).m_val = this->val_exchange(static_cast<cb&>(value).m_val);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Simple atomic load is much more effective than load(), but it's a non-owning reference
|
||||
const void* observe() const noexcept
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return observe() != nullptr;
|
||||
}
|
||||
|
||||
bool is_equal(const base& r) const noexcept
|
||||
{
|
||||
return observe() == r.get();
|
||||
}
|
||||
|
||||
// bool compare_and_swap_test_weak(const base& expected, base value) noexcept
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
// bool compare_and_swap_test(const base& expected, base value) noexcept
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
// bool compare_exchange_weak(base& expected, base value) noexcept
|
||||
// {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
// bool compare_exchange(base& expected, base value) noexcept
|
||||
// {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
// void atomic_op();
|
||||
|
||||
using base::make;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using atomic_mptr = atomic_cptr<T, false>;
|
||||
}
|
711
rpcs3/util/shared_ptr.hpp
Normal file
711
rpcs3/util/shared_ptr.hpp
Normal file
@ -0,0 +1,711 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "atomic.hpp"
|
||||
|
||||
namespace stx
|
||||
{
|
||||
// TODO
|
||||
template <typename T, typename U>
|
||||
constexpr bool is_same_ptr_v = true;
|
||||
|
||||
template <typename T, typename U>
|
||||
constexpr bool is_same_ptr_cast_v = std::is_convertible_v<U, T> && is_same_ptr_v<T, U>;
|
||||
|
||||
template <typename T>
|
||||
class single_ptr;
|
||||
|
||||
template <typename T>
|
||||
class shared_ptr;
|
||||
|
||||
template <typename T>
|
||||
class atomic_ptr;
|
||||
|
||||
// Basic assumption of userspace pointer size
|
||||
constexpr uint c_ptr_size = 47;
|
||||
|
||||
// Use lower 17 bits as atomic_ptr internal refcounter (pointer is shifted)
|
||||
constexpr uint c_ref_mask = 0x1ffff, c_ref_size = 17;
|
||||
|
||||
struct shared_counter
|
||||
{
|
||||
// Stored destructor
|
||||
void (*destroy)(void* ptr);
|
||||
|
||||
// Reference counter
|
||||
atomic_t<std::size_t> refs{0};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class unique_data
|
||||
{
|
||||
public:
|
||||
T data;
|
||||
|
||||
template <typename... Args>
|
||||
explicit constexpr unique_data(Args&&... args) noexcept
|
||||
: data(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class unique_data<T[]>
|
||||
{
|
||||
std::size_t count;
|
||||
};
|
||||
|
||||
// Control block with data and reference counter
|
||||
template <typename T>
|
||||
class alignas(T) shared_data final : public shared_counter, public unique_data<T>
|
||||
{
|
||||
public:
|
||||
using data_type = T;
|
||||
|
||||
template <typename... Args>
|
||||
explicit constexpr shared_data(Args&&... args) noexcept
|
||||
: shared_counter{}
|
||||
, unique_data<T>(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class alignas(T) shared_data<T[]> final : public shared_counter, public unique_data<T>
|
||||
{
|
||||
public:
|
||||
using data_type = T;
|
||||
};
|
||||
|
||||
// Simplified unique pointer (well, not simplified, std::unique_ptr is preferred)
|
||||
template <typename T>
|
||||
class single_ptr
|
||||
{
|
||||
std::remove_extent_t<T>* m_ptr{};
|
||||
|
||||
shared_data<T>* d() const noexcept
|
||||
{
|
||||
// Shared counter, deleter, should be at negative offset
|
||||
return std::launder(static_cast<shared_data<T>*>(reinterpret_cast<unique_data<T>*>(m_ptr)));
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
friend class shared_ptr;
|
||||
|
||||
template <typename U>
|
||||
friend class atomic_ptr;
|
||||
|
||||
public:
|
||||
using pointer = T*;
|
||||
|
||||
using element_type = std::remove_extent_t<T>;
|
||||
|
||||
constexpr single_ptr() noexcept = default;
|
||||
|
||||
constexpr single_ptr(std::nullptr_t) noexcept {}
|
||||
|
||||
single_ptr(const single_ptr&) = delete;
|
||||
|
||||
single_ptr(single_ptr&& r) noexcept
|
||||
: m_ptr(r.m_ptr)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
single_ptr(single_ptr<U>&& r) noexcept
|
||||
: m_ptr(r.m_ptr)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
~single_ptr()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
single_ptr& operator=(const single_ptr&) = delete;
|
||||
|
||||
single_ptr& operator=(std::nullptr_t) noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
single_ptr& operator=(single_ptr&& r) noexcept
|
||||
{
|
||||
m_ptr = r.m_ptr;
|
||||
r.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
single_ptr& operator=(single_ptr<U>&& r) noexcept
|
||||
{
|
||||
m_ptr = r.m_ptr;
|
||||
r.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
if (m_ptr) [[likely]]
|
||||
{
|
||||
d()->destroy(d());
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(single_ptr& r) noexcept
|
||||
{
|
||||
std::swap(m_ptr, r.m_ptr);
|
||||
}
|
||||
|
||||
element_type* get() const noexcept
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
decltype(auto) operator*() const noexcept
|
||||
{
|
||||
if constexpr (std::is_void_v<element_type>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_type* operator->() const noexcept
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
decltype(auto) operator[](std::ptrdiff_t idx) const noexcept
|
||||
{
|
||||
if constexpr (std::is_void_v<element_type>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if constexpr (std::is_array_v<T>)
|
||||
{
|
||||
return m_ptr[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool Init = true, typename... Args>
|
||||
static std::enable_if_t<!(std::is_unbounded_array_v<T>) && (Init || !sizeof...(Args)), single_ptr<T>> make_single(Args&&... args) noexcept
|
||||
{
|
||||
shared_data<T>* ptr = nullptr;
|
||||
|
||||
if constexpr (Init)
|
||||
{
|
||||
ptr = new shared_data<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = new shared_data<T>;
|
||||
}
|
||||
|
||||
ptr->destroy = [](void* p)
|
||||
{
|
||||
delete static_cast<shared_data<T>*>(p);
|
||||
};
|
||||
|
||||
single_ptr<T> r;
|
||||
reinterpret_cast<std::remove_extent_t<T>*&>(r) = &ptr->data;
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename T, bool Init = true>
|
||||
static std::enable_if_t<std::is_unbounded_array_v<T>, single_ptr<T>> make_single(std::size_t count) noexcept
|
||||
{
|
||||
const std::size_t size = sizeof(shared_data<T>) + count * sizeof(std::remove_extent_t<T>);
|
||||
|
||||
std::byte* bytes = nullptr;
|
||||
|
||||
if constexpr (alignof(std::remove_extent_t<T>) > (__STDCPP_DEFAULT_NEW_ALIGNMENT__))
|
||||
{
|
||||
bytes = new (std::align_val_t{alignof(std::remove_extent_t<T>)}) std::byte[size];
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes = new std::byte[size];
|
||||
}
|
||||
|
||||
// Initialize control block
|
||||
shared_data<T>* ptr = new (reinterpret_cast<shared_data<T>*>(bytes)) shared_data<T>();
|
||||
|
||||
// Initialize array next to the control block
|
||||
T arr = reinterpret_cast<T>(ptr + 1);
|
||||
|
||||
if constexpr (Init)
|
||||
{
|
||||
std::uninitialized_value_construct_n(arr, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::uninitialized_default_construct_n(arr, count);
|
||||
}
|
||||
|
||||
ptr->m_count = count;
|
||||
|
||||
ptr->destroy = [](void* p)
|
||||
{
|
||||
shared_data<T>* ptr = static_cast<shared_data<T>*>(p);
|
||||
|
||||
std::destroy_n(std::launder(reinterpret_cast<T>(ptr + 1)), ptr->m_count);
|
||||
|
||||
ptr->~shared_data<T>();
|
||||
|
||||
if constexpr (alignof(std::remove_extent_t<T>) > (__STDCPP_DEFAULT_NEW_ALIGNMENT__))
|
||||
{
|
||||
::operator delete[](reinterpret_cast<std::byte*>(p), std::align_val_t{alignof(std::remove_extent_t<T>)});
|
||||
}
|
||||
else
|
||||
{
|
||||
delete[] reinterpret_cast<std::byte*>(p);
|
||||
}
|
||||
};
|
||||
|
||||
single_ptr<T> r;
|
||||
reinterpret_cast<std::remove_extent_t<T>*&>(r) = std::launder(arr);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Simplified shared pointer
|
||||
template <typename T>
|
||||
class shared_ptr
|
||||
{
|
||||
std::remove_extent_t<T>* m_ptr{};
|
||||
|
||||
shared_data<T>* d() const noexcept
|
||||
{
|
||||
// Shared counter, deleter, should be at negative offset
|
||||
return std::launder(static_cast<shared_data<T>*>(reinterpret_cast<unique_data<T>*>(m_ptr)));
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
friend class atomic_ptr;
|
||||
|
||||
public:
|
||||
using pointer = T*;
|
||||
|
||||
using element_type = std::remove_extent_t<T>;
|
||||
|
||||
constexpr shared_ptr() noexcept = default;
|
||||
|
||||
constexpr shared_ptr(std::nullptr_t) noexcept {}
|
||||
|
||||
shared_ptr(const shared_ptr& r) noexcept
|
||||
: m_ptr(r.m_ptr)
|
||||
{
|
||||
if (m_ptr)
|
||||
d()->refs++;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr(const shared_ptr<U>& 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 <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr(shared_ptr<U>&& r) noexcept
|
||||
: m_ptr(r.m_ptr)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr(single_ptr<U>&& r) noexcept
|
||||
: m_ptr(r.m_ptr)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
~shared_ptr()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
shared_ptr& operator=(const shared_ptr& r) noexcept
|
||||
{
|
||||
shared_ptr(r).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr& operator=(const shared_ptr<U>& 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 <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr& operator=(shared_ptr<U>&& r) noexcept
|
||||
{
|
||||
shared_ptr(std::move(r)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
shared_ptr& operator=(single_ptr<U>&& r) noexcept
|
||||
{
|
||||
shared_ptr(std::move(r)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set to null
|
||||
void reset() noexcept
|
||||
{
|
||||
if (m_ptr && !--d()->refs) [[unlikely]]
|
||||
{
|
||||
d()->destroy(d());
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts to unique (single) ptr if reference is 1, otherwise returns null. Nullifies self.
|
||||
explicit operator single_ptr<T>() && noexcept
|
||||
{
|
||||
if (m_ptr && !--d()->refs)
|
||||
{
|
||||
d()->refs.release(1);
|
||||
return {std::move(*this)};
|
||||
}
|
||||
|
||||
m_ptr = nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
void swap(shared_ptr& r) noexcept
|
||||
{
|
||||
std::swap(this->m_ptr, r.m_ptr);
|
||||
}
|
||||
|
||||
element_type* get() const noexcept
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
decltype(auto) operator*() const noexcept
|
||||
{
|
||||
if constexpr (std::is_void_v<element_type>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_type* operator->() const noexcept
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
decltype(auto) operator[](std::ptrdiff_t idx) const noexcept
|
||||
{
|
||||
if constexpr (std::is_void_v<element_type>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if constexpr (std::is_array_v<T>)
|
||||
{
|
||||
return m_ptr[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t use_count() const noexcept
|
||||
{
|
||||
if (m_ptr)
|
||||
{
|
||||
return d()->refs;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr_v<U, T>>>
|
||||
explicit operator shared_ptr<U>() const noexcept
|
||||
{
|
||||
if (m_ptr)
|
||||
{
|
||||
d()->refs++;
|
||||
}
|
||||
|
||||
shared_ptr<U> r;
|
||||
r.m_ptr = m_ptr;
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool Init = true, typename... Args>
|
||||
static std::enable_if_t<!std::is_unbounded_array_v<T> && (!Init || !sizeof...(Args)), shared_ptr<T>> make_shared(Args&&... args) noexcept
|
||||
{
|
||||
return make_single<T, Init>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, bool Init = true>
|
||||
static std::enable_if_t<std::is_unbounded_array_v<T>, shared_ptr<T>> make_shared(std::size_t count) noexcept
|
||||
{
|
||||
return make_single<T, Init>(count);
|
||||
}
|
||||
|
||||
// Atomic simplified shared pointer
|
||||
template <typename T>
|
||||
class atomic_ptr
|
||||
{
|
||||
mutable atomic_t<uptr> m_val{0};
|
||||
|
||||
static shared_data<T>* d(uptr val)
|
||||
{
|
||||
return std::launder(static_cast<shared_data<T>*>(reinterpret_cast<unique_data<T>*>(val >> c_ref_size)));
|
||||
}
|
||||
|
||||
shared_data<T>* d() const noexcept
|
||||
{
|
||||
return d(m_val);
|
||||
}
|
||||
|
||||
public:
|
||||
using pointer = T*;
|
||||
|
||||
using element_type = std::remove_extent_t<T>;
|
||||
|
||||
using shared_type = shared_ptr<T>;
|
||||
|
||||
constexpr atomic_ptr() noexcept = default;
|
||||
|
||||
constexpr atomic_ptr(std::nullptr_t) noexcept {}
|
||||
|
||||
explicit atomic_ptr(T value) noexcept
|
||||
{
|
||||
auto r = make_single<T>(std::move(value));
|
||||
m_val = reinterpret_cast<uptr>(std::exchange(r.m_ptr, nullptr)) << c_ref_size;
|
||||
d()->refs += c_ref_mask;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr(const shared_ptr<U>& r) noexcept
|
||||
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
|
||||
{
|
||||
// Obtain a ref + as many refs as an atomic_ptr can additionally reference
|
||||
if (m_val)
|
||||
d()->refs += c_ref_mask + 1;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr(shared_ptr<U>&& r) noexcept
|
||||
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
|
||||
if (m_val)
|
||||
d()->refs += c_ref_mask;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr(single_ptr<U>&& r) noexcept
|
||||
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
|
||||
{
|
||||
r.m_ptr = nullptr;
|
||||
|
||||
if (m_val)
|
||||
d()->refs += c_ref_mask;
|
||||
}
|
||||
|
||||
~atomic_ptr()
|
||||
{
|
||||
const uptr v = m_val.raw();
|
||||
|
||||
if (v && !d(v)->refs.sub_fetch(c_ref_mask + 1 - (v & c_ref_mask)))
|
||||
{
|
||||
d(v)->destroy(d(v));
|
||||
}
|
||||
}
|
||||
|
||||
atomic_ptr& operator=(T value) noexcept
|
||||
{
|
||||
// TODO: does it make sense?
|
||||
store(make_single<T>(std::move(value)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr& operator=(const shared_ptr<U>& r) noexcept
|
||||
{
|
||||
store(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr& operator=(shared_ptr<U>&& r) noexcept
|
||||
{
|
||||
store(std::move(r));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>>
|
||||
atomic_ptr& operator=(single_ptr<U>&& r) noexcept
|
||||
{
|
||||
store(std::move(r));
|
||||
return *this;
|
||||
}
|
||||
|
||||
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(reinterpret_cast<element_type*>(prev >> c_ref_size));
|
||||
|
||||
// Dereference if same pointer
|
||||
m_val.fetch_op([prev = prev](uptr& val)
|
||||
{
|
||||
if (val >> c_ref_size == prev >> c_ref_size)
|
||||
{
|
||||
val--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void store(T value) noexcept
|
||||
{
|
||||
store(make_single<T>(std::move(value)));
|
||||
}
|
||||
|
||||
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(reinterpret_cast<uptr>(std::exchange(value.m_ptr, nullptr)) << c_ref_size);
|
||||
}
|
||||
|
||||
[[nodiscard]] shared_type exchange(shared_type value) noexcept
|
||||
{
|
||||
atomic_ptr old;
|
||||
|
||||
if (value.m_ptr)
|
||||
{
|
||||
// Consume value and add refs
|
||||
value.d()->refs += c_ref_mask;
|
||||
old.m_val.raw() += 1;
|
||||
}
|
||||
|
||||
old.m_val.raw() += m_val.exchange(reinterpret_cast<uptr>(std::exchange(value.m_ptr, nullptr)) << c_ref_size);
|
||||
|
||||
shared_type r;
|
||||
r.m_ptr = old.m_val >> c_ref_size;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Simple atomic load is much more effective than load(), but it's a non-owning reference
|
||||
const volatile void* observe() const noexcept
|
||||
{
|
||||
return reinterpret_cast<const volatile void*>(m_val >> c_ref_size);
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept
|
||||
{
|
||||
return m_val != 0;
|
||||
}
|
||||
|
||||
bool is_equal(const shared_ptr<T>& r) const noexcept
|
||||
{
|
||||
return observe() == r.get();
|
||||
}
|
||||
|
||||
bool is_equal(const single_ptr<T>& r) const noexcept
|
||||
{
|
||||
return observe() == r.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <typename T>
|
||||
void swap(stx::single_ptr<T>& lhs, stx::single_ptr<T>& rhs) noexcept
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void swap(stx::shared_ptr<T>& lhs, stx::shared_ptr<T>& rhs) noexcept
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
using stx::single_ptr;
|
||||
using stx::shared_ptr;
|
||||
using stx::atomic_ptr;
|
||||
using stx::make_single;
|
Loading…
Reference in New Issue
Block a user