Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions include/hemlock_limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,35 @@
#define HML_ASCII_PRINTABLE_START 32
#define HML_ASCII_PRINTABLE_END 127

// ========== THREAD POOL CONFIGURATION ==========

// Default number of worker threads (0 = auto-detect based on CPU count)
#define HML_THREADPOOL_DEFAULT_WORKERS 0

// Maximum number of worker threads
#define HML_THREADPOOL_MAX_WORKERS 256

// Minimum number of worker threads
#define HML_THREADPOOL_MIN_WORKERS 2

// Initial capacity of per-worker work-stealing deque
#define HML_THREADPOOL_DEQUE_INITIAL_CAPACITY 64

// Maximum capacity of per-worker work-stealing deque
#define HML_THREADPOOL_DEQUE_MAX_CAPACITY 65536

// Global submission queue capacity
#define HML_THREADPOOL_SUBMISSION_QUEUE_CAPACITY 4096

// Number of steal attempts before sleeping
#define HML_THREADPOOL_STEAL_ATTEMPTS 32

// Sleep duration when no work is available (microseconds)
#define HML_THREADPOOL_IDLE_SLEEP_US 100

// Work stealing random seed multiplier (for LFSR)
#define HML_THREADPOOL_STEAL_SEED_MULT 1103515245

// ========== SANDBOX CONFIGURATION ==========

// Sandbox restriction flags (bitmask)
Expand Down
170 changes: 170 additions & 0 deletions include/thread_pool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Hemlock Thread Pool with Work-Stealing Scheduler
*
* A fixed-size thread pool where workers can steal work from each other
* to balance load. Uses Chase-Lev work-stealing deques for efficient
* lock-free operations on the local end.
*
* Design:
* - Each worker has a local deque (double-ended queue)
* - Workers push/pop from bottom of their own deque (LIFO - cache locality)
* - Workers steal from top of other workers' deques (FIFO - oldest tasks)
* - External submissions go to a global queue with condition variable
* - Workers check local deque, then global queue, then steal from others
*/

#ifndef HEMLOCK_THREAD_POOL_H
#define HEMLOCK_THREAD_POOL_H

#include <stdint.h>
#include <stdatomic.h>
#include <pthread.h>
#include "hemlock_limits.h"

// Forward declarations
typedef struct ThreadPool ThreadPool;
typedef struct Worker Worker;
typedef struct WorkStealingDeque WorkStealingDeque;
typedef struct WorkItem WorkItem;

// Work item callback type
// The callback receives the work item data and returns a result
typedef void* (*WorkItemFunc)(void* data, void* ctx);

// Work item - a unit of work to execute
typedef struct WorkItem {
WorkItemFunc func; // Function to execute
void* data; // Data to pass to function
void* ctx; // Context (ExecutionContext for interpreter)
void* result; // Result storage (set by callback)
atomic_int completed; // 1 when work is done
pthread_mutex_t* mutex; // For waiting on completion
pthread_cond_t* cond; // For signaling completion
int has_waiter; // 1 if someone is waiting for result
struct WorkItem* next; // For linked list (global queue)
} WorkItem;

// Chase-Lev work-stealing deque
// Lock-free for push/pop (owner), uses CAS for steal
typedef struct WorkStealingDeque {
WorkItem** items; // Circular buffer of work items
atomic_long bottom; // Bottom index (owner pushes/pops here)
atomic_long top; // Top index (thieves steal from here)
int capacity; // Current capacity
int max_capacity; // Maximum allowed capacity
pthread_mutex_t resize_lock; // Lock for resizing only
} WorkStealingDeque;

// Per-worker state
typedef struct Worker {
int id; // Worker ID (0 to num_workers-1)
pthread_t thread; // Worker thread
ThreadPool* pool; // Back-pointer to pool
WorkStealingDeque* deque; // Local work-stealing deque
unsigned int steal_seed; // Random seed for choosing steal victim
atomic_int active; // 1 if actively working, 0 if idle
atomic_long tasks_executed; // Statistics: number of tasks executed
atomic_long tasks_stolen; // Statistics: number of tasks stolen
} Worker;

