From 39f4bd35a3b0cfb4792360b590c4675722dabecf Mon Sep 17 00:00:00 2001 From: fallenoak Date: Sat, 4 Mar 2023 10:50:12 -0600 Subject: [PATCH] feat(thread): add basic thread functions --- bc/System_Thread.cpp | 161 +++++++++++++++++++++++++++++++++++++++++++ bc/System_Thread.hpp | 37 ++++++++++ bc/Thread.cpp | 33 +++++++++ bc/Thread.hpp | 44 ++++++++++++ test/Thread.cpp | 21 ++++++ 5 files changed, 296 insertions(+) create mode 100644 bc/System_Thread.cpp create mode 100644 bc/System_Thread.hpp create mode 100644 bc/Thread.cpp create mode 100644 bc/Thread.hpp create mode 100644 test/Thread.cpp diff --git a/bc/System_Thread.cpp b/bc/System_Thread.cpp new file mode 100644 index 0000000..2dbff92 --- /dev/null +++ b/bc/System_Thread.cpp @@ -0,0 +1,161 @@ +#include "bc/System_Thread.hpp" +#include "bc/Debug.hpp" +#include "bc/Memory.hpp" +#include "bc/String.hpp" + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) +#include +#endif + +bool Blizzard::System_Thread::s_initialized; +Blizzard::Thread::ThreadRecord* Blizzard::System_Thread::s_mainThread; +Blizzard::Lock::Mutex Blizzard::System_Thread::s_mutex; +Blizzard::Lock::Mutex Blizzard::System_Thread::s_registryMutex; +Blizzard::Thread::TLSSlot Blizzard::System_Thread::s_stackTraceEntryPointTLS; +Blizzard::Thread::TLSSlot Blizzard::System_Thread::s_threadRecordTLS; +std::map* Blizzard::System_Thread::s_threadRegistry; +Blizzard::Thread::TLSSlot* Blizzard::System_Thread::s_slotList[128]; +int32_t Blizzard::System_Thread::s_slotListUsed; + +void Blizzard::System_Thread::AddToRegistry(Thread::ThreadRecord* thread) { + Blizzard::Lock::MutexEnter(System_Thread::s_registryMutex); + + if (!System_Thread::s_threadRegistry) { + auto m = Blizzard::Memory::Allocate(sizeof(std::map), 0, __FILE__, __LINE__, nullptr); + System_Thread::s_threadRegistry = new (m) std::map(); + } + + System_Thread::s_threadRegistry->insert(std::pair(thread, thread)); + + Blizzard::Lock::MutexLeave(System_Thread::s_registryMutex); +} + +bool Blizzard::System_Thread::AllocateLocalStorage(Thread::TLSSlot* slot, void (*destructor)(void*)) { + System_Thread::InitThreadSystem(); + + BLIZZARD_ASSERT(!System_Thread::TLSSlotIsAllocated(slot)); + + if (!System_Thread::InternalAllocateLocalStorage(slot, destructor)) { + BLIZZARD_ASSERT(!"failed to allocate TLS"); + return false; + } + + slot->destructor = destructor; + + Blizzard::Lock::MutexEnter(System_Thread::s_mutex); + + System_Thread::s_slotList[System_Thread::s_slotListUsed] = slot; + System_Thread::s_slotListUsed++; + + Blizzard::Lock::MutexLeave(System_Thread::s_mutex); + + return true; +} + +bool Blizzard::System_Thread::AllocateTLSSlot(Thread::TLSSlot* slot, void (*destructor)(void*)) { + System_Thread::InitThreadSystem(); + + Blizzard::Lock::MutexEnter(System_Thread::s_mutex); + + auto result = false; + + if (System_Thread::TLSSlotIsAllocated(slot) || System_Thread::AllocateLocalStorage(slot, destructor)) { + result = true; + } + + Blizzard::Lock::MutexLeave(System_Thread::s_mutex); + + return result; +} + +void Blizzard::System_Thread::InitThreadSystem() { + if (System_Thread::s_initialized) { + return; + } + + System_Thread::s_initialized = true; + + Blizzard::Lock::MutexCreate(System_Thread::s_mutex); + Blizzard::Lock::MutexCreate(System_Thread::s_registryMutex); + + Blizzard::Thread::AllocateLocalStorage(&System_Thread::s_threadRecordTLS); + Blizzard::Thread::AllocateLocalStorage(&System_Thread::s_stackTraceEntryPointTLS); + + System_Thread::s_mainThread = System_Thread::NewThread(nullptr, nullptr, nullptr); + Blizzard::Thread::SetLocalStorage(&System_Thread::s_threadRecordTLS, System_Thread::s_mainThread); + + System_Thread::s_mainThread->unkC = 1; + System_Thread::s_mainThread->unk10 = 1; +} + +bool Blizzard::System_Thread::InternalAllocateLocalStorage(Thread::TLSSlot* slot, void (*destructor)(void*)) { +#if defined(WHOA_SYSTEM_WIN) + auto index = TlsAlloc(); + if (index == TLS_OUT_OF_INDEXES) { + return false; + } + + slot->key = index; + slot->allocated = true; + + return true; +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + if (pthread_key_create(&slot->key, destructor)) { + return false; + } + + slot->allocated = true; + return true; +#endif +} + +void* Blizzard::System_Thread::InternalGetLocalStorage(const Thread::TLSSlot* slot) { +#if defined(WHOA_SYSTEM_WIN) + return TlsGetValue(slot->key); +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + return pthread_getspecific(slot->key); +#endif +} + +void Blizzard::System_Thread::InternalSetLocalStorage(const Thread::TLSSlot* slot, const void* value) { +#if defined(WHOA_SYSTEM_WIN) + auto result = TlsSetValue(slot->key, const_cast(value)); + BLIZZARD_ASSERT(result); +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + auto err = pthread_setspecific(slot->key, value); + BLIZZARD_ASSERT(err == 0); +#endif +} + +Blizzard::Thread::ThreadRecord* Blizzard::System_Thread::NewThread(uint32_t (*a1)(void*), void* a2, const char* name) { + if (!name) { + name = ""; + } + + auto nameLen = Blizzard::String::Length(name); + + auto thread = static_cast( + Blizzard::Memory::Allocate(sizeof(Blizzard::Thread::ThreadRecord) + nameLen + 1, 0, __FILE__, __LINE__, nullptr) + ); + + Blizzard::String::MemFill(thread, sizeof(Blizzard::Thread::ThreadRecord), 0); + + thread->unk4 = a2; + thread->unk8 = a1; + thread->unkC = 0; + thread->unk10 = 2; + + Blizzard::String::Copy(&thread->name, name, nameLen + 1); + + System_Thread::AddToRegistry(thread); + + return thread; +} + +bool Blizzard::System_Thread::TLSSlotIsAllocated(const Thread::TLSSlot* slot) { + return slot->allocated; +} diff --git a/bc/System_Thread.hpp b/bc/System_Thread.hpp new file mode 100644 index 0000000..eda7c01 --- /dev/null +++ b/bc/System_Thread.hpp @@ -0,0 +1,37 @@ +#ifndef BC_SYSTEM_THREAD_HPP +#define BC_SYSTEM_THREAD_HPP + +#include "bc/Lock.hpp" +#include "bc/Thread.hpp" +#include +#include + +namespace Blizzard { +namespace System_Thread { + +// Variables +extern bool s_initialized; +extern Thread::ThreadRecord* s_mainThread; +extern Lock::Mutex s_mutex; +extern Lock::Mutex s_registryMutex; +extern Thread::TLSSlot s_stackTraceEntryPointTLS; +extern Thread::TLSSlot s_threadRecordTLS; +extern std::map* s_threadRegistry; +extern Thread::TLSSlot* s_slotList[128]; +extern int32_t s_slotListUsed; + +// Functions +void AddToRegistry(Thread::ThreadRecord* thread); +bool AllocateLocalStorage(Thread::TLSSlot* slot, void (*destructor)(void*)); +bool AllocateTLSSlot(Thread::TLSSlot* slot, void (*destructor)(void*)); +void InitThreadSystem(); +bool InternalAllocateLocalStorage(Thread::TLSSlot* slot, void (*destructor)(void*)); +void* InternalGetLocalStorage(const Thread::TLSSlot* slot); +void InternalSetLocalStorage(const Thread::TLSSlot* slot, const void* value); +Thread::ThreadRecord* NewThread(uint32_t (*a1)(void*), void* a2, const char* name); +bool TLSSlotIsAllocated(const Thread::TLSSlot* slot); + +} // namespace System_Thread +} // namespace Blizzard + +#endif diff --git a/bc/Thread.cpp b/bc/Thread.cpp new file mode 100644 index 0000000..ae5c2cf --- /dev/null +++ b/bc/Thread.cpp @@ -0,0 +1,33 @@ +#include "bc/Thread.hpp" +#include "bc/Debug.hpp" +#include "bc/System_Thread.hpp" + +void Blizzard::Thread::AllocateLocalStorage(TLSSlot* slot) { + System_Thread::AllocateLocalStorage(slot, nullptr); +} + +void* Blizzard::Thread::RegisterLocalStorage(TLSSlot* slot, void* (*constructor)(void*), void* userData, void (*destructor)(void*)) { + if (!System_Thread::TLSSlotIsAllocated(slot) && !System_Thread::AllocateTLSSlot(slot, destructor)) { + BLIZZARD_ASSERT(!"Unable to allocate thread-local storage"); + } + + auto value = System_Thread::InternalGetLocalStorage(slot); + if (value) { + return value; + } + + value = constructor(userData); + System_Thread::InternalSetLocalStorage(slot, value); + + return value; +} + +void Blizzard::Thread::SetLocalStorage(const TLSSlot* slot, const void* value) { + BLIZZARD_ASSERT(Blizzard::Thread::TLSSlotIsAllocated(slot)); + + System_Thread::InternalSetLocalStorage(slot, value); +} + +bool Blizzard::Thread::TLSSlotIsAllocated(const TLSSlot* slot) { + return System_Thread::TLSSlotIsAllocated(slot); +} diff --git a/bc/Thread.hpp b/bc/Thread.hpp new file mode 100644 index 0000000..9334ef3 --- /dev/null +++ b/bc/Thread.hpp @@ -0,0 +1,44 @@ +#ifndef BC_THREAD_HPP +#define BC_THREAD_HPP + +#include + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) +#include +#endif + +namespace Blizzard { +namespace Thread { + +// Types +struct ThreadRecord { +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + pthread_t unk0; +#endif + void* unk4; + uint32_t (*unk8)(void*); + int32_t unkC; + int32_t unk10; + char name; +}; + +struct TLSSlot { +#if defined(WHOA_SYSTEM_WIN) + uint32_t key; +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + pthread_key_t key; +#endif + void (*destructor)(void*); + bool allocated; +}; + +// Functions +void AllocateLocalStorage(TLSSlot* slot); +void* RegisterLocalStorage(TLSSlot* slot, void* (*constructor)(void*), void* userData, void (*destructor)(void*)); +void SetLocalStorage(const TLSSlot* slot, const void* value); +bool TLSSlotIsAllocated(const TLSSlot* slot); + +} // namespace Thread +} // namespace Blizzard + +#endif diff --git a/test/Thread.cpp b/test/Thread.cpp new file mode 100644 index 0000000..6c62d62 --- /dev/null +++ b/test/Thread.cpp @@ -0,0 +1,21 @@ +#include "bc/Thread.hpp" +#include "bc/Memory.hpp" +#include "test/Test.hpp" + +void* ConstructInteger(void* value) { + auto ptr = reinterpret_cast(Blizzard::Memory::Allocate(sizeof(int32_t))); + *ptr = *reinterpret_cast(value); + return ptr; +} + +TEST_CASE("Blizzard::Thread::RegisterLocalStorage", "[thread]") { + SECTION("constructs value and stores it in available TLS slot") { + Blizzard::Thread::TLSSlot slot {}; + Blizzard::Thread::AllocateLocalStorage(&slot); + + int32_t value = 12345; + auto ptr = Blizzard::Thread::RegisterLocalStorage(&slot, ConstructInteger, &value, nullptr); + + REQUIRE(*reinterpret_cast(ptr) == value); + } +}