// Copyright (C) 2008 Borislav Trifonov
// Based on algorithm from "Formal Verification of An Array-based Nonblocking Queue"
// TODO: For 64-bit system, if cannot adapt to _InterlockedCompare64Exchange128() or _InterlockedCompareExchange128(),
// use instead algorithm from Fig.5 in "Non-Blocking Concurrent FIFO Queues with Single Word Synchronization Primitives"
#if !defined ATOMIC_QUEUE
#define ATOMIC_QUEUE
#include <exception>
#include <cassert>
#include "processor.h"
template<class T, unsigned long size>
class AtomicQueue
{
public:
class Exc : public std::exception
{
public:
inline Exc(const char []);
};
inline AtomicQueue(void);
inline ~AtomicQueue(void);
inline bool Push(T const); // Returns false if full
inline bool Pop(T &); // Returns false if emtpy
inline T operator[](unsigned long); // No error checking
private:
__declspec(align(4)) volatile unsigned long head, tail;
unsigned long long *volatile buff; // TODO: Find out if the volatile is necessary
};
template<class T, unsigned long size>
inline AtomicQueue<T, size>::AtomicQueue(void) : head(0), tail(0), buff(0)
{
assert(sizeof(T) == 4);
assert(sizeof(unsigned long) == 4);
assert(sizeof(unsigned long long) == 8);
buff = static_cast<unsigned long long *>(_mm_malloc(size * sizeof(unsigned long long), 8));
if (!buff) throw Exc("Not enough memory");
for (int i = 0; i < size; ++i) buff = 0ull;
}
#include <iostream>
template<class T, unsigned long size>
inline AtomicQueue<T, size>::~AtomicQueue(void)
{
_mm_free(buff);
}
template<class T, unsigned long size>
inline bool AtomicQueue<T, size>::Push(T const item)
{
while (true)
{
__declspec(align(4)) unsigned long const t(tail), i(t % size), h(head);
__declspec(align(8)) unsigned long long const a(buff);
_ReadBarrier(); // TODO: Test any time environment changed: on _MSC_VER/_M_IX86
// _WriteBarrier() and _mm_lfence() also seem sufficient, but _mm_sfence() is not
if (t != tail) continue;
if (t == head + size)
{
if (buff[h % size] & 0xFFFFFFFFull && h == head) return false;
_InterlockedCompareExchange(&head, h + 1, h);
continue;
}
// Item is stored in lower two bytes since the interlocked intrinsics ignore the MSB
if (a & 0xFFFFFFFFull)
{
if (buff & 0xFFFFFFFFull) _InterlockedCompareExchange(&tail, t + 1, t);
}
else if (_InterlockedCompareExchange64(buff + i, static_cast<unsigned long long>(*reinterpret_cast<unsigned long const *>(&item))
| ((a & 0xFFFFFFFF00000000ull) + 0x100000000ull), a) == static_cast<long long>(a))
{
_InterlockedCompareExchange(&tail, t + 1, t);
return true;
}
}
}
template<class T, unsigned long size>
inline bool AtomicQueue<T, size>::Pop(T &item)
{
while (true)
{
__declspec(align(4)) unsigned long const h(head), i(h % size), t(tail);
__declspec(align(8)) unsigned long long const a(buff);
_ReadBarrier(); // TODO: Test any time environment changed: on _MSC_VER/_M_IX86
// _WriteBarrier() and _mm_lfence() also seem sufficient, but _mm_sfence() is not
if (h != head) continue;
if (h == tail)
{
if (!(buff[t % size] & 0xFFFFFFFFull) && t == tail) return false;
_InterlockedCompareExchange(&tail, t + 1, t);
continue;
}
if (!(a & 0xFFFFFFFFull))
{
if (!(buff & 0xFFFFFFFFull)) _InterlockedCompareExchange(&head, h + 1, h);
}
else if (_InterlockedCompareExchange64(buff + i, (a & 0xFFFFFFFF00000000ull) + 0x100000000ull, a) == static_cast<long long>(a))
{
_InterlockedCompareExchange(&head, h + 1, h);
unsigned long dummy(a & 0xFFFFFFFFull);
item = *reinterpret_cast<T *>(&dummy);
return true;
}
}
}
template<class T, unsigned long size>
inline T AtomicQueue<T, size>::operator[](unsigned long i)
{
unsigned long dummy(buff[(head + i) % size] & 0xFFFFFFFFull);
return *reinterpret_cast<T *>(&dummy);
}
template<class T, unsigned long size>
inline AtomicQueue<T, size>::Exc::Exc(const char msg[]) : std::exception(msg)
{
}
#endif
#if !defined PROCESSOR_H
#define PROCESSOR_H
// Note: MSVC may require /Oi compiler option
// CPU load/store reordering barriers and fast yield
// TODO: For other than _MSC_VER; might be OK for __ICL
#if defined _M_IA64 // TODO: Probably no IA64 support needed
extern "C" __mf
#pragma intrinsic(__mf)
#define MemoryBarrier __mf
extern "C" void __yield(void);
#pragma intrinsic(__yield)
#define YieldProcessor __yield
#else
extern "C" void _mm_mfence(void);
#pragma intrinsic(_mm_mfence)
extern "C" void _mm_lfence(void);
#pragma intrinsic(_mm_lfence)
extern "C" void _mm_sfence(void);
#pragma intrinsic(_mm_sfence)
extern "C" void _mm_pause(void);
#pragma intrinsic(_mm_pause)
#define YieldProcessor _mm_pause
#if defined _M_AMD64 // TODO: Check if this is OK if load barrier needed
extern "C" void __faststorefence(void);
#pragma intrinsic(__faststorefence)
#define MemoryBarrier __faststorefence
#elif defined _M_IX86
/*__forceinline void MemoryBarrier(void)
{
long Barrier;
__asm xchg Barrier, eax
}*/
#define MemoryBarrier _mm_mfence
#else
#error "Unsupported environment"
#endif
#endif
// Compiler load/store reordering barriers
// TODO: Check if these are implied by the CPU barriers
#if defined __ICL || defined __ICC || defined __ECC
#define _ReadWriteBarrier __memory_barrier
#define _ReadBarrier __memory_barrier
#define _WriteBarrier __memory_barrier
#elif defined _MSC_VER
extern "C" void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
extern "C" void _ReadBarrier(void);
#pragma intrinsic(_ReadBarrier)
extern "C" void _WriteBarrier(void);
#pragma intrinsic(_WriteBarrier)
#elif defined __GNUC__ // TODO: Other options for read- or write-only barriers?
#define _ReadWriteBarrier() __asm__ __volatile__("" ::: "memory")
#define _ReadBarrier() __asm__ __volatile__("" ::: "memory")
#define _WriteBarrier() __asm__ __volatile__("" ::: "memory")
#else
#error "Unsupported environment"
#endif
// Interlocked intrinsics
// TODO: For other than _MSC_VER; might be OK for __ICL;
// note that __GNUC__ __sync_bool_compare_and_swap does the comparison but _Interlocked... doesn't
extern "C" long _InterlockedCompareExchange(volatile unsigned long *, unsigned long, unsigned long);
#pragma intrinsic(_InterlockedCompareExchange)
extern "C" long long _InterlockedCompareExchange64(volatile unsigned long long *, unsigned long long, unsigned long long);
#pragma intrinsic(_InterlockedCompareExchange64)
// Aligned memory allocator and deallocator
#if defined __ICL || defined __ICC || defined __ECC // TODO: May not be necessary; also, probably __ECC (IA64) support not needed
extern "C" void* __cdecl _mm_malloc(size_t, size_t);
extern "C" void __cdecl _mm_free(void *);
#elif defined _MSC_VER
#include <malloc.h>
#elif defined __GNUC__
#include <mm_malloc.h>
#else
#error "Unsupported environment"
#endif
#endif