diff --git a/include/bx/inline/thread.inl b/include/bx/inline/thread.inl new file mode 100644 index 0000000..6eb2052 --- /dev/null +++ b/include/bx/inline/thread.inl @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#ifndef BX_THREAD_H_HEADER_GUARD +# error "Must be included from bx/thread.h!" +#endif // BX_THREAD_H_HEADER_GUARD + +#define BX_TLS_DATA_TEB (BX_PLATFORM_WINDOWS && BX_CPU_X86 && !BX_CRT_NONE) + +#if BX_TLS_DATA_TEB +# if BX_COMPILER_MSVC +# if BX_ARCH_64BIT +extern "C" unsigned __int64 __readgsqword(unsigned long _offset); +# pragma intrinsic(__readgsqword) +# else +extern "C" unsigned long __readfsdword(unsigned long _offset); +# pragma intrinsic(__readfsdword) +# endif // BX_ARCH_64BIT +# endif // BX_COMPILER_MSVC + +namespace bx +{ + constexpr uint32_t kTebTlsSlotsOffset = BX_ARCH_64BIT ? 0x1480 : 0xe10; + constexpr uint32_t kTebTlsExpansionSlotsOffset = BX_ARCH_64BIT ? 0x1780 : 0xf94; + constexpr uint32_t kTebTlsSlotsNum = 64; + +# if BX_COMPILER_MSVC && BX_ARCH_64BIT + inline void* readTeb(uint32_t _offset) + { + return (void*)__readgsqword(_offset); + } +# elif BX_COMPILER_MSVC + inline void* readTeb(uint32_t _offset) + { + return (void*)__readfsdword(_offset); + } +# elif BX_ARCH_64BIT + inline void* readTeb(uint32_t _offset) + { + void* ptr; + __asm__ volatile ("movq %%gs:(%1), %0" : "=r" (ptr) : "r" (uintptr_t(_offset) ) ); + return ptr; + } +# else + inline void* readTeb(uint32_t _offset) + { + void* ptr; + __asm__ volatile ("movl %%fs:(%1), %0" : "=r" (ptr) : "r" (uintptr_t(_offset) ) ); + return ptr; + } +# endif // BX_COMPILER_* + + inline void* TlsData::get() const + { + const uint32_t slot = *(const uint32_t*)m_internal; + + if (slot < kTebTlsSlotsNum) [[likely]] + { + return readTeb(kTebTlsSlotsOffset + slot*sizeof(void*) ); + } + + void** expansionSlots = (void**)readTeb(kTebTlsExpansionSlotsOffset); + + return NULL == expansionSlots + ? NULL + : expansionSlots[slot - kTebTlsSlotsNum] + ; + } + +} // namespace bx +#endif // BX_TLS_DATA_TEB diff --git a/include/bx/thread.h b/include/bx/thread.h index c5d9e1a..764c4e5 100644 --- a/include/bx/thread.h +++ b/include/bx/thread.h @@ -97,6 +97,8 @@ namespace bx } // namespace bx +#include "inline/thread.inl" + #endif #endif // BX_THREAD_H_HEADER_GUARD diff --git a/src/thread.cpp b/src/thread.cpp index 111ffba..9a631f9 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -358,11 +358,13 @@ namespace bx BX_ASSERT(0 != result, "Failed to free TLS index (err: 0x%08x).", GetLastError() ); BX_UNUSED(result); } +#if !BX_TLS_DATA_TEB void* TlsData::get() const { TlsDataInternal* ti = (TlsDataInternal*)m_internal; return TlsGetValue(ti->m_id); } +#endif // !BX_TLS_DATA_TEB void TlsData::set(void* _ptr) { diff --git a/tests/thread_test.cpp b/tests/thread_test.cpp index 9ac9ab5..cb5a69c 100644 --- a/tests/thread_test.cpp +++ b/tests/thread_test.cpp @@ -72,4 +72,97 @@ TEST_CASE("Thread", "") } } +static constexpr uint32_t kNumTlsData = 80; + +struct TlsDataCtx +{ + bx::TlsData* tls; + uint32_t seed; + uint32_t numErrors; +}; + +static int32_t tlsDataThread(bx::Thread* _thread, void* _userData) +{ + BX_UNUSED(_thread); + + TlsDataCtx& ctx = *(TlsDataCtx*)_userData; + + for (uint32_t ii = 0; ii < kNumTlsData; ++ii) + { + ctx.numErrors += NULL != ctx.tls[ii].get(); + ctx.tls[ii].set( (void*)uintptr_t(ctx.seed + ii) ); + } + + for (uint32_t jj = 0; jj < 1000; ++jj) + { + for (uint32_t ii = 0; ii < kNumTlsData; ++ii) + { + ctx.numErrors += uintptr_t(ctx.seed + ii) != uintptr_t(ctx.tls[ii].get() ); + } + } + + return bx::kExitSuccess; +} + +TEST_CASE("TlsData", "") +{ + bx::TlsData* tls = new bx::TlsData[kNumTlsData]; + + for (uint32_t ii = 0; ii < kNumTlsData; ++ii) + { + tls[ii].set( (void*)uintptr_t(0x1000 + ii) ); + } + + TlsDataCtx ctx[4]; + bx::Thread thread[4]; + + for (uint32_t ii = 0; ii < BX_COUNTOF(thread); ++ii) + { + ctx[ii] = { tls, (ii + 1) << 16, 0 }; + thread[ii].init(tlsDataThread, &ctx[ii]); + } + + for (uint32_t ii = 0; ii < BX_COUNTOF(thread); ++ii) + { + thread[ii].shutdown(); + REQUIRE(0 == ctx[ii].numErrors); + } + + for (uint32_t ii = 0; ii < kNumTlsData; ++ii) + { + REQUIRE(uintptr_t(0x1000 + ii) == uintptr_t(tls[ii].get() ) ); + } + + delete [] tls; +} + +static thread_local void* s_threadLocal; + +BX_NO_INLINE void* getTlsData(const bx::TlsData& _tls) +{ + return _tls.get(); +} + +BX_NO_INLINE void* getThreadLocal() +{ + return s_threadLocal; +} + +TEST_CASE("TlsData-bench", "[.][!benchmark]") +{ + bx::TlsData tls; + tls.set(&tls); + s_threadLocal = &tls; + + BENCHMARK("TlsData::get") + { + return getTlsData(tls); + }; + + BENCHMARK("thread_local") + { + return getThreadLocal(); + }; +} + #endif // BX_CONFIG_SUPPORTS_THREADING