duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

threading.cpp (15031B)


      1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "threading.h"
      5 #include "assert.h"
      6 #include <memory>
      7 
      8 #if !defined(_WIN32) && !defined(__APPLE__)
      9 #ifndef _GNU_SOURCE
     10 #define _GNU_SOURCE
     11 #endif
     12 #endif
     13 
     14 #if defined(_WIN32)
     15 #include "windows_headers.h"
     16 #include <process.h>
     17 #else
     18 #include <pthread.h>
     19 #include <unistd.h>
     20 #if defined(__linux__)
     21 #include <sched.h>
     22 #include <sys/prctl.h>
     23 #include <sys/types.h>
     24 
     25 // glibc < v2.30 doesn't define gettid...
     26 #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30
     27 #include <sys/syscall.h>
     28 #define gettid() syscall(SYS_gettid)
     29 #endif
     30 #elif defined(__APPLE__)
     31 #include <mach/mach.h>
     32 #include <mach/mach_error.h>
     33 #include <mach/mach_time.h>
     34 #include <mach/semaphore.h>
     35 #include <mach/task.h>
     36 #else
     37 #include <pthread_np.h>
     38 #endif
     39 #endif
     40 
     41 #ifdef _WIN32
     42 union FileTimeU64Union
     43 {
     44   FILETIME filetime;
     45   u64 u64time;
     46 };
     47 #endif
     48 
     49 #ifdef __APPLE__
     50 // gets the CPU time used by the current thread (both system and user), in
     51 // microseconds, returns 0 on failure
     52 static u64 getthreadtime(thread_port_t thread)
     53 {
     54   mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
     55   thread_basic_info_data_t info;
     56 
     57   kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&info, &count);
     58   if (kr != KERN_SUCCESS)
     59     return 0;
     60 
     61   // add system and user time
     62   return (u64)info.user_time.seconds * (u64)1e6 + (u64)info.user_time.microseconds +
     63          (u64)info.system_time.seconds * (u64)1e6 + (u64)info.system_time.microseconds;
     64 }
     65 #endif
     66 
     67 #if defined(__linux__) || defined(__FreeBSD__)
     68 // Helper function to get either either the current cpu usage
     69 // in called thread or in id thread
     70 static u64 get_thread_time(void* id = 0)
     71 {
     72   clockid_t cid;
     73   if (id)
     74   {
     75     int err = pthread_getcpuclockid((pthread_t)id, &cid);
     76     if (err)
     77       return 0;
     78   }
     79   else
     80   {
     81     cid = CLOCK_THREAD_CPUTIME_ID;
     82   }
     83 
     84   struct timespec ts;
     85   int err = clock_gettime(cid, &ts);
     86   if (err)
     87     return 0;
     88 
     89   return (u64)ts.tv_sec * (u64)1e6 + (u64)ts.tv_nsec / (u64)1e3;
     90 }
     91 #endif
     92 
     93 void Threading::Timeslice()
     94 {
     95 #if defined(_WIN32)
     96   ::Sleep(0);
     97 #elif defined(__APPLE__)
     98   sched_yield();
     99 #else
    100   sched_yield();
    101 #endif
    102 }
    103 
    104 Threading::ThreadHandle::ThreadHandle() = default;
    105 
    106 #ifdef _WIN32
    107 Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
    108 {
    109   if (handle.m_native_handle)
    110   {
    111     HANDLE new_handle;
    112     if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle,
    113                         THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
    114     {
    115       m_native_handle = (void*)new_handle;
    116     }
    117   }
    118 }
    119 #else
    120 Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
    121   : m_native_handle(handle.m_native_handle)
    122 #ifdef __linux__
    123     ,
    124     m_native_id(handle.m_native_id)
    125 #endif
    126 {
    127 }
    128 #endif
    129 
    130 #ifdef _WIN32
    131 Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle) : m_native_handle(handle.m_native_handle)
    132 {
    133   handle.m_native_handle = nullptr;
    134 }
    135 #else
    136 Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle)
    137   : m_native_handle(handle.m_native_handle)
    138 #ifdef __linux__
    139     ,
    140     m_native_id(handle.m_native_id)
    141 #endif
    142 {
    143   handle.m_native_handle = nullptr;
    144 #ifdef __linux__
    145   handle.m_native_id = 0;
    146 #endif
    147 }
    148 #endif
    149 
    150 Threading::ThreadHandle::~ThreadHandle()
    151 {
    152 #ifdef _WIN32
    153   if (m_native_handle)
    154     CloseHandle(m_native_handle);
    155 #endif
    156 }
    157 
    158 Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
    159 {
    160   ThreadHandle ret;
    161 #ifdef _WIN32
    162   ret.m_native_handle =
    163     (void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, GetCurrentThreadId());
    164 #else
    165   ret.m_native_handle = (void*)pthread_self();
    166 #ifdef __linux__
    167   ret.m_native_id = gettid();
    168 #endif
    169 #endif
    170   return ret;
    171 }
    172 
    173 Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle)
    174 {
    175 #ifdef _WIN32
    176   if (m_native_handle)
    177     CloseHandle((HANDLE)m_native_handle);
    178   m_native_handle = handle.m_native_handle;
    179   handle.m_native_handle = nullptr;
    180 #else
    181   m_native_handle = handle.m_native_handle;
    182   handle.m_native_handle = nullptr;
    183 #ifdef __linux__
    184   m_native_id = handle.m_native_id;
    185   handle.m_native_id = 0;
    186 #endif
    187 #endif
    188   return *this;
    189 }
    190 
    191 Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle)
    192 {
    193 #ifdef _WIN32
    194   if (m_native_handle)
    195   {
    196     CloseHandle((HANDLE)m_native_handle);
    197     m_native_handle = nullptr;
    198   }
    199 
    200   HANDLE new_handle;
    201   if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle,
    202                       THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
    203   {
    204     m_native_handle = (void*)new_handle;
    205   }
    206 #else
    207   m_native_handle = handle.m_native_handle;
    208 #ifdef __linux__
    209   m_native_id = handle.m_native_id;
    210 #endif
    211 #endif
    212 
    213   return *this;
    214 }
    215 
    216 u64 Threading::ThreadHandle::GetCPUTime() const
    217 {
    218 #if defined(_WIN32) && !defined(_M_ARM64)
    219   u64 ret = 0;
    220   if (m_native_handle)
    221     QueryThreadCycleTime((HANDLE)m_native_handle, &ret);
    222   return ret;
    223 #elif defined(_WIN32)
    224   FileTimeU64Union user = {}, kernel = {};
    225   FILETIME dummy;
    226   GetThreadTimes((HANDLE)m_native_handle, &dummy, &dummy, &kernel.filetime, &user.filetime);
    227   return user.u64time + kernel.u64time;
    228 #elif defined(__APPLE__)
    229   return getthreadtime(pthread_mach_thread_np((pthread_t)m_native_handle));
    230 #elif defined(__linux__) || defined(__FreeBSD__)
    231   return get_thread_time(m_native_handle);
    232 #else
    233   return 0;
    234 #endif
    235 }
    236 
    237 bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
    238 {
    239 #if defined(_WIN32)
    240   if (processor_mask == 0)
    241     processor_mask = ~processor_mask;
    242 
    243   return (SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)processor_mask) != 0 || GetLastError() != ERROR_SUCCESS);
    244 #elif defined(__linux__)
    245   cpu_set_t set;
    246   CPU_ZERO(&set);
    247 
    248   if (processor_mask != 0)
    249   {
    250     for (u32 i = 0; i < 64; i++)
    251     {
    252       if (processor_mask & (static_cast<u64>(1) << i))
    253       {
    254         CPU_SET(i, &set);
    255       }
    256     }
    257   }
    258   else
    259   {
    260     long num_processors = sysconf(_SC_NPROCESSORS_CONF);
    261     for (long i = 0; i < num_processors; i++)
    262     {
    263       CPU_SET(i, &set);
    264     }
    265   }
    266 
    267   return sched_setaffinity((pid_t)m_native_id, sizeof(set), &set) >= 0;
    268 #else
    269   return false;
    270 #endif
    271 }
    272 
    273 Threading::Thread::Thread() = default;
    274 
    275 Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread), m_stack_size(thread.m_stack_size)
    276 {
    277   thread.m_stack_size = 0;
    278 }
    279 
    280 Threading::Thread::Thread(EntryPoint func) : ThreadHandle()
    281 {
    282   if (!Start(std::move(func)))
    283     Panic("Failed to start implicitly started thread.");
    284 }
    285 
    286 Threading::Thread::~Thread()
    287 {
    288   AssertMsg(!m_native_handle, "Thread should be detached or joined at destruction");
    289 }
    290 
    291 void Threading::Thread::SetStackSize(u32 size)
    292 {
    293   AssertMsg(!m_native_handle, "Can't change the stack size on a started thread");
    294   m_stack_size = size;
    295 }
    296 
    297 #if defined(_WIN32)
    298 
    299 unsigned Threading::Thread::ThreadProc(void* param)
    300 {
    301   std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
    302   (*entry.get())();
    303   return 0;
    304 }
    305 
    306 bool Threading::Thread::Start(EntryPoint func)
    307 {
    308   AssertMsg(!m_native_handle, "Can't start an already-started thread");
    309 
    310   std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
    311   unsigned thread_id;
    312   m_native_handle =
    313     reinterpret_cast<void*>(_beginthreadex(nullptr, m_stack_size, ThreadProc, func_clone.get(), 0, &thread_id));
    314   if (!m_native_handle)
    315     return false;
    316 
    317   // thread started, it'll release the memory
    318   func_clone.release();
    319   return true;
    320 }
    321 
    322 #elif defined(__linux__)
    323 
    324 // For Linux, we have to do a bit of trickery here to get the thread's ID back from
    325 // the thread itself, because it's not part of pthreads. We use a semaphore to signal
    326 // when the thread has started, and filled in thread_id_ptr.
    327 struct ThreadProcParameters
    328 {
    329   Threading::Thread::EntryPoint func;
    330   Threading::KernelSemaphore* start_semaphore;
    331   unsigned int* thread_id_ptr;
    332 };
    333 
    334 void* Threading::Thread::ThreadProc(void* param)
    335 {
    336   std::unique_ptr<ThreadProcParameters> entry(static_cast<ThreadProcParameters*>(param));
    337   *entry->thread_id_ptr = gettid();
    338   entry->start_semaphore->Post();
    339   entry->func();
    340   return nullptr;
    341 }
    342 
    343 bool Threading::Thread::Start(EntryPoint func)
    344 {
    345   AssertMsg(!m_native_handle, "Can't start an already-started thread");
    346 
    347   KernelSemaphore start_semaphore;
    348   std::unique_ptr<ThreadProcParameters> params(std::make_unique<ThreadProcParameters>());
    349   params->func = std::move(func);
    350   params->start_semaphore = &start_semaphore;
    351   params->thread_id_ptr = &m_native_id;
    352 
    353   pthread_attr_t attrs;
    354   bool has_attributes = false;
    355 
    356   if (m_stack_size != 0)
    357   {
    358     has_attributes = true;
    359     pthread_attr_init(&attrs);
    360   }
    361   if (m_stack_size != 0)
    362     pthread_attr_setstacksize(&attrs, m_stack_size);
    363 
    364   pthread_t handle;
    365   const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, params.get());
    366   if (res != 0)
    367     return false;
    368 
    369   // wait until it sets our native id
    370   start_semaphore.Wait();
    371 
    372   // thread started, it'll release the memory
    373   m_native_handle = (void*)handle;
    374   params.release();
    375   return true;
    376 }
    377 
    378 #else
    379 
    380 void* Threading::Thread::ThreadProc(void* param)
    381 {
    382   std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
    383   (*entry.get())();
    384   return nullptr;
    385 }
    386 
    387 bool Threading::Thread::Start(EntryPoint func)
    388 {
    389   AssertMsg(!m_native_handle, "Can't start an already-started thread");
    390 
    391   std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
    392 
    393   pthread_attr_t attrs;
    394   bool has_attributes = false;
    395 
    396   if (m_stack_size != 0)
    397   {
    398     has_attributes = true;
    399     pthread_attr_init(&attrs);
    400   }
    401   if (m_stack_size != 0)
    402     pthread_attr_setstacksize(&attrs, m_stack_size);
    403 
    404   pthread_t handle;
    405   const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, func_clone.get());
    406   if (res != 0)
    407     return false;
    408 
    409   // thread started, it'll release the memory
    410   m_native_handle = (void*)handle;
    411   func_clone.release();
    412   return true;
    413 }
    414 
    415 #endif
    416 
    417 void Threading::Thread::Detach()
    418 {
    419   AssertMsg(m_native_handle, "Can't detach without a thread");
    420 #ifdef _WIN32
    421   CloseHandle((HANDLE)m_native_handle);
    422   m_native_handle = nullptr;
    423 #else
    424   pthread_detach((pthread_t)m_native_handle);
    425   m_native_handle = nullptr;
    426 #ifdef __linux__
    427   m_native_id = 0;
    428 #endif
    429 #endif
    430 }
    431 
    432 void Threading::Thread::Join()
    433 {
    434   AssertMsg(m_native_handle, "Can't join without a thread");
    435 #ifdef _WIN32
    436   const DWORD res = WaitForSingleObject((HANDLE)m_native_handle, INFINITE);
    437   if (res != WAIT_OBJECT_0)
    438     Panic("WaitForSingleObject() for thread join failed");
    439 
    440   CloseHandle((HANDLE)m_native_handle);
    441   m_native_handle = nullptr;
    442 #else
    443   void* retval;
    444   const int res = pthread_join((pthread_t)m_native_handle, &retval);
    445   if (res != 0)
    446     Panic("pthread_join() for thread join failed");
    447 
    448   m_native_handle = nullptr;
    449 #ifdef __linux__
    450   m_native_id = 0;
    451 #endif
    452 #endif
    453 }
    454 
    455 Threading::ThreadHandle& Threading::Thread::operator=(Thread&& thread)
    456 {
    457   ThreadHandle::operator=(thread);
    458   m_stack_size = thread.m_stack_size;
    459   thread.m_stack_size = 0;
    460   return *this;
    461 }
    462 
    463 u64 Threading::GetThreadCpuTime()
    464 {
    465 #if defined(_WIN32) && !defined(_M_ARM64)
    466   u64 ret = 0;
    467   QueryThreadCycleTime(GetCurrentThread(), &ret);
    468   return ret;
    469 #elif defined(_WIN32)
    470   FileTimeU64Union user = {}, kernel = {};
    471   FILETIME dummy;
    472   GetThreadTimes(GetCurrentThread(), &dummy, &dummy, &kernel.filetime, &user.filetime);
    473   return user.u64time + kernel.u64time;
    474 #elif defined(__APPLE__)
    475   return getthreadtime(pthread_mach_thread_np(pthread_self()));
    476 #else
    477   return get_thread_time(nullptr);
    478 #endif
    479 }
    480 
    481 u64 Threading::GetThreadTicksPerSecond()
    482 {
    483 #if defined(_WIN32) && !defined(_M_ARM64)
    484   // On x86, despite what the MS documentation says, this basically appears to be rdtsc.
    485   // So, the frequency is our base clock speed (and stable regardless of power management).
    486   static u64 frequency = 0;
    487   if (frequency == 0) [[unlikely]]
    488   {
    489     frequency = 1000000;
    490 
    491     HKEY hKey;
    492     if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey) ==
    493         ERROR_SUCCESS)
    494     {
    495       DWORD value;
    496       DWORD value_size = sizeof(value);
    497       if (RegQueryValueExW(hKey, L"~MHz", 0, nullptr, reinterpret_cast<LPBYTE>(&value), &value_size) == ERROR_SUCCESS)
    498       {
    499         // value is in mhz, convert to hz
    500         frequency *= value;
    501       }
    502 
    503       RegCloseKey(hKey);
    504     }
    505   }
    506 
    507   return frequency;
    508 #elif defined(_WIN32)
    509   return 10000000;
    510 #elif defined(__APPLE__)
    511   return 1000000;
    512 
    513 #else
    514   return 1000000;
    515 #endif
    516 }
    517 
    518 void Threading::SetNameOfCurrentThread(const char* name)
    519 {
    520   // This feature needs Windows headers and MSVC's SEH support:
    521 
    522 #if defined(_WIN32) && defined(_MSC_VER)
    523 
    524   // This code sample was borrowed form some obscure MSDN article.
    525   // In a rare bout of sanity, it's an actual Microsoft-published hack
    526   // that actually works!
    527 
    528   static const int MS_VC_EXCEPTION = 0x406D1388;
    529 
    530 #pragma pack(push, 8)
    531   struct THREADNAME_INFO
    532   {
    533     DWORD dwType;     // Must be 0x1000.
    534     LPCSTR szName;    // Pointer to name (in user addr space).
    535     DWORD dwThreadID; // Thread ID (-1=caller thread).
    536     DWORD dwFlags;    // Reserved for future use, must be zero.
    537   };
    538 #pragma pack(pop)
    539 
    540   THREADNAME_INFO info;
    541   info.dwType = 0x1000;
    542   info.szName = name;
    543   info.dwThreadID = GetCurrentThreadId();
    544   info.dwFlags = 0;
    545 
    546   __try
    547   {
    548     RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
    549   }
    550   __except (EXCEPTION_EXECUTE_HANDLER)
    551   {
    552   }
    553 #elif defined(__linux__)
    554   // Extract of manpage: "The name can be up to 16 bytes long, and should be
    555   //						null-terminated if it contains fewer bytes."
    556   prctl(PR_SET_NAME, name, 0, 0, 0);
    557 #elif defined(__APPLE__)
    558   pthread_setname_np(name);
    559 #else
    560   pthread_set_name_np(pthread_self(), name);
    561 #endif
    562 }
    563 
    564 Threading::KernelSemaphore::KernelSemaphore()
    565 {
    566 #ifdef _WIN32
    567   m_sema = CreateSemaphore(nullptr, 0, LONG_MAX, nullptr);
    568 #elif defined(__APPLE__)
    569   semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, 0);
    570 #else
    571   sem_init(&m_sema, false, 0);
    572 #endif
    573 }
    574 
    575 Threading::KernelSemaphore::~KernelSemaphore()
    576 {
    577 #ifdef _WIN32
    578   CloseHandle(m_sema);
    579 #elif defined(__APPLE__)
    580   semaphore_destroy(mach_task_self(), m_sema);
    581 #else
    582   sem_destroy(&m_sema);
    583 #endif
    584 }
    585 
    586 void Threading::KernelSemaphore::Post()
    587 {
    588 #ifdef _WIN32
    589   ReleaseSemaphore(m_sema, 1, nullptr);
    590 #elif defined(__APPLE__)
    591   semaphore_signal(m_sema);
    592 #else
    593   sem_post(&m_sema);
    594 #endif
    595 }
    596 
    597 void Threading::KernelSemaphore::Wait()
    598 {
    599 #ifdef _WIN32
    600   WaitForSingleObject(m_sema, INFINITE);
    601 #elif defined(__APPLE__)
    602   semaphore_wait(m_sema);
    603 #else
    604   sem_wait(&m_sema);
    605 #endif
    606 }
    607 
    608 bool Threading::KernelSemaphore::TryWait()
    609 {
    610 #ifdef _WIN32
    611   return WaitForSingleObject(m_sema, 0) == WAIT_OBJECT_0;
    612 #elif defined(__APPLE__)
    613   mach_timespec_t time = {};
    614   kern_return_t res = semaphore_timedwait(m_sema, time);
    615   return (res != KERN_OPERATION_TIMED_OUT);
    616 #else
    617   return sem_trywait(&m_sema) == 0;
    618 #endif
    619 }