// Global submission queue (MPSC - multiple producers, single consumers)
typedef struct SubmissionQueue {
WorkItem* head; // Head of linked list
WorkItem* tail; // Tail of linked list
int count; // Number of items in queue
int capacity; // Maximum capacity
pthread_mutex_t mutex; // Protects queue access
pthread_cond_t not_empty; // Signal when items are available
} SubmissionQueue;

// Thread pool statistics
typedef struct ThreadPoolStats {
long total_tasks_submitted; // Total tasks submitted to pool
long total_tasks_completed; // Total tasks completed
long total_steals; // Total successful steals
long total_steal_attempts; // Total steal attempts
} ThreadPoolStats;

// The thread pool
typedef struct ThreadPool {
int num_workers; // Number of worker threads
Worker* workers; // Array of workers
SubmissionQueue* submission; // Global submission queue
atomic_int shutdown; // 1 when shutting down
atomic_int started; // 1 when workers have started
pthread_mutex_t start_mutex; // For coordinating startup
pthread_cond_t start_cond; // For signaling startup complete
ThreadPoolStats stats; // Pool statistics
} ThreadPool;

// ========== THREAD POOL API ==========

// Initialize the global thread pool
// num_workers: number of worker threads (0 = auto-detect based on CPU count)
// Returns: 0 on success, -1 on error
int thread_pool_init(int num_workers);

// Shutdown the global thread pool
// Waits for all pending work to complete
void thread_pool_shutdown(void);

// Submit a work item to the thread pool
// If called from a worker thread, pushes to local deque
// Otherwise, pushes to global submission queue
// func: function to execute
// data: data to pass to function
// ctx: execution context
// Returns: WorkItem pointer (can be used to wait for result)
WorkItem* thread_pool_submit(WorkItemFunc func, void* data, void* ctx);

// Submit work and wait for result (blocking)
// Returns: result from work item function
void* thread_pool_submit_wait(WorkItemFunc func, void* data, void* ctx);

// Wait for a work item to complete
// Returns: result from work item function
void* work_item_wait(WorkItem* item);

// Free a work item (must be called after waiting or if not waiting)
void work_item_free(WorkItem* item);

// Check if current thread is a pool worker
// Returns: worker ID (0 to num_workers-1) or -1 if not a worker
int thread_pool_current_worker_id(void);

// Get thread pool statistics
ThreadPoolStats thread_pool_get_stats(void);

// Check if thread pool is initialized
int thread_pool_is_initialized(void);

// Get the global thread pool (for internal use)
ThreadPool* thread_pool_get(void);

// ========== WORK-STEALING DEQUE API (internal) ==========

// Create a new work-stealing deque
WorkStealingDeque* deque_new(int initial_capacity);

// Free a work-stealing deque
void deque_free(WorkStealingDeque* deque);

// Push a work item to the bottom of the deque (owner only)
// Returns: 0 on success, -1 if full
int deque_push(WorkStealingDeque* deque, WorkItem* item);

// Pop a work item from the bottom of the deque (owner only)
// Returns: work item or NULL if empty
WorkItem* deque_pop(WorkStealingDeque* deque);

// Steal a work item from the top of the deque (any thread)
// Returns: work item or NULL if empty
WorkItem* deque_steal(WorkStealingDeque* deque);

// Check if deque is empty
int deque_is_empty(WorkStealingDeque* deque);

// Get number of items in deque (approximate)
long deque_size(WorkStealingDeque* deque);

#endif // HEMLOCK_THREAD_POOL_H
115 changes: 115 additions & 0 deletions runtime/include/thread_pool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Hemlock Runtime Thread Pool with Work-Stealing Scheduler
*
* A fixed-size thread pool where workers can steal work from each other
* to balance load. Uses Chase-Lev work-stealing deques for efficient
* lock-free operations on the local end.
*/

#ifndef HEMLOCK_RUNTIME_THREAD_POOL_H
#define HEMLOCK_RUNTIME_THREAD_POOL_H

#include <stdint.h>
#include <stdatomic.h>
#include <pthread.h>

// Thread pool configuration constants
#define HML_THREADPOOL_DEFAULT_WORKERS 0 // 0 = auto-detect
#define HML_THREADPOOL_MAX_WORKERS 256
#define HML_THREADPOOL_MIN_WORKERS 2
#define HML_THREADPOOL_DEQUE_INITIAL_CAPACITY 64
#define HML_THREADPOOL_DEQUE_MAX_CAPACITY 65536
#define HML_THREADPOOL_SUBMISSION_QUEUE_CAPACITY 4096
#define HML_THREADPOOL_STEAL_ATTEMPTS 32
#define HML_THREADPOOL_IDLE_SLEEP_US 100
#define HML_THREADPOOL_STEAL_SEED_MULT 1103515245

// Forward declarations
typedef struct HmlThreadPool HmlThreadPool;
typedef struct HmlWorker HmlWorker;
typedef struct HmlWorkStealingDeque HmlWorkStealingDeque;
typedef struct HmlWorkItem HmlWorkItem;

// Work item callback type
typedef void* (*HmlWorkItemFunc)(void* data, void* ctx);

// Work item - a unit of work to execute
typedef struct HmlWorkItem {
HmlWorkItemFunc func; // Function to execute
void* data; // Data to pass to function
void* ctx; // Context
void* result; // Result storage
atomic_int completed; // 1 when work is done
pthread_mutex_t* mutex; // For waiting on completion
pthread_cond_t* cond; // For signaling completion
int has_waiter; // 1 if someone is waiting for result
struct HmlWorkItem* next; // For linked list
} HmlWorkItem;

// Chase-Lev work-stealing deque
typedef struct HmlWorkStealingDeque {
HmlWorkItem** items;
atomic_long bottom;
atomic_long top;
int capacity;
int max_capacity;
pthread_mutex_t resize_lock;
} HmlWorkStealingDeque;

// Per-worker state
typedef struct HmlWorker {
int id;
pthread_t thread;
HmlThreadPool* pool;
HmlWorkStealingDeque* deque;
unsigned int steal_seed;
atomic_int active;
atomic_long tasks_executed;
atomic_long tasks_stolen;
} HmlWorker;

// Global submission queue
typedef struct HmlSubmissionQueue {
HmlWorkItem* head;
HmlWorkItem* tail;
int count;
int capacity;
pthread_mutex_t mutex;
pthread_cond_t not_empty;
} HmlSubmissionQueue;

// The thread pool
typedef struct HmlThreadPool {
int num_workers;
HmlWorker* workers;
HmlSubmissionQueue* submission;
atomic_int shutdown;
atomic_int started;
pthread_mutex_t start_mutex;
pthread_cond_t start_cond;
} HmlThreadPool;

// ========== THREAD POOL API ==========

// Initialize the global thread pool
int hml_thread_pool_init(int num_workers);

// Shutdown the global thread pool
void hml_thread_pool_shutdown(void);

// Submit a work item to the thread pool
HmlWorkItem* hml_thread_pool_submit(HmlWorkItemFunc func, void* data, void* ctx);

// Wait for a work item to complete
void* hml_work_item_wait(HmlWorkItem* item);

// Free a work item
void hml_work_item_free(HmlWorkItem* item);

// Check if current thread is a pool worker
int hml_thread_pool_current_worker_id(void);

// Check if thread pool is initialized
int hml_thread_pool_is_initialized(void);

#endif // HEMLOCK_RUNTIME_THREAD_POOL_H
Loading
Loading