aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nu/win-x86
diff options
context:
space:
mode:
Diffstat (limited to 'Src/replicant/nu/win-x86')
-rw-r--r--Src/replicant/nu/win-x86/LockFreeLIFO.asm50
-rw-r--r--Src/replicant/nu/win-x86/LockFreeLIFO.c48
-rw-r--r--Src/replicant/nu/win-x86/LockFreeLIFO.h31
-rw-r--r--Src/replicant/nu/win-x86/ThreadLoop.cpp84
-rw-r--r--Src/replicant/nu/win-x86/ThreadLoop.h37
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