/* * Copyright (c) 1997-1999 * Silicon Graphics Computer Systems, Inc. * * Copyright (c) 1999 * Boris Fomitchev * * This material is provided "as is", with absolutely no warranty expressed * or implied. Any use is at your own risk. * * Permission to use or copy this software for any purpose is hereby granted * without fee, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was * modified is included with the above copyright notice. * */ #ifndef _STLP_LOCK_FREE_SLIST_H #define _STLP_LOCK_FREE_SLIST_H #if defined(_STLP_PTHREADS) # include # if defined (__GNUC__) && defined (__i386__) # define _STLP_HAS_ATOMIC_FREELIST /** * Class that implements a non-blocking and thread-safe freelist. * It is used for the lock-free node allocation engine. * * @author felixw@inin.com */ class _STLP_atomic_freelist { public: /** * Type representing items of the freelist */ struct item { item* _M_next; }; _STLP_atomic_freelist() { // Statically assert layout of member is as expected by assembly code _STLP_STATIC_ASSERT(sizeof(_M) == 8) _M._M_data._M_top = 0; _M._M_data._M_sequence = 0; } /** * Atomically pushes the specified item onto the freelist. * * @param __item [in] Item to add to the front of the list */ void push(item* __item) { // NOTE: GCC uses ebx as the PIC register for globals in shared libraries. // The GCC version I'm using (3.4.1) won't temporarily spill it if it's // used as input, output, or clobber. Instead, it complains with a // "can't find a register in class `BREG' while reloading `asm'" error. // This is probably a compiler bug, but as the cmpxchg8b instruction // requires ebx, I work around this here by using ecx for the '__item' // input and spilling ebx into edi. This also precludes us from using // a "m" operand for the cmpxchg8b argument (GCC might think it can make // it relative to ebx). Instead, we're using esi for the address of _M_data. // int __tmp1; // These dummy variables are used to tell GCC that the eax, ecx, int __tmp2; // and edx registers will not have the same value as their input. int __tmp3; // The optimizer will remove them as their values are not used. __asm__ __volatile__ (" movl %%ebx, %%edi\n\t" " movl %%ecx, %%ebx\n\t" "L1_%=: movl %%eax, (%%ebx)\n\t" // __item._M_next = _M._M_data._M_top " leal 1(%%edx),%%ecx\n\t" // new sequence = _M._M_data._M_sequence + 1 "lock; cmpxchg8b (%%esi)\n\t" " jne L1_%=\n\t" // Failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) " movl %%edi, %%ebx" :"=a" (__tmp1), "=d" (__tmp2), "=c" (__tmp3) :"a" (_M._M_data._M_top), "d" (_M._M_data._M_sequence), "c" (__item), "S" (&_M._M_data) :"edi", "memory", "cc"); } /** * Atomically removes the topmost item from the freelist and returns a * pointer to it. Returns NULL if the list is empty. * * @return Item that was removed from front of list; NULL if list empty */ item* pop() { item* __result; int __tmp; __asm__ __volatile__ (" movl %%ebx, %%edi\n\t" "L1_%=: testl %%eax, %%eax\n\t" // _M_top == NULL? " je L2_%=\n\t" // If yes, we're done " movl (%%eax), %%ebx\n\t" // new top = _M._M_data._M_top->_M_next " leal 1(%%edx),%%ecx\n\t" // new sequence = _M._M_data._M_sequence + 1 "lock; cmpxchg8b (%%esi)\n\t" " jne L1_%=\n\t" // We failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) "L2_%=: movl %%edi, %%ebx" :"=a" (__result), "=d" (__tmp) :"a" (_M._M_data._M_top), "d" (_M._M_data._M_sequence), "S" (&_M._M_data) :"edi", "ecx", "memory", "cc"); return __result; } /** * Atomically detaches all items from the list and returns a pointer to the * topmost item. The items are still chained and may be traversed safely as * they're now "owned" by the calling thread. * * @return Pointer to topmost item in the list; NULL if list empty */ item* clear() { item* __result; int __tmp; __asm__ __volatile__ (" movl %%ebx, %%edi\n\t" "L1_%=: testl %%eax, %%eax\n\t" // _M_top == NULL? " je L2_%=\n\t" // If yes, we're done " xorl %%ebx, %%ebx\n\t" // We're attempting to set _M_top to NULL " leal 1(%%edx),%%ecx\n\t" // new sequence = _M._M_data._M_sequence + 1 "lock; cmpxchg8b (%%esi)\n\t" " jne L1_%=\n\t" // Failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) "L2_%=: movl %%edi, %%ebx" :"=a" (__result), "=d" (__tmp) :"a" (_M._M_data._M_top), "d" (_M._M_data._M_sequence), "S" (&_M._M_data) :"edi", "ecx", "memory", "cc"); return __result; } private: union { long long _M_align; struct { item* _M_top; // Topmost element in the freelist unsigned int _M_sequence; // Sequence counter to prevent "ABA problem" } _M_data; } _M; _STLP_atomic_freelist(const _STLP_atomic_freelist&); _STLP_atomic_freelist& operator=(const _STLP_atomic_freelist&); }; # endif /* if defined(__GNUC__) && defined(__i386__) */ #elif defined (_STLP_WIN32THREADS) # if !defined (_WIN64) # define _STLP_USE_ASM_IMPLEMENTATION # endif // Here are the compiler/platform requirements for the thread safe and // lock free singly linked list implementation: # if defined (_STLP_USE_ASM_IMPLEMENTATION) // For the asm version: # if defined (_STLP_MSVC) && defined (_M_IX86) && (_M_IX86 >= 500) # define _STLP_HAS_ATOMIC_FREELIST # endif # else // For the API based version: # if defined (_STLP_NEW_PLATFORM_SDK) && (!defined (WINVER) || (WINVER >= 0x0501)) && \ (!defined (_WIN32_WINNT) || (_WIN32_WINNT >= 0x0501)) # define _STLP_HAS_ATOMIC_FREELIST # endif # endif # if defined (_STLP_HAS_ATOMIC_FREELIST) # if defined (_STLP_USE_ASM_IMPLEMENTATION) # if defined (_STLP_MSVC) && (_STLP_MSVC < 1300) || defined (__ICL) # pragma warning (push) # pragma warning (disable : 4035) //function has no return value # endif # endif /** * Class that implements a non-blocking and thread-safe freelist. * It is used for the lock-free node allocation engine. * * @author felixw@inin.com */ class _STLP_atomic_freelist { public: /** * Type representing items of the freelist */ # if defined (_STLP_USE_ASM_IMPLEMENTATION) struct item { item* _M_next; }; # else typedef SLIST_ENTRY item; # endif _STLP_atomic_freelist() { // Statically assert layout of member is as expected by assembly code # if defined (_STLP_USE_ASM_IMPLEMENTATION) _STLP_STATIC_ASSERT((sizeof(item) == sizeof(item*)) && (sizeof(_M) == 8)) _M._M_data._M_top = 0; _M._M_data._M_sequence = 0; # else InitializeSListHead(&_M_head); # endif } /** * Atomically pushes the specified item onto the freelist. * * @param __item [in] Item to add to the front of the list */ void push(item* __item) { # if defined (_STLP_USE_ASM_IMPLEMENTATION) __asm { mov esi, this mov ebx, __item mov eax, [esi] // _M._M_data._M_top mov edx, [esi+4] // _M._M_data._M_sequence L1: mov [ebx], eax // __item._M_next = _M._M_data._M_top lea ecx, [edx+1] // new sequence = _M._M_data._M_sequence + 1 lock cmpxchg8b qword ptr [esi] jne L1 // Failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) } # else InterlockedPushEntrySList(&_M_head, __item); # endif } /** * Atomically removes the topmost item from the freelist and returns a * pointer to it. Returns NULL if the list is empty. * * @return Item that was removed from front of list; NULL if list empty */ item* pop() { # if defined (_STLP_USE_ASM_IMPLEMENTATION) __asm { mov esi, this mov eax, [esi] // _M._M_data._M_top mov edx, [esi+4] // _M._M_data._M_sequence L1: test eax, eax // _M_top == NULL? je L2 // Yes, we're done mov ebx, [eax] // new top = _M._M_data._M_top->_M_next lea ecx, [edx+1] // new sequence = _M._M_data._M_sequence + 1 lock cmpxchg8b qword ptr [esi] jne L1 // Failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) L2: } # else return InterlockedPopEntrySList(&_M_head); # endif } /** * Atomically detaches all items from the list and returns pointer to the * topmost. The items are still chained and may be traversed safely as * they're now "owned" by the calling thread. * * @return Pointer to topmost item in the list; NULL if list empty */ item* clear() { # if defined (_STLP_USE_ASM_IMPLEMENTATION) __asm { mov esi, this mov eax, [esi] // _M._M_data._M_top mov edx, [esi+4] // _M._M_data._M_sequence L1: test eax, eax // _M_top == NULL? je L2 // Yes, we're done xor ebx,ebx // We're attempting to set _M._M_data._M_top to NULL lea ecx, [edx+1] // new sequence = _M._M_data._M_sequence + 1 lock cmpxchg8b qword ptr [esi] jne L1 // Failed, retry! (edx:eax now contain most recent _M_sequence:_M_top) L2: } # else return InterlockedFlushSList(&_M_head); # endif } private: # if defined (_STLP_USE_ASM_IMPLEMENTATION) union { __int64 _M_align; struct { item* _M_top; // Topmost element in the freelist unsigned int _M_sequence; // Sequence counter to prevent "ABA problem" } _M_data; } _M; # else SLIST_HEADER _M_head; # endif _STLP_atomic_freelist(const _STLP_atomic_freelist&); _STLP_atomic_freelist& operator = (const _STLP_atomic_freelist&); }; # if defined (_STLP_USE_ASM_IMPLEMENTATION) # if defined (_STLP_MSVC) && (_STLP_MSVC < 1300) || defined (__ICL) # pragma warning (pop) # endif # endif # endif /* _STLP_HAS_ATOMIC_FREELIST */ #endif #endif /* _STLP_LOCK_FREE_SLIST_H */