Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;
};