/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include #include "xenia/base/assert.h" #include "xenia/base/chrono_steady_cast.h" #include "xenia/base/logging.h" #include "xenia/base/platform_win.h" #include "xenia/base/threading.h" #include "xenia/base/threading_timer_queue.h" #if defined(__clang__) // chrispy: i do not understand why this is an error for clang here // something about the quoted __FUNCTION__ freaks it out (clang 14.0.1) #define LOG_LASTERROR() \ do { \ XELOGI("Win32 Error 0x{:08X} in {} (...)", GetLastError(), __FUNCTION__); \ } while (false) #else #define LOG_LASTERROR() \ do { \ XELOGI("Win32 Error 0x{:08X} in " __FUNCTION__ "(...)", GetLastError()); \ } while (false) #endif typedef HANDLE (*SetThreadDescriptionFn)(HANDLE hThread, PCWSTR lpThreadDescription); // sys function for ntyieldexecution, by calling it we sidestep // RtlGetCurrentUmsThread XE_NTDLL_IMPORT(NtYieldExecution, cls_NtYieldExecution, NtYieldExecutionPointer); // sidestep the activation context/remapping special windows handles like stdout XE_NTDLL_IMPORT(NtWaitForSingleObject, cls_NtWaitForSingleObject, NtWaitForSingleObjectPointer); XE_NTDLL_IMPORT(NtSetEvent, cls_NtSetEvent, NtSetEventPointer); XE_NTDLL_IMPORT(NtSetEventBoostPriority, cls_NtSetEventBoostPriority, NtSetEventBoostPriorityPointer); // difference between NtClearEvent and NtResetEvent is that NtResetEvent returns // the events state prior to the call, but we dont need that. might need to // check whether one or the other is faster in the kernel though yeah, just // checked, the code in ntoskrnl is way simpler for clearevent than resetevent XE_NTDLL_IMPORT(NtClearEvent, cls_NtClearEvent, NtClearEventPointer); XE_NTDLL_IMPORT(NtPulseEvent, cls_NtPulseEvent, NtPulseEventPointer); // heavily called, we dont skip much garbage by calling this, but every bit // counts XE_NTDLL_IMPORT(NtReleaseSemaphore, cls_NtReleaseSemaphore, NtReleaseSemaphorePointer); XE_NTDLL_IMPORT(NtDelayExecution, cls_NtDelayExecution, NtDelayExecutionPointer); XE_NTDLL_IMPORT(NtQueryEvent, cls_NtQueryEvent, NtQueryEventPointer); namespace xe { namespace threading { void EnableAffinityConfiguration() { // chrispy: i don't think this is necessary, // affinity always seems to be the system mask? research more // also, maybe if ignore_thread_affinities is on we should use // SetProcessAffinityUpdateMode to allow windows to dynamically update // our process' affinity (by default windows cannot change the affinity itself // at runtime, user code must do it) HANDLE process_handle = GetCurrentProcess(); DWORD_PTR process_affinity_mask; DWORD_PTR system_affinity_mask; GetProcessAffinityMask(process_handle, &process_affinity_mask, &system_affinity_mask); SetProcessAffinityMask(process_handle, system_affinity_mask); } uint32_t current_thread_system_id() { return static_cast(GetCurrentThreadId()); } // https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx #pragma pack(push, 8) struct THREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. }; #pragma pack(pop) void raise_thread_name_exception(HANDLE thread, const std::string& name) { if (!IsDebuggerPresent()) { return; } THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name.c_str(); info.dwThreadID = ::GetThreadId(thread); info.dwFlags = 0; __try { RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast(&info)); } __except (EXCEPTION_EXECUTE_HANDLER) { // NOLINT } } static void set_name(HANDLE thread, const std::string_view name) { auto kernel = GetModuleHandleW(L"kernel32.dll"); if (kernel) { auto func = (SetThreadDescriptionFn)GetProcAddress(kernel, "SetThreadDescription"); if (func) { auto u16name = xe::to_utf16(name); func(thread, reinterpret_cast(u16name.c_str())); } } raise_thread_name_exception(thread, std::string(name)); } void set_name(const std::string_view name) { set_name(GetCurrentThread(), name); } // checked ntoskrnl, it does not modify delay, so we can place this as a // constant and avoid creating a stack variable static const LARGE_INTEGER sleepdelay0_for_maybeyield{{0LL}}; void MaybeYield() { #if 0 #if defined(XE_USE_NTDLL_FUNCTIONS) NtYieldExecutionPointer.invoke(); #else SwitchToThread(); #endif #else // chrispy: SwitchToThread will only switch to a ready thread on the current // processor, so if one is not ready we end up spinning, constantly calling // switchtothread without doing any work, heating up the users cpu sleep(0) // however will yield to threads on other processors and surrenders the // current timeslice #if defined(XE_USE_NTDLL_FUNCTIONS) NtDelayExecutionPointer.invoke(0, &sleepdelay0_for_maybeyield); #else ::Sleep(0); #endif #endif // memorybarrier is really not necessary here... // MemoryBarrier(); } void NanoSleep(int64_t ns) { // nanosleep is done in 100 nanosecond increments int64_t in_nt_increments = ns / 100LL; if (in_nt_increments == 0 && ns != 0) { // if we're explicitly requesting a delay of 0 ns, let it go through, // otherwise if it was less than a 100ns increment we round up to 100ns in_nt_increments = 1; } in_nt_increments = -in_nt_increments; NtDelayExecutionPointer.invoke(0, &in_nt_increments); } void SyncMemory() { MemoryBarrier(); } void Sleep(std::chrono::microseconds duration) { if (duration.count() < 100) { MaybeYield(); } else { ::Sleep(static_cast(duration.count() / 1000)); } } SleepResult AlertableSleep(std::chrono::microseconds duration) { if (SleepEx(static_cast(duration.count() / 1000), true) == WAIT_IO_COMPLETION) { return SleepResult::kAlerted; } return SleepResult::kSuccess; } TlsHandle AllocateTlsHandle() { return TlsAlloc(); } bool FreeTlsHandle(TlsHandle handle) { return TlsFree(handle) ? true : false; } uintptr_t GetTlsValue(TlsHandle handle) { return reinterpret_cast(TlsGetValue(handle)); } bool SetTlsValue(TlsHandle handle, uintptr_t value) { return TlsSetValue(handle, reinterpret_cast(value)) ? true : false; } template class Win32Handle : public T { public: explicit Win32Handle(HANDLE handle) : handle_(handle) { assert_not_null(handle); } ~Win32Handle() override { CloseHandle(handle_); handle_ = nullptr; } protected: void* native_handle() const override { return handle_; } HANDLE handle_ = nullptr; }; WaitResult Wait(WaitHandle* wait_handle, bool is_alertable, std::chrono::milliseconds timeout) { HANDLE handle = wait_handle->native_handle(); DWORD result; DWORD timeout_dw = DWORD(timeout.count()); BOOL bAlertable = is_alertable ? true : false; // todo: we might actually be able to use NtWaitForSingleObject even if its // alertable, just need to study whether // RtlDeactivateActivationContextUnsafeFast/RtlActivateActivationContext are // actually needed for us #if XE_USE_NTDLL_FUNCTIONS == 1 if (bAlertable) { result = WaitForSingleObjectEx(handle, timeout_dw, bAlertable); } else { LARGE_INTEGER timeout_big; timeout_big.QuadPart = -10000LL * static_cast(timeout_dw); result = NtWaitForSingleObjectPointer.invoke( handle, bAlertable, timeout_dw == INFINITE ? nullptr : &timeout_big); } #else result = WaitForSingleObjectEx(handle, timeout_dw, bAlertable); #endif switch (result) { case WAIT_OBJECT_0: return WaitResult::kSuccess; case WAIT_ABANDONED: return WaitResult::kAbandoned; case WAIT_IO_COMPLETION: return WaitResult::kUserCallback; case WAIT_TIMEOUT: return WaitResult::kTimeout; default: case WAIT_FAILED: return WaitResult::kFailed; } } WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on, bool is_alertable, std::chrono::milliseconds timeout) { HANDLE handle_to_signal = wait_handle_to_signal->native_handle(); HANDLE handle_to_wait_on = wait_handle_to_wait_on->native_handle(); DWORD result = SignalObjectAndWait(handle_to_signal, handle_to_wait_on, DWORD(timeout.count()), is_alertable ? true : false); switch (result) { case WAIT_OBJECT_0: return WaitResult::kSuccess; case WAIT_ABANDONED: return WaitResult::kAbandoned; case WAIT_IO_COMPLETION: return WaitResult::kUserCallback; case WAIT_TIMEOUT: return WaitResult::kTimeout; default: case WAIT_FAILED: return WaitResult::kFailed; } } std::pair WaitMultiple(WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all, bool is_alertable, std::chrono::milliseconds timeout) { xenia_assert(wait_handle_count <= 64); HANDLE handles[64]; for (size_t i = 0; i < wait_handle_count; ++i) { handles[i] = wait_handles[i]->native_handle(); } DWORD result = WaitForMultipleObjectsEx( static_cast(wait_handle_count), handles, wait_all ? true : false, DWORD(timeout.count()), is_alertable ? true : false); if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + wait_handle_count) { return std::pair(WaitResult::kSuccess, result - WAIT_OBJECT_0); } else if (result >= WAIT_ABANDONED_0 && result < WAIT_ABANDONED_0 + wait_handle_count) { return std::pair(WaitResult::kAbandoned, result - WAIT_ABANDONED_0); } switch (result) { case WAIT_IO_COMPLETION: return std::pair(WaitResult::kUserCallback, 0); case WAIT_TIMEOUT: return std::pair(WaitResult::kTimeout, 0); default: case WAIT_FAILED: return std::pair(WaitResult::kFailed, 0); } } class Win32Event : public Win32Handle { public: explicit Win32Event(HANDLE handle) : Win32Handle(handle) {} ~Win32Event() override = default; #if XE_USE_NTDLL_FUNCTIONS == 1 void Set() override { NtSetEventPointer.invoke(handle_, nullptr); } void Reset() override { NtClearEventPointer.invoke(handle_); } void Pulse() override { NtPulseEventPointer.invoke(handle_, nullptr); } void SetBoostPriority() override { // no previous state for boostpriority // Boost priority is unimplemented under wine probably because it's not used // anywhere in user mode except by us. Maybe some Windows internals uses it // see: // https://discord.com/channels/308194948048486401/308207592482668545/1027178776599216228 if (NtSetEventBoostPriorityPointer) { NtSetEventBoostPriorityPointer.invoke(handle_); } else { NtSetEventPointer.invoke(handle_, nullptr); } } #else void Set() override { SetEvent(handle_); } void Reset() override { ResetEvent(handle_); } void Pulse() override { PulseEvent(handle_); } void SetBoostPriority() override { // no win32 version of boostpriority SetEvent(handle_); } #endif EventInfo Query() override { EventInfo result{}; NtQueryEventPointer.invoke(handle_, 0, &result, sizeof(EventInfo), nullptr); return result; } }; std::unique_ptr Event::CreateManualResetEvent(bool initial_state) { HANDLE handle = CreateEvent(nullptr, true, initial_state ? true : false, nullptr); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } std::unique_ptr Event::CreateAutoResetEvent(bool initial_state) { HANDLE handle = CreateEvent(nullptr, false, initial_state ? true : false, nullptr); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } class Win32Semaphore : public Win32Handle { public: explicit Win32Semaphore(HANDLE handle) : Win32Handle(handle) {} ~Win32Semaphore() override = default; bool Release(int release_count, int* out_previous_count) override { #if XE_USE_NTDLL_FUNCTIONS == 1 return NtReleaseSemaphorePointer.invoke(handle_, release_count, out_previous_count) >= 0; #else return ReleaseSemaphore(handle_, release_count, reinterpret_cast(out_previous_count)) ? true : false; #endif } }; std::unique_ptr Semaphore::Create(int initial_count, int maximum_count) { HANDLE handle = CreateSemaphore(nullptr, initial_count, maximum_count, nullptr); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } class Win32Mutant : public Win32Handle { public: explicit Win32Mutant(HANDLE handle) : Win32Handle(handle) {} ~Win32Mutant() = default; bool Release() override { return ReleaseMutex(handle_) ? true : false; } }; std::unique_ptr Mutant::Create(bool initial_owner) { HANDLE handle = CreateMutex(nullptr, initial_owner ? true : false, nullptr); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } class Win32Timer : public Win32Handle { using WClock_ = Timer::WClock_; using GClock_ = Timer::GClock_; public: explicit Win32Timer(HANDLE handle) : Win32Handle(handle) {} ~Win32Timer() = default; bool SetOnceAfter(xe::chrono::hundrednanoseconds rel_time, std::function opt_callback) override { return SetOnceAt(WClock_::now() + rel_time, std::move(opt_callback)); } bool SetOnceAt(GClock_::time_point due_time, std::function opt_callback) override { return SetOnceAt(date::clock_cast(due_time), std::move(opt_callback)); } bool SetOnceAt(WClock_::time_point due_time, std::function opt_callback) override { std::lock_guard lock(mutex_); callback_ = std::move(opt_callback); LARGE_INTEGER due_time_li; due_time_li.QuadPart = WClock_::to_file_time(due_time); auto completion_routine = callback_ ? reinterpret_cast(CompletionRoutine) : NULL; return SetWaitableTimer(handle_, &due_time_li, 0, completion_routine, this, false) ? true : false; } bool SetRepeatingAfter( xe::chrono::hundrednanoseconds rel_time, std::chrono::milliseconds period, std::function opt_callback = nullptr) override { return SetRepeatingAt(WClock_::now() + rel_time, period, std::move(opt_callback)); } bool SetRepeatingAt(GClock_::time_point due_time, std::chrono::milliseconds period, std::function opt_callback = nullptr) override { return SetRepeatingAt(date::clock_cast(due_time), period, std::move(opt_callback)); } bool SetRepeatingAt(WClock_::time_point due_time, std::chrono::milliseconds period, std::function opt_callback) override { std::lock_guard lock(mutex_); callback_ = std::move(opt_callback); LARGE_INTEGER due_time_li; due_time_li.QuadPart = WClock_::to_file_time(due_time); auto completion_routine = callback_ ? reinterpret_cast(CompletionRoutine) : NULL; return SetWaitableTimer(handle_, &due_time_li, int32_t(period.count()), completion_routine, this, false) ? true : false; } bool Cancel() override { // Reset the callback immediately so that any completions don't call it. std::lock_guard lock(mutex_); callback_ = nullptr; return CancelWaitableTimer(handle_) ? true : false; } private: static void CompletionRoutine(Win32Timer* timer, DWORD timer_low, DWORD timer_high) { // As the callback may reset the timer, store local. std::function callback; { std::lock_guard lock(timer->mutex_); callback = timer->callback_; } callback(); } std::mutex mutex_; std::function callback_; }; std::unique_ptr Timer::CreateManualResetTimer() { HANDLE handle = CreateWaitableTimer(NULL, true, NULL); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } std::unique_ptr Timer::CreateSynchronizationTimer() { HANDLE handle = CreateWaitableTimer(NULL, false, NULL); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); return nullptr; } } class Win32Thread : public Win32Handle { public: explicit Win32Thread(HANDLE handle) : Win32Handle(handle) {} ~Win32Thread() = default; void set_name(std::string name) override { // this can actually happen in some debug builds if (&name == nullptr) { return; } xe::threading::set_name(handle_, name); Thread::set_name(name); } int32_t priority() override { return GetThreadPriority(handle_); } uint32_t system_id() const override { return GetThreadId(handle_); } void set_priority(int32_t new_priority) override { SetThreadPriority(handle_, new_priority); } uint64_t affinity_mask() override { uint64_t value = 0; SetThreadAffinityMask(handle_, reinterpret_cast(&value)); return value; } void set_affinity_mask(uint64_t new_affinity_mask) override { SetThreadAffinityMask(handle_, new_affinity_mask); } struct ApcData { std::function callback; }; static void NTAPI DispatchApc(ULONG_PTR parameter) { auto apc_data = reinterpret_cast(parameter); apc_data->callback(); delete apc_data; } void QueueUserCallback(std::function callback) override { auto apc_data = new ApcData({std::move(callback)}); QueueUserAPC(DispatchApc, handle_, reinterpret_cast(apc_data)); } bool Resume(uint32_t* out_previous_suspend_count = nullptr) override { if (out_previous_suspend_count) { *out_previous_suspend_count = 0; } DWORD result = ResumeThread(handle_); if (result == UINT_MAX) { return false; } if (out_previous_suspend_count) { *out_previous_suspend_count = result; } return true; } bool Suspend(uint32_t* out_previous_suspend_count = nullptr) override { if (out_previous_suspend_count) { *out_previous_suspend_count = 0; } DWORD result = SuspendThread(handle_); if (result == UINT_MAX) { return false; } if (out_previous_suspend_count) { *out_previous_suspend_count = result; } return true; } void Terminate(int exit_code) override { TerminateThread(handle_, exit_code); } private: void AssertCallingThread() { assert_true(GetCurrentThreadId() == GetThreadId(handle_)); } }; thread_local std::unique_ptr current_thread_ = nullptr; struct ThreadStartData { std::function start_routine; }; DWORD WINAPI ThreadStartRoutine(LPVOID parameter) { current_thread_ = std::make_unique(::GetCurrentThread()); auto start_data = reinterpret_cast(parameter); start_data->start_routine(); delete start_data; return 0; } std::unique_ptr Thread::Create(CreationParameters params, std::function start_routine) { auto start_data = new ThreadStartData({std::move(start_routine)}); HANDLE handle = CreateThread(NULL, params.stack_size, ThreadStartRoutine, start_data, params.create_suspended ? CREATE_SUSPENDED : 0, NULL); if (handle) { return std::make_unique(handle); } else { LOG_LASTERROR(); delete start_data; return nullptr; } } Thread* Thread::GetCurrentThread() { if (current_thread_) { return current_thread_.get(); } HANDLE handle = ::GetCurrentThread(); if (handle == INVALID_HANDLE_VALUE) { return nullptr; } current_thread_ = std::make_unique(handle); return current_thread_.get(); } void Thread::Exit(int exit_code) { ExitThread(exit_code); } } // namespace threading } // namespace xe