Files
snmalloc/src/ds/aba.h
Matthew Parkinson a9cfc3a2b4 Various minor changes to aid compiling with std14 (#182)
These changes make the code compile in clang10 with -std14.
2020-05-07 15:02:31 +01:00

193 lines
4.3 KiB
C++

#pragma once
#include "bits.h"
/**
* This file contains an abstraction of ABA protection. This API should be
* implementable with double-word compare and exchange or with load-link
* store conditional.
*
* We provide a lock based implementation as a backup for other platforms
* without appropriate intrinsics.
*/
namespace snmalloc
{
#ifndef NDEBUG
// LL/SC typically can only perform one operation at a time
// check this on other platforms using a thread_local.
inline thread_local bool operation_in_flight = false;
#endif
// The !(defined(GCC_NOT_CLANG) && defined(OPEN_ENCLAVE)) is required as
// GCC is outputing a function call to libatomic, rather than just the x86
// instruction, this causes problems for linking later. For this case
// fall back to locked implementation.
#if defined(PLATFORM_IS_X86) && \
!(defined(GCC_NOT_CLANG) && defined(OPEN_ENCLAVE))
template<typename T, Construction c = RequiresInit>
class ABA
{
public:
struct alignas(2 * sizeof(std::size_t)) Linked
{
T* ptr;
uintptr_t aba;
};
struct Independent
{
std::atomic<T*> ptr;
std::atomic<uintptr_t> aba;
};
static_assert(
sizeof(Linked) == sizeof(Independent),
"Expecting identical struct sizes in union");
static_assert(
sizeof(Linked) == (2 * sizeof(std::size_t)),
"Expecting ABA to be the size of two pointers");
private:
union
{
alignas(2 * sizeof(std::size_t)) std::atomic<Linked> linked;
Independent independent;
};
public:
ABA()
{
if constexpr (c == RequiresInit)
init(nullptr);
}
void init(T* x)
{
independent.ptr.store(x, std::memory_order_relaxed);
independent.aba.store(0, std::memory_order_relaxed);
}
struct Cmp;
Cmp read()
{
# ifndef NDEBUG
if (operation_in_flight)
error("Only one inflight ABA operation at a time is allowed.");
operation_in_flight = true;
# endif
return Cmp{{independent.ptr.load(std::memory_order_relaxed),
independent.aba.load(std::memory_order_relaxed)},
this};
}
struct Cmp
{
Linked old;
ABA* parent;
T* ptr()
{
return old.ptr;
}
bool store_conditional(T* value)
{
# if defined(_MSC_VER) && defined(SNMALLOC_VA_BITS_64)
auto result = _InterlockedCompareExchange128(
(volatile __int64*)parent,
(__int64)(old.aba + (uintptr_t)1),
(__int64)value,
(__int64*)&old);
# else
# if defined(__GNUC__) && defined(SNMALLOC_VA_BITS_64) && \
!defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
#error You must compile with -mcx16 to enable 16-byte atomic compare and swap.
# endif
Linked xchg{value, old.aba + 1};
std::atomic<Linked>& addr = parent->linked;
auto result = addr.compare_exchange_weak(
old, xchg, std::memory_order_relaxed, std::memory_order_relaxed);
# endif
return result;
}
~Cmp()
{
# ifndef NDEBUG
operation_in_flight = false;
# endif
}
Cmp(const Cmp&) = delete;
Cmp(Cmp&&) noexcept = default;
};
// This method is used in Verona
T* peek()
{
return independent.ptr.load(std::memory_order_relaxed);
}
};
#else
/**
* Naive implementation of ABA protection using a spin lock.
*/
template<typename T, Construction c = RequiresInit>
class ABA
{
std::atomic<T*> ptr = nullptr;
std::atomic_flag lock = ATOMIC_FLAG_INIT;
public:
struct Cmp;
Cmp read()
{
while (lock.test_and_set(std::memory_order_acquire))
Aal::pause();
# ifndef NDEBUG
if (operation_in_flight)
error("Only one inflight ABA operation at a time is allowed.");
operation_in_flight = true;
# endif
return Cmp{this};
}
struct Cmp
{
ABA* parent;
public:
T* ptr()
{
return parent->ptr;
}
bool store_conditional(T* t)
{
parent->ptr = t;
return true;
}
~Cmp()
{
parent->lock.clear(std::memory_order_release);
# ifndef NDEBUG
operation_in_flight = false;
# endif
}
};
// This method is used in Verona
T* peek()
{
return ptr.load(std::memory_order_relaxed);
}
};
#endif
} // namespace snmalloc