/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. ////////////////////////////////////////////////////////////////////////// // NOTE: INTERNAL HEADER NOT FOR PUBLIC USE // This header should only be include by SystemThreading.cpp only // It provides an interface for WinApi intrinsics // It's only client should be CThreadManager which should manage all thread interaction #pragma once #if defined(AZ_RESTRICTED_PLATFORM) #undef AZ_RESTRICTED_SECTION #define CRYTHREADUTIL_WIN32_THREAD_H_SECTION_1 1 #define CRYTHREADUTIL_WIN32_THREAD_H_SECTION_2 2 #endif #if !defined(INCLUDED_FROM_SYSTEM_THREADING_CPP) # error "CRYTEK INTERNAL HEADER. ONLY INCLUDE FROM SYSTEMTHRADING.CPP." #endif ////////////////////////////////////////////////////////////////////////// #define DEFAULT_THREAD_STACK_SIZE_KB 0 // Returns the last Win32 error, in string format. Returns an empty string if there is no error. static string GetLastErrorAsString() { // Get the error message, if any. DWORD errorMessageID = GetLastError(); if (errorMessageID == 0) { return ""; } #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION CRYTHREADUTIL_WIN32_THREAD_H_SECTION_1 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/CryThreadUtil_win32_thread_h_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/CryThreadUtil_win32_thread_h_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/CryThreadUtil_win32_thread_h_salem.inl" #endif #endif #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) #undef AZ_RESTRICTED_SECTION_IMPLEMENTED #else LPSTR messageBuffer = nullptr; size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), messageBuffer, 0, NULL); string message(messageBuffer, size); // Free the buffer. LocalFree(messageBuffer); return message; #endif } ////////////////////////////////////////////////////////////////////////// // THREAD CREATION AND MANAGMENT ////////////////////////////////////////////////////////////////////////// namespace CryThreadUtil { // Define type for platform specific thread handle typedef THREAD_HANDLE TThreadHandle; struct SThreadCreationDesc { // Define platform specific thread entry function functor type typedef unsigned int(_stdcall * EntryFunc)(void*); const char* szThreadName; EntryFunc fpEntryFunc; void* pArgList; uint32 nStackSizeInBytes; }; ////////////////////////////////////////////////////////////////////////// TThreadHandle CryGetCurrentThreadHandle() { return GetCurrentThread(); // most likely returns pseudo handle (0xfffffffe) } ////////////////////////////////////////////////////////////////////////// // Note: Handle must be closed lated via CryCloseThreadHandle() TThreadHandle CryDuplicateThreadHandle(const TThreadHandle& hThreadHandle) { // NOTES: // GetCurrentThread() may return a psydo handle to the current thread // to avoid going into the slower kernel mode. // Hence the handle is useless when being used from an other thread. // - GetCurrentThread() -> 0xfffffffe // - GetCurrentProcess() -> 0xffffffff HANDLE hRealHandle = 0; DuplicateHandle(GetCurrentProcess(), // Source Process Handle. hThreadHandle, // Source Handle to dup. GetCurrentProcess(), // Target Process Handle. &hRealHandle, // Target Handle pointer. 0, // Options flag. TRUE, // Inheritable flag DUPLICATE_SAME_ACCESS); // Options return (TThreadHandle)hRealHandle; } ////////////////////////////////////////////////////////////////////////// void CryCloseThreadHandle(TThreadHandle& hThreadHandle) { if (hThreadHandle) { CloseHandle(hThreadHandle); } } ////////////////////////////////////////////////////////////////////////// threadID CryGetCurrentThreadId() { return GetCurrentThreadId(); } ////////////////////////////////////////////////////////////////////////// threadID CryGetThreadId(TThreadHandle hThreadHandle) { return GetThreadId(hThreadHandle); } ////////////////////////////////////////////////////////////////////////// void CrySetThreadName(TThreadHandle pThreadHandle, const char* sThreadName) { const DWORD MS_VC_EXCEPTION = 0x406D1388; struct SThreadNameDesc { 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. }; SThreadNameDesc info; info.dwType = 0x1000; info.szName = sThreadName; info.dwThreadID = GetThreadId(pThreadHandle); info.dwFlags = 0; #pragma warning(push) #pragma warning(disable : 6312 6322) // warning C6312: Possible infinite loop: use of the constant EXCEPTION_CONTINUE_EXECUTION in the exception-filter expression of a try-except // warning C6322: empty _except block __try { // Raise exception to set thread name for attached debugger RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR*)&info); } __except (GetExceptionCode() == MS_VC_EXCEPTION ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER) { } #pragma warning(pop) } ////////////////////////////////////////////////////////////////////////// void CrySetThreadAffinityMask(TThreadHandle pThreadHandle, DWORD dwAffinityMask) { SetThreadAffinityMask(pThreadHandle, dwAffinityMask); } ////////////////////////////////////////////////////////////////////////// void CrySetThreadPriority(TThreadHandle pThreadHandle, DWORD dwPriority) { if (!SetThreadPriority(pThreadHandle, dwPriority)) { string errMsg = GetLastErrorAsString(); CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, " Unable to set thread priority. System Error Msg: \"%s\"", errMsg.c_str()); return; } } ////////////////////////////////////////////////////////////////////////// void CrySetThreadPriorityBoost(TThreadHandle pThreadHandle, bool bEnabled) { SetThreadPriorityBoost(pThreadHandle, !bEnabled); } ////////////////////////////////////////////////////////////////////////// bool CryCreateThread(TThreadHandle* pThreadHandle, const SThreadCreationDesc& threadDesc) { const uint32 nStackSize = threadDesc.nStackSizeInBytes != 0 ? threadDesc.nStackSizeInBytes : DEFAULT_THREAD_STACK_SIZE_KB * 1024; // Create thread unsigned int threadId = 0; #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION CRYTHREADUTIL_WIN32_THREAD_H_SECTION_2 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/CryThreadUtil_win32_thread_h_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/CryThreadUtil_win32_thread_h_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/CryThreadUtil_win32_thread_h_salem.inl" #endif #endif #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) #undef AZ_RESTRICTED_SECTION_IMPLEMENTED #else *pThreadHandle = (void*)_beginthreadex(NULL, nStackSize, threadDesc.fpEntryFunc, threadDesc.pArgList, CREATE_SUSPENDED, &threadId); #endif if (!(*pThreadHandle)) { string errMsg = GetLastErrorAsString(); CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, " Unable to create thread \"%s\". System Error Msg: \"%s\"", threadDesc.szThreadName, errMsg.c_str()); return false; } // Start thread ResumeThread(*pThreadHandle); // Print info to log CryComment(": New thread \"%s\" | StackSize: %u(KB)", threadDesc.szThreadName, threadDesc.nStackSizeInBytes / 1024); return true; } ////////////////////////////////////////////////////////////////////////// void CryThreadExitCall() { // Note on: ExitThread() (from MSDN) // ExitThread is the preferred method of exiting a thread in C code. // However, in C++ code, the thread is exited before any destructor can be called or any other automatic cleanup can be performed. // Therefore, in C++ code, you should return from your thread function. } } ////////////////////////////////////////////////////////////////////////// // FLOATING POINT EXCEPTIONS ////////////////////////////////////////////////////////////////////////// namespace CryThreadUtil { /////////////////////////////////////////////////////////////////////////// void EnableFloatExceptions(EFPE_Severity eFPESeverity) { #pragma warning( push ) #pragma warning(disable: 4996) // Optimization // Enable DAZ/FZ // Denormals Are Zeros // Flush-to-Zero _controlfp(_DN_FLUSH, _MCW_DN); _mm_setcsr(_mm_getcsr() | _MM_FLUSH_ZERO_ON); #ifndef _RELEASE if (eFPESeverity == eFPE_None) { // mask all floating exceptions off. _controlfp(_MCW_EM, _MCW_EM); _mm_setcsr(_mm_getcsr() | _MM_MASK_MASK); } else { // Clear pending exceptions _fpreset(); if (eFPESeverity == eFPE_Basic) { // Enable: // - _EM_ZERODIVIDE // - _EM_INVALID // // Disable: // - _EM_DENORMAL // - _EM_OVERFLOW // - _EM_UNDERFLOW // - _EM_INEXACT _controlfp(_EM_INEXACT | _EM_DENORMAL | _EM_UNDERFLOW | _EM_OVERFLOW, _MCW_EM); _mm_setcsr((_mm_getcsr() & ~_MM_MASK_MASK) | (_MM_MASK_DENORM | _MM_MASK_INEXACT | _MM_MASK_UNDERFLOW | _MM_MASK_OVERFLOW)); //_mm_setcsr(_mm_getcsr() & ~0x280); } if (eFPESeverity == eFPE_All) { // Enable: // - _EM_ZERODIVIDE // - _EM_INVALID // - _EM_UNDERFLOW // - _EM_OVERFLOW // // Disable: // - _EM_INEXACT // - _EM_DENORMAL _controlfp(_EM_INEXACT | _EM_DENORMAL, _MCW_EM); _mm_setcsr((_mm_getcsr() & ~_MM_MASK_MASK) | (_MM_MASK_INEXACT | _MM_MASK_DENORM)); } } #endif // _RELEASE #pragma warning( pop ) } ////////////////////////////////////////////////////////////////////////// void EnableFloatExceptions(threadID nThreadId, EFPE_Severity eFPESeverity) { if (eFPESeverity >= eFPE_LastEntry) { CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Floating Point Exception (FPE) severity is out of range. (%i)", eFPESeverity); } // Check if the thread ID matches the current thread if (nThreadId == 0 || nThreadId == CryGetCurrentThreadId()) { EnableFloatExceptions(eFPESeverity); return; } HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, true, nThreadId); if (hThread == 0) { CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Unable to open thread. %p", hThread); return; } SuspendThread(hThread); CONTEXT ctx; memset(&ctx, 0, sizeof(ctx)); ctx.ContextFlags = CONTEXT_ALL; if (GetThreadContext(hThread, &ctx) == 0) { CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Unable to get thread context"); ResumeThread(hThread); CloseHandle(hThread); return; } #ifdef PLATFORM_64BIT ////////////////////////////////////////////////////////////////////////// // Note: // DO NOT USE ctx.FltSave.MxCsr ... SetThreadContext() will copy the value of ctx.MxCsr into it ////////////////////////////////////////////////////////////////////////// DWORD& floatMxCsr = ctx.MxCsr; // Hold FPE Mask and Status for MMX (SSE) floating point registers WORD& floatControlWord = ctx.FltSave.ControlWord; // Hold FPE Mask for floating point registers WORD& floatStatuslWord = ctx.FltSave.StatusWord; // Holds FPE Status for floating point registers #else DWORD& floatMxCsr = *(DWORD*)(&ctx.ExtendedRegisters[24]); // Hold FPE Mask and Status for MMX (SSE) floating point registers DWORD& floatControlWord = ctx.FloatSave.ControlWord; // Hold FPE Mask for floating point registers DWORD& floatStatuslWord = ctx.FloatSave.StatusWord; // Holds FPE Status for floating point registers #endif // Flush-To-Zero Mode // Two conditions must be met for FTZ processing to occur: // - The FTZ bit (bit 15) in the MXCSR register must be masked (value = 1). // - The underflow exception (bit 11) needs to be masked (value = 1). // Set flush mode to zero mode floatControlWord = (floatControlWord & ~_MCW_DN) | _DN_FLUSH; floatMxCsr = (floatMxCsr & ~_MM_FLUSH_ZERO_MASK) | (_MM_FLUSH_ZERO_ON); #ifndef _RELEASE // Reset FPE bits floatControlWord = floatControlWord | _MCW_EM; floatMxCsr = floatMxCsr | _MM_MASK_MASK; // Clear pending exceptions floatStatuslWord = floatStatuslWord & ~(_SW_INEXACT | _SW_UNDERFLOW | _SW_OVERFLOW | _SW_ZERODIVIDE | _SW_INVALID | _SW_DENORMAL); floatMxCsr = floatMxCsr & ~(_MM_EXCEPT_INEXACT | _MM_EXCEPT_UNDERFLOW | _MM_EXCEPT_OVERFLOW | _MM_EXCEPT_DIV_ZERO | _MM_EXCEPT_INVALID | _MM_EXCEPT_DENORM); if (eFPESeverity == eFPE_Basic) { // Enable: // - _EM_ZERODIVIDE // - _EM_INVALID // // Disable: // - _EM_DENORMAL // - _EM_OVERFLOW // - _EM_UNDERFLOW // - _EM_INEXACT floatControlWord = (floatControlWord & ~_MCW_EM) | (_EM_DENORMAL | _EM_INEXACT | EM_UNDERFLOW | _EM_OVERFLOW); floatMxCsr = (floatMxCsr & ~_MM_MASK_MASK) | (_MM_MASK_DENORM | _MM_MASK_INEXACT | _MM_MASK_UNDERFLOW | _MM_MASK_OVERFLOW); } if (eFPESeverity == eFPE_All) { // Enable: // - _EM_ZERODIVIDE // - _EM_INVALID // - _EM_UNDERFLOW // - _EM_OVERFLOW // // Disable: // - _EM_INEXACT // - _EM_DENORMAL floatControlWord = (floatControlWord & ~_MCW_EM) | (_EM_INEXACT | _EM_DENORMAL); floatMxCsr = (floatMxCsr & ~_MM_MASK_MASK) | (_MM_MASK_INEXACT | _MM_MASK_DENORM); } #endif ctx.ContextFlags = CONTEXT_ALL; if (SetThreadContext(hThread, &ctx) == 0) { CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Error setting ThreadContext for ThreadID: %u", nThreadId); ResumeThread(hThread); CloseHandle(hThread); return; } ResumeThread(hThread); CloseHandle(hThread); } ////////////////////////////////////////////////////////////////////////// uint GetFloatingPointExceptionMask() { uint nMask = 0; _clearfp(); _controlfp_s(&nMask, 0, 0); return nMask; } ////////////////////////////////////////////////////////////////////////// void SetFloatingPointExceptionMask(uint nMask) { uint temp = 0; _clearfp(); const unsigned int kAllowedBits = _MCW_DN | _MCW_EM | _MCW_RC; _controlfp_s(&temp, nMask, kAllowedBits); } }