diff --git a/kernel/sched/sched.cpp b/kernel/sched/sched.cpp index e4dbefd5..31949340 100644 --- a/kernel/sched/sched.cpp +++ b/kernel/sched/sched.cpp @@ -816,7 +816,7 @@ __PRIVILEGED_CODE task* create_user_thread( t->exec.system_stack_top = sys_stack_top; t->exec.pt_root = paging::supervisor_pt_root_for_user_task(creator->exec.mm_ctx->pt_root); t->exec.user_pt_root = creator->exec.mm_ctx->pt_root; - t->exec.tls_base = 0; + t->exec.tls_base = creator->exec.tls_base; t->task_stack_base = 0; // user stack is not VMM-allocated t->sys_stack_base = sys_stack_base; diff --git a/userland/apps/Makefile b/userland/apps/Makefile index 1c5551b1..e60102db 100644 --- a/userland/apps/Makefile +++ b/userland/apps/Makefile @@ -5,7 +5,7 @@ APP_DIRS := init hello shell ls cat rm stat touch sleep true false clear ptytest date \ clockbench stlxdm stlxterm doom ping ifconfig nslookup arp udpecho tcpecho \ fetch polltest dropbear blackjack wordle hangman snake tetris \ - grep wc head threadtest uname kill cxxtest + grep wc head threadtest uname kill cxxtest synctest APP_COUNT := $(words $(APP_DIRS)) all: diff --git a/userland/apps/synctest/Makefile b/userland/apps/synctest/Makefile new file mode 100644 index 00000000..f0e3775d --- /dev/null +++ b/userland/apps/synctest/Makefile @@ -0,0 +1,3 @@ +APP_NAME := synctest +APP_LIBS := stlxcxx +include ../../mk/cxxapp.mk diff --git a/userland/apps/synctest/src/synctest.cpp b/userland/apps/synctest/src/synctest.cpp new file mode 100644 index 00000000..ad52af5a --- /dev/null +++ b/userland/apps/synctest/src/synctest.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include + +static int g_passed = 0; +static int g_failed = 0; + +static void check(const char* name, bool ok) { + printf(" %s: %s\n", ok ? "PASS" : "FAIL", name); + if (ok) g_passed++; + else g_failed++; +} + +// --- Test 1: Mutex stress --- + +static constexpr int MUTEX_THREADS = 8; +static constexpr int MUTEX_ITERS = 10000; + +static void test_mutex_stress() { + stlxstd::mutex mtx; + int counter = 0; + + stlxstd::thread threads[MUTEX_THREADS]; + for (int i = 0; i < MUTEX_THREADS; i++) { + threads[i] = stlxstd::thread([&] { + for (int j = 0; j < MUTEX_ITERS; j++) { + stlxstd::lock_guard guard(mtx); + counter++; + } + }); + } + for (int i = 0; i < MUTEX_THREADS; i++) { + threads[i].join(); + } + + check("mutex stress (8 threads x 10000)", counter == MUTEX_THREADS * MUTEX_ITERS); +} + +// --- Test 2: Condition variable producer/consumer --- + +static constexpr int ITEMS = 100; + +static void test_condvar_producer_consumer() { + stlxstd::mutex mtx; + stlxstd::condition_variable cv; + int produced = 0; + int consumed = 0; + bool done = false; + + stlxstd::thread consumer([&] { + stlxstd::unique_lock lock(mtx); + while (!done || produced > consumed) { + cv.wait(lock, [&] { return produced > consumed || done; }); + while (produced > consumed) { + consumed++; + } + } + }); + + stlxstd::thread producer([&] { + for (int i = 0; i < ITEMS; i++) { + { + stlxstd::lock_guard guard(mtx); + produced++; + } + cv.notify_one(); + } + { + stlxstd::lock_guard guard(mtx); + done = true; + } + cv.notify_one(); + }); + + producer.join(); + consumer.join(); + + check("condvar producer/consumer (100 items)", consumed == ITEMS); +} + +// --- Test 3: Barrier synchronization --- + +static constexpr int BARRIER_THREADS = 4; + +static void test_barrier() { + stlxstd::barrier bar(BARRIER_THREADS); + volatile int flags[BARRIER_THREADS] = {}; + volatile int verified[BARRIER_THREADS] = {}; + + stlxstd::thread threads[BARRIER_THREADS]; + for (int i = 0; i < BARRIER_THREADS; i++) { + threads[i] = stlxstd::thread([&, i] { + __atomic_store_n(&flags[i], 1, __ATOMIC_RELEASE); + bar.arrive_and_wait(); + // After barrier: all flags must be set + bool all_set = true; + for (int j = 0; j < BARRIER_THREADS; j++) { + if (!__atomic_load_n(&flags[j], __ATOMIC_ACQUIRE)) { + all_set = false; + } + } + __atomic_store_n(&verified[i], all_set ? 1 : 0, __ATOMIC_RELEASE); + }); + } + for (int i = 0; i < BARRIER_THREADS; i++) { + threads[i].join(); + } + + bool ok = true; + for (int i = 0; i < BARRIER_THREADS; i++) { + if (!verified[i]) ok = false; + } + check("barrier (4 threads)", ok); +} + +// --- Test 4: Thread join --- + +static void test_thread_join() { + volatile int value = 0; + + stlxstd::thread t([&] { + __atomic_store_n(&value, 42, __ATOMIC_RELEASE); + }); + t.join(); + + check("thread join", __atomic_load_n(&value, __ATOMIC_ACQUIRE) == 42); +} + +// --- Test 5: Thread detach --- + +static void test_thread_detach() { + volatile int flag = 0; + + { + stlxstd::thread t([&] { + __atomic_store_n(&flag, 1, __ATOMIC_RELEASE); + }); + t.detach(); + } + + // Brief busy-wait for the detached thread to run + for (int i = 0; i < 10000000; i++) { + if (__atomic_load_n(&flag, __ATOMIC_ACQUIRE)) break; + asm volatile("" ::: "memory"); + } + + check("thread detach", __atomic_load_n(&flag, __ATOMIC_ACQUIRE) == 1); +} + +// --- Test 6: Multiple independent mutexes --- + +static constexpr int MULTI_THREADS = 4; +static constexpr int MULTI_ITERS = 5000; + +static void test_multi_mutex() { + stlxstd::mutex mtx_a, mtx_b; + int counter_a = 0; + int counter_b = 0; + + stlxstd::thread threads[MULTI_THREADS]; + for (int i = 0; i < MULTI_THREADS; i++) { + threads[i] = stlxstd::thread([&] { + for (int j = 0; j < MULTI_ITERS; j++) { + { + stlxstd::lock_guard guard(mtx_a); + counter_a++; + } + { + stlxstd::lock_guard guard(mtx_b); + counter_b++; + } + } + }); + } + for (int i = 0; i < MULTI_THREADS; i++) { + threads[i].join(); + } + + bool ok = (counter_a == MULTI_THREADS * MULTI_ITERS) && + (counter_b == MULTI_THREADS * MULTI_ITERS); + check("multi-mutex (2 locks, 4 threads x 5000)", ok); +} + +int main() { + printf("\nsynctest: Stellux synchronization test suite\n\n"); + + printf("[mutex]\n"); + test_mutex_stress(); + + printf("\n[condition variable]\n"); + test_condvar_producer_consumer(); + + printf("\n[barrier]\n"); + test_barrier(); + + printf("\n[thread lifecycle]\n"); + test_thread_join(); + test_thread_detach(); + + printf("\n[multi-mutex]\n"); + test_multi_mutex(); + + printf("\n--- Results: %d passed, %d failed ---\n\n", g_passed, g_failed); + return g_failed > 0 ? 1 : 0; +} diff --git a/userland/lib/Makefile b/userland/lib/Makefile index 20506879..e8801107 100644 --- a/userland/lib/Makefile +++ b/userland/lib/Makefile @@ -2,7 +2,7 @@ # Stellux Userland - Libraries # -LIB_DIRS := libstlx libstlxgfx libbearssl +LIB_DIRS := libstlx libstlxcxx libstlxgfx libbearssl LIB_COUNT := $(words $(LIB_DIRS)) all: diff --git a/userland/lib/libstlx/include/stlx/barrier.h b/userland/lib/libstlx/include/stlx/barrier.h new file mode 100644 index 00000000..2b6cabb8 --- /dev/null +++ b/userland/lib/libstlx/include/stlx/barrier.h @@ -0,0 +1,25 @@ +#ifndef STLX_BARRIER_H +#define STLX_BARRIER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint64_t count; + uint32_t generation; + uint32_t total; +} stlx_barrier_t; + +void stlx_barrier_init(stlx_barrier_t* b, uint32_t count); + +/* Block until all count threads have called barrier_wait. Reusable. */ +void stlx_barrier_wait(stlx_barrier_t* b); + +#ifdef __cplusplus +} +#endif + +#endif /* STLX_BARRIER_H */ diff --git a/userland/lib/libstlx/include/stlx/cond.h b/userland/lib/libstlx/include/stlx/cond.h new file mode 100644 index 00000000..5c27b8e2 --- /dev/null +++ b/userland/lib/libstlx/include/stlx/cond.h @@ -0,0 +1,29 @@ +#ifndef STLX_COND_H +#define STLX_COND_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { uint32_t seq; } stlx_cond_t; + +#define STLX_COND_INIT { 0 } + +/* Atomically unlocks the mutex and sleeps until signaled, then re-locks. + * Callers must re-check the predicate in a loop (spurious wakeups allowed). */ +void stlx_cond_wait(stlx_cond_t* cv, stlx_mutex_t* m); + +/* Wake one waiter. */ +void stlx_cond_signal(stlx_cond_t* cv); + +/* Wake all waiters. */ +void stlx_cond_broadcast(stlx_cond_t* cv); + +#ifdef __cplusplus +} +#endif + +#endif /* STLX_COND_H */ diff --git a/userland/lib/libstlx/include/stlx/futex.h b/userland/lib/libstlx/include/stlx/futex.h new file mode 100644 index 00000000..39e31966 --- /dev/null +++ b/userland/lib/libstlx/include/stlx/futex.h @@ -0,0 +1,24 @@ +#ifndef STLX_FUTEX_H +#define STLX_FUTEX_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Block if *addr == expected. timeout_ns=0 waits indefinitely. + * Returns 0 on wake, negative errno on error (-EAGAIN, -ETIMEDOUT). */ +int stlx_futex_wait(uint32_t* addr, uint32_t expected, uint64_t timeout_ns); + +/* Wake up to count threads waiting on addr. Returns number woken. */ +int stlx_futex_wake(uint32_t* addr, uint32_t count); + +/* Wake all threads waiting on addr. Returns number woken. */ +int stlx_futex_wake_all(uint32_t* addr); + +#ifdef __cplusplus +} +#endif + +#endif /* STLX_FUTEX_H */ diff --git a/userland/lib/libstlx/include/stlx/mutex.h b/userland/lib/libstlx/include/stlx/mutex.h new file mode 100644 index 00000000..36ef9e27 --- /dev/null +++ b/userland/lib/libstlx/include/stlx/mutex.h @@ -0,0 +1,25 @@ +#ifndef STLX_MUTEX_H +#define STLX_MUTEX_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* State 0: unlocked, 1: locked (no waiters), 2: locked (with waiters) */ +typedef struct { uint32_t state; } stlx_mutex_t; + +#define STLX_MUTEX_INIT { 0 } + +void stlx_mutex_lock(stlx_mutex_t* m); +void stlx_mutex_unlock(stlx_mutex_t* m); + +/* Returns 0 if acquired, -1 if already held. */ +int stlx_mutex_trylock(stlx_mutex_t* m); + +#ifdef __cplusplus +} +#endif + +#endif /* STLX_MUTEX_H */ diff --git a/userland/lib/libstlx/include/stlx/proc.h b/userland/lib/libstlx/include/stlx/proc.h index 13c1541b..84e96684 100644 --- a/userland/lib/libstlx/include/stlx/proc.h +++ b/userland/lib/libstlx/include/stlx/proc.h @@ -3,6 +3,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* Wait status decode macros (Linux-compatible bit layout). */ #define STLX_WIFEXITED(s) (((s) & 0x7F) == 0) #define STLX_WEXITSTATUS(s) (((s) >> 8) & 0xFF) @@ -96,4 +100,8 @@ static inline int proc_thread_join(int handle, int* exit_code) { return proc_wai static inline int proc_thread_detach(int handle) { return proc_detach(handle); } static inline int proc_thread_kill(int handle) { return proc_kill(handle); } +#ifdef __cplusplus +} +#endif + #endif /* STLX_PROC_H */ diff --git a/userland/lib/libstlx/include/stlx/syscall_nums.h b/userland/lib/libstlx/include/stlx/syscall_nums.h index f6cc7590..b4b07213 100644 --- a/userland/lib/libstlx/include/stlx/syscall_nums.h +++ b/userland/lib/libstlx/include/stlx/syscall_nums.h @@ -12,4 +12,8 @@ #define SYS_PROC_KILL_TID 1018 #define SYS_PTY_CREATE 1020 +#define SYS_FUTEX_WAIT 1030 +#define SYS_FUTEX_WAKE 1031 +#define SYS_FUTEX_WAKE_ALL 1032 + #endif /* STLX_SYSCALL_NUMS_H */ diff --git a/userland/lib/libstlx/src/barrier.c b/userland/lib/libstlx/src/barrier.c new file mode 100644 index 00000000..b474e759 --- /dev/null +++ b/userland/lib/libstlx/src/barrier.c @@ -0,0 +1,26 @@ +#include +#include + +void stlx_barrier_init(stlx_barrier_t* b, uint32_t count) { + b->count = 0; + b->generation = 0; + b->total = count; +} + +void stlx_barrier_wait(stlx_barrier_t* b) { + uint32_t gen = __atomic_load_n(&b->generation, __ATOMIC_ACQUIRE); + + // Count grows monotonically (no reset). The last thread in each round + // is identified by count being a multiple of total. This eliminates + // the race between resetting count and advancing generation. + uint64_t arrived = __atomic_fetch_add(&b->count, 1, __ATOMIC_ACQ_REL) + 1; + + if (arrived % b->total == 0) { + __atomic_fetch_add(&b->generation, 1, __ATOMIC_RELEASE); + stlx_futex_wake_all(&b->generation); + } else { + while (__atomic_load_n(&b->generation, __ATOMIC_ACQUIRE) == gen) { + stlx_futex_wait(&b->generation, gen, 0); + } + } +} diff --git a/userland/lib/libstlx/src/cond.c b/userland/lib/libstlx/src/cond.c new file mode 100644 index 00000000..29c28399 --- /dev/null +++ b/userland/lib/libstlx/src/cond.c @@ -0,0 +1,19 @@ +#include +#include + +void stlx_cond_wait(stlx_cond_t* cv, stlx_mutex_t* m) { + uint32_t seq = __atomic_load_n(&cv->seq, __ATOMIC_RELAXED); + stlx_mutex_unlock(m); + stlx_futex_wait(&cv->seq, seq, 0); + stlx_mutex_lock(m); +} + +void stlx_cond_signal(stlx_cond_t* cv) { + __atomic_fetch_add(&cv->seq, 1, __ATOMIC_RELEASE); + stlx_futex_wake(&cv->seq, 1); +} + +void stlx_cond_broadcast(stlx_cond_t* cv) { + __atomic_fetch_add(&cv->seq, 1, __ATOMIC_RELEASE); + stlx_futex_wake_all(&cv->seq); +} diff --git a/userland/lib/libstlx/src/futex.c b/userland/lib/libstlx/src/futex.c new file mode 100644 index 00000000..2645aeed --- /dev/null +++ b/userland/lib/libstlx/src/futex.c @@ -0,0 +1,16 @@ +#define _GNU_SOURCE +#include +#include +#include + +int stlx_futex_wait(uint32_t* addr, uint32_t expected, uint64_t timeout_ns) { + return (int)syscall(SYS_FUTEX_WAIT, addr, expected, timeout_ns); +} + +int stlx_futex_wake(uint32_t* addr, uint32_t count) { + return (int)syscall(SYS_FUTEX_WAKE, addr, count); +} + +int stlx_futex_wake_all(uint32_t* addr) { + return (int)syscall(SYS_FUTEX_WAKE_ALL, addr); +} diff --git a/userland/lib/libstlx/src/mutex.c b/userland/lib/libstlx/src/mutex.c new file mode 100644 index 00000000..779e48be --- /dev/null +++ b/userland/lib/libstlx/src/mutex.c @@ -0,0 +1,35 @@ +#include +#include + +void stlx_mutex_lock(stlx_mutex_t* m) { + uint32_t c = 0; + if (__atomic_compare_exchange_n(&m->state, &c, 1, 0, + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { + return; + } + + do { + if (c == 2 || __atomic_compare_exchange_n(&m->state, &c, 2, 0, + __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + stlx_futex_wait(&m->state, 2, 0); + } + c = 0; + } while (!__atomic_compare_exchange_n(&m->state, &c, 2, 0, + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); +} + +void stlx_mutex_unlock(stlx_mutex_t* m) { + uint32_t prev = __atomic_exchange_n(&m->state, 0, __ATOMIC_RELEASE); + if (prev == 2) { + stlx_futex_wake(&m->state, 1); + } +} + +int stlx_mutex_trylock(stlx_mutex_t* m) { + uint32_t c = 0; + if (__atomic_compare_exchange_n(&m->state, &c, 1, 0, + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { + return 0; + } + return -1; +} diff --git a/userland/lib/libstlxcxx/Makefile b/userland/lib/libstlxcxx/Makefile new file mode 100644 index 00000000..97f6ba08 --- /dev/null +++ b/userland/lib/libstlxcxx/Makefile @@ -0,0 +1,47 @@ +# +# Stellux Userland - libstlxcxx Static Library +# +# C++ threading wrappers (stlxstd::thread, mutex, etc.) built on libstlx. +# + +USERLAND_ROOT ?= $(shell cd $(dir $(lastword $(MAKEFILE_LIST)))/../.. && pwd) +include $(USERLAND_ROOT)/mk/toolchain.mk + +AR ?= llvm-ar + +SRC_DIR := src +INCLUDE_DIR := include +BUILD_DIR := build/$(ARCH) +LIB_NAME := libstlxcxx.a +TARGET := $(BUILD_DIR)/$(LIB_NAME) + +SYSROOT_LIB := $(USERLAND_ROOT)/sysroot/$(ARCH)/lib +SYSROOT_INC := $(USERLAND_ROOT)/sysroot/$(ARCH)/include/stlxstd + +SOURCES := $(wildcard $(SRC_DIR)/*.cpp) +OBJECTS := $(SOURCES:$(SRC_DIR)/%.cpp=$(BUILD_DIR)/%.o) + +all: $(TARGET) install-headers + +$(TARGET): $(OBJECTS) + $(UQ)mkdir -p $(dir $@) + @echo "[AR] libstlxcxx.a ($(ARCH))" + $(UQ)$(AR) crs $@ $(OBJECTS) + $(UQ)mkdir -p $(SYSROOT_LIB) + $(UQ)cp $@ $(SYSROOT_LIB)/ + +install-headers: + $(UQ)mkdir -p $(SYSROOT_INC) + $(UQ)cp $(INCLUDE_DIR)/stlxstd/*.h $(SYSROOT_INC)/ + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp + $(UQ)mkdir -p $(dir $@) + @echo "[CXX] $< ($(ARCH))" + $(UQ)$(CXX) $(CXXFLAGS_COMMON) -I$(INCLUDE_DIR) -c $< -o $@ + +clean: + $(UQ)rm -rf build + $(UQ)rm -f $(SYSROOT_LIB)/$(LIB_NAME) + $(UQ)rm -rf $(SYSROOT_INC) + +.PHONY: all install-headers clean diff --git a/userland/lib/libstlxcxx/include/stlxstd/barrier.h b/userland/lib/libstlxcxx/include/stlxstd/barrier.h new file mode 100644 index 00000000..d3dba748 --- /dev/null +++ b/userland/lib/libstlxcxx/include/stlxstd/barrier.h @@ -0,0 +1,28 @@ +#ifndef STLXSTD_BARRIER_H +#define STLXSTD_BARRIER_H + +#include +#include + +namespace stlxstd { + +class barrier { +public: + explicit barrier(uint32_t count) { + stlx_barrier_init(&b_, count); + } + + ~barrier() = default; + + barrier(const barrier&) = delete; + barrier& operator=(const barrier&) = delete; + + void arrive_and_wait() { stlx_barrier_wait(&b_); } + +private: + stlx_barrier_t b_; +}; + +} // namespace stlxstd + +#endif // STLXSTD_BARRIER_H diff --git a/userland/lib/libstlxcxx/include/stlxstd/condition_variable.h b/userland/lib/libstlxcxx/include/stlxstd/condition_variable.h new file mode 100644 index 00000000..f6a1308c --- /dev/null +++ b/userland/lib/libstlxcxx/include/stlxstd/condition_variable.h @@ -0,0 +1,35 @@ +#ifndef STLXSTD_CONDITION_VARIABLE_H +#define STLXSTD_CONDITION_VARIABLE_H + +#include +#include + +namespace stlxstd { + +class condition_variable { +public: + condition_variable() : cv_(STLX_COND_INIT) {} + ~condition_variable() = default; + + condition_variable(const condition_variable&) = delete; + condition_variable& operator=(const condition_variable&) = delete; + + void wait(unique_lock& lock) { + stlx_cond_wait(&cv_, lock.mutex_ptr()->native()); + } + + template + void wait(unique_lock& lock, Pred pred) { + while (!pred()) wait(lock); + } + + void notify_one() { stlx_cond_signal(&cv_); } + void notify_all() { stlx_cond_broadcast(&cv_); } + +private: + stlx_cond_t cv_; +}; + +} // namespace stlxstd + +#endif // STLXSTD_CONDITION_VARIABLE_H diff --git a/userland/lib/libstlxcxx/include/stlxstd/mutex.h b/userland/lib/libstlxcxx/include/stlxstd/mutex.h new file mode 100644 index 00000000..940be96b --- /dev/null +++ b/userland/lib/libstlxcxx/include/stlxstd/mutex.h @@ -0,0 +1,82 @@ +#ifndef STLXSTD_MUTEX_H +#define STLXSTD_MUTEX_H + +#include + +namespace stlxstd { + +struct defer_lock_t {}; +inline constexpr defer_lock_t defer_lock{}; + +class mutex { +public: + mutex() : m_(STLX_MUTEX_INIT) {} + ~mutex() = default; + + mutex(const mutex&) = delete; + mutex& operator=(const mutex&) = delete; + + void lock() { stlx_mutex_lock(&m_); } + void unlock() { stlx_mutex_unlock(&m_); } + bool try_lock() { return stlx_mutex_trylock(&m_) == 0; } + + stlx_mutex_t* native() { return &m_; } + +private: + stlx_mutex_t m_; +}; + +template +class lock_guard { +public: + explicit lock_guard(Mutex& m) : m_(m) { m_.lock(); } + ~lock_guard() { m_.unlock(); } + + lock_guard(const lock_guard&) = delete; + lock_guard& operator=(const lock_guard&) = delete; + +private: + Mutex& m_; +}; + +template +class unique_lock { +public: + explicit unique_lock(Mutex& m) : m_(&m), owned_(true) { m_->lock(); } + unique_lock(Mutex& m, defer_lock_t) : m_(&m), owned_(false) {} + ~unique_lock() { if (owned_) m_->unlock(); } + + unique_lock(const unique_lock&) = delete; + unique_lock& operator=(const unique_lock&) = delete; + + unique_lock(unique_lock&& o) : m_(o.m_), owned_(o.owned_) { + o.m_ = nullptr; + o.owned_ = false; + } + + unique_lock& operator=(unique_lock&& o) { + if (this != &o) { + if (owned_) m_->unlock(); + m_ = o.m_; + owned_ = o.owned_; + o.m_ = nullptr; + o.owned_ = false; + } + return *this; + } + + void lock() { m_->lock(); owned_ = true; } + void unlock() { m_->unlock(); owned_ = false; } + bool try_lock() { owned_ = m_->try_lock(); return owned_; } + + bool owns_lock() const { return owned_; } + Mutex* mutex_ptr() const { return m_; } + +private: + Mutex* m_; + bool owned_; +}; + +} // namespace stlxstd + +#endif // STLXSTD_MUTEX_H diff --git a/userland/lib/libstlxcxx/include/stlxstd/thread.h b/userland/lib/libstlxcxx/include/stlxstd/thread.h new file mode 100644 index 00000000..ca07943b --- /dev/null +++ b/userland/lib/libstlxcxx/include/stlxstd/thread.h @@ -0,0 +1,134 @@ +#ifndef STLXSTD_THREAD_H +#define STLXSTD_THREAD_H + +#include +#include +#include +#include +#include + +namespace stlxstd { + +template struct remove_ref { using type = T; }; +template struct remove_ref { using type = T; }; +template struct remove_ref { using type = T; }; + +namespace detail { + +struct thread_context { + void (*invoke)(thread_context*); + void (*destroy)(thread_context*); +}; + +template +struct thread_context_impl : thread_context { + Fn fn; + + static void call(thread_context* base) { + auto* self = static_cast(base); + self->fn(); + } + + static void destruct(thread_context* base) { + static_cast(base)->~thread_context_impl(); + } + + explicit thread_context_impl(Fn&& f) : fn(static_cast(f)) { + invoke = &call; + destroy = &destruct; + } +}; + +// Defined in thread.cpp +extern "C" void stlxstd_thread_entry(void* arg); + +} // namespace detail + +class thread { +public: + static constexpr size_t STACK_SIZE = 64 * 1024; + + thread() : m_handle(-1), m_stack(nullptr) {} + + template + explicit thread(Fn&& fn) { + // Decay Fn so lvalue references become value types (same as std::thread). + // Without this, passing an lvalue stores a reference that can dangle. + using Decayed = typename remove_ref::type; + using ctx_t = detail::thread_context_impl; + auto* ctx = static_cast(malloc(sizeof(ctx_t))); + if (!ctx) abort(); + new (ctx) ctx_t(static_cast(fn)); + + m_stack = mmap(nullptr, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (m_stack == MAP_FAILED) { + ctx->~ctx_t(); + free(ctx); + abort(); + } + + void* stack_top = static_cast(m_stack) + STACK_SIZE; + m_handle = proc_create_thread(detail::stlxstd_thread_entry, ctx, + stack_top, "stlxstd"); + if (m_handle < 0) { + ctx->~ctx_t(); + free(ctx); + munmap(m_stack, STACK_SIZE); + m_stack = nullptr; + abort(); + } + + proc_thread_start(m_handle); + } + + ~thread() { + if (joinable()) abort(); + } + + thread(const thread&) = delete; + thread& operator=(const thread&) = delete; + + thread(thread&& o) : m_handle(o.m_handle), m_stack(o.m_stack) { + o.m_handle = -1; + o.m_stack = nullptr; + } + + thread& operator=(thread&& o) { + if (this != &o) { + if (joinable()) abort(); + m_handle = o.m_handle; + m_stack = o.m_stack; + o.m_handle = -1; + o.m_stack = nullptr; + } + return *this; + } + + bool joinable() const { return m_handle >= 0; } + + void join() { + if (!joinable()) return; + proc_thread_join(m_handle, nullptr); + munmap(m_stack, STACK_SIZE); + m_handle = -1; + m_stack = nullptr; + } + + // Stack is intentionally not freed on detach. The thread is still + // using it. It will be reclaimed when the process exits. + void detach() { + if (!joinable()) return; + proc_thread_detach(m_handle); + m_handle = -1; + m_stack = nullptr; + } + +private: + int m_handle; + void* m_stack; +}; + +} // namespace stlxstd + +#endif // STLXSTD_THREAD_H diff --git a/userland/lib/libstlxcxx/src/thread.cpp b/userland/lib/libstlxcxx/src/thread.cpp new file mode 100644 index 00000000..4a967eea --- /dev/null +++ b/userland/lib/libstlxcxx/src/thread.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +namespace stlxstd::detail { + +extern "C" void stlxstd_thread_entry(void* arg) { + auto* ctx = static_cast(arg); + ctx->invoke(ctx); + ctx->destroy(ctx); + free(ctx); + _exit(0); +} + +} // namespace stlxstd::detail