|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// LIFO from disassembly of Windows API and http://perso.wanadoo.fr/gmem/evenements/jim2002/articles/L17_Fober.pdf
// FIFO from http://perso.wanadoo.fr/gmem/evenements/jim2002/articles/L17_Fober.pdf
//
//=============================================================================
#ifndef TSLIST_H
#define TSLIST_H
#if defined( _WIN32 )
#pragma once
// Suppress this spurious warning:
// warning C4700: uninitialized local variable 'oldHead' used
#pragma warning( push )
#pragma warning( disable : 4700 )
#endif
#if defined( USE_NATIVE_SLIST ) && !defined( _X360 )
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include "tier0/dbg.h"
#include "tier0/threadtools.h"
#include "tier0/memalloc.h"
#include "tier0/memdbgoff.h"
#if defined( _X360 )
#define USE_NATIVE_SLIST
#endif
//-----------------------------------------------------------------------------
#if defined( PLATFORM_64BITS )
#if defined (PLATFORM_WINDOWS)
//typedef __m128i int128;
//inline int128 int128_zero() { return _mm_setzero_si128(); }
#else // PLATFORM_WINDOWS
typedef __int128_t int128; #define int128_zero() 0
#endif// PLATFORM_WINDOWS
#define TSLIST_HEAD_ALIGNMENT 16
#define TSLIST_NODE_ALIGNMENT 16
#ifdef POSIX
inline bool ThreadInterlockedAssignIf128( int128 volatile * pDest, const int128 &value, const int128 &comparand ) { // We do not want the original comparand modified by the swap
// so operate on a local copy.
int128 local_comparand = comparand; return __sync_bool_compare_and_swap( pDest, local_comparand, value ); } #endif
inline bool ThreadInterlockedAssignIf64x128( volatile int128 *pDest, const int128 &value, const int128 &comperand ) { return ThreadInterlockedAssignIf128( pDest, value, comperand ); } #else
#define TSLIST_HEAD_ALIGNMENT 8
#define TSLIST_NODE_ALIGNMENT 8
inline bool ThreadInterlockedAssignIf64x128( volatile int64 *pDest, const int64 value, const int64 comperand ) { return ThreadInterlockedAssignIf64( pDest, value, comperand ); } #endif
#ifdef _MSC_VER
#define TSLIST_HEAD_ALIGN DECL_ALIGN(TSLIST_HEAD_ALIGNMENT)
#define TSLIST_NODE_ALIGN DECL_ALIGN(TSLIST_NODE_ALIGNMENT)
#define TSLIST_HEAD_ALIGN_POST
#define TSLIST_NODE_ALIGN_POST
#elif defined( GNUC )
#define TSLIST_HEAD_ALIGN
#define TSLIST_NODE_ALIGN
#define TSLIST_HEAD_ALIGN_POST DECL_ALIGN(TSLIST_HEAD_ALIGNMENT)
#define TSLIST_NODE_ALIGN_POST DECL_ALIGN(TSLIST_NODE_ALIGNMENT)
#elif defined( _PS3 )
#define TSLIST_HEAD_ALIGNMENT 8
#define TSLIST_NODE_ALIGNMENT 8
#define TSLIST_HEAD_ALIGN ALIGN8
#define TSLIST_NODE_ALIGN ALIGN8
#define TSLIST_HEAD_ALIGN_POST ALIGN8_POST
#define TSLIST_NODE_ALIGN_POST ALIGN8_POST
#else
#error
#endif
//-----------------------------------------------------------------------------
PLATFORM_INTERFACE bool RunTSQueueTests( int nListSize = 10000, int nTests = 1 ); PLATFORM_INTERFACE bool RunTSListTests( int nListSize = 10000, int nTests = 1 );
//-----------------------------------------------------------------------------
// Lock free list.
//-----------------------------------------------------------------------------
//#define USE_NATIVE_SLIST
#ifdef USE_NATIVE_SLIST
typedef SLIST_ENTRY TSLNodeBase_t; typedef SLIST_HEADER TSLHead_t; #else
struct TSLIST_NODE_ALIGN TSLNodeBase_t { TSLNodeBase_t *Next; // name to match Windows
} TSLIST_NODE_ALIGN_POST;
union TSLIST_HEAD_ALIGN TSLHead_t { struct Value_t { TSLNodeBase_t *Next; // <sergiy> Depth must be in the least significant halfword when atomically loading into register,
// to avoid carrying digits from Sequence. Carrying digits from Depth to Sequence is ok,
// because Sequence can be pretty much random. We could operate on both of them separately,
// but it could perhaps (?) lead to problems with store forwarding. I don't know 'cause I didn't
// performance-test or design original code, I'm just making it work on PowerPC.
#ifdef VALVE_BIG_ENDIAN
int16 Sequence; int16 Depth; #else
int16 Depth; int16 Sequence; #endif
#ifdef PLATFORM_64BITS
int32 Padding; #endif
} value;
struct Value32_t { TSLNodeBase_t *Next_do_not_use_me; int32 DepthAndSequence; } value32;
#ifdef PLATFORM_64BITS
int128 value64x128; #else
int64 value64x128; #endif
} TSLIST_HEAD_ALIGN_POST;
#endif
//-------------------------------------
class CTSListBase { public:
// override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size) { CTSListBase *pNode = (CTSListBase *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, __FILE__, __LINE__ ); return pNode; }
static void * operator new(size_t size, int nBlockUse, const char *pFileName, int nLine) { CTSListBase *pNode = (CTSListBase *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, pFileName, nLine ); return pNode; }
static void operator delete(void *p) { MemAlloc_FreeAligned( p ); }
static void operator delete(void *p, int nBlockUse, const char *pFileName, int nLine) { MemAlloc_FreeAligned( p ); }
private: // These ain't gonna work
static void * operator new[]( size_t size ); static void operator delete[]( void *p );
public:
CTSListBase() { if ( ((size_t)&m_Head) % TSLIST_HEAD_ALIGNMENT != 0 ) { Error( "CTSListBase: Misaligned list\n" ); DebuggerBreak(); }
#ifdef USE_NATIVE_SLIST
InitializeSListHead( &m_Head ); #elif defined(PLATFORM_64BITS)
m_Head.value64x128 = int128_zero(); #else
m_Head.value64x128 = (int64)0; #endif
}
~CTSListBase() { Detach(); }
TSLNodeBase_t *Push( TSLNodeBase_t *pNode ) { #ifdef _DEBUG
if ( (size_t)pNode % TSLIST_NODE_ALIGNMENT != 0 ) { Error( "CTSListBase: Misaligned node\n" ); DebuggerBreak(); } #endif
#ifdef USE_NATIVE_SLIST
#ifdef _X360
// integrated write-release barrier
return (TSLNodeBase_t *)InterlockedPushEntrySListRelease( &m_Head, pNode ); #else
return (TSLNodeBase_t *)InterlockedPushEntrySList( &m_Head, pNode ); #endif
#else
TSLHead_t oldHead; TSLHead_t newHead;
#if defined( PLATFORM_PS3 ) || defined( PLATFORM_X360 )
__lwsync(); // write-release barrier
#endif
#ifdef PLATFORM_64BITS
newHead.value.Padding = 0; #endif
for ( ;; ) { oldHead.value64x128 = m_Head.value64x128; pNode->Next = oldHead.value.Next; newHead.value.Next = pNode;
newHead.value32.DepthAndSequence = oldHead.value32.DepthAndSequence + 0x10001;
if ( ThreadInterlockedAssignIf64x128( &m_Head.value64x128, newHead.value64x128, oldHead.value64x128 ) ) { break; } ThreadPause(); };
return (TSLNodeBase_t *)oldHead.value.Next; #endif
}
TSLNodeBase_t *Pop() { #ifdef USE_NATIVE_SLIST
#ifdef _X360
// integrated read-acquire barrier
TSLNodeBase_t *pNode = (TSLNodeBase_t *)InterlockedPopEntrySListAcquire( &m_Head ); #else
TSLNodeBase_t *pNode = (TSLNodeBase_t *)InterlockedPopEntrySList( &m_Head ); #endif
return pNode; #else
TSLHead_t oldHead; TSLHead_t newHead;
#ifdef PLATFORM_64BITS
newHead.value.Padding = 0; #endif
for ( ;; ) { oldHead.value64x128 = m_Head.value64x128; if ( !oldHead.value.Next ) return NULL;
newHead.value.Next = oldHead.value.Next->Next; newHead.value32.DepthAndSequence = oldHead.value32.DepthAndSequence - 1;
if ( ThreadInterlockedAssignIf64x128( &m_Head.value64x128, newHead.value64x128, oldHead.value64x128 ) ) { #if defined( PLATFORM_PS3 ) || defined( PLATFORM_X360 )
__lwsync(); // read-acquire barrier
#endif
break; } ThreadPause(); };
return (TSLNodeBase_t *)oldHead.value.Next; #endif
}
TSLNodeBase_t *Detach() { #ifdef USE_NATIVE_SLIST
TSLNodeBase_t *pBase = (TSLNodeBase_t *)InterlockedFlushSList( &m_Head ); #if defined( _X360 ) || defined( _PS3 )
__lwsync(); // read-acquire barrier
#endif
return pBase; #else
TSLHead_t oldHead; TSLHead_t newHead;
#ifdef PLATFORM_64BITS
newHead.value.Padding = 0; #endif
do { ThreadPause();
oldHead.value64x128 = m_Head.value64x128; if ( !oldHead.value.Next ) return NULL;
newHead.value.Next = NULL; // <sergiy> the reason for AND'ing it instead of poking a short into memory
// is probably to avoid store forward issues, but I'm not sure because
// I didn't construct this code. In any case, leaving it as is on big-endian
newHead.value32.DepthAndSequence = oldHead.value32.DepthAndSequence & 0xffff0000;
} while ( !ThreadInterlockedAssignIf64x128( &m_Head.value64x128, newHead.value64x128, oldHead.value64x128 ) );
return (TSLNodeBase_t *)oldHead.value.Next; #endif
}
TSLHead_t *AccessUnprotected() { return &m_Head; }
int Count() const { #ifdef USE_NATIVE_SLIST
return QueryDepthSList( const_cast<TSLHead_t*>(&m_Head) ); #else
return m_Head.value.Depth; #endif
}
private: TSLHead_t m_Head; } TSLIST_HEAD_ALIGN_POST;
//-------------------------------------
template <typename T> class TSLIST_HEAD_ALIGN CTSSimpleList : public CTSListBase { public: void Push( T *pNode ) { Assert( sizeof(T) >= sizeof(TSLNodeBase_t) ); CTSListBase::Push( (TSLNodeBase_t *)pNode ); }
T *Pop() { return (T *)CTSListBase::Pop(); } } TSLIST_HEAD_ALIGN_POST;
//-------------------------------------
// this is a replacement for CTSList<> and CObjectPool<> that does not
// have a per-item, per-alloc new/delete overhead
// similar to CTSSimpleList except that it allocates it's own pool objects
// and frees them on destruct. Also it does not overlay the TSNodeBase_t memory
// on T's memory
template< class T > class TSLIST_HEAD_ALIGN CTSPool : public CTSListBase { // packs the node and the item (T) into a single struct and pools those
struct TSLIST_NODE_ALIGN simpleTSPoolStruct_t : public TSLNodeBase_t { T elem; } TSLIST_NODE_ALIGN_POST;
public:
~CTSPool() { Purge(); }
void Purge() { simpleTSPoolStruct_t *pNode = NULL; while ( 1 ) { pNode = (simpleTSPoolStruct_t *)CTSListBase::Pop(); if ( !pNode ) break; delete pNode; } }
void PutObject( T *pInfo ) { char *pElem = (char *)pInfo; pElem -= offsetof( simpleTSPoolStruct_t, elem ); simpleTSPoolStruct_t *pNode = (simpleTSPoolStruct_t *)pElem;
CTSListBase::Push( pNode ); }
T *GetObject() { simpleTSPoolStruct_t *pNode = (simpleTSPoolStruct_t *)CTSListBase::Pop(); if ( !pNode ) { pNode = new simpleTSPoolStruct_t; } return &pNode->elem; }
// omg windows sdk - why do you #define GetObject()?
FORCEINLINE T *Get() { return GetObject(); } } TSLIST_HEAD_ALIGN_POST; //-------------------------------------
template <typename T> class TSLIST_HEAD_ALIGN CTSList : public CTSListBase { public: struct TSLIST_NODE_ALIGN Node_t : public TSLNodeBase_t { Node_t() {} Node_t( const T &init ) : elem( init ) {} T elem;
// override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size) { Node_t *pNode = (Node_t *)MemAlloc_AllocAlignedFileLine( size, TSLIST_NODE_ALIGNMENT, __FILE__, __LINE__ ); return pNode; }
// override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size, int nBlockUse, const char *pFileName, int nLine) { Node_t *pNode = (Node_t *)MemAlloc_AllocAlignedFileLine( size, TSLIST_NODE_ALIGNMENT, pFileName, nLine ); return pNode; }
static void operator delete(void *p) { MemAlloc_FreeAligned( p ); } static void operator delete(void *p, int nBlockUse, const char *pFileName, int nLine) { MemAlloc_FreeAligned( p ); }
} TSLIST_NODE_ALIGN_POST;
~CTSList() { Purge(); }
void Purge() { Node_t *pCurrent = Detach(); Node_t *pNext; while ( pCurrent ) { pNext = (Node_t *)pCurrent->Next; delete pCurrent; pCurrent = pNext; } }
void RemoveAll() { Purge(); }
Node_t *Push( Node_t *pNode ) { return (Node_t *)CTSListBase::Push( pNode ); }
Node_t *Pop() { return (Node_t *)CTSListBase::Pop(); }
void PushItem( const T &init ) { Push( new Node_t( init ) ); }
bool PopItem( T *pResult ) { Node_t *pNode = Pop(); if ( !pNode ) return false; *pResult = pNode->elem; delete pNode; return true; }
Node_t *Detach() { return (Node_t *)CTSListBase::Detach(); }
} TSLIST_HEAD_ALIGN_POST;
//-------------------------------------
template <typename T> class TSLIST_HEAD_ALIGN CTSListWithFreeList : public CTSListBase { public: struct TSLIST_NODE_ALIGN Node_t : public TSLNodeBase_t { Node_t() {} Node_t( const T &init ) : elem( init ) {}
T elem; } TSLIST_NODE_ALIGN_POST;
~CTSListWithFreeList() { Purge(); }
void Purge() { Node_t *pCurrent = Detach(); Node_t *pNext; while ( pCurrent ) { pNext = (Node_t *)pCurrent->Next; delete pCurrent; pCurrent = pNext; } pCurrent = (Node_t *)m_FreeList.Detach(); while ( pCurrent ) { pNext = (Node_t *)pCurrent->Next; delete pCurrent; pCurrent = pNext; } }
void RemoveAll() { Node_t *pCurrent = Detach(); Node_t *pNext; while ( pCurrent ) { pNext = (Node_t *)pCurrent->Next; m_FreeList.Push( pCurrent ); pCurrent = pNext; } }
Node_t *Push( Node_t *pNode ) { return (Node_t *)CTSListBase::Push( pNode ); }
Node_t *Pop() { return (Node_t *)CTSListBase::Pop(); }
void PushItem( const T &init ) { Node_t *pNode = (Node_t *)m_FreeList.Pop(); if ( !pNode ) { pNode = new Node_t; } pNode->elem = init; Push( pNode ); }
bool PopItem( T *pResult ) { Node_t *pNode = Pop(); if ( !pNode ) return false; *pResult = pNode->elem; m_FreeList.Push( pNode ); return true; }
Node_t *Detach() { return (Node_t *)CTSListBase::Detach(); }
void FreeNode( Node_t *pNode ) { m_FreeList.Push( pNode ); }
private: CTSListBase m_FreeList; } TSLIST_HEAD_ALIGN_POST;
//-----------------------------------------------------------------------------
// Lock free queue
//
// A special consideration: the element type should be simple. This code
// actually dereferences freed nodes as part of pop, but later detects
// that. If the item in the queue is a complex type, only bad things can
// come of that. Also, therefore, if you're using Push/Pop instead of
// push item, be aware that the node memory cannot be freed until
// all threads that might have been popping have completed the pop.
// The PushItem()/PopItem() for handles this by keeping a persistent
// free list. Dont mix Push/PushItem. Note also nodes will be freed at the end,
// and are expected to have been allocated with operator new.
//-----------------------------------------------------------------------------
template <typename T, bool bTestOptimizer = false> class TSLIST_HEAD_ALIGN CTSQueue { public:
// override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size) { CTSQueue *pNode = (CTSQueue *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, __FILE__, __LINE__ ); return pNode; }
// override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size, int nBlockUse, const char *pFileName, int nLine) { CTSQueue *pNode = (CTSQueue *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, pFileName, nLine ); return pNode; }
static void operator delete(void *p) { MemAlloc_FreeAligned( p ); }
static void operator delete(void *p, int nBlockUse, const char *pFileName, int nLine) { MemAlloc_FreeAligned( p ); }
private: // These ain't gonna work
static void * operator new[]( size_t size ) throw() { return NULL; }
static void operator delete []( void *p ) { }
public:
struct TSLIST_NODE_ALIGN Node_t { // override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size) { Node_t *pNode = (Node_t *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, __FILE__, __LINE__ ); return pNode; }
static void * operator new(size_t size, int nBlockUse, const char *pFileName, int nLine) { Node_t *pNode = (Node_t *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, pFileName, nLine ); return pNode; }
static void operator delete(void *p) { MemAlloc_FreeAligned( p ); }
static void operator delete(void *p, int nBlockUse, const char *pFileName, int nLine) { MemAlloc_FreeAligned( p ); }
Node_t() {} Node_t( const T &init ) : elem( init ) {}
Node_t *pNext; T elem; } TSLIST_NODE_ALIGN_POST;
union TSLIST_HEAD_ALIGN NodeLink_t { // override new/delete so we can guarantee 8-byte aligned allocs
static void * operator new(size_t size) { NodeLink_t *pNode = (NodeLink_t *)MemAlloc_AllocAlignedFileLine( size, TSLIST_HEAD_ALIGNMENT, __FILE__, __LINE__ ); return pNode; }
static void operator delete(void *p) { MemAlloc_FreeAligned( p ); }
struct Value_t { Node_t *pNode; intp sequence; } value;
#ifdef PLATFORM_64BITS
int128 value64x128; #else
int64 value64x128; #endif
} TSLIST_HEAD_ALIGN_POST;
CTSQueue() { COMPILE_TIME_ASSERT( sizeof(Node_t) >= sizeof(TSLNodeBase_t) ); if ( ((size_t)&m_Head) % TSLIST_HEAD_ALIGNMENT != 0 ) { Error( "CTSQueue: Misaligned queue\n" ); DebuggerBreak(); } if ( ((size_t)&m_Tail) % TSLIST_HEAD_ALIGNMENT != 0 ) { Error( "CTSQueue: Misaligned queue\n" ); DebuggerBreak(); } m_Count = 0; m_Head.value.sequence = m_Tail.value.sequence = 0; m_Head.value.pNode = m_Tail.value.pNode = new Node_t; // list always contains a dummy node
m_Head.value.pNode->pNext = End(); }
~CTSQueue() { Purge(); Assert( m_Count == 0 ); Assert( m_Head.value.pNode == m_Tail.value.pNode ); Assert( m_Head.value.pNode->pNext == End() ); delete m_Head.value.pNode; }
// Note: Purge, RemoveAll, and Validate are *not* threadsafe
void Purge() { if ( IsDebug() ) { ValidateQueue(); }
Node_t *pNode; while ( (pNode = Pop()) != NULL ) { delete pNode; }
while ( (pNode = (Node_t *)m_FreeNodes.Pop()) != NULL ) { delete pNode; }
Assert( m_Count == 0 ); Assert( m_Head.value.pNode == m_Tail.value.pNode ); Assert( m_Head.value.pNode->pNext == End() );
m_Head.value.sequence = m_Tail.value.sequence = 0; }
void RemoveAll() { if ( IsDebug() ) { ValidateQueue(); }
Node_t *pNode; while ( (pNode = Pop()) != NULL ) { m_FreeNodes.Push( (TSLNodeBase_t *)pNode ); } }
bool ValidateQueue() { if ( IsDebug() ) { bool bResult = true; int nNodes = 0; if ( m_Tail.value.pNode->pNext != End() ) { DebuggerBreakIfDebugging(); bResult = false; }
if ( m_Count == 0 ) { if ( m_Head.value.pNode != m_Tail.value.pNode ) { DebuggerBreakIfDebugging(); bResult = false; } }
Node_t *pNode = m_Head.value.pNode; while ( pNode != End() ) { nNodes++; pNode = pNode->pNext; }
nNodes--;// skip dummy node
if ( nNodes != m_Count ) { DebuggerBreakIfDebugging(); bResult = false; }
if ( !bResult ) { Msg( "Corrupt CTSQueueDetected" ); }
return bResult; } else { return true; } }
void FinishPush( Node_t *pNode, const NodeLink_t &oldTail ) { NodeLink_t newTail;
newTail.value.pNode = pNode; newTail.value.sequence = oldTail.value.sequence + 1;
ThreadMemoryBarrier();
InterlockedCompareExchangeNodeLink( &m_Tail, newTail, oldTail ); }
Node_t *Push( Node_t *pNode ) { #ifdef _DEBUG
if ( (size_t)pNode % TSLIST_NODE_ALIGNMENT != 0 ) { Error( "CTSListBase: Misaligned node\n" ); DebuggerBreak(); } #endif
NodeLink_t oldTail;
pNode->pNext = End();
for ( ;; ) { oldTail.value.sequence = m_Tail.value.sequence; oldTail.value.pNode = m_Tail.value.pNode; if ( InterlockedCompareExchangeNode( &(oldTail.value.pNode->pNext), pNode, End() ) == End() ) { break; } else { // Another thread is trying to push, help it along
FinishPush( oldTail.value.pNode->pNext, oldTail ); } }
FinishPush( pNode, oldTail ); // This can fail if another thread pushed between the sequence and node grabs above. Later pushes or pops corrects
m_Count++;
return oldTail.value.pNode; }
Node_t *Pop() { #define TSQUEUE_BAD_NODE_LINK ( (Node_t *)INT_TO_POINTER( 0xdeadbeef ) )
NodeLink_t * volatile pHead = &m_Head; NodeLink_t * volatile pTail = &m_Tail; Node_t * volatile * pHeadNode = &m_Head.value.pNode; volatile intp * volatile pHeadSequence = &m_Head.value.sequence; Node_t * volatile * pTailNode = &pTail->value.pNode;
NodeLink_t head; NodeLink_t newHead; Node_t *pNext; intp tailSequence; T elem;
for ( ;; ) { head.value.sequence = *pHeadSequence; // must grab sequence first, which allows condition below to ensure pNext is valid
ThreadMemoryBarrier(); // need a barrier to prevent reordering of these assignments
head.value.pNode = *pHeadNode; tailSequence = pTail->value.sequence; pNext = head.value.pNode->pNext;
// Checking pNext only to force optimizer to not reorder the assignment
// to pNext and the compare of the sequence
if ( !pNext || head.value.sequence != *pHeadSequence ) continue;
if ( bTestOptimizer ) { if ( pNext == TSQUEUE_BAD_NODE_LINK ) { Msg( "Bad node link detected\n" ); continue; } }
if ( head.value.pNode == *pTailNode ) { if ( pNext == End() ) return NULL;
// Another thread is trying to push, help it along
NodeLink_t &oldTail = head; // just reuse local memory for head to build old tail
oldTail.value.sequence = tailSequence; // reuse head pNode
FinishPush( pNext, oldTail ); continue; }
if ( pNext != End() ) { elem = pNext->elem; // NOTE: next could be a freed node here, by design
newHead.value.pNode = pNext; newHead.value.sequence = head.value.sequence + 1; if ( InterlockedCompareExchangeNodeLink( pHead, newHead, head ) ) { ThreadMemoryBarrier(); if ( bTestOptimizer ) { head.value.pNode->pNext = TSQUEUE_BAD_NODE_LINK; } break; } } }
m_Count--; head.value.pNode->elem = elem; return head.value.pNode; }
void FreeNode( Node_t *pNode ) { m_FreeNodes.Push( (TSLNodeBase_t *)pNode ); }
void PushItem( const T &init ) { Node_t *pNode = (Node_t *)m_FreeNodes.Pop(); if ( pNode ) { pNode->elem = init; } else { pNode = new Node_t( init ); } Push( pNode ); }
bool PopItem( T *pResult ) { Node_t *pNode = Pop(); if ( !pNode ) return false;
*pResult = pNode->elem; m_FreeNodes.Push( (TSLNodeBase_t *)pNode ); return true; }
int Count() const { return m_Count; }
private: Node_t *End() { return (Node_t *)this; } // just need a unique signifier
Node_t *InterlockedCompareExchangeNode( Node_t * volatile *ppNode, Node_t *value, Node_t *comperand ) { return (Node_t *)::ThreadInterlockedCompareExchangePointer( (void **)ppNode, value, comperand ); }
bool InterlockedCompareExchangeNodeLink( NodeLink_t volatile *pLink, const NodeLink_t &value, const NodeLink_t &comperand ) { return ThreadInterlockedAssignIf64x128( &pLink->value64x128, value.value64x128, comperand.value64x128 ); }
NodeLink_t m_Head; NodeLink_t m_Tail;
CInterlockedInt m_Count;
CTSListBase m_FreeNodes; } TSLIST_NODE_ALIGN_POST;
#if defined( _WIN32 )
// Suppress this spurious warning:
// warning C4700: uninitialized local variable 'oldHead' used
#pragma warning( pop )
#endif
#endif // TSLIST_H
|