diff options
Diffstat (limited to 'Src/replicant/nu/win-x86')
-rw-r--r-- | Src/replicant/nu/win-x86/LockFreeLIFO.asm | 50 | ||||
-rw-r--r-- | Src/replicant/nu/win-x86/LockFreeLIFO.c | 48 | ||||
-rw-r--r-- | Src/replicant/nu/win-x86/LockFreeLIFO.h | 31 | ||||
-rw-r--r-- | Src/replicant/nu/win-x86/ThreadLoop.cpp | 84 | ||||
-rw-r--r-- | Src/replicant/nu/win-x86/ThreadLoop.h | 37 |
5 files changed, 250 insertions, 0 deletions
diff --git a/Src/replicant/nu/win-x86/LockFreeLIFO.asm b/Src/replicant/nu/win-x86/LockFreeLIFO.asm new file mode 100644 index 00000000..dd67e5b1 --- /dev/null +++ b/Src/replicant/nu/win-x86/LockFreeLIFO.asm @@ -0,0 +1,50 @@ +.686 +.model FLAT + +PUBLIC _lifo_push +_TEXT SEGMENT +lifo = 4 ; size = 4 +entry = 8 ; size = 4 +_lifo_push PROC + mov ecx, DWORD PTR 4[esp] ; ecx holds lifo + mov edx, DWORD PTR 8[esp] ; edx holds the new entry +again: + mov eax, DWORD PTR [ecx] ; eax holds the old head + mov DWORD PTR[edx], eax ; new node's 'next' is set to the old head + lock cmpxchg DWORD PTR [ecx], edx + jnz again + ret 0 +_lifo_push ENDP + +PUBLIC _lifo_pop +_TEXT SEGMENT +lifo = 4 ; size = 4 +_lifo_pop PROC + push esi + push ebx + mov esi, DWORD PTR 12[esp] ; esi holds lifo +again: + ; if re-ordered loads become an issue, we could use cmpxchg8b to read in (after zeroing ebx/ecx) or maybe use movq + mov edx, DWORD PTR [esi+4] ; counter + ; or we could put an LFENCE here + mov eax, DWORD PTR [esi] ; pointer + test eax, eax + jz bail + + mov ecx, edx ; counter + mov ebx, DWORD PTR [eax] ; pointer->next + inc ecx + + lock cmpxchg8b QWORD PTR [esi] + jnz again + +bail: + pop ebx + pop esi + ret 0 +_lifo_pop ENDP + +_TEXT ENDS + +END + diff --git a/Src/replicant/nu/win-x86/LockFreeLIFO.c b/Src/replicant/nu/win-x86/LockFreeLIFO.c new file mode 100644 index 00000000..ecffde9a --- /dev/null +++ b/Src/replicant/nu/win-x86/LockFreeLIFO.c @@ -0,0 +1,48 @@ +#include "LockFreeLIFO.h" +#include "foundation/atomics.h" + +/* win32 implementation */ + +void lifo_init(lifo_t *lifo) +{ + lifo->head = 0; + lifo->aba = 0; +} + +#if 0 // defined in LockFreeLIFO.asm +void lifo_push(lifo_t *lifo, queue_node_t *cl) +{ + queue_node_t *new_head = cl; + queue_node_t *old_head = 0; + do + { + old_head = (queue_node_t *)lifo->head; + new_head->Next = old_head; + } while (!nx_atomic_cmpxchg_pointer(old_head, new_head, (void * volatile *)&lifo->head)); +} + +queue_node_t *lifo_pop(lifo_t *lifo) +{ + lifo_t old_head, new_head; + do + { + old_head = *lifo; + if (!old_head.head) + return 0; + + new_head.head = old_head.head->Next; + new_head.aba = old_head.aba+1; + } while (!nx_atomic_cmpxchg2(*(int64_t *)&old_head, *(int64_t *)&new_head, (volatile int64_t *)&lifo->head)); + return (queue_node_t *)old_head.head; +} +#endif + +queue_node_t *lifo_malloc(size_t bytes) +{ + return _aligned_malloc(bytes, MEMORY_ALLOCATION_ALIGNMENT); +} + +void lifo_free(queue_node_t *ptr) +{ + _aligned_free(ptr); +}
\ No newline at end of file diff --git a/Src/replicant/nu/win-x86/LockFreeLIFO.h b/Src/replicant/nu/win-x86/LockFreeLIFO.h new file mode 100644 index 00000000..90ba9d33 --- /dev/null +++ b/Src/replicant/nu/win-x86/LockFreeLIFO.h @@ -0,0 +1,31 @@ +#pragma once +#include "foundation/types.h" +#include "nu/queue_node.h" +#include "foundation/align.h" +/* lock free stack object +multiple threads can push and pop without locking +note that order is not guaranteed. that is, if Thread 1 calls Insert before Thread 2, Thread 2's item might still make it in before. +*/ +#ifdef __cplusplus +extern "C" { +#endif + + typedef NALIGN(8) struct lifo_struct_t + { + volatile queue_node_t *head; + uint32_t aba; + } lifo_t; + + /* use this to allocate an object that will go into this */ + queue_node_t *lifo_malloc(size_t bytes); + void lifo_free(queue_node_t *ptr); + + void lifo_init(lifo_t *lifo); + void lifo_push(lifo_t *lifo, queue_node_t *cl); + queue_node_t *lifo_pop(lifo_t *lifo); + + + +#ifdef __cplusplus +} +#endif diff --git a/Src/replicant/nu/win-x86/ThreadLoop.cpp b/Src/replicant/nu/win-x86/ThreadLoop.cpp new file mode 100644 index 00000000..5dae339d --- /dev/null +++ b/Src/replicant/nu/win-x86/ThreadLoop.cpp @@ -0,0 +1,84 @@ +#include "ThreadLoop.h" + +lifo_t ThreadLoop::procedure_cache = {0,}; +lifo_t ThreadLoop::cache_bases= {0,}; + +#define PROCEDURE_CACHE_SEED 64 +ThreadLoop::ThreadLoop() +{ + mpscq_init(&procedure_queue); + procedure_notification = CreateSemaphore(0, 0, LONG_MAX, 0); + kill_switch = CreateEvent(0, TRUE, FALSE, 0); +} + +void ThreadLoop::RefillCache() +{ + threadloop_node_t *cache_seed = (threadloop_node_t *)malloc(PROCEDURE_CACHE_SEED*sizeof(threadloop_node_t)); + + if (cache_seed) + { + int i=PROCEDURE_CACHE_SEED; + while (--i) + { + lifo_push(&procedure_cache, (queue_node_t *)&cache_seed[i]); + } + lifo_push(&cache_bases, (queue_node_t *)cache_seed); + } + else + { + Sleep(0); // yield and hope that someone else pops something off soon + } + +} + +void ThreadLoop::Run() +{ + HANDLE events[] = {kill_switch, procedure_notification}; + while (WaitForMultipleObjects(2, events, FALSE, INFINITE) == WAIT_OBJECT_0 + 1) + { + for (;;) + { + threadloop_node_t *apc = (threadloop_node_t *)mpscq_pop(&procedure_queue); + if (apc == (threadloop_node_t *)1) /* special return value that indicates a busy list */ + { + Sleep(0); // yield so that the thread that got pre-empted during push can finish + } + else + { + if (apc) + { + apc->func(apc->param1, apc->param2, apc->real_value); + lifo_push(&procedure_cache, apc); + } + else + { + break; + } + } + } + } +} + +threadloop_node_t *ThreadLoop::GetAPC() +{ + threadloop_node_t *apc = 0; + + do + { + apc = (threadloop_node_t *)lifo_pop(&procedure_cache); + if (!apc) + RefillCache(); + } while (!apc); + return apc; +} + +void ThreadLoop::Schedule(threadloop_node_t *apc) +{ + if (mpscq_push(&procedure_queue, apc) == 0) + ReleaseSemaphore(procedure_notification, 1, 0); +} + +void ThreadLoop::Kill() +{ + SetEvent(kill_switch); +} diff --git a/Src/replicant/nu/win-x86/ThreadLoop.h b/Src/replicant/nu/win-x86/ThreadLoop.h new file mode 100644 index 00000000..cc412386 --- /dev/null +++ b/Src/replicant/nu/win-x86/ThreadLoop.h @@ -0,0 +1,37 @@ +#pragma once +#include "nu/lfmpscq.h" +#include "nu/LockFreeLIFO.h" +#include <windows.h> + +struct threadloop_node_t : public queue_node_t +{ + void (*func)(void *param1, void *param2, double real_value); + + void *param1; + void *param2; + double real_value; +}; + +class ThreadLoop +{ +public: + ThreadLoop(); + threadloop_node_t *GetAPC(); // returns a node for you to fill out + void Schedule(threadloop_node_t *apc); + void Run(); + void Kill(); +private: + void RefillCache(); + + HANDLE procedure_notification; + HANDLE kill_switch; + mpscq_t procedure_queue; + + /* Memory cache to be able to run APCs without having the memory manager lock + we'll allocate 100 at a time (#defined by PROCEDURE_CACHE_SEED) + and allocate new ones only if the cache is empty (which unfortunately will lock) + cache_bases holds the pointers we've allocated (to free on destruction of this object) + and procedure_cache holds the individual pointers */ + static lifo_t procedure_cache; + static lifo_t cache_bases; +};
\ No newline at end of file |