You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/CryThreadUtil_win32_thread.h

433 lines
16 KiB
C++

/*
* 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
#include AZ_RESTRICTED_FILE(CryThreadUtil_win32_thread_h)
#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;
AZ_PUSH_DISABLE_WARNING(6312 6322, "-Wunknown-warning-option")
// 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)
{
}
AZ_POP_DISABLE_WARNING
}
//////////////////////////////////////////////////////////////////////////
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, "<ThreadInfo> 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
#include AZ_RESTRICTED_FILE(CryThreadUtil_win32_thread_h)
#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, "<ThreadInfo> 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("<ThreadInfo>: 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([[maybe_unused]] EFPE_Severity eFPESeverity)
{
AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option")
// 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
AZ_POP_DISABLE_WARNING
}
//////////////////////////////////////////////////////////////////////////
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
#ifndef _RELEASE
WORD& floatStatuslWord = ctx.FltSave.StatusWord; // Holds FPE Status for floating point registers
#endif
#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);
}
